-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add fiber safety to crystal/once #15370
base: master
Are you sure you want to change the base?
Add fiber safety to crystal/once #15370
Conversation
02e70e6
to
d3eb541
Compare
Possible change:
Possible evolution: We can use the simpler |
As demonstrated by #15387 this is broken on Windows. I assume this is because we always start threads on Win32 yet We can always enable the MT safety of |
So we can use the getter and property macros in crystal/once
Removes the global mutex that prevents concurrent initializers to run, while still being able to detect reentrancy, by keeping a list of pending operations, protected by a global lock that doesn't block other initializers from running. Using a linked-list of stack allocated operations may sound inefficient compared to a `Hash` but there should usually not be many concurrent or parallel lazy initialization that following a few pointers will matter much in practice. This isn't very important for the eager initialization of class vars and constants that happen sequentially at the start of the program and before we even start threads, but we plan to reuse the feature to protect lazy initialization of class variables (i.e. the `class_getter` and `class_property` macros) when it would matter more. Introduces the `Crystal::Once` namespace. The initialization entry is now `Crystal::Once.init` instead of `Crystal.once_init`. Co-authored-by: David Keller <[email protected]>
This avoids having two implementations and it will simplify the call site when we protect class getter/property macros with lazy initializers: the flag is always a Bool not either a Crystal::Once::State or a Bool depending on the compiler.
94745fd
to
cb536e9
Compare
Removes the global
Mutex
(previously only used when thepreview_mt
flag is defined) that would prevent concurrent initializers to run in parallel, while still being able to detect reentrancy.It works by keeping a list of pending operations (one per
flag
pointer) protected by a global lock that doesn't block other initializers from running.Using a linked-list of stack allocated structs may sound inefficient compared to a
Hash
but there should usually not be many concurrent / parallel / nested lazy initializations that following a few pointers would be significant compared to calculating a hash and (re)allocating memory.This isn't very important for the initialization of class vars and constants (happens sequentially at the start of the program, before we even start threads) but we plan to reuse the feature to implement #14905 where the lazy initialization can kick in at any time.
Follow up to #15369 and #15371
CREDITS: @BlobCodes wrote the initial implementation in master...BlobCodes:crystal:perf/crystal-once-v2. I mostly removed the fences (SpinLock already deals with memory order), made it compatible with both the enum or bool variants, where the enum allows to skip over checking the operations when processing —maybe not very significant?
I shall read his code much more carefully now 🙇