Project

General

Profile

Actions

Feature #19062

closed

Introduce `Fiber#locals` for shared inheritable state.

Added by ioquatix (Samuel Williams) about 2 years ago. Updated about 2 years ago.

Status:
Closed
Target version:
-
[ruby-core:110320]

Description

After exploring https://bugs.ruby-lang.org/issues/19058, 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.

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.

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:

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 https://github.com/ruby/ruby/pull/6566 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 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.


Related issues 2 (0 open2 closed)

Related to Ruby master - Feature #19058: Introduce `Fiber.inheritable` attributes/variables for dealing with shared state.Closedioquatix (Samuel Williams)Actions
Related to Ruby master - Feature #19078: Introduce `Fiber#storage` for inheritable fiber-scoped variables.Closedioquatix (Samuel Williams)Actions
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0