Like many others, we use Ruby EventMachine to make most use of CPU time in the cloud. Instead of blocking and waiting for responses from remote systems, we just memorize what is to be done and return to the main loop, taking care other things that are currently going on. The asynchronous programming model is callback-based and makes otherwise pretty Ruby code look a bit unwieldy:
server.get(key) do |content| content.modify! server.set(key, content) do proceed_doing_stuff end end
Call with Current Continuation (callcc) and the advent of Fibers in Ruby 1.9 to the rescue. They can help us make the code look pretty again:
content = server.get(key) content.modify! server.set(key, content) proceed_doing_stuff
This works because
server.set can obtain the remaining code as a reference (like the callback block before): the Current Continuation. The semantics are only subtly different, we can regard these simply as two ways to express code.
This article is not a praise on the pretty syntax and features of Ruby. Remember what we gain with the asynchronous programming model: not only raw performance by avoiding threads and locking, but also the convenience of always running in a single thread and not having the current state made inconsistent by getting preempted by other code.
Now what are Fibers introducing? We still run only one high-performing Ruby thread, but we gain cooperative multi-tasking. The situations a coder may find him/herself in are quite controllable through these “well-definable” preemption points. Still, to illustrate take a look at this stupid but trivial example:
received, sent = get_stats content = server.get(key) received += content.size content.modify! server.set(key, content) sent += content.size set_stats received, sent proceed_doing_stuff
This is the code from above, extended with very simple statistics collection. The statistics are global state, but we’re safe because we don’t use threads. Are we?
No. All the issues come back to life that are difficult with writing threaded code. During
server.set the Fiber has been yielded and resumed, and another Fiber may have modified the stats in the meanwhile. This could have been caused by the very same code, just handling another server from your connection pool.
By using callback blocks instead of Fibers the problem is still there but the code looks explicitly asynchronous. They tell the reader: “I am running somewhat later, please pay attention to any shared state!”