lib/opentelemetry/context.rb in opentelemetry-api-1.0.0.rc1 vs lib/opentelemetry/context.rb in opentelemetry-api-1.0.0.rc2
- old
+ new
@@ -8,13 +8,16 @@
require 'opentelemetry/context/propagation'
module OpenTelemetry
# Manages context on a per-fiber basis
class Context
- KEY = :__opentelemetry_context__
EMPTY_ENTRIES = {}.freeze
+ STACK_KEY = :__opentelemetry_context_storage__
+ private_constant :EMPTY_ENTRIES, :STACK_KEY
+ DetachError = Class.new(OpenTelemetry::Error)
+
class << self
# Returns a key used to index a value in a Context
#
# @param [String] name The key name
# @return [Context::Key]
@@ -24,30 +27,52 @@
# Returns current context, which is never nil
#
# @return [Context]
def current
- Thread.current[KEY] ||= ROOT
+ stack.last || ROOT
end
- # Sets the current context
+ # Associates a Context with the caller's current Fiber. Every call to
+ # this operation should be paired with a corresponding call to detach.
#
- # @param [Context] ctx The context to be made active
- def current=(ctx)
- Thread.current[KEY] = ctx
+ # Returns a token to be used with the matching call to detach
+ #
+ # @param [Context] context The new context
+ # @return [Object] A token to be used when detaching
+ def attach(context)
+ s = stack
+ s.push(context)
+ s.size
end
+ # Restores the previous Context associated with the current Fiber.
+ # The supplied token is used to check if the call to detach is balanced
+ # with a corresponding attach call. A warning is logged if the
+ # calls are unbalanced.
+ #
+ # @param [Object] token The token provided by the matching call to attach
+ # @return [Boolean] True if the calls matched, false otherwise
+ def detach(token)
+ s = stack
+ calls_matched = (token == s.size)
+ OpenTelemetry.handle_error(exception: DetachError.new('calls to detach should match corresponding calls to attach.')) unless calls_matched
+
+ s.pop
+ calls_matched
+ end
+
# Executes a block with ctx as the current context. It restores
# the previous context upon exiting.
#
# @param [Context] ctx The context to be made active
# @yield [context] Yields context to the block
def with_current(ctx)
- prev = ctx.attach
+ token = attach(ctx)
yield ctx
ensure
- ctx.detach(prev)
+ detach(token)
end
# Execute a block in a new context with key set to value. Restores the
# previous context after the block executes.
@@ -56,14 +81,14 @@
# @param [Callable] Block to execute in a new context
# @yield [context, value] Yields the newly created context and value to
# the block
def with_value(key, value)
ctx = current.set_value(key, value)
- prev = ctx.attach
+ token = attach(ctx)
yield ctx, value
ensure
- ctx.detach(prev)
+ detach(token)
end
# Execute a block in a new context where its values are merged with the
# incoming values. Restores the previous context after the block executes.
@@ -73,34 +98,39 @@
# @param [Callable] Block to execute in a new context
# @yield [context, values] Yields the newly created context and values
# to the block
def with_values(values)
ctx = current.set_values(values)
- prev = ctx.attach
+ token = attach(ctx)
yield ctx, values
ensure
- ctx.detach(prev)
+ detach(token)
end
# Returns the value associated with key in the current context
#
# @param [String] key The lookup key
def value(key)
current.value(key)
end
def clear
- self.current = ROOT
+ stack.clear
end
def empty
- new(nil, EMPTY_ENTRIES)
+ new(EMPTY_ENTRIES)
end
+
+ private
+
+ def stack
+ Thread.current[STACK_KEY] ||= []
+ end
end
- def initialize(parent, entries)
- @parent = parent
+ def initialize(entries)
@entries = entries.freeze
end
# Returns the corresponding value (or nil) for key
#
@@ -118,36 +148,21 @@
# @param [Object] value Object to be stored under key
# @return [Context]
def set_value(key, value)
new_entries = @entries.dup
new_entries[key] = value
- Context.new(self, new_entries)
+ Context.new(new_entries)
end
# Returns a new Context with the current context's entries merged with the
# new entries
#
# @param [Hash] values The values to be merged with the current context's
# entries.
# @param [Object] value Object to be stored under key
# @return [Context]
def set_values(values) # rubocop:disable Naming/AccessorMethodName:
- Context.new(self, @entries.merge(values))
- end
-
- # @api private
- def attach
- prev = self.class.current
- self.class.current = self
- prev
- end
-
- # @api private
- def detach(ctx_to_attach = nil)
- OpenTelemetry.logger.warn 'Calls to detach should match corresponding calls to attach' if self.class.current != self
-
- ctx_to_attach ||= @parent || ROOT
- ctx_to_attach.attach
+ Context.new(@entries.merge(values))
end
ROOT = empty.freeze
end
end