Bug 236996 - [Cocoa] Only clear ICU cache when time zone is changed
Summary: [Cocoa] Only clear ICU cache when time zone is changed
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: JavaScriptCore (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Chris Dumez
URL:
Keywords: InRadar
: 218365 (view as bug list)
Depends on:
Blocks: 218365
  Show dependency treegraph
 
Reported: 2022-02-21 14:25 PST by Chris Dumez
Modified: 2022-04-07 14:01 PDT (History)
9 users (show)

See Also:


Attachments
Patch (4.92 KB, patch)
2022-02-21 14:30 PST, Chris Dumez
no flags Details | Formatted Diff | Diff
Patch (4.94 KB, patch)
2022-02-22 08:18 PST, Chris Dumez
no flags Details | Formatted Diff | Diff
Patch (9.46 KB, patch)
2022-02-23 13:08 PST, Chris Dumez
no flags Details | Formatted Diff | Diff
Patch (5.89 KB, patch)
2022-02-23 17:32 PST, Chris Dumez
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Chris Dumez 2022-02-21 14:25:05 PST
Only clear ICU cache when time zone is changed.

Otherwise, VMEntryScope keeps invalidating the ICU cache and we keep going to the file system to figure out the current host time zone:
Sample Count, Samples %, Normalized CPU %, Symbol
2, 0.0%, 0.0%, JSC::DateCache::defaultTimeZone() (in JavaScriptCore)
2, 0.0%, 0.0%,     JSC::DateCache::timeZoneCacheSlow() (in JavaScriptCore)
2, 0.0%, 0.0%,         ucal_getHostTimeZone (in libicucore.A.dylib)
2, 0.0%, 0.0%,             icu::TimeZone::detectHostTimeZone() (in libicucore.A.dylib)
1, 0.0%, 0.0%,                 readlink (in libsystem_kernel.dylib)
1, 0.0%, 0.0%,                     unix_syscall64 (in kernel.development)
1, 0.0%, 0.0%,                         readlink (in kernel.development)
Comment 1 Chris Dumez 2022-02-21 14:30:58 PST
Created attachment 452774 [details]
Patch
Comment 2 Chris Dumez 2022-02-22 08:18:39 PST
Created attachment 452873 [details]
Patch
Comment 3 Yusuke Suzuki 2022-02-22 16:52:12 PST
Comment on attachment 452873 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=452873&action=review

> Source/JavaScriptCore/runtime/JSDateMath.cpp:358
> +    static_cast<DateCache*>(observer)->reset();

I think this needs some care: in non-WebKit JavaScriptCore.framework, JSC VM can be moved from one thread to the other. So let's consider the case,

1. Initializing JSC VM on the main thread
2. It registers notification
3. Then moving this VM to a different thread
4. Using this VM in the different thread
5. Now notification comes to the main thread
6. We reset the cache from the main thread while the different thread is using VM, which can be in the middle of using this cache.
Comment 4 Chris Dumez 2022-02-22 16:53:52 PST
(In reply to Yusuke Suzuki from comment #3)
> Comment on attachment 452873 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=452873&action=review
> 
> > Source/JavaScriptCore/runtime/JSDateMath.cpp:358
> > +    static_cast<DateCache*>(observer)->reset();
> 
> I think this needs some care: in non-WebKit JavaScriptCore.framework, JSC VM
> can be moved from one thread to the other. So let's consider the case,
> 
> 1. Initializing JSC VM on the main thread
> 2. It registers notification
> 3. Then moving this VM to a different thread
> 4. Using this VM in the different thread
> 5. Now notification comes to the main thread
> 6. We reset the cache from the main thread while the different thread is
> using VM, which can be in the middle of using this cache.

Ouch, I didn't realize a VM would be moved to a different thread. If so, then it makes things more complicated indeed..
Comment 5 Chris Dumez 2022-02-23 13:08:52 PST
Created attachment 453018 [details]
Patch
Comment 6 Yusuke Suzuki 2022-02-23 16:15:42 PST
Comment on attachment 453018 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=453018&action=review

> Source/JavaScriptCore/runtime/JSDateMath.h:135
> +inline OpaqueICUTimeZone* DateCache::timeZoneCache()
> +{
> +    auto& caches = ensureCaches();
> +    if (!caches.timeZoneCache)
> +        caches.timeZoneCacheSlow();
> +    return caches.timeZoneCache.get();
> +}
> +
> +inline auto DateCache::ensureCaches() -> Caches&
> +{
> +    if (!m_caches || !m_caches->isUpToDate())
> +        m_caches.emplace();
> +    return *m_caches;
> +}

Let's reset DateCache only when entering VMEntryScope.
In the current implementation, DateCache can be reset and cleared anytime we use a cache.
This means that, when running a code,

let date1 = new Date();
let date2 = new Date();

in the above sequence, suddenly, date1 and date2 get different timezone, which is surprising.
So, instead, let's ensure the same timezone while running one VMEntryScope, and clear if necessary when entering next VMEntryScope.
Comment 7 Chris Dumez 2022-02-23 16:17:42 PST
Comment on attachment 453018 [details]
Patch

Can do :)
Comment 8 Chris Dumez 2022-02-23 17:32:29 PST
Created attachment 453058 [details]
Patch
Comment 9 Yusuke Suzuki 2022-02-24 11:33:40 PST
Comment on attachment 453058 [details]
Patch

r=me
Comment 10 Chris Dumez 2022-02-24 11:34:04 PST
Comment on attachment 453058 [details]
Patch

Thanks for reviewing.
Comment 11 Yusuke Suzuki 2022-02-24 11:35:06 PST
Comment on attachment 453058 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=453058&action=review

> Source/JavaScriptCore/runtime/JSDateMath.cpp:370
> +#if PLATFORM(COCOA)
> +static void timeZoneChangeNotification(CFNotificationCenterRef, void*, CFStringRef, const void*, CFDictionaryRef)
> +{
> +    ASSERT(isMainThread());
> +    ++lastTimeZoneID;
> +}
> +#endif
> +
>  // To confine icu::TimeZone destructor invocation in this file.
> -DateCache::DateCache() = default;
> +DateCache::DateCache()
> +{
> +#if PLATFORM(COCOA)
> +    static std::once_flag onceKey;
> +    std::call_once(onceKey, [&] {
> +        CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), nullptr, timeZoneChangeNotification, kCFTimeZoneSystemTimeZoneDidChangeNotification, nullptr, CFNotificationSuspensionBehaviorDeliverImmediately);
> +    });

Will it always notified into the main thread even if we are calling this from non main thread? (in JavaScriptCore.framework use case, it is possible that VM's first launch is on the non main thread.)
Comment 12 Chris Dumez 2022-02-24 11:36:50 PST
(In reply to Yusuke Suzuki from comment #11)
> Comment on attachment 453058 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=453058&action=review
> 
> > Source/JavaScriptCore/runtime/JSDateMath.cpp:370
> > +#if PLATFORM(COCOA)
> > +static void timeZoneChangeNotification(CFNotificationCenterRef, void*, CFStringRef, const void*, CFDictionaryRef)
> > +{
> > +    ASSERT(isMainThread());
> > +    ++lastTimeZoneID;
> > +}
> > +#endif
> > +
> >  // To confine icu::TimeZone destructor invocation in this file.
> > -DateCache::DateCache() = default;
> > +DateCache::DateCache()
> > +{
> > +#if PLATFORM(COCOA)
> > +    static std::once_flag onceKey;
> > +    std::call_once(onceKey, [&] {
> > +        CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), nullptr, timeZoneChangeNotification, kCFTimeZoneSystemTimeZoneDidChangeNotification, nullptr, CFNotificationSuspensionBehaviorDeliverImmediately);
> > +    });
> 
> Will it always notified into the main thread even if we are calling this
> from non main thread? (in JavaScriptCore.framework use case, it is possible
> that VM's first launch is on the non main thread.)

Per my understanding of the documentation (https://developer.apple.com/documentation/corefoundation/1543316-cfnotificationcenteraddobserver?language=objc), yes, it would always get called on the main thread.
Comment 13 Chris Dumez 2022-02-24 11:37:41 PST
(In reply to Chris Dumez from comment #12)
> (In reply to Yusuke Suzuki from comment #11)
> > Comment on attachment 453058 [details]
> > Patch
> > 
> > View in context:
> > https://bugs.webkit.org/attachment.cgi?id=453058&action=review
> > 
> > > Source/JavaScriptCore/runtime/JSDateMath.cpp:370
> > > +#if PLATFORM(COCOA)
> > > +static void timeZoneChangeNotification(CFNotificationCenterRef, void*, CFStringRef, const void*, CFDictionaryRef)
> > > +{
> > > +    ASSERT(isMainThread());
> > > +    ++lastTimeZoneID;
> > > +}
> > > +#endif
> > > +
> > >  // To confine icu::TimeZone destructor invocation in this file.
> > > -DateCache::DateCache() = default;
> > > +DateCache::DateCache()
> > > +{
> > > +#if PLATFORM(COCOA)
> > > +    static std::once_flag onceKey;
> > > +    std::call_once(onceKey, [&] {
> > > +        CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), nullptr, timeZoneChangeNotification, kCFTimeZoneSystemTimeZoneDidChangeNotification, nullptr, CFNotificationSuspensionBehaviorDeliverImmediately);
> > > +    });
> > 
> > Will it always notified into the main thread even if we are calling this
> > from non main thread? (in JavaScriptCore.framework use case, it is possible
> > that VM's first launch is on the non main thread.)
> 
> Per my understanding of the documentation
> (https://developer.apple.com/documentation/corefoundation/1543316-
> cfnotificationcenteraddobserver?language=objc), yes, it would always get
> called on the main thread.

That said, we could also drop the main thread assertion. In this latest patch iteration, it doesn't really matter what thread the callback is called on..
Comment 14 Yusuke Suzuki 2022-02-24 11:39:25 PST
(In reply to Chris Dumez from comment #13)
> (In reply to Chris Dumez from comment #12)
> > (In reply to Yusuke Suzuki from comment #11)
> > > Comment on attachment 453058 [details]
> > > Patch
> > > 
> > > View in context:
> > > https://bugs.webkit.org/attachment.cgi?id=453058&action=review
> > > 
> > > > Source/JavaScriptCore/runtime/JSDateMath.cpp:370
> > > > +#if PLATFORM(COCOA)
> > > > +static void timeZoneChangeNotification(CFNotificationCenterRef, void*, CFStringRef, const void*, CFDictionaryRef)
> > > > +{
> > > > +    ASSERT(isMainThread());
> > > > +    ++lastTimeZoneID;
> > > > +}
> > > > +#endif
> > > > +
> > > >  // To confine icu::TimeZone destructor invocation in this file.
> > > > -DateCache::DateCache() = default;
> > > > +DateCache::DateCache()
> > > > +{
> > > > +#if PLATFORM(COCOA)
> > > > +    static std::once_flag onceKey;
> > > > +    std::call_once(onceKey, [&] {
> > > > +        CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), nullptr, timeZoneChangeNotification, kCFTimeZoneSystemTimeZoneDidChangeNotification, nullptr, CFNotificationSuspensionBehaviorDeliverImmediately);
> > > > +    });
> > > 
> > > Will it always notified into the main thread even if we are calling this
> > > from non main thread? (in JavaScriptCore.framework use case, it is possible
> > > that VM's first launch is on the non main thread.)
> > 
> > Per my understanding of the documentation
> > (https://developer.apple.com/documentation/corefoundation/1543316-
> > cfnotificationcenteraddobserver?language=objc), yes, it would always get
> > called on the main thread.
> 
> That said, we could also drop the main thread assertion. In this latest
> patch iteration, it doesn't really matter what thread the callback is called
> on..

Perfect! Thank you.
Comment 15 EWS 2022-02-24 12:10:00 PST
Committed r290449 (247751@main): <https://commits.webkit.org/247751@main>

All reviewed patches have been landed. Closing bug and clearing flags on attachment 453058 [details].
Comment 16 Radar WebKit Bug Importer 2022-02-24 12:11:23 PST
<rdar://problem/89432384>
Comment 17 Yusuke Suzuki 2022-03-23 14:49:53 PDT
*** Bug 218365 has been marked as a duplicate of this bug. ***