I'm running this code snippet under Ruby 1.9.2:
require "eventmachine"
require "fiber"
EM.run do
fiber = Fiber.new do
current_fiber = Fiber.current
EM.add_timer(2) do
print "B"
current_fiber.resume("D")
end
Fiber.yield
end
print "A"
val = fiber.resume
print "C"
print val
EM.stop
end
I'm expecting the output to be "ABCD", with the program pausing for two seconds after the "A". However, instead it just prints out "AC" right away, then waits around for two seconds before exiting. What am I doing wrong?
(For reference, I'm trying to reproduce the em-synchrony-style behaviour described in this article without using em-synchrony.)
Edit: Here are some more details about what I'm ultimately trying to accomplish. I'm developing a Grape API running on Thin, and each route handler has to make various calls in series to datastores, ZooKeeper, other HTTP services, etc. before returning a response.
em-synchrony is really cool, but I keep running into issues with yielding from the root fiber or with results showing the non-synchronous symptoms of the case above. rack-fiber_pool also seems potentially useful, but I'm reluctant to commit to using it because, out of the box, it breaks all my Rack::Test unit tests.
I reduced my problems into the simple example above because I seem to have a fundamental misunderstanding about how fibers and EventMachine should be used together that is preventing me from using the more complex frameworks effectively.
You probably wanted something like this:
require "eventmachine"
require "fiber"
def value
current_fiber = Fiber.current
EM.add_timer(2) do
puts "B"
current_fiber.resume("D") # Wakes the fiber
end
Fiber.yield # Suspends the Fiber, and returns "D" after #resume is called
end
EM.run do
Fiber.new {
puts "A"
val = value
puts "C"
puts val
EM.stop
}.resume
puts "(Async stuff happening)"
end
This should yield the following result:
A
(Async stuff happening)
B
C
D
A more conceptual explanation:
Fibers help untangle asynchronous code because they chunks of code to be suspended and reanimated, much like manual threads. This allows for clever tricks regarding the order on which things happen. A small example:
fiberA = Fiber.new {
puts "A"
Fiber.yield
puts "C"
}
fiberB = Fiber.new {
puts "B"
Fiber.yield
puts "D"
}
fiberA.resume # prints "A"
fiberB.resume # prints "B"
fiberA.resume # prints "C"
fiberB.resume # prints "D"
So, when #resume
is called on a fiber, it resumes its execution, be it from the start of the block (for new fibers), or from a previous Fiber.yield
call, and then it executes until another Fiber.yield
is found or the block ends.
It is important to note that placing a sequence of actions inside a fiber is a way to state a temporal dependency between them (puts "C"
can't run before puts "A"
), while actions on "parallel" fibers can't count on (and shouldn't care about) whether or not the actions on the other fibers have executed: We would print "BACD" only by swapping the first two resume
calls.
So, here's how rack-fiber_pool
does its magic: It places every request your application receives inside a fiber (which implies order-independence), and then expects you to Fiber.yield
on IO actions, so that the server can accept other requests. Then, inside the EventMachine callbacks, you pass in a block that contains a current_fiber.resume
, so that your fiber is reanimated when the answer to the query/request/whatever is ready.
This is already a lengthy answer, but I can provide an EventMachine example if it's still not clear (I get this is a hairy concept to grok, I struggled a lot).
Update: I've created an example that might help anyone that is still struggling with the concepts: https://gist.github.com/renato-zannon/4698724. I recommend to run and play with it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With