For those of you who have played around with some bleeding edge asynchronous HTTP servers like python’s tornado and Ruby’s goliath, I am sure there have been several ahaaaa! moments finding out things that need improvement. As part of these things, I find that structuring evented code and taking a bottom up approach to debugging nested calls can really dissuade you from asynchronous http approaches itself.
For the sake of convenience, I have taken a small block of code that you can write with eventmachine (Ruby’s most popular gem for building evented apps).
require 'eventmachine'
require 'em-http'
require 'fiber'
def asynchronous_call
current_fiber = Fiber.current
puts "making calls"
http = EventMachine::HttpRequest.new("http://www.google.com").get :timeout => 10
puts "declaring callbacks"
http.callback {
puts "resuming things";
current_fiber.resume(http);
}
http.errback {
puts "resuming things";
current_fiber.resume(http);
}
end
EventMachine.run do
asynchronous_call
puts "its all over"
end
##making calls
##declaring callbacks
##its all over
##resuming things
Its easy to see how cumbersome it gets to debug evented code apart from the fact that it may get less cohesive progressively. Taking a bottom-up approach to debug nested calls is painful. Obviously it is possible to thread your way out of the situation and get things in order. However, that would tend to contribute to making the code less cohesive as well.
Fibers are a construct in ruby that are not essentially a concurrency construct but can effectively solve some of the same problems that threads intend to. Fibers are really nothing but coroutines that can simply be entered at several points and exitted at several points as well. In fact, a subroutine is a specific strain of a coroutine where there is only a single entry and exit point.
require 'eventmachine'
require 'em-http'
require 'fiber'
def asynchronous_call
current_fiber = Fiber.current
puts "making calls"
http = EventMachine::HttpRequest.new("http://www.google.com").get :timeout => 10
puts "declaring callbacks"
http.callback {
puts "resuming things";
current_fiber.resume(http);
}
http.errback {
puts "resuming things";
current_fiber.resume(http);
}
puts "pausing fiber until call is complete"
Fiber.yield
end
EventMachine.run do
Fiber.new do
asynchronous_call
puts "its all over"
end.resume
end
##making calls
##declaring callbacks
##pausing fiber until call is complete
##resuming things
##its all over
Obviously a cleaner way to structure and write evented code. Not only do co routines help you clearly bind the order of execution of code to the order that is apparent, but they are quite efficient in memory as well.
Unlike threads, Fibers are blessed with a memory stack of 20KB each. Every time a coroutine/Fiber is paused and restarted, the overhead of switching context is avoided since the memory stack is .. well already available.
Clearly this comes at the limitation of not having 100s of Fibers concurrently paused. It will simply blow your stack. However, with the above example the cohesiveness they bring into the code is commendable.
Its important to bear in mind that Fiber i not really a concurrency construct. Threads are. Clearly understanding the differences and similarities will help writing better evented code.
If you wish to understand more about Fibers in Ruby, here are a list of must read articles.
One Trackback/Pingback
[...] is constrained to underutilization of CPU. i want cohesive scalable and simple code. for example: http://blog.ashwinraghav.com/2011/05…t-driven-code/ https://github.com/igrigorik/em-synchrony https://github.com/davidbalbert/eventless it isn't a [...]