Class: Fiber
- Inherits:
-
Object
- Object
- Fiber
- Defined in:
- lib/cfiber.rb,
lib/cfiber/debug.rb
Instance Attribute Summary collapse
-
#alive ⇒ Object
readonly
Returns the value of attribute alive.
-
#state ⇒ Object
Returns the value of attribute state.
Class Method Summary collapse
- .current ⇒ Object
-
.yield(*args) ⇒ Object
Fiber.yield delegates to the current fiber instance #yield method.
Instance Method Summary collapse
- #alive? ⇒ Boolean
-
#initialize(&block) ⇒ Fiber
constructor
Like an implicit Fiber.yield, the first one: it acts the same as Fiber.yield but for the &block call!.
-
#resume(*args) ⇒ Object
Wake a fiber up, passing its args to the resumed block.
-
#transfer(*args) ⇒ Object
Useful to implement coroutines.
- #transfer_yield(*args) ⇒ Object
-
#yield(*args) ⇒ Object
Pause the current fiber, while yielding its args in the fiber’s context.
Constructor Details
#initialize(&block) ⇒ Fiber
Like an implicit Fiber.yield, the first one: it acts the same as Fiber.yield but for the &block call!
14 15 16 17 18 19 20 21 |
# File 'lib/cfiber.rb', line 14 def initialize(&block) @alive = true unless @yield = callcc { |cc| cc } @args = Fiber.current.instance_exec(@args, &block) @resume.call end self.class.log.debug "initial state for #{self}: #{@yield}" end |
Instance Attribute Details
#alive ⇒ Object (readonly)
Returns the value of attribute alive.
8 9 10 |
# File 'lib/cfiber.rb', line 8 def alive @alive end |
#state ⇒ Object
Returns the value of attribute state.
9 10 11 |
# File 'lib/cfiber.rb', line 9 def state @state end |
Class Method Details
.current ⇒ Object
23 24 25 |
# File 'lib/cfiber.rb', line 23 def self.current Thread.current[:__cfiber] end |
.yield(*args) ⇒ Object
Fiber.yield delegates to the current fiber instance #yield method.
In order to retrieve the current fiber, a reference should have been stored. It’s thread-safe for it uses a Thread.current-local variable to store the ref.
33 34 35 |
# File 'lib/cfiber.rb', line 33 def self.yield(*args) Fiber.current.yield(*args) end |
Instance Method Details
#alive? ⇒ Boolean
163 164 165 |
# File 'lib/cfiber.rb', line 163 def alive? Fiber.current.instance_variable_get(:@alive) end |
#resume(*args) ⇒ Object
Wake a fiber up, passing its args to the resumed block.
First, it captures a new continuation to save a breakpoint, then it yields its arguments. Along the way, a reference for the current fiber object is stored within the current thread.
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/cfiber.rb', line 43 def resume(*args) # Using callcc as a breakpoint, storing the continuation outside the block # scope. This allows for the lazy-evaluation of an else clause. if @resume = callcc { |cc| cc } Fiber.current = self Fiber.current.state = :awake @args = args.size > 1 ? args : args.first @yield.call # the first #resume call will trigger the #initialize block # execution, until a call to Fiber.yield is made inside the # very block; whereas subsequent #resume calls will resume # the block's execution from the last Fiber.yield point. else log.debug "outputing inside #resume for #{self}" @args end rescue NoMethodError => e # undefined method call for nil:NilClass, that is @alive = false raise FiberError, "dead fiber #{Fiber.current} called" end |
#transfer(*args) ⇒ Object
Useful to implement coroutines.
FIXME: the tricky part is init. In the example above, f.transfer(100) triggers f.resume(100), which in turns reach g.transfer(101). This triggers g.yield(f.resume(101)) but this is wrong. One must atomically pause (yield) g and resume f.
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/cfiber.rb', line 135 def transfer(*args) if Fiber.current.nil? log.debug "" log.debug "cas 1" Thread.current[:__cfiber_transfer] = nil self.resume(*args) elsif Thread.current[:__cfiber_transfer].nil? log.debug "" log.debug "cas 2" log.debug "resuming #{self}" Thread.current[:__cfiber_transfer] = Fiber.current self.resume(*args) elsif Thread.current[:__cfiber_transfer].is_a?(Fiber) log.debug "" log.debug "cas 3" log.debug "pausing #{Fiber.current}" Thread.current[:__cfiber_transfer] = nil Fiber.yield(*args) #Fiber.yield(self.resume(*args)) # TODO: handle multiple coroutines else log.debug "" log.debug "cas 4" raise FiberError, "transfer mismatch" end end |
#transfer_yield(*args) ⇒ Object
81 82 83 84 85 86 87 |
# File 'lib/cfiber.rb', line 81 def transfer_yield(*args) self.state = :paused unless @yield = callcc { |cc| cc } puts "yielding #{self}" @resume.call end end |
#yield(*args) ⇒ Object
Pause the current fiber, while yielding its args in the fiber’s context.
First, it captures a new continuation to save a breakpoint, then it yields its arguments (“passing along any arguments that were passed to it”).
68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/cfiber.rb', line 68 def yield(*args) if @yield = callcc { |cc| cc } @args = args.size > 1 ? args : args.first @resume.call else log.debug "outputing inside #yield for #{self}" @args end rescue NoMethodError => e # undefined method call for nil:NilClass, that is @alive = false raise FiberError, "dead fiber #{Fiber.current} called" end |