From: Daniel Brockman Date: 2005-08-26T00:13:29+09:00 Subject: [ANN] EventLoop 0.0.20050825.1600 Hi list, Due to the somewhat popular demand of an event loop for Ruby, I've recently been working on packaging the one I've written for a network application of mine (Refusde, an NMDC client). With the help of Tilman Sauerbeck, I've now managed to put together some documentation and a gem/tarball of what I've decided is going to be the first publicly announced version. Here's the canonical short package overview: EventLoop is a simple IO::select-based main event loop featuring IO event notification and timeout callbacks. It comes with a signal system inspired by that of GLib. The code is licensed under the GPL and can be found at . At this point, some of you will probably want to see an example of how it works --- a kind of screenshot. For this purpose, I chose to implement a simple asynchronous buffered IO reader: require "event-loop" class BufferedReader include SignalEmitter define_signals :line, :done def initialize(io, eol="\n") yield self if block_given? io = File.new(io) if io.kind_of? String buffer = String.new io.on_readable do begin buffer << io.readpartial(1024) while i = buffer.index(eol) signal :line, buffer.slice!(0, i) buffer.slice!(0, eol.size) end rescue EOFError signal :done, buffer io.close end end end end reader = BufferedReader.new("/etc/passwd") do |r| r.on_line { |content| puts "Line: #{content}" } r.on_done { |leftover| puts "Done: #{leftover}" } r.on_done { EventLoop.quit } end EventLoop.run See how easy the event loop is to use, and how nicely it blends into the rest of Ruby? For good measure, maybe I should also attach a section of the manual (i.e., the README file) that describes how event loops fit into the rest of the world: The Event Loop ============== This section explains how IO multiplexing works in general (albeit briefly and not very in-depth), and specifically the issues relevant for Ruby applications. You may safely skip it if you (a) already know this subject, or (b) don't care. Plain ol' blocking IO works well when you're reading from just a single file descriptor. But when you're interested in a whole bunch of FDs, you can't wait for any single one of them to become readable or writable, because then you'll inevitably miss that happening to the other ones. Instead, you need a multiplexer that can wait for them *all at once*. There are a handful of low-level multiplexing primitives: ‘select’, ‘poll’, ‘epoll’, ‘/dev/poll’, and ‘kqueue’. In addition, there are portable low-level wrapper libraries such as libevent, which can use any of those primitives. The event loop in this package uses the standard ‘select’ wrapper shipped with Ruby, ‘IO::select’. But in the future, I'd like to use libevent instead, because that'd be cooler. Most applications use a higher-level abstraction built on top of the low-level multiplexer, usually called a ‘main loop’, an ‘event loop’, or an ‘event source’. There are also libraries such as liboop, which generalizes the event source and event sink concepts, so that components (event sinks) written against liboop become event-source-agnostic. Actually, the combination of blocking IO and Ruby's green threads works well in most cases where you would normally use an event loop. When you call ‘IO#read’ on an empty file descriptor, for instance, Ruby suspends that thread until its internal event loop, known as the scheduler (currently based on ‘select’), determines that the file descriptor has become readable. In particular, Ruby never calls the low-level ‘read’ function unless it knows that it will not block (because ‘select’ said it wouldn't, but see below). There are several reasons why you would use an event loop such as the one implemented by this library instead of not-so-plain ol' blocking IO with Ruby's green threads. First of all, you may consider the event loop API more pleasant than Ruby's threads and not-quite-blocking IO. Otherwise, don't listen to me; go on using the latter. :-) Blocking IO can occasionally cause unexpected problems. For example, in some cases a blocking read *can* block even though select said that the file descriptor was readable. This problem may be rare (it can happen, for instance, when the checksum of a piece of data fails to match the payload), but the bottom line is that non-blocking IO is safer. Perhaps most importantly, while Ruby's threads are green, they are still effectively preemptively scheduled, with all the implications thereof — in a word, synchronization hell. By contrast, event handlers are executed in a strictly sequential manner; an event loop will never run two event handlers simultaneously. (Though, of course, all bets are off if you run multiple event loops in separate threads.) -- Daniel Brockman