revactor 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +3 -0
- data/LICENSE +58 -0
- data/README +338 -0
- data/Rakefile +48 -0
- data/examples/echo_server.rb +39 -0
- data/examples/google.rb +24 -0
- data/examples/mongrel.rb +14 -0
- data/lib/revactor/actor.rb +316 -0
- data/lib/revactor/behaviors/server.rb +87 -0
- data/lib/revactor/filters/line.rb +53 -0
- data/lib/revactor/filters/packet.rb +59 -0
- data/lib/revactor/mongrel.rb +62 -0
- data/lib/revactor/server.rb +153 -0
- data/lib/revactor/tcp.rb +397 -0
- data/lib/revactor.rb +29 -0
- data/revactor.gemspec +28 -0
- data/spec/actor_spec.rb +127 -0
- data/spec/line_filter_spec.rb +36 -0
- data/spec/packet_filter_spec.rb +59 -0
- data/spec/tcp_spec.rb +84 -0
- data/tools/messaging_throughput.rb +33 -0
- metadata +92 -0
@@ -0,0 +1,153 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C)2007 Tony Arcieri
|
3
|
+
# You can redistribute this under the terms of the Ruby license
|
4
|
+
# See file LICENSE for details
|
5
|
+
#++
|
6
|
+
|
7
|
+
require File.dirname(__FILE__) + '/../revactor'
|
8
|
+
|
9
|
+
module Revactor
|
10
|
+
# Revactor::Server wraps an Actor's receive loop and issues callbacks to
|
11
|
+
# a class which implements Revactor::Behaviors::Server. It eases the
|
12
|
+
# creation of standard synchronous "blocking" calls by abstracting away
|
13
|
+
# inter-Actor communication and also providing baked-in state management.
|
14
|
+
#
|
15
|
+
# When used properly, Revactor::Server can implement transactional
|
16
|
+
# semantics, ensuring only successful calls mutate the previous state
|
17
|
+
# and erroneous/exception-raising ones do not.
|
18
|
+
#
|
19
|
+
# The design is modeled off Erlang/OTP's gen_server
|
20
|
+
class Server
|
21
|
+
# How long to wait for a response to a call before timing out
|
22
|
+
# This value also borrowed from Erlang. More cargo culting!
|
23
|
+
DEFAULT_CALL_TIMEOUT = 5
|
24
|
+
|
25
|
+
# Create a new server. Accepts the following options:
|
26
|
+
#
|
27
|
+
# register: Register the Actor in the Actor registry under
|
28
|
+
# the given term
|
29
|
+
#
|
30
|
+
# Any options passed after the options hash are passed to the
|
31
|
+
# start method of the given object.
|
32
|
+
#
|
33
|
+
def initialize(obj, options = {}, *args)
|
34
|
+
@obj = obj
|
35
|
+
@timeout = nil
|
36
|
+
@state = obj.start(*args)
|
37
|
+
@actor = Actor.new(&method(:start).to_proc)
|
38
|
+
|
39
|
+
Actor[options[:register]] = @actor if options[:register]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Call the server with the given message
|
43
|
+
def call(message, options = {})
|
44
|
+
options[:timeout] ||= DEFAULT_CALL_TIMEOUT
|
45
|
+
|
46
|
+
@actor << T[:call, Actor.current, message]
|
47
|
+
Actor.receive do |filter|
|
48
|
+
filter.when(Case[:call_reply, @actor, Object]) { |_, _, reply| reply }
|
49
|
+
filter.when(Case[:call_error, @actor, Object]) { |_, _, ex| raise ex }
|
50
|
+
filter.after(options[:timeout]) { raise 'timeout' }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Send a cast to the server
|
55
|
+
def cast(message)
|
56
|
+
@actor << T[:cast, message]
|
57
|
+
message
|
58
|
+
end
|
59
|
+
|
60
|
+
#########
|
61
|
+
protected
|
62
|
+
#########
|
63
|
+
|
64
|
+
# Start the server
|
65
|
+
def start
|
66
|
+
@running = true
|
67
|
+
while @running do
|
68
|
+
Actor.receive do |filter|
|
69
|
+
filter.when(Object) { |message| handle_message(message) }
|
70
|
+
filter.after(@timeout) { stop(:timeout) } if @timeout
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Dispatch the incoming message to the appropriate handler
|
76
|
+
def handle_message(message)
|
77
|
+
case message.first
|
78
|
+
when :call then handle_call(message)
|
79
|
+
when :cast then handle_cast(message)
|
80
|
+
else handle_info(message)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Wrapper for calling the provided object's handle_call method
|
85
|
+
def handle_call(message)
|
86
|
+
_, from, body = message
|
87
|
+
|
88
|
+
begin
|
89
|
+
result = @obj.handle_call(body, from, @state)
|
90
|
+
case result.first
|
91
|
+
when :reply
|
92
|
+
_, reply, @state, @timeout = result
|
93
|
+
from << T[:call_reply, Actor.current, reply]
|
94
|
+
when :noreply
|
95
|
+
_, @state, @timeout = result
|
96
|
+
when :stop
|
97
|
+
_, reason, @state = result
|
98
|
+
stop(reason)
|
99
|
+
end
|
100
|
+
rescue Exception => ex
|
101
|
+
log_exception(ex)
|
102
|
+
from << T[:call_error, Actor.current, ex]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Wrapper for calling the provided object's handle_cast method
|
107
|
+
def handle_cast(message)
|
108
|
+
_, body = message
|
109
|
+
|
110
|
+
begin
|
111
|
+
result = @obj.handle_cast(body, @state)
|
112
|
+
case result.first
|
113
|
+
when :noreply
|
114
|
+
_, @state, @timeout = result
|
115
|
+
when :stop
|
116
|
+
_, reason, @state = result
|
117
|
+
stop(reason)
|
118
|
+
end
|
119
|
+
rescue Exception => e
|
120
|
+
log_exception(e)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Wrapper for calling the provided object's handle_info method
|
125
|
+
def handle_info(message)
|
126
|
+
begin
|
127
|
+
result = @obj.handle_info(message, @state)
|
128
|
+
case result.first
|
129
|
+
when :noreply
|
130
|
+
_, @state, @timeout = result
|
131
|
+
when :stop
|
132
|
+
_, reason, @state = result
|
133
|
+
stop(reason)
|
134
|
+
end
|
135
|
+
rescue Exception => e
|
136
|
+
log_exception(e)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Stop the server
|
141
|
+
def stop(reason)
|
142
|
+
@running = false
|
143
|
+
@obj.terminate(reason, @state)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Log an exception
|
147
|
+
def log_exception(exception)
|
148
|
+
# FIXME this should really go to a logger, not STDERR
|
149
|
+
STDERR.puts "Rev::Server exception: #{exception}"
|
150
|
+
STDERR.puts exception.backtrace
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
data/lib/revactor/tcp.rb
ADDED
@@ -0,0 +1,397 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C)2007 Tony Arcieri
|
3
|
+
# You can redistribute this under the terms of the Ruby license
|
4
|
+
# See file LICENSE for details
|
5
|
+
#++
|
6
|
+
|
7
|
+
require File.dirname(__FILE__) + '/../revactor'
|
8
|
+
|
9
|
+
module Revactor
|
10
|
+
# The TCP module holds all Revactor functionality related to the
|
11
|
+
# Transmission Control Protocol, including drop-in replacements
|
12
|
+
# for Ruby TCP Sockets which can operate concurrently using Actors.
|
13
|
+
module TCP
|
14
|
+
# Number of seconds to wait for a connection
|
15
|
+
CONNECT_TIMEOUT = 10
|
16
|
+
|
17
|
+
# Raised when a connection to a remote server fails
|
18
|
+
class ConnectError < StandardError; end
|
19
|
+
|
20
|
+
# Raised when hostname resolution for a remote server fails
|
21
|
+
class ResolveError < ConnectError; end
|
22
|
+
|
23
|
+
# Connect to the specified host and port. Host may be a domain name
|
24
|
+
# or IP address. Accepts the following options:
|
25
|
+
#
|
26
|
+
# :active - Controls how data is read from the socket. See the
|
27
|
+
# documentation for Revactor::TCP::Socket#active=
|
28
|
+
#
|
29
|
+
def self.connect(host, port, options = {})
|
30
|
+
socket = Socket.connect host, port, options
|
31
|
+
socket.attach Rev::Loop.default
|
32
|
+
|
33
|
+
Actor.receive do |filter|
|
34
|
+
filter.when(Case[Object, socket]) do |message|
|
35
|
+
case message[0]
|
36
|
+
when :tcp_connected
|
37
|
+
return socket
|
38
|
+
when :tcp_connect_failed
|
39
|
+
raise ConnectError, "connection refused"
|
40
|
+
when :tcp_resolve_failed
|
41
|
+
raise ResolveError, "couldn't resolve #{host}"
|
42
|
+
else raise "unexpected message for #{socket.inspect}: #{message.first}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
filter.after(CONNECT_TIMEOUT) do
|
47
|
+
raise ConnectError, "connection timed out"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Listen on the specified address and port. Accepts the following options:
|
53
|
+
#
|
54
|
+
# :active - Default active setting for new connections. See the
|
55
|
+
# documentation Rev::TCP::Socket#active= for more info
|
56
|
+
#
|
57
|
+
# :controller - The controlling actor, default Actor.current
|
58
|
+
#
|
59
|
+
def self.listen(addr, port, options = {})
|
60
|
+
Listener.new(addr, port, options).attach(Rev::Loop.default).disable
|
61
|
+
end
|
62
|
+
|
63
|
+
# TCP socket class, returned by Revactor::TCP.connect and
|
64
|
+
# Revactor::TCP::Listener#accept
|
65
|
+
class Socket < Rev::TCPSocket
|
66
|
+
attr_reader :active
|
67
|
+
attr_reader :controller
|
68
|
+
|
69
|
+
class << self
|
70
|
+
# Connect to the specified host and port. Host may be a domain name
|
71
|
+
# or IP address. Accepts the following options:
|
72
|
+
#
|
73
|
+
# :active - Controls how data is read from the socket. See the
|
74
|
+
# documentation for #active=
|
75
|
+
#
|
76
|
+
# :controller - The controlling actor, default Actor.current
|
77
|
+
#
|
78
|
+
# :filter - An symbol/class or array of symbols/classes which implement
|
79
|
+
# #encode and #decode methods to transform data sent and
|
80
|
+
# received data respectively via Revactor::TCP::Socket.
|
81
|
+
#
|
82
|
+
def connect(host, port, options = {})
|
83
|
+
options[:active] ||= false
|
84
|
+
options[:controller] ||= Actor.current
|
85
|
+
|
86
|
+
super(host, port, options).instance_eval {
|
87
|
+
@active, @controller = options[:active], options[:controller]
|
88
|
+
@filterset = initialize_filter(*options[:filter])
|
89
|
+
self
|
90
|
+
}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def initialize(socket, options = {})
|
95
|
+
super(socket)
|
96
|
+
|
97
|
+
@active ||= options[:active] || false
|
98
|
+
@controller ||= options[:controller] || Actor.current
|
99
|
+
@filterset ||= initialize_filter(*options[:filter])
|
100
|
+
|
101
|
+
@receiver = @controller
|
102
|
+
@read_buffer = Rev::Buffer.new
|
103
|
+
end
|
104
|
+
|
105
|
+
# Enable or disable active mode data reception. State can be any
|
106
|
+
# of the following:
|
107
|
+
#
|
108
|
+
# true - All received data is sent to the controlling actor
|
109
|
+
# false - Receiving data is disabled
|
110
|
+
# :once - A single message will be sent to the controlling actor
|
111
|
+
# then active mode will be disabled
|
112
|
+
def active=(state)
|
113
|
+
unless @receiver == @controller
|
114
|
+
raise "cannot change active state during a synchronous call"
|
115
|
+
end
|
116
|
+
|
117
|
+
unless [true, false, :once].include? state
|
118
|
+
raise ArgumentError, "must be true, false, or :once"
|
119
|
+
end
|
120
|
+
|
121
|
+
if [true, :once].include?(state)
|
122
|
+
unless @read_buffer.empty?
|
123
|
+
@receiver << [:tcp, self, @read_buffer.read]
|
124
|
+
return if state == :once
|
125
|
+
end
|
126
|
+
|
127
|
+
enable unless enabled?
|
128
|
+
end
|
129
|
+
|
130
|
+
@active = state
|
131
|
+
end
|
132
|
+
|
133
|
+
# Set the controlling actor
|
134
|
+
def controller=(controller)
|
135
|
+
raise ArgumentError, "controller must be an actor" unless controller.is_a? Actor
|
136
|
+
|
137
|
+
@receiver = controller if @receiver == @controller
|
138
|
+
@controller = controller
|
139
|
+
end
|
140
|
+
|
141
|
+
# Read data from the socket synchronously. If a length is specified
|
142
|
+
# then the call blocks until the given length has been read. Otherwise
|
143
|
+
# the call blocks until it receives any data.
|
144
|
+
def read(length = nil)
|
145
|
+
# Only one synchronous call allowed at a time
|
146
|
+
raise "already being called synchronously" unless @receiver == @controller
|
147
|
+
|
148
|
+
unless @read_buffer.empty? or (length and @read_buffer.size < length)
|
149
|
+
return @read_buffer.read(length)
|
150
|
+
end
|
151
|
+
|
152
|
+
active = @active
|
153
|
+
@active = :once
|
154
|
+
@receiver = Actor.current
|
155
|
+
enable unless enabled?
|
156
|
+
|
157
|
+
loop do
|
158
|
+
Actor.receive do |filter|
|
159
|
+
filter.when(Case[:tcp, self, Object]) do |_, _, data|
|
160
|
+
if length.nil?
|
161
|
+
@receiver = @controller
|
162
|
+
@active = active
|
163
|
+
enable if @active
|
164
|
+
|
165
|
+
return data
|
166
|
+
end
|
167
|
+
|
168
|
+
@read_buffer << data
|
169
|
+
|
170
|
+
if @read_buffer.size >= length
|
171
|
+
@receiver = @controller
|
172
|
+
@active = active
|
173
|
+
enable if @active
|
174
|
+
|
175
|
+
return @read_buffer.read(length)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
filter.when(Case[:tcp_closed, self]) do
|
180
|
+
unless @receiver == @controller
|
181
|
+
@receiver = @controller
|
182
|
+
@receiver << T[:tcp_closed, self]
|
183
|
+
end
|
184
|
+
|
185
|
+
raise EOFError, "connection closed"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Write data to the socket. The call blocks until all data has been written.
|
192
|
+
def write(data)
|
193
|
+
# Only one synchronous call allowed at a time
|
194
|
+
raise "already being called synchronously" unless @receiver == @controller
|
195
|
+
|
196
|
+
active = @active
|
197
|
+
@active = false
|
198
|
+
@receiver = Actor.current
|
199
|
+
disable if @active
|
200
|
+
|
201
|
+
super(encode(data))
|
202
|
+
|
203
|
+
Actor.receive do |filter|
|
204
|
+
filter.when(Case[:tcp_write_complete, self]) do
|
205
|
+
@receiver = @controller
|
206
|
+
@active = active
|
207
|
+
enable if @active
|
208
|
+
|
209
|
+
return data.size
|
210
|
+
end
|
211
|
+
|
212
|
+
filter.when(Case[:tcp_closed, self]) do
|
213
|
+
@receiver = @controller
|
214
|
+
@active = active
|
215
|
+
enable if @active
|
216
|
+
|
217
|
+
raise EOFError, "connection closed"
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
alias_method :<<, :write
|
223
|
+
|
224
|
+
#########
|
225
|
+
protected
|
226
|
+
#########
|
227
|
+
|
228
|
+
#
|
229
|
+
# Filter setup
|
230
|
+
#
|
231
|
+
|
232
|
+
# Initialize filter change
|
233
|
+
def initialize_filter(*filterset)
|
234
|
+
return filterset if filterset.empty?
|
235
|
+
|
236
|
+
filterset.map do |filter|
|
237
|
+
case filter
|
238
|
+
when Array
|
239
|
+
name = filter.shift
|
240
|
+
case name
|
241
|
+
when Class
|
242
|
+
name.new(*filter)
|
243
|
+
when Symbol
|
244
|
+
symbol_to_filter(name).new(*filter)
|
245
|
+
else raise ArgumentError, "unrecognized filter type: #{name.class}"
|
246
|
+
end
|
247
|
+
when Class
|
248
|
+
filter.new
|
249
|
+
when Symbol
|
250
|
+
symbol_to_filter(filter).new
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# Lookup filters referenced as symbols
|
256
|
+
def symbol_to_filter(filter)
|
257
|
+
case filter
|
258
|
+
when :line then Revactor::Filters::Line
|
259
|
+
when :packet then Revactor::Filters::Packet
|
260
|
+
else raise ArgumentError, "unrecognized filter type: #{filter}"
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# Decode data through the filter chain
|
265
|
+
def decode(data)
|
266
|
+
@filterset.reduce([data]) do |a, filter|
|
267
|
+
a.reduce([]) do |a2, d|
|
268
|
+
a2 + filter.decode(d)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
# Encode data through the filter chain
|
274
|
+
def encode(message)
|
275
|
+
@filterset.reverse.reduce(message) { |m, filter| filter.encode(*m) }
|
276
|
+
end
|
277
|
+
|
278
|
+
#
|
279
|
+
# Rev::TCPSocket callback
|
280
|
+
#
|
281
|
+
|
282
|
+
def on_connect
|
283
|
+
@receiver << T[:tcp_connected, self]
|
284
|
+
end
|
285
|
+
|
286
|
+
def on_connect_failed
|
287
|
+
@receiver << T[:tcp_connect_failed, self]
|
288
|
+
end
|
289
|
+
|
290
|
+
def on_resolve_failed
|
291
|
+
@receiver << T[:tcp_resolve_failed, self]
|
292
|
+
end
|
293
|
+
|
294
|
+
def on_close
|
295
|
+
@receiver << T[:tcp_closed, self]
|
296
|
+
end
|
297
|
+
|
298
|
+
def on_read(data)
|
299
|
+
# Run incoming message through the filter chain
|
300
|
+
message = decode(data)
|
301
|
+
|
302
|
+
if message.is_a?(Array) and not message.empty?
|
303
|
+
message.each { |msg| @receiver << T[:tcp, self, msg] }
|
304
|
+
elsif message
|
305
|
+
@receiver << T[:tcp, self, message]
|
306
|
+
else return
|
307
|
+
end
|
308
|
+
|
309
|
+
if @active == :once
|
310
|
+
@active = false
|
311
|
+
disable
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def on_write_complete
|
316
|
+
@receiver << T[:tcp_write_complete, self]
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
# TCP Listener returned from Revactor::TCP.listen
|
321
|
+
class Listener < Rev::TCPListener
|
322
|
+
attr_reader :active
|
323
|
+
attr_reader :controller
|
324
|
+
|
325
|
+
# Listen on the specified address and port. Accepts the following options:
|
326
|
+
#
|
327
|
+
# :active - Default active setting for new connections. See the
|
328
|
+
# documentation Rev::TCP::Socket#active= for more info
|
329
|
+
#
|
330
|
+
# :controller - The controlling actor, default Actor.current
|
331
|
+
#
|
332
|
+
def initialize(host, port, options = {})
|
333
|
+
super(host, port)
|
334
|
+
opts = {
|
335
|
+
active: false,
|
336
|
+
controller: Actor.current
|
337
|
+
}.merge(options)
|
338
|
+
|
339
|
+
@active, @controller = opts[:active], opts[:controller]
|
340
|
+
@filterset = options[:filter]
|
341
|
+
|
342
|
+
@accepting = false
|
343
|
+
end
|
344
|
+
|
345
|
+
# Change the default active setting for newly accepted connections
|
346
|
+
def active=(state)
|
347
|
+
unless [true, false, :once].include? state
|
348
|
+
raise ArgumentError, "must be true, false, or :once"
|
349
|
+
end
|
350
|
+
|
351
|
+
@active = state
|
352
|
+
end
|
353
|
+
|
354
|
+
# Change the default controller for newly accepted connections
|
355
|
+
def controller=(controller)
|
356
|
+
raise ArgumentError, "controller must be an actor" unless controller.is_a? Actor
|
357
|
+
@controller = controller
|
358
|
+
end
|
359
|
+
|
360
|
+
# Accept an incoming connection
|
361
|
+
def accept
|
362
|
+
raise "another actor is already accepting" if @accepting
|
363
|
+
|
364
|
+
@accepting = true
|
365
|
+
@receiver = Actor.current
|
366
|
+
enable
|
367
|
+
|
368
|
+
Actor.receive do |filter|
|
369
|
+
filter.when(Case[:tcp_connection, self, Object]) do |_, _, sock|
|
370
|
+
@accepting = false
|
371
|
+
return sock
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
#########
|
377
|
+
protected
|
378
|
+
#########
|
379
|
+
|
380
|
+
#
|
381
|
+
# Rev::TCPListener callbacks
|
382
|
+
#
|
383
|
+
|
384
|
+
def on_connection(socket)
|
385
|
+
sock = Socket.new(socket,
|
386
|
+
:controller => @controller,
|
387
|
+
:active => @active,
|
388
|
+
:filter => @filterset
|
389
|
+
)
|
390
|
+
sock.attach(evloop)
|
391
|
+
|
392
|
+
@receiver << T[:tcp_connection, self, sock]
|
393
|
+
disable
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
data/lib/revactor.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C)2007 Tony Arcieri
|
3
|
+
# You can redistribute this under the terms of the Ruby license
|
4
|
+
# See file LICENSE for details
|
5
|
+
#++
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rev'
|
9
|
+
require 'case'
|
10
|
+
|
11
|
+
# This is mostly in hopes of a bright future with Rubinius
|
12
|
+
# The recommended container for all datagrams sent between
|
13
|
+
# Actors is a Tuple, defined below, and with a 'T' shortcut:
|
14
|
+
unless defined? Tuple
|
15
|
+
# A Tuple class. Will (eventually) be a subset of Array
|
16
|
+
# with fixed size and faster performance, at least that's
|
17
|
+
# the hope with Rubinius...
|
18
|
+
class Tuple < Array; end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Shortcut Tuple as T
|
22
|
+
T = Tuple unless defined? T
|
23
|
+
|
24
|
+
require File.dirname(__FILE__) + '/revactor/actor'
|
25
|
+
require File.dirname(__FILE__) + '/revactor/server'
|
26
|
+
require File.dirname(__FILE__) + '/revactor/tcp'
|
27
|
+
require File.dirname(__FILE__) + '/revactor/behaviors/server'
|
28
|
+
require File.dirname(__FILE__) + '/revactor/filters/line'
|
29
|
+
require File.dirname(__FILE__) + '/revactor/filters/packet'
|
data/revactor.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
GEMSPEC = Gem::Specification.new do |s|
|
4
|
+
s.name = "revactor"
|
5
|
+
s.version = "0.1.0"
|
6
|
+
s.authors = "Tony Arcieri"
|
7
|
+
s.email = "tony@medioh.com"
|
8
|
+
s.date = "2008-1-15"
|
9
|
+
s.summary = "Revactor is an Actor implementation for writing high performance concurrent programs"
|
10
|
+
s.platform = Gem::Platform::RUBY
|
11
|
+
s.required_ruby_version = '>= 1.9.0'
|
12
|
+
|
13
|
+
# Gem contents
|
14
|
+
s.files = Dir.glob("{lib,examples,tools,spec}/**/*") + ['Rakefile', 'revactor.gemspec']
|
15
|
+
|
16
|
+
# Dependencies
|
17
|
+
s.add_dependency("rev", ">= 0.1.3")
|
18
|
+
s.add_dependency("case", ">= 0.3")
|
19
|
+
|
20
|
+
# RubyForge info
|
21
|
+
s.homepage = "http://revactor.org"
|
22
|
+
s.rubyforge_project = "revactor"
|
23
|
+
|
24
|
+
# RDoc settings
|
25
|
+
s.has_rdoc = true
|
26
|
+
s.rdoc_options = %w(--title Revactor --main README --line-numbers)
|
27
|
+
s.extra_rdoc_files = ["LICENSE", "README", "CHANGES"]
|
28
|
+
end
|