7

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.

breaker
  • 73
  • 1
  • 5
  • About `Rack::Test`, you can try to `use` it only on your `config.ru`, and wrap your tests in Fibers using an rspec `around` block or something like that. – Renato Zannon Oct 04 '12 at 05:26
  • 1
    Due to Grape not playing nice with added middleware (always putting it last instead of first where it needs to be in this case) I ended up having to wrap the Grape app using Rack::Builder in my config and adding Rack::FiberPool to that. Which bypassed the unit testing issue entirely. :) – breaker Oct 05 '12 at 04:32
  • You are stopping EM right after `AC` has been printed. – phil pirozhkov Oct 06 '12 at 16:57

1 Answers1

10

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.

Renato Zannon
  • 28,805
  • 6
  • 38
  • 42
  • Thanks -- though this doesn't really help, for example, in the context of serving something from a web app, which is my ultimate goal. I'd like to do a bunch of asynchronous actions like this in series when servicing an HTTP request, then return some result to the client. – breaker Oct 04 '12 at 03:53
  • 1
    Well, you probably should've mentined it on your question then :) Can you edit the question to expand it a little on what exactly you want? But anyway, the basic principles are the same, though in a webapp environment you're probably better off using `em-synchrony` along with something like [`rack-fiber_pool`](https://github.com/mperham/rack-fiber_pool) instead of rolling your own infrastructure. – Renato Zannon Oct 04 '12 at 04:15
  • 1
    Sure; I added more details. I've been wrestling with em-synchrony and rack-fiber_pool for half a day without progress, so I figured I was just missing out on some fundamental concept. – breaker Oct 04 '12 at 04:49
  • Thanks! I think I've got a much better grasp on it now. The bit about having all the sequential actions wrapped in the same fiber to establish ordering definitely helps. – breaker Oct 04 '12 at 05:43
  • Nope, nothing more needed; I got it all working today. Thanks! – breaker Oct 05 '12 at 04:29