File: fiber_local_var.rb

package info (click to toggle)
ruby-concurrent 1.3.5-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 4,136 kB
  • sloc: ruby: 30,875; java: 6,128; ansic: 265; makefile: 26; sh: 19
file content (109 lines) | stat: -rw-r--r-- 3,093 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
require 'concurrent/constants'
require_relative 'locals'

module Concurrent

  # A `FiberLocalVar` is a variable where the value is different for each fiber.
  # Each variable may have a default value, but when you modify the variable only
  # the current fiber will ever see that change.
  #
  # This is similar to Ruby's built-in fiber-local variables (`Thread.current[:name]`),
  # but with these major advantages:
  # * `FiberLocalVar` has its own identity, it doesn't need a Symbol.
  # * Each Ruby's built-in fiber-local variable leaks some memory forever (it's a Symbol held forever on the fiber),
  #   so it's only OK to create a small amount of them.
  #   `FiberLocalVar` has no such issue and it is fine to create many of them.
  # * Ruby's built-in fiber-local variables leak forever the value set on each fiber (unless set to nil explicitly).
  #   `FiberLocalVar` automatically removes the mapping for each fiber once the `FiberLocalVar` instance is GC'd.
  #
  # @example
  #   v = FiberLocalVar.new(14)
  #   v.value #=> 14
  #   v.value = 2
  #   v.value #=> 2
  #
  # @example
  #   v = FiberLocalVar.new(14)
  #
  #   Fiber.new do
  #     v.value #=> 14
  #     v.value = 1
  #     v.value #=> 1
  #   end.resume
  #
  #   Fiber.new do
  #     v.value #=> 14
  #     v.value = 2
  #     v.value #=> 2
  #   end.resume
  #
  #   v.value #=> 14
  class FiberLocalVar
    LOCALS = FiberLocals.new

    # Creates a fiber local variable.
    #
    # @param [Object] default the default value when otherwise unset
    # @param [Proc] default_block Optional block that gets called to obtain the
    #   default value for each fiber
    def initialize(default = nil, &default_block)
      if default && block_given?
        raise ArgumentError, "Cannot use both value and block as default value"
      end

      if block_given?
        @default_block = default_block
        @default = nil
      else
        @default_block = nil
        @default = default
      end

      @index = LOCALS.next_index(self)
    end

    # Returns the value in the current fiber's copy of this fiber-local variable.
    #
    # @return [Object] the current value
    def value
      LOCALS.fetch(@index) { default }
    end

    # Sets the current fiber's copy of this fiber-local variable to the specified value.
    #
    # @param [Object] value the value to set
    # @return [Object] the new value
    def value=(value)
      LOCALS.set(@index, value)
    end

    # Bind the given value to fiber local storage during
    # execution of the given block.
    #
    # @param [Object] value the value to bind
    # @yield the operation to be performed with the bound variable
    # @return [Object] the value
    def bind(value)
      if block_given?
        old_value = self.value
        self.value = value
        begin
          yield
        ensure
          self.value = old_value
        end
      end
    end

    protected

    # @!visibility private
    def default
      if @default_block
        self.value = @default_block.call
      else
        @default
      end
    end
  end
end