RESOLVED FIXED 223033
cachedCGColor() and nsColor() are not thread-safe
https://bugs.webkit.org/show_bug.cgi?id=223033
Summary cachedCGColor() and nsColor() are not thread-safe
Sam Weinig
Reported 2021-03-10 10:57:37 PST
cachedCGColor() is not thread-safe, and will break if used of OffscreenCanvas or multi-queue display list replaying.
Attachments
Patch (1.59 KB, patch)
2021-04-16 15:32 PDT, Said Abou-Hallawa
no flags
Patch (1.68 KB, patch)
2021-04-16 16:00 PDT, Said Abou-Hallawa
no flags
Patch (3.07 KB, patch)
2021-04-19 15:42 PDT, Said Abou-Hallawa
ews-feeder: commit-queue-
Patch (5.47 KB, patch)
2021-04-19 17:34 PDT, Said Abou-Hallawa
cdumez: review+
ews-feeder: commit-queue-
Patch (5.50 KB, patch)
2021-04-19 19:24 PDT, Said Abou-Hallawa
no flags
Radar WebKit Bug Importer
Comment 1 2021-03-11 08:53:08 PST
Said Abou-Hallawa
Comment 2 2021-04-16 15:32:36 PDT
Chris Dumez
Comment 3 2021-04-16 15:34:00 PDT
Comment on attachment 426285 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=426285&action=review > Source/WebCore/platform/graphics/cg/ColorCG.cpp:145 > + LockHolder lockHolder(&cachedCGColorLock); You static initializations above are not thread-safe either as far as I know.
Chris Dumez
Comment 4 2021-04-16 15:34:15 PDT
(In reply to Chris Dumez from comment #3) > Comment on attachment 426285 [details] > Patch > > View in context: > https://bugs.webkit.org/attachment.cgi?id=426285&action=review > > > Source/WebCore/platform/graphics/cg/ColorCG.cpp:145 > > + LockHolder lockHolder(&cachedCGColorLock); > > You static initializations above are not thread-safe either as far as I know. *Your*
Yusuke Suzuki
Comment 5 2021-04-16 15:48:53 PDT
Comment on attachment 426285 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=426285&action=review >>> Source/WebCore/platform/graphics/cg/ColorCG.cpp:145 >>> + LockHolder lockHolder(&cachedCGColorLock); >> >> You static initializations above are not thread-safe either as far as I know. > > *Your* Is it correct? Lock has constexpr constructor, and compiler initializes the content in __DATA section.
Chris Dumez
Comment 6 2021-04-16 15:50:53 PDT
(In reply to Yusuke Suzuki from comment #5) > Comment on attachment 426285 [details] > Patch > > View in context: > https://bugs.webkit.org/attachment.cgi?id=426285&action=review > > >>> Source/WebCore/platform/graphics/cg/ColorCG.cpp:145 > >>> + LockHolder lockHolder(&cachedCGColorLock); > >> > >> You static initializations above are not thread-safe either as far as I know. > > > > *Your* > > Is it correct? Lock has constexpr constructor, and compiler initializes the > content in __DATA section. To clarify, I was talking about these kind of static initialization that happen before your lock, in the same function: ``` static NeverDestroyed<RetainPtr<CGColorRef>> transparentCGColor = createCGColor(color); return transparentCGColor.get().get(); ```
Yusuke Suzuki
Comment 7 2021-04-16 15:58:42 PDT
Comment on attachment 426285 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=426285&action=review >>>>> Source/WebCore/platform/graphics/cg/ColorCG.cpp:145 >>>>> + LockHolder lockHolder(&cachedCGColorLock); >>>> >>>> You static initializations above are not thread-safe either as far as I know. >>> >>> *Your* >> >> Is it correct? Lock has constexpr constructor, and compiler initializes the content in __DATA section. > > To clarify, I was talking about these kind of static initialization that happen before your lock, in the same function: > ``` > static NeverDestroyed<RetainPtr<CGColorRef>> transparentCGColor = createCGColor(color); > return transparentCGColor.get().get(); > ``` Right. Using std::call_once & LazyNeverDestroyed<> would be better here.
Said Abou-Hallawa
Comment 8 2021-04-16 16:00:58 PDT
Said Abou-Hallawa
Comment 9 2021-04-16 16:08:21 PDT
Comment on attachment 426285 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=426285&action=review >>>>>> Source/WebCore/platform/graphics/cg/ColorCG.cpp:145 >>>>>> + LockHolder lockHolder(&cachedCGColorLock); >>>>> >>>>> You static initializations above are not thread-safe either as far as I know. >>>> >>>> *Your* >>> >>> Is it correct? Lock has constexpr constructor, and compiler initializes the content in __DATA section. >> >> To clarify, I was talking about these kind of static initialization that happen before your lock, in the same function: >> ``` >> static NeverDestroyed<RetainPtr<CGColorRef>> transparentCGColor = createCGColor(color); >> return transparentCGColor.get().get(); >> ``` > > Right. Using std::call_once & LazyNeverDestroyed<> would be better here. You are right. The call to createCGColor() in the statement: static NeverDestroyed<RetainPtr<CGColorRef>> transparentCGColor = createCGColor(color); happens only once when this code is first hit. Subsequent calls will use the static cached value 'transparentCGColor'.
Chris Dumez
Comment 10 2021-04-16 17:13:44 PDT
Do we need to perf-test this (MotionMark for e.g.)? Seems like this might be a hot function although I am not familiar with this code.
Sam Weinig
Comment 11 2021-04-17 11:44:34 PDT
You may also want to do the same for nsColor() in ColorMac.mm, not sure if that is used from a background thread or not, but it unlikely to be hot and has the same problem.
Sam Weinig
Comment 12 2021-04-17 11:47:30 PDT
It would also be good if we could avoid the lock in the case of the transparent/white/black cases, as those represent a large percentage of the calls (last time I measured). Using std::call_once / LazyNeverDestroyed would allow the lock to be moved below the fast case.
Chris Dumez
Comment 13 2021-04-17 12:01:49 PDT
(In reply to Sam Weinig from comment #12) > It would also be good if we could avoid the lock in the case of the > transparent/white/black cases, as those represent a large percentage of the > calls (last time I measured). > > Using std::call_once / LazyNeverDestroyed would allow the lock to be moved > below the fast case. I Agree. That is also was Yusuke suggested earlier.
Darin Adler
Comment 14 2021-04-17 20:52:28 PDT
Comment on attachment 426288 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=426288&action=review Is the additional slowness from a lock in this function acceptable? > Source/WebCore/platform/graphics/cg/ColorCG.cpp:128 > + static Lock cachedCGColorLock; > + LockHolder lockHolder(&cachedCGColorLock); I suggest locking *after* the fast path special cases for transparentBlack, black, and white. The lock is not needed for that code.
Darin Adler
Comment 15 2021-04-17 20:52:54 PDT
Oh, I see that you already discussed all of this above.
Darin Adler
Comment 16 2021-04-17 20:53:44 PDT
Comment on attachment 426288 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=426288&action=review >> Source/WebCore/platform/graphics/cg/ColorCG.cpp:128 >> + LockHolder lockHolder(&cachedCGColorLock); > > I suggest locking *after* the fast path special cases for transparentBlack, black, and white. The lock is not needed for that code. I guess I’m wrong about that since we don’t use thread-safe statics. Even those need the locking the way this is currently written. That’s unfortunate and fixable if we needed to fix it.
Said Abou-Hallawa
Comment 17 2021-04-19 13:39:58 PDT
Comment on attachment 426288 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=426288&action=review >>> Source/WebCore/platform/graphics/cg/ColorCG.cpp:128 >>> + LockHolder lockHolder(&cachedCGColorLock); >> >> I suggest locking *after* the fast path special cases for transparentBlack, black, and white. The lock is not needed for that code. > > I guess I’m wrong about that since we don’t use thread-safe statics. Even those need the locking the way this is currently written. That’s unfortunate and fixable if we needed to fix it. I do not know what "thread-safe statics" means. Also in this page https://en.cppreference.com/w/cpp/thread/call_once, I read this statement "Initialization of function-local statics is guaranteed to occur only once even when called from multiple threads, and may be more efficient than the equivalent code using std::call_once." Does not this mean this statement will be safely executed once? static NeverDestroyed<RetainPtr<CGColorRef>> whiteCGColor = createCGColor(color);
Chris Dumez
Comment 18 2021-04-19 13:41:19 PDT
(In reply to Said Abou-Hallawa from comment #17) > Comment on attachment 426288 [details] > Patch > > View in context: > https://bugs.webkit.org/attachment.cgi?id=426288&action=review > > >>> Source/WebCore/platform/graphics/cg/ColorCG.cpp:128 > >>> + LockHolder lockHolder(&cachedCGColorLock); > >> > >> I suggest locking *after* the fast path special cases for transparentBlack, black, and white. The lock is not needed for that code. > > > > I guess I’m wrong about that since we don’t use thread-safe statics. Even those need the locking the way this is currently written. That’s unfortunate and fixable if we needed to fix it. > > I do not know what "thread-safe statics" means. Also in this page > https://en.cppreference.com/w/cpp/thread/call_once, I read this statement > > "Initialization of function-local statics is guaranteed to occur only once > even when called from multiple threads, and may be more efficient than the > equivalent code using std::call_once." > > Does not this mean this statement will be safely executed once? > > static NeverDestroyed<RetainPtr<CGColorRef>> whiteCGColor = > createCGColor(color); No because we compile with -fno-threadsafe-statics for perf reasons.
Chris Dumez
Comment 19 2021-04-19 13:43:38 PDT
(In reply to Chris Dumez from comment #18) > (In reply to Said Abou-Hallawa from comment #17) > > Comment on attachment 426288 [details] > > Patch > > > > View in context: > > https://bugs.webkit.org/attachment.cgi?id=426288&action=review > > > > >>> Source/WebCore/platform/graphics/cg/ColorCG.cpp:128 > > >>> + LockHolder lockHolder(&cachedCGColorLock); > > >> > > >> I suggest locking *after* the fast path special cases for transparentBlack, black, and white. The lock is not needed for that code. > > > > > > I guess I’m wrong about that since we don’t use thread-safe statics. Even those need the locking the way this is currently written. That’s unfortunate and fixable if we needed to fix it. > > > > I do not know what "thread-safe statics" means. Also in this page > > https://en.cppreference.com/w/cpp/thread/call_once, I read this statement > > > > "Initialization of function-local statics is guaranteed to occur only once > > even when called from multiple threads, and may be more efficient than the > > equivalent code using std::call_once." > > > > Does not this mean this statement will be safely executed once? > > > > static NeverDestroyed<RetainPtr<CGColorRef>> whiteCGColor = > > createCGColor(color); > > No because we compile with -fno-threadsafe-statics for perf reasons. AFAIK, the recommended pattern in WebKit for local statics when multiple threads are involved is LazyNeverDestroyed + std::call_once.
Darin Adler
Comment 20 2021-04-19 14:30:33 PDT
(In reply to Said Abou-Hallawa from comment #17) > I do not know what "thread-safe statics" means. C++ normally guarantees that initialization of static local variables <https://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables> happens exactly once. Compilers like clang refer to that C++ language feature as "thread-safe statics". In the WebKit project we turn that language feature off, putting the compiler into a non-standard mode that is more efficient, by compiling with clang's "-fno-threadsafe-statics", for example. It would be good for us to some day do the performance measurement work to prove that we can move to the standard C++ semantics without a major performance cost. If we could do that, we could then simplify a lot of our C++ code, which does extra work to get the thread safety when we need it.
Said Abou-Hallawa
Comment 21 2021-04-19 15:42:50 PDT
Darin Adler
Comment 22 2021-04-19 15:45:21 PDT
Comment on attachment 426482 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=426482&action=review What about the NSColor method? > Source/WebCore/platform/graphics/cg/ColorCG.cpp:134 > + std::call_once(onceFlag, [&color] { > + transparentCGColor.construct(createCGColor(color)); > + }); We don’t really need to capture color here. Could write this instead: std::call(onceFlag, [] { transparentCGColor.construct(createCGColor(Color::transparentBlack)); }); > Source/WebCore/platform/graphics/cg/ColorCG.cpp:142 > + std::call_once(onceFlag, [&color] { > + blackCGColor.construct(createCGColor(color)); > + }); Ditto. > Source/WebCore/platform/graphics/cg/ColorCG.cpp:150 > + std::call_once(onceFlag, [&color] { > + whiteCGColor.construct(createCGColor(color)); > + }); Ditto.
Darin Adler
Comment 23 2021-04-19 15:46:52 PDT
Comment on attachment 426482 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=426482&action=review >> Source/WebCore/platform/graphics/cg/ColorCG.cpp:134 >> + }); > > We don’t really need to capture color here. Could write this instead: > > std::call(onceFlag, [] { > transparentCGColor.construct(createCGColor(Color::transparentBlack)); > }); Wait, we need to capture transparentCGColor. I don’t understand how the code in the patch successfully compiles.
Darin Adler
Comment 24 2021-04-19 15:47:56 PDT
Comment on attachment 426482 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=426482&action=review > Source/WebCore/platform/graphics/cg/ColorCG.cpp:157 > + LockHolder lockHolder(&cachedCGColorLock); I think the preferred idiom is: auto holder = holdLock(cachedCGColorLock);
Chris Dumez
Comment 25 2021-04-19 15:52:01 PDT
Comment on attachment 426482 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=426482&action=review >>> Source/WebCore/platform/graphics/cg/ColorCG.cpp:134 >>> + }); >> >> We don’t really need to capture color here. Could write this instead: >> >> std::call(onceFlag, [] { >> transparentCGColor.construct(createCGColor(Color::transparentBlack)); >> }); > > Wait, we need to capture transparentCGColor. I don’t understand how the code in the patch successfully compiles. AFAIK, you don't need to capture static variables in lambdas.
Darin Adler
Comment 26 2021-04-19 15:52:36 PDT
Comment on attachment 426482 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=426482&action=review >>>> Source/WebCore/platform/graphics/cg/ColorCG.cpp:134 >>>> + }); >>> >>> We don’t really need to capture color here. Could write this instead: >>> >>> std::call(onceFlag, [] { >>> transparentCGColor.construct(createCGColor(Color::transparentBlack)); >>> }); >> >> Wait, we need to capture transparentCGColor. I don’t understand how the code in the patch successfully compiles. > > AFAIK, you don't need to capture static variables in lambdas. OK, got it! Then my comment stands as-is.
Said Abou-Hallawa
Comment 27 2021-04-19 17:34:12 PDT
Said Abou-Hallawa
Comment 28 2021-04-19 17:35:47 PDT
Comment on attachment 426482 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=426482&action=review I did similar changes in nsColor(). >>>>> Source/WebCore/platform/graphics/cg/ColorCG.cpp:134 >>>>> + }); >>>> >>>> We don’t really need to capture color here. Could write this instead: >>>> >>>> std::call(onceFlag, [] { >>>> transparentCGColor.construct(createCGColor(Color::transparentBlack)); >>>> }); >>> >>> Wait, we need to capture transparentCGColor. I don’t understand how the code in the patch successfully compiles. >> >> AFAIK, you don't need to capture static variables in lambdas. > > OK, got it! Then my comment stands as-is. Done. >> Source/WebCore/platform/graphics/cg/ColorCG.cpp:157 >> + LockHolder lockHolder(&cachedCGColorLock); > > I think the preferred idiom is: > > auto holder = holdLock(cachedCGColorLock); Done.
Chris Dumez
Comment 29 2021-04-19 18:33:19 PDT
Comment on attachment 426497 [details] Patch Looks correct. I hope this doesn't regress perf though.
Said Abou-Hallawa
Comment 30 2021-04-19 19:24:31 PDT
Created attachment 426510 [details] Patch Fix win port
EWS
Comment 31 2021-04-19 21:45:49 PDT
Committed r276283 (236765@main): <https://commits.webkit.org/236765@main> All reviewed patches have been landed. Closing bug and clearing flags on attachment 426510 [details].
Darin Adler
Comment 32 2021-10-18 11:20:34 PDT
This doesn’t work yet, because the return values are unretained objects that can be released from the cache on any other thread.
Chris Dumez
Comment 33 2021-10-18 13:24:59 PDT
(In reply to Darin Adler from comment #32) > This doesn’t work yet, because the return values are unretained objects that > can be released from the cache on any other thread. Indeed, will follow up via Bug 231909.
Note You need to log in before you can comment on or make changes to this bug.