io-reactor 0.05
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CATALOG +10 -0
- data/ChangeLog +303 -0
- data/README +77 -0
- data/examples/chatserver.rb +347 -0
- data/install.rb +85 -0
- data/io-reactor.gemspec +21 -0
- data/lib/io/reactor.rb +317 -0
- data/makedocs.rb +79 -0
- data/test.rb +212 -0
- data/utils.rb +484 -0
- metadata +53 -0
data/install.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
#
|
3
|
+
# $Date: 2003/07/21 14:21:30 $
|
4
|
+
# Copyright (c) 2000 Masatoshi SEKI
|
5
|
+
#
|
6
|
+
# install.rb is copyrighted free software by Masatoshi SEKI.
|
7
|
+
# You can redistribute it and/or modify it under the same term as Ruby.
|
8
|
+
|
9
|
+
require 'rbconfig'
|
10
|
+
require 'find'
|
11
|
+
require 'ftools'
|
12
|
+
|
13
|
+
include Config
|
14
|
+
|
15
|
+
class Installer
|
16
|
+
protected
|
17
|
+
def install(from, to, mode = nil, verbose = false)
|
18
|
+
str = "install '#{from}' to '#{to}'"
|
19
|
+
str += ", mode=#{mode}" if mode
|
20
|
+
puts str if verbose
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
def makedirs(*dirs)
|
25
|
+
for d in dirs
|
26
|
+
puts "mkdir #{d}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(test=false)
|
31
|
+
@version = CONFIG["MAJOR"]+"."+CONFIG["MINOR"]
|
32
|
+
@libdir = File.join(CONFIG["libdir"], "ruby", @version)
|
33
|
+
@sitelib = find_site_libdir
|
34
|
+
@ftools = (test) ? self : File
|
35
|
+
end
|
36
|
+
public
|
37
|
+
attr_reader(:libdir, :sitelib)
|
38
|
+
|
39
|
+
private
|
40
|
+
def find_site_libdir
|
41
|
+
site_libdir = $:.find {|x| x =~ /site_ruby$/}
|
42
|
+
if !site_libdir
|
43
|
+
site_libdir = File.join(@libdir, "site_ruby")
|
44
|
+
elsif site_libdir !~ Regexp::new( Regexp.quote(@version) )
|
45
|
+
site_libdir = File.join(site_libdir, @version)
|
46
|
+
end
|
47
|
+
site_libdir
|
48
|
+
end
|
49
|
+
|
50
|
+
public
|
51
|
+
def files_in_dir(dir)
|
52
|
+
list = []
|
53
|
+
Find.find(dir) do |f|
|
54
|
+
list.push(f)
|
55
|
+
end
|
56
|
+
list
|
57
|
+
end
|
58
|
+
|
59
|
+
public
|
60
|
+
def install_files(srcdir, files, destdir=@sitelib)
|
61
|
+
path = []
|
62
|
+
dir = []
|
63
|
+
|
64
|
+
for f in files
|
65
|
+
next if (f = f[srcdir.length+1..-1]) == nil
|
66
|
+
path.push f if File.ftype(File.join(srcdir, f)) == 'file'
|
67
|
+
dir |= [ File.dirname(File.join(destdir, f)) ]
|
68
|
+
end
|
69
|
+
@ftools.makedirs(*dir)
|
70
|
+
for f in path
|
71
|
+
@ftools.install(File.join(srcdir, f), File.join(destdir, f), nil, true)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
public
|
76
|
+
def install_rb
|
77
|
+
intall_files('lib', files_in_dir('lib'))
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
if __FILE__ == $0
|
82
|
+
inst = Installer.new(ARGV.shift == '-n')
|
83
|
+
inst.install_files('lib', ['lib/io/reactor.rb'])
|
84
|
+
end
|
85
|
+
|
data/io-reactor.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'date'
|
2
|
+
Gem::Specification.new do |s|
|
3
|
+
s.name = %q{io-reactor}
|
4
|
+
s.version = "0.05"
|
5
|
+
s.date = Date.today.to_s
|
6
|
+
s.summary = %q{An implementation of the Reactor design pattern for multiplexed asynchronous single-thread IO.}
|
7
|
+
s.description =<<DESCRIPTION
|
8
|
+
An implementation of the Reactor design pattern for multiplexed asynchronous single-thread IO.
|
9
|
+
DESCRIPTION
|
10
|
+
s.author = %q{Michael Granger}
|
11
|
+
s.email = %q{ged@FaerieMUD.org}
|
12
|
+
s.homepage = %q{http://www.deveiate.org/code/IO-Reactor.html}
|
13
|
+
s.files = Dir.glob('**/*')
|
14
|
+
s.require_paths = %w{lib .}
|
15
|
+
s.autorequire = %q{io/reactor}
|
16
|
+
s.has_rdoc = true
|
17
|
+
s.rdoc_options = ["--main", "README"]
|
18
|
+
s.extra_rdoc_files = ["README"]
|
19
|
+
s.test_files = %w{test.rb}
|
20
|
+
s.required_ruby_version = Gem::Version::Requirement.new(">= 1.8.0")
|
21
|
+
end
|
data/lib/io/reactor.rb
ADDED
@@ -0,0 +1,317 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
#
|
3
|
+
# An object-oriented multiplexing asynchronous IO mechanism for Ruby.
|
4
|
+
#
|
5
|
+
# == Synopsis
|
6
|
+
#
|
7
|
+
# require 'io/reactor'
|
8
|
+
# require 'socket'
|
9
|
+
#
|
10
|
+
# reactorobj = IO::Reactor::new
|
11
|
+
#
|
12
|
+
# # Open a listening socket on port 1138 and register it with the
|
13
|
+
# # reactor. When a :read event occurs on it, it means an incoming
|
14
|
+
# # connection has been established
|
15
|
+
# sock = TCPServer::new('localhost', 1138)
|
16
|
+
# reactorobj.register( sock, :read ) {|sock,event|
|
17
|
+
# case event
|
18
|
+
#
|
19
|
+
# # Accept the incoming connection, registering it also with
|
20
|
+
# # the reactor; events on client sockets are handled by the
|
21
|
+
# # #clientHandler method.
|
22
|
+
# when :read
|
23
|
+
# clsock = sock.accept
|
24
|
+
# reactorobj.register( clsock, :read, :write,
|
25
|
+
# &method(:clientHandler) )
|
26
|
+
#
|
27
|
+
# # Errors on the main listening socket cause the whole
|
28
|
+
# # reactor to be shut down
|
29
|
+
# when :error
|
30
|
+
# reactorobj.clear
|
31
|
+
# $stderr.puts "Server error: Shutting down"
|
32
|
+
#
|
33
|
+
# else
|
34
|
+
# $stderr.puts "Unhandled event: #{event}"
|
35
|
+
# end
|
36
|
+
# }
|
37
|
+
#
|
38
|
+
# # Drive the reactor until it's empty with a single thread
|
39
|
+
# Thread::new { reactorobj.poll until reactorobj.empty? }
|
40
|
+
#
|
41
|
+
# == Author
|
42
|
+
#
|
43
|
+
# Michael Granger <ged@FaerieMUD.org>
|
44
|
+
#
|
45
|
+
# Copyright (c) 2002, 2003 The FaerieMUD Consortium. All rights reserved.
|
46
|
+
#
|
47
|
+
# This module is free software. You may use, modify, and/or redistribute this
|
48
|
+
# software under the same terms as Ruby itself.
|
49
|
+
#
|
50
|
+
# This library is distributed in the hope that it will be useful, but WITHOUT
|
51
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
52
|
+
# FOR A PARTICULAR PURPOSE.
|
53
|
+
#
|
54
|
+
# == Version
|
55
|
+
#
|
56
|
+
# $Id: reactor.rb,v 1.13 2003/08/04 23:56:14 deveiant Exp $
|
57
|
+
#
|
58
|
+
|
59
|
+
require 'delegate'
|
60
|
+
require 'rbconfig'
|
61
|
+
|
62
|
+
class IO
|
63
|
+
|
64
|
+
### An object-oriented multiplexing asynchronous IO reactor class.
|
65
|
+
class Reactor
|
66
|
+
|
67
|
+
### Class constants
|
68
|
+
Version = /([\d\.]+)/.match( %q{$Revision: 1.13 $} )[1]
|
69
|
+
Rcsid = %q$Id: reactor.rb,v 1.13 2003/08/04 23:56:14 deveiant Exp $
|
70
|
+
|
71
|
+
ValidEvents = [:read, :write, :error]
|
72
|
+
|
73
|
+
### Create and return a new IO reactor object.
|
74
|
+
def initialize
|
75
|
+
@handles = Hash::new {|hsh,key|
|
76
|
+
hsh[ key ] = {
|
77
|
+
:events => [],
|
78
|
+
:handler => nil,
|
79
|
+
:args => [],
|
80
|
+
}
|
81
|
+
}
|
82
|
+
@pendingEvents = Hash::new {|hsh,key| hsh[ key ] = []}
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
######
|
87
|
+
public
|
88
|
+
######
|
89
|
+
|
90
|
+
# The Hash of handles (instances of IO or its subclasses) associated with
|
91
|
+
# the reactor. The keys are the IO objects, and the values are a Hash of
|
92
|
+
# event/s => handler.
|
93
|
+
attr_reader :handles
|
94
|
+
|
95
|
+
# The Hash of unhandled events which occurred in the last call to #poll,
|
96
|
+
# keyed by handle.
|
97
|
+
attr_reader :pendingEvents
|
98
|
+
|
99
|
+
|
100
|
+
### Register the specified IO object with the reactor for events given as
|
101
|
+
### <tt>args</tt>. The reactor will test the given <tt>io</tt> for the
|
102
|
+
### events specified whenever #poll is called. See the #poll method for a
|
103
|
+
### list of valid events. If no events are specified, only <tt>:error</tt>
|
104
|
+
### events will be polled for.
|
105
|
+
###
|
106
|
+
### If a <tt>handler</tt> is specified, it will be called whenever the
|
107
|
+
### <tt>io</tt> has any of the specified <tt>events</tt> occur to it. It
|
108
|
+
### should take at least two parameters: the <tt>io</tt> and the event.
|
109
|
+
###
|
110
|
+
### If +args+ contains any objects except the Symbols '<tt>:read</tt>',
|
111
|
+
### '<tt>:write</tt>', or '<tt>:error</tt>', and a +handler+ is specified,
|
112
|
+
### they will be saved and passed to handler for each event.
|
113
|
+
###
|
114
|
+
### Registering a handle will unregister any previously registered
|
115
|
+
### event/handler+arguments pairs associated with the handle.
|
116
|
+
def register( io, *args, &handler )
|
117
|
+
events = [:read, :write, :error] & args
|
118
|
+
args -= events
|
119
|
+
|
120
|
+
self.unregister( io )
|
121
|
+
self.enableEvents( io, *events )
|
122
|
+
if handler
|
123
|
+
self.setHandler( io, *args, &handler )
|
124
|
+
else
|
125
|
+
self.setArgs( io, *args )
|
126
|
+
end
|
127
|
+
|
128
|
+
return self
|
129
|
+
end
|
130
|
+
alias_method :add, :register
|
131
|
+
|
132
|
+
|
133
|
+
### Add the specified +events+ to the list that will be polled for on the
|
134
|
+
### given +io+ handle.
|
135
|
+
def enableEvents( io, *events )
|
136
|
+
@handles[ io ][:events] |= events
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
### Remove the specified +events+ from the list that will be polled for on
|
141
|
+
### the given +io+ handle.
|
142
|
+
def disableEvents( io, *events )
|
143
|
+
raise RuntimeError, "Cannot disable the :error event" if
|
144
|
+
events.include?( :error )
|
145
|
+
@handles[ io ][:events] -= events
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
### Set the handler for events on the given +io+ handle to the specified
|
150
|
+
### +handler+. If any +args+ are present, they will be passed as an exploded
|
151
|
+
### array to the handler for each event. Returns the previously-registered
|
152
|
+
### handler, if any.
|
153
|
+
def setHandler( io, *args, &handler )
|
154
|
+
rval = @handles[ io ][:handler]
|
155
|
+
@handles[ io ][:handler] = handler
|
156
|
+
self.setArgs( io, *args )
|
157
|
+
return rval
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
### Remove and return the handler for events on the given +io+ handle.
|
162
|
+
def removeHandler( io )
|
163
|
+
rval = @handles[ io ][:handler]
|
164
|
+
@handles[ io ][:handler] = nil
|
165
|
+
self.removeArgs( io )
|
166
|
+
return rval
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
### Set the additional arguments to pass to the handler for the given +io+
|
171
|
+
### handle on each event to the given +args+.
|
172
|
+
def setArgs( io, *args )
|
173
|
+
rval = @handles[ io ][:args]
|
174
|
+
@handles[ io ][:args] = args
|
175
|
+
return rval
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
### Remove the arguments for the given handle to the given +args+.
|
180
|
+
def removeArgs( io )
|
181
|
+
return @handles[ io ][:args].clear
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
### Remove the specified <tt>io</tt> from the receiver's list of registered
|
186
|
+
### handles, if present. Returns the handle if it was registered, or
|
187
|
+
### <tt>nil</tt> if it was not.
|
188
|
+
def unregister( io )
|
189
|
+
@pendingEvents.delete( io )
|
190
|
+
@handles.delete( io )
|
191
|
+
end
|
192
|
+
alias_method :remove, :unregister
|
193
|
+
|
194
|
+
|
195
|
+
### Returns true if the specified <tt>io</tt> is registered with the poll
|
196
|
+
### object.
|
197
|
+
def registered?( io )
|
198
|
+
return @handles.has_key?( io )
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
### Clear all registered handles from the poll object. Returns the handles
|
203
|
+
### that were cleared.
|
204
|
+
def clear
|
205
|
+
rv = @handles.keys
|
206
|
+
|
207
|
+
@pendingEvents.clear
|
208
|
+
@handles.clear
|
209
|
+
|
210
|
+
return rv
|
211
|
+
end
|
212
|
+
|
213
|
+
|
214
|
+
|
215
|
+
### Poll the handles registered to the reactor for pending events. The
|
216
|
+
### following event types are defined:
|
217
|
+
###
|
218
|
+
### [<tt>:read</tt>]
|
219
|
+
### Data may be read from the handle without blocking.
|
220
|
+
### [<tt>:write</tt>]
|
221
|
+
### Data may be written to the handle without blocking.
|
222
|
+
### [<tt>:error</tt>]
|
223
|
+
### An error has occurred on the handle. This event type is always
|
224
|
+
### enabled, regardless of whether or not it is passed as one of the
|
225
|
+
### <tt>events</tt>.
|
226
|
+
###
|
227
|
+
### Any handlers specified when the handles were registered are run for
|
228
|
+
### those handles with events. If a block is given, it will be invoked once
|
229
|
+
### for each handle which doesn't have an explicit handler. If no block is
|
230
|
+
### given, events without explicit handlers are inserted into the reactor's
|
231
|
+
### <tt>pendingEvents</tt> attribute.
|
232
|
+
###
|
233
|
+
### The <tt>timeout</tt> argument is the number of floating-point seconds to
|
234
|
+
### wait for an event before returning (ie., fourth argument to the
|
235
|
+
### underlying <tt>select()</tt> call); negative timeout values will cause
|
236
|
+
### #poll to block until there is at least one event to report.
|
237
|
+
###
|
238
|
+
### This method returns the number of handles on which one or more events
|
239
|
+
### occurred.
|
240
|
+
def poll( timeout=-1 ) # :yields: io, eventMask
|
241
|
+
timeout = timeout.to_f
|
242
|
+
@pendingEvents.clear
|
243
|
+
count = 0
|
244
|
+
|
245
|
+
unless @handles.empty?
|
246
|
+
timeout = nil if timeout < 0
|
247
|
+
eventedHandles = self.getPendingEvents( timeout )
|
248
|
+
|
249
|
+
# For each event of each io that had an event happen, call any
|
250
|
+
# associated callback, or any provided block, or failing both of
|
251
|
+
# those, add the event to the hash of unhandled pending events.
|
252
|
+
eventedHandles.each {|io,events|
|
253
|
+
count += 1
|
254
|
+
events.each {|ev|
|
255
|
+
args = @handles[ io ][:args]
|
256
|
+
|
257
|
+
if @handles[ io ][:handler]
|
258
|
+
@handles[ io ][:handler].call( io, ev, *args )
|
259
|
+
elsif block_given?
|
260
|
+
yield( io, ev, *args )
|
261
|
+
else
|
262
|
+
@pendingEvents[io].push( ev )
|
263
|
+
end
|
264
|
+
}
|
265
|
+
}
|
266
|
+
end
|
267
|
+
|
268
|
+
return count
|
269
|
+
end
|
270
|
+
|
271
|
+
|
272
|
+
### Returns <tt>true</tt> if no handles are associated with the receiver.
|
273
|
+
def empty?
|
274
|
+
@handles.empty?
|
275
|
+
end
|
276
|
+
|
277
|
+
|
278
|
+
#########
|
279
|
+
protected
|
280
|
+
#########
|
281
|
+
|
282
|
+
### Select on the registered handles, returning a Hash of handles => events
|
283
|
+
### for handles which had events occur.
|
284
|
+
def getPendingEvents( timeout )
|
285
|
+
eventHandles = IO::select( self.getReadHandles, self.getWriteHandles,
|
286
|
+
@handles.keys, timeout ) or return {}
|
287
|
+
eventHash = Hash::new {|hsh,io| hsh[io] = []}
|
288
|
+
|
289
|
+
# Fill in the hash with pending events of each type
|
290
|
+
[:read, :write, :error].each_with_index {|event,i|
|
291
|
+
eventHandles[i].each {|io| eventHash[io].push( event )}
|
292
|
+
}
|
293
|
+
return eventHash
|
294
|
+
end
|
295
|
+
|
296
|
+
|
297
|
+
### Return an Array of handles which have handlers for the <tt>:read</tt>
|
298
|
+
### event.
|
299
|
+
def getReadHandles
|
300
|
+
@handles.
|
301
|
+
find_all {|io,hsh| hsh[:events].include?( :read )}.
|
302
|
+
collect {|io,hsh| io}
|
303
|
+
end
|
304
|
+
|
305
|
+
|
306
|
+
### Return an Array of handles which have handlers for the <tt>:write</tt>
|
307
|
+
### event.
|
308
|
+
def getWriteHandles
|
309
|
+
@handles.
|
310
|
+
find_all {|io,hsh| hsh[:events].include?( :write )}.
|
311
|
+
collect {|io,hsh| io}
|
312
|
+
end
|
313
|
+
|
314
|
+
|
315
|
+
end # class Reactor
|
316
|
+
end # class IO
|
317
|
+
|
data/makedocs.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
#
|
3
|
+
# MUES Documentation Generation Script
|
4
|
+
# $Id: makedocs.rb,v 1.3 2002/09/18 12:33:35 deveiant Exp $
|
5
|
+
#
|
6
|
+
# Copyright (c) 2001,2002 The FaerieMUD Consortium.
|
7
|
+
#
|
8
|
+
# This is free software. You may use, modify, and/or redistribute this
|
9
|
+
# software under the terms of the Perl Artistic License. (See
|
10
|
+
# http://language.perl.com/misc/Artistic.html)
|
11
|
+
#
|
12
|
+
|
13
|
+
# Muck with the load path and the cwd
|
14
|
+
$basedir = File::expand_path( $0 ).sub( %r{/makedocs.rb}, '' )
|
15
|
+
unless $basedir.empty? || Dir.getwd == $basedir
|
16
|
+
$stderr.puts "Changing working directory from '#{Dir.getwd}' to '#$basedir'"
|
17
|
+
Dir.chdir( $basedir )
|
18
|
+
end
|
19
|
+
|
20
|
+
$LOAD_PATH.unshift "docs/lib"
|
21
|
+
|
22
|
+
# Load modules
|
23
|
+
require 'getoptlong'
|
24
|
+
require 'rdoc/rdoc'
|
25
|
+
require "utils"
|
26
|
+
include UtilityFunctions
|
27
|
+
|
28
|
+
opts = GetoptLong.new
|
29
|
+
opts.set_options(
|
30
|
+
[ '--debug', '-d', GetoptLong::NO_ARGUMENT ],
|
31
|
+
[ '--verbose', '-v', GetoptLong::NO_ARGUMENT ]
|
32
|
+
)
|
33
|
+
|
34
|
+
$docsdir = "docs/html"
|
35
|
+
$libdirs = %w{poll.c lib examples README}
|
36
|
+
opts.each {|opt,val|
|
37
|
+
case opt
|
38
|
+
|
39
|
+
when '--debug'
|
40
|
+
$debug = true
|
41
|
+
|
42
|
+
when '--verbose'
|
43
|
+
$verbose = true
|
44
|
+
|
45
|
+
when '--upload'
|
46
|
+
$upload = true
|
47
|
+
|
48
|
+
end
|
49
|
+
}
|
50
|
+
|
51
|
+
|
52
|
+
header "Making documentation in #$docsdir from files in #{$libdirs.join(', ')}."
|
53
|
+
|
54
|
+
flags = [
|
55
|
+
'--main', 'README',
|
56
|
+
'--fmt', 'html',
|
57
|
+
'--include', 'docs',
|
58
|
+
'--op', $docsdir,
|
59
|
+
'--title', "Ruby-Poll"
|
60
|
+
]
|
61
|
+
|
62
|
+
message "Running 'rdoc #{flags.join(' ')} #{$libdirs.join(' ')}'\n" if $verbose
|
63
|
+
|
64
|
+
unless $debug
|
65
|
+
begin
|
66
|
+
r = RDoc::RDoc.new
|
67
|
+
r.document( flags + $libdirs )
|
68
|
+
rescue RDoc::RDocError => e
|
69
|
+
$stderr.puts e.message
|
70
|
+
exit(1)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# rdoc \
|
75
|
+
# --all \
|
76
|
+
# --inline_source \
|
77
|
+
# --main "lib/mues.rb" \
|
78
|
+
# --title "Multi-User Environment Server (MUES)" \
|
79
|
+
# lib server
|