Waiting for asynchronous calls with EventMachine and Ruby fibers

I run this piece of code 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 expect the output to be “ABCD”, with the program pausing for two seconds after “A”. However, instead, it simply prints “AC” immediately, then waits two seconds before exiting. What am I doing wrong?

(For reference, I'm trying to reproduce the em-synchrony style behavior described in this article without using em-synchrony.)

Edit: Here are some details about what I'm ultimately trying to accomplish. I am developing a Thin-based Grape API, and each route handler must make various calls sequentially to the data stores, ZooKeeper, other HTTP services, etc., before returning the response.

em-synchrony is really cool, but I continue to run into problems with root fiber assignment or results showing non-synchronous symptoms of the above case. rack-fiber_pool also seems potentially useful, but I am reluctant to commit to using it because out of the box it breaks all my Rack :: Test tests.

I cited my problems in the simple example above because I seem to have a fundamental misunderstanding of how fibers and EventMachine should be used together, which prevents me from using more complex structures efficiently.

+6
source share
1 answer

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 lead to the following result:

 A (Async stuff happening) B C D 

A more conceptual explanation:

Fibers help unravel asynchronous code because they block code that will be paused and reanimated, like manual threads. This allows you to use smart tricks regarding the order in which events occur. 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 onto the fiber, it resumes execution, whether from the beginning of the block (for new fibers) or from a previous call to Fiber.yield , and then runs until the next Fiber.yield or the end of the block.

It is important to note that placing a sequence of actions inside a fiber is a way to indicate a time relationship between them ( puts "C" cannot work until puts "A" ), while actions on "parallel" fibers can "t be calculated (and not care) about whether actions were performed on other fibers: we will print "BACD" only by replacing the first two calls to resume .

So this is how rack-fiber_pool does its magic: it puts every request your application receives inside the fiber (which implies order independence), and then Fiber.yield expects you for I / O, so the server can accept other requests. Then, inside the EventMachine callbacks, you pass a block containing current_fiber.resume , so that your fiber is reanimated when the response to the request / request / is ready.

This is already a long answer, but I can present an example of EventMachine if it is still not clear (I get that it is a hairy concept, to look, I fought a lot).


Update . I created an example that can help anyone still struggling with concepts: https://gist.github.com/renato-zannon/4698724 . I recommend running and playing with it.

+8
source

Source: https://habr.com/ru/post/926904/


All Articles