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:
This should yield the following result:
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:
So, when
#resumeis called on a fiber, it resumes its execution, be it from the start of the block (for new fibers), or from a previousFiber.yieldcall, and then it executes until anotherFiber.yieldis 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 beforeputs "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 tworesumecalls.So, here’s how
rack-fiber_pooldoes its magic: It places every request your application receives inside a fiber (which implies order-independence), and then expects you toFiber.yieldon IO actions, so that the server can accept other requests. Then, inside the EventMachine callbacks, you pass in a block that contains acurrent_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.