#!/usr/bin/ruby
#
# An object-oriented multiplexing asynchronous IO mechanism for Ruby.
#
# == Synopsis
#
# require 'io/reactor'
# require 'socket'
#
# reactorobj = IO::Reactor::new
#
# # Open a listening socket on port 1138 and register it with the
# # reactor. When a :read event occurs on it, it means an incoming
# # connection has been established
# sock = TCPServer::new('localhost', 1138)
# reactorobj.register( sock, :read ) {|sock,event|
# case event
#
# # Accept the incoming connection, registering it also with
# # the reactor; events on client sockets are handled by the
# # #clientHandler method.
# when :read
# clsock = sock.accept
# reactorobj.register( clsock, :read, :write,
# &method(:clientHandler) )
#
# # Errors on the main listening socket cause the whole
# # reactor to be shut down
# when :error
# reactorobj.clear
# $stderr.puts "Server error: Shutting down"
#
# else
# $stderr.puts "Unhandled event: #{event}"
# end
# }
#
# # Drive the reactor until it's empty with a single thread
# Thread::new { reactorobj.poll until reactorobj.empty? }
#
# == Author
#
# Michael Granger <ged@FaerieMUD.org>
#
# Copyright (c) 2002, 2003 The FaerieMUD Consortium. All rights reserved.
#
# This module is free software. You may use, modify, and/or redistribute this
# software under the same terms as Ruby itself.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.
#
# == Version
#
# $Id: reactor.rb 76 2003-12-13 22:11:42Z deveiant $
#
require 'delegate'
require 'rbconfig'
class IO
### An object-oriented multiplexing asynchronous IO reactor class.
class Reactor
### Class constants
Version = /([\d\.]+)/.match( %q{$Revision: 1.14 $} )[1]
Rcsid = %q$Id: reactor.rb 76 2003-12-13 22:11:42Z deveiant $
ValidEvents = [:read, :write, :error]
### Create and return a new IO reactor object.
def initialize
@handles = Hash::new {|hsh,key|
hsh[ key ] = {
:events => [],
:handler => nil,
:args => [],
}
}
@pendingEvents = Hash::new {|hsh,key| hsh[ key ] = []}
end
######
public
######
# The Hash of handles (instances of IO or its subclasses) associated with
# the reactor. The keys are the IO objects, and the values are a Hash of
# event/s => handler.
attr_reader :handles
# The Hash of unhandled events which occurred in the last call to #poll,
# keyed by handle.
attr_reader :pendingEvents
### Register the specified IO object with the reactor for events given as
### <tt>args</tt>. The reactor will test the given <tt>io</tt> for the
### events specified whenever #poll is called. See the #poll method for a
### list of valid events. If no events are specified, only <tt>:error</tt>
### events will be polled for.
###
### If a <tt>handler</tt> is specified, it will be called whenever the
### <tt>io</tt> has any of the specified <tt>events</tt> occur to it. It
### should take at least two parameters: the <tt>io</tt> and the event.
###
### If +args+ contains any objects except the Symbols '<tt>:read</tt>',
### '<tt>:write</tt>', or '<tt>:error</tt>', and a +handler+ is specified,
### they will be saved and passed to handler for each event.
###
### Registering a handle will unregister any previously registered
### event/handler+arguments pairs associated with the handle.
def register( io, *args, &handler )
events = [:read, :write, :error] & args
args -= events
self.unregister( io )
self.enableEvents( io, *events )
if handler
self.setHandler( io, *args, &handler )
else
self.setArgs( io, *args )
end
return self
end
alias_method :add, :register
### Returns +true+ if the given +io+ handle is registered with the reactor.
def registered?( io )
return @handles.key?( io )
end
### Add the specified +events+ to the list that will be polled for on the
### given +io+ handle.
def enableEvents( io, *events )
@handles[ io ][:events] |= events
end
### Remove the specified +events+ from the list that will be polled for on
### the given +io+ handle.
def disableEvents( io, *events )
raise RuntimeError, "Cannot disable the :error event" if
events.include?( :error )
@handles[ io ][:events] -= events
end
### Set the handler for events on the given +io+ handle to the specified
### +handler+. If any +args+ are present, they will be passed as an exploded
### array to the handler for each event. Returns the previously-registered
### handler, if any.
def setHandler( io, *args, &handler )
rval = @handles[ io ][:handler]
@handles[ io ][:handler] = handler
self.setArgs( io, *args )
return rval
end
### Remove and return the handler for events on the given +io+ handle.
def removeHandler( io )
rval = @handles[ io ][:handler]
@handles[ io ][:handler] = nil
self.removeArgs( io )
return rval
end
### Set the additional arguments to pass to the handler for the given +io+
### handle on each event to the given +args+.
def setArgs( io, *args )
rval = @handles[ io ][:args]
@handles[ io ][:args] = args
return rval
end
### Remove the arguments for the given handle to the given +args+.
def removeArgs( io )
return @handles[ io ][:args].clear
end
### Remove the specified <tt>io</tt> from the receiver's list of registered
### handles, if present. Returns the handle if it was registered, or
### <tt>nil</tt> if it was not.
def unregister( io )
@pendingEvents.delete( io )
@handles.delete( io )
end
alias_method :remove, :unregister
### Clear all registered handles from the poll object. Returns the handles
### that were cleared.
def clear
rv = @handles.keys
@pendingEvents.clear
@handles.clear
return rv
end
### Poll the handles registered to the reactor for pending events. The
### following event types are defined:
###
### [<tt>:read</tt>]
### Data may be read from the handle without blocking.
### [<tt>:write</tt>]
### Data may be written to the handle without blocking.
### [<tt>:error</tt>]
### An error has occurred on the handle. This event type is always
### enabled, regardless of whether or not it is passed as one of the
### <tt>events</tt>.
###
### Any handlers specified when the handles were registered are run for
### those handles with events. If a block is given, it will be invoked once
### for each handle which doesn't have an explicit handler. If no block is
### given, events without explicit handlers are inserted into the reactor's
### <tt>pendingEvents</tt> attribute.
###
### The <tt>timeout</tt> argument is the number of floating-point seconds to
### wait for an event before returning (ie., fourth argument to the
### underlying <tt>select()</tt> call); negative timeout values will cause
### #poll to block until there is at least one event to report.
###
### This method returns the number of handles on which one or more events
### occurred.
def poll( timeout=-1 ) # :yields: io, eventMask
timeout = timeout.to_f
@pendingEvents.clear
count = 0
unless @handles.empty?
timeout = nil if timeout < 0
eventedHandles = self.getPendingEvents( timeout )
# For each event of each io that had an event happen, call any
# associated callback, or any provided block, or failing both of
# those, add the event to the hash of unhandled pending events.
eventedHandles.each {|io,events|
count += 1
events.each {|ev|
args = @handles[ io ][:args]
if @handles[ io ][:handler]
@handles[ io ][:handler].call( io, ev, *args )
elsif block_given?
yield( io, ev, *args )
else
@pendingEvents[io].push( ev )
end
}
}
end
return count
end
### Returns <tt>true</tt> if no handles are associated with the receiver.
def empty?
@handles.empty?
end
#########
protected
#########
### Select on the registered handles, returning a Hash of handles => events
### for handles which had events occur.
def getPendingEvents( timeout )
eventHandles = IO::select( self.getReadHandles, self.getWriteHandles,
@handles.keys, timeout ) or return {}
eventHash = Hash::new {|hsh,io| hsh[io] = []}
# Fill in the hash with pending events of each type
[:read, :write, :error].each_with_index {|event,i|
eventHandles[i].each {|io| eventHash[io].push( event )}
}
return eventHash
end
### Return an Array of handles which have handlers for the <tt>:read</tt>
### event.
def getReadHandles
@handles.
find_all {|io,hsh| hsh[:events].include?( :read )}.
collect {|io,hsh| io}
end
### Return an Array of handles which have handlers for the <tt>:write</tt>
### event.
def getWriteHandles
@handles.
find_all {|io,hsh| hsh[:events].include?( :write )}.
collect {|io,hsh| io}
end
end # class Reactor
end # class IO
syntax highlighted by Code2HTML, v. 0.9.1