Project

General

Profile

Feature #19062

Updated by ioquatix (Samuel Williams) over 1 year ago

After exploring <https://bugs.ruby-lang.org/issues/19058>, I felt uncomfortable about the performance issue of 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: nil) 
     @locals = locals 
   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 <https://github.com/ruby/ruby/pull/6566> for the full proposed implementation. Pull Request: https://github.com/ruby/ruby/pull/6566 

 ## 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 <https://github.com/rails/rails/pull/43596> for more details). 

 We hope by introducing `Fiber#locals`, we can avoid all the confusion and bugs of the past designs.

Back