Skip to content
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

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

ysbaddaden
Copy link
Contributor

@ysbaddaden ysbaddaden commented Jan 24, 2025

Removes the global Mutex (previously only used when the preview_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 🙇

ysbaddaden and others added 6 commits January 24, 2025 15:26
Replaces the implicit initialization of Thread and Fiber class variables
with an explicit initializer since both should be initialized before
`__crystal_once_init` is called.

This is working for now because the `class_[getter|property]` macros
in Thread and Fiber don't protect against either recursion nor parallel
initializations, but we plan to protect them (using Mutex) which would
immediately break with an infinite recursion:

Mutex#lock -> Fiber#current -> Thread#current -> Mutex#lock -> ...
Prefer a def explictly called by `Crystal.init_runtime` over the
implicit `__crystal_once_init` fun injected by the compiler.

The fun is no longer defined for new compiler builds, however it's still
defined to support older compiler releases.
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]>
@ysbaddaden ysbaddaden force-pushed the refactor/add-fiber-safety-to-crystal-once branch from 02e70e6 to d3eb541 Compare January 24, 2025 14:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: In Progress
Development

Successfully merging this pull request may close these issues.

1 participant