From: "byroot (Jean Boussier)" Date: 2022-10-18T10:37:25+00:00 Subject: [ruby-core:110391] [Ruby master Bug#19062] Introduce `Fiber#locals` for shared inheritable state. Issue #19062 has been updated by byroot (Jean Boussier). > I'm concerned about the memory and performance cost of calling dup per fiber. It's extremely negligible. > I'm also concerned about hidden fibers (and threads) causing the visible behaviour to change unexpectedly. I really don't see how. > It also prevents shared optimisations which is something I want to use for my connection pool code. No, again, new fibers are initialized with a dup of the `locals` hash, so they have access to the same `ConnectionPool` instance, which can be mutable if you desire so. > It also stands to reason, that even if we followed your suggestion, simply having Fiber.curent.locals[:shared] = {} at the very root reintroduces the same behaviour as I'm proposing Yes and it's fine, only the top level store is shallow-copied, it's ok if you wish to use mutable values in there. Now imagine something like this: ```ruby module MyLibrary extend self def with_log_level(level) old_level = Fiber[:log_level] Fiber[:log_level] = level yield ensure Fiber[:log_level] = old_level end end ``` - Should a newly spawn fiber inherit whatever log_level it's parent set? Yes definitely. - Should a child fiber be able to flip the log level in its parent and sibblings? Definitely not. That is for this kind of use cases I think `dup` is necessary. If you don't `dup`, there this is absolutely not "locals" it's just some fairly contrived global variable. > But especially the performance cost concerns me a lot Hash#dup is something Ruby code does constantly. That isn't remotely a performance concern. ---------------------------------------- Bug #19062: Introduce `Fiber#locals` for shared inheritable state. https://bugs.ruby-lang.org/issues/19062#change-99697 * Author: ioquatix (Samuel Williams) * Status: Open * Priority: Normal * Assignee: ioquatix (Samuel Williams) * Backport: 2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN ---------------------------------------- After exploring , I felt uncomfortable about the performance of copying lots of inheritable attributes. Please review that issue for the background and summary of the problem. ## Proposal Introduce `Fiber#locals` which is a hash table of local attributes which are inherited by child fibers. ```ruby Fiber.current.locals[:x] = 10 Fiber.new do pp Fiber.current.locals[:x] # => 10 end ``` It's possible to reset `Fiber.current.locals`, e.g. ```ruby def accept_connection(peer) Fiber.new(locals: nil) do # This causes a new hash table to be allocated. # Generate a new request id for all fibers nested in this one: Fiber[:request_id] = SecureRandom.hex(32) @app.call(env) end.resume end ``` A high level overview of the proposed changes: ```ruby class Fiber def initialize(..., locals: Fiber.current.locals) @locals = locals || Hash.new end attr_accessor :locals def self.[] key self.current.locals[key] end def self.[]= key, value self.current.locals[key] = value end end ``` See the pull request for the full proposed implementation. ## Expected Usage Currently, a lot of libraries use `Thread.current[:x]` which is unexpectedly "fiber local". A common bug shows up when lazy enumerators are used, because it may create an internal fiber. Because `locals` are inherited, code which uses `Fiber[:x]` will not suffer from this problem. Any program that uses true thread locals for per-request state, can adopt the proposed `Fiber#locals` and get similar behaviour, without breaking on per-fiber servers like Falcon, because Falcon can "reset" `Fiber.current.locals` for each request fiber, while servers like Puma won't have to do that and will retain thread-local behaviour. Libraries like ActiveRecord can adopt `Fiber#locals` to avoid the need for users to opt into different "IsolatedExecutionState" models, since it can be transparently handled by the web server (see for more details). We hope by introducing `Fiber#locals`, we can avoid all the confusion and bugs of the past designs. -- https://bugs.ruby-lang.org/ Unsubscribe: