From: "nevans (Nicholas Evans)" Date: 2022-12-02T16:23:32+00:00 Subject: [ruby-core:111159] [Ruby master Feature#19078] Introduce `Fiber#storage` for inheritable fiber-scoped variables. Issue #19078 has been updated by nevans (Nicholas Evans). @Dan0042 (Daniel DeLorme) wrote in #note-21: > What about when we want per-ractor storage... a fourth type? FWIW, `Ractor#[]` has existed since 3.0: https://docs.ruby-lang.org/en/3.0/Ractor.html#method-i-5B-5D :) > Per-ractor inheritable storage... a fifth type? In my opinion, absolutely not. The following is just a hypothetical--if and when I have an actual proposal, I'll post it in another ticket. :) This feature is brand-new so it has some "rough edges", but I think should be updated to automatically share all ractor-sharable values. Of course, the big question is: what to do for non-ractor-sharable values? Hypothetically: 1) By default, simple raise an exception from Ractor.new, just like it does any other time you accidentally attempt to send non-sharable state into a new Ractor. 2) Add a kwarg to Ractor.new, and a method to the fiber storage, both of which will simply drop all non-ractor sharable storage. Either way: non-inheritance should always be an explicit opt-out. 3) Add an API for more fine-grained control at certain points, so that library authors can designate different behaviors via callback. Because often the desired behavior is more complicated than simply dropping or inheriting a variable: e.g. rather than inherit the current task id, a new fiber/thread/ractor might automatically create a brand new task which is a child of the current task. For inspiration here, we might look to Kotlin, which delegates a great deal of functionality to the current `coroutineContext[CoroutineDispatcher]` (this would be handled entirely by library code, and invisible to most users), to `coroutineContext[Job]` (which combines with `CoroutineScope` and a few others to handle almost everything related to structured concurrency), and any [ThreadContextElement] that's been added to the context. That last one is mostly used for backward compatibility with libraries that expect thread-locals. Similarly, go contexts delegate internally to `context.Value(cancelContextKey)`, where `cancelContextKey` is a private package var which is used purely for comparison-by-identity. This is essentially the `@key = Object.new` trick demonstrated by @ioquatix in #note-10, and it's the recommended usage pattern for context keys in go. [ThreadContextElement]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-thread-context-element/ Anyway: my goal here isn't to propose any particular approach, but to show this feature might be used as a foundation for other future features. IMO, fine-grained inheritance control might be out-of-scope for 3.2 (at least, any API we put together now would need to be marked "experimental"). I simply wanted to show how other languages build on a similar foundation to handle the problems you mentioned. ---------------------------------------- Feature #19078: Introduce `Fiber#storage` for inheritable fiber-scoped variables. https://bugs.ruby-lang.org/issues/19078#change-100443 * Author: ioquatix (Samuel Williams) * Status: Closed * Priority: Normal * Assignee: ioquatix (Samuel Williams) ---------------------------------------- Pull Request: https://github.com/ruby/ruby/pull/6612 This is an evolution of the previous ideas: - https://bugs.ruby-lang.org/issues/19058 - https://bugs.ruby-lang.org/issues/19062 This PR introduces fiber scoped variables, and is a solution for problems like . The main interface is: ```ruby Fiber[key] = value Fiber[key] # => value ``` The variables are scoped (local to) a fiber and inherited into child fibers and threads. ```ruby Fiber[:request_id] = SecureRandom.hex(16) Fiber.new do p Fiber[:request_id] # prints the above request id end ``` The fiber scoped variables are stored and can be accessed: ```ruby Fiber.current.storage # => returns a Hash (copy) of the internal storage. Fiber.current.storage= # => assigns a Hash (copy) to the internal storage. ``` Fiber itself has one new keyword argument: ``` Fiber.new(..., storage: hash, false, undef, nil) ``` This can control how the fiber variables are setup in a child context. To minimise the performance overhead of some of the implementation choices, we are also simultaneously implementing . ## Examples ### Request loop ```ruby Thread.new do while request = queue.pop Fiber.new(storage: {id: SecureRandom.hex(16)}) do handle_request.call(request) end end end ``` OR ```ruby Thread.new do while request = queue.pop Fiber.current.storage = {id: SecureRandom.hex(16)} handle_request.call(request) end end ``` -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/