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

fix(context): use a Fiber attribute for Context #1807

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

fbogsany
Copy link
Contributor

@fbogsany fbogsany commented Feb 5, 2025

Alternative to #1760

This PR implements Context storage using an attribute added to Fiber. This is inspired by Rails' implementation of IsolatedExecutionState. It adds 3 tests that fail with alternative implementations of Context storage. The tests represent the sort of Fiber-local storage and Thread-based Fiber-local variable manipulation performed by things like ActionController::Live. The approach used in this PR is a form of "security via obscurity" - side-stepping this kind of brute-force manipulation of Fiber-scoped data by stashing our data elsewhere. It won't prevent malicious manipulation of Fiber.current.opentelemetry_context, of course.

A benchmark is included with this PR exploring the design space with 8 different implementations of Context. Most of these, including the most performant implementations, are not safe in the presence of ActionController::Live, but they're included for comparison.

Results with Ruby 3.4.1 (note that this PR, FiberAttributeContext, is a performance increase over the existing implementation, ArrayContext):

Comparison:
FiberLocalArrayContext.with_value:  2697865.6 i/s
FiberLocalLinkedListContext.with_value:  2287481.6 i/s - 1.18x slower
FiberAttributeContext.with_value:  2241011.1 i/s - 1.20x slower
ArrayContext.with_value:  2187952.5 i/s - 1.23x slower
LinkedListContext.with_value:  1992425.8 i/s - 1.35x slower
ImmutableArrayContext.with_value:  1769365.2 i/s - 1.52x slower
FiberLocalImmutableArrayContext.with_value:  1761293.6 i/s - 1.53x slower
FiberLocalVarContext.with_value:  1517009.7 i/s - 1.78x slower

Comparison:
FiberLocalArrayContext.with_value recursive:   296797.6 i/s
FiberAttributeContext.with_value recursive:   236727.5 i/s - 1.25x slower
FiberLocalLinkedListContext.with_value recursive:   235673.7 i/s - 1.26x slower
ArrayContext.with_value recursive:   230727.3 i/s - 1.29x slower
LinkedListContext.with_value recursive:   205255.5 i/s - 1.45x slower
FiberLocalImmutableArrayContext.with_value recursive:   180077.8 i/s - 1.65x slower
ImmutableArrayContext.with_value recursive:   177865.2 i/s - 1.67x slower
FiberLocalVarContext.with_value recursive:   158603.8 i/s - 1.87x slower

The recursive benchmark measures 10 nested calls to Context.with_value, similar to a typical uses of nested spans (OpenTelemetry.tracer.in_span('foo') { ... }), which utilize Context.with_value.

Implementations:

  1. FiberLocalArrayContext - mutable array held in a Fiber[STACK_KEY] that corrects for shared ownership after Fiber.new (see below). Unfortunately, this trickery permits the owning Fiber to mutate the shared array after Fiber.new before the new Fiber uses it. It was a nice try, though. This is unsafe in the presence of bulk manipulation of Fiber#storage.
  2. FiberLocalLinkedListContext - linked list held in Fiber[STACK_KEY]. This is unsafe in the presence of bulk manipulation of Fiber#storage.
  3. FiberAttributeContext - mutable array held in Fiber.current.opentelemetry_context. This is the implementation proposed in this PR and is the most performant "safe" implementation.
  4. ArrayContext - mutable array held in Thread.current[STACK_KEY]. This is unsafe in the presence of bulk manipulation of Thread#[]. This is the existing implementation.
  5. LinkedListContext - linked list held in Thread.current[STACK_KEY]. This is unsafe in the presence of bulk manipulation of Thread#[].
  6. ImmutableArrayContext - immutable array held in Thread.current[STACK_KEY]. This is unsafe in the presence of bulk manipulation of Thread#[]. This is the implementation in fix(context): do not modify stack array directly when attach and detach #1760.
  7. FiberLocalImmutableArrayContext - immutable array held in a Fiber[STACK_KEY]. This is unsafe in the presence of bulk manipulation of Fiber#storage.
  8. FiberLocalVarContext - mutable array held in a Concurrent::FiberLocalVar. This is unsafe in the presence of bulk manipulation of Fiber#storage.

Tricky implementation of Fiber-local Stack that isn't quite safe enough:

  # NOTE: This is cool, but is isn't safe for concurrent use because it allows the
  # owner to modify the stack after it has been shared with another fiber.
  class Stack < Array
    def self.current
      s = Fiber[STACK_KEY] ||= new
      s.correct_owner!
    end

    def initialize
      super
      @owner = Fiber.current
    end

    def correct_owner!
      if @owner != Fiber.current
        Fiber[STACK_KEY] = self.class.new.replace(self)
      else
        self
      end
    end
  end

@fbogsany fbogsany changed the title Use a Fiber attribute for Context fix(context): use a Fiber attribute for Context Feb 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant