revactor 0.1.0
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/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
|