revactor 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +17 -0
- data/README +5 -12
- data/examples/chat_server.rb +93 -0
- data/examples/mongrel.rb +4 -6
- data/lib/revactor.rb +13 -4
- data/lib/revactor/actor.rb +11 -2
- data/lib/revactor/filters/packet.rb +1 -1
- data/lib/revactor/http_client.rb +349 -0
- data/lib/revactor/mailbox.rb +14 -14
- data/lib/revactor/mongrel.rb +31 -23
- data/lib/revactor/scheduler.rb +51 -24
- data/lib/revactor/tcp.rb +22 -13
- data/revactor.gemspec +3 -4
- data/spec/actor_spec.rb +15 -0
- metadata +53 -54
- data/tools/messaging_throughput.rb +0 -31
data/CHANGES
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
0.1.3:
|
2
|
+
|
3
|
+
* Removed case gem usage in Revactor internals
|
4
|
+
|
5
|
+
* Add a guaranteed blocking idle state for the Actor scheduler, fixing a bug
|
6
|
+
where an idle root Actor would spin on the scheduler when calling receive
|
7
|
+
|
8
|
+
* Fixed bug where subclasses of Actor still create Actors when spawned
|
9
|
+
|
10
|
+
* Implement initial HttpClient on top of Rev::HttpClient
|
11
|
+
|
12
|
+
* Optimize scheduler loop and message dispatch for a 25% speed boost
|
13
|
+
|
14
|
+
* Fix bug with the toplevel Actor never resuming if a newly spawend Actor
|
15
|
+
registeres interest in Rev events. Toplevel Actor is now rescheduled if
|
16
|
+
the event loop isn't running.
|
17
|
+
|
1
18
|
0.1.2:
|
2
19
|
|
3
20
|
* Change Revactor::TCP::Socket#active to #active? (same for Listener)
|
data/README
CHANGED
@@ -350,12 +350,9 @@ Revactor is still in its infancy. Erlang and its Open Telcom Platform are an
|
|
350
350
|
extremely feature rich platform, and many features can be borrowed and
|
351
351
|
incorporated into Revactor.
|
352
352
|
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
dies, it kills all linked Actors is well. However, special Actors trap the
|
357
|
-
exit messages from the Actors they're linked with. These Actors are generally
|
358
|
-
supervisors and restart any linked Actor graphs which crash.
|
353
|
+
Short term goals include adding thread safety. This will allow Actors in
|
354
|
+
different threads to send meassages to each other, making it easy to spin off
|
355
|
+
long-term blocking tasks into separate threads.
|
359
356
|
|
360
357
|
Next on the agenda is implementing DRb. This should be possible simply by
|
361
358
|
monkeypatching the existing DRb implementation to run on top of Revactor::TCP.
|
@@ -363,10 +360,6 @@ Once DRb has been implemented it should be fairly trivial to implement
|
|
363
360
|
distributed Actor networks over TCP using DRb as the underlying message
|
364
361
|
passing protocol.
|
365
362
|
|
366
|
-
Long term items include implementation of more Filters
|
363
|
+
Long term items include implementation of more Filters and protocol support
|
367
364
|
modules. These include an HTTP client (subclassed from the client in Rev),
|
368
|
-
an HTTP server adapter (using the Mongrel parser)
|
369
|
-
Erlang. Additional areas of concern are addressing the problems of mutable
|
370
|
-
state in conjunction with Server and FSM behaviors. A possible solution is
|
371
|
-
to implement a tuple which stores immutable collections of primitive types
|
372
|
-
like numbers, strings, and symbols, but this approach is likely to be slow.
|
365
|
+
as well as an HTTP server adapter (using the Mongrel parser).
|
@@ -0,0 +1,93 @@
|
|
1
|
+
#
|
2
|
+
# A simple chat server implemented using 1 <-> N actors
|
3
|
+
# 1 server, N client managers, plus a listener
|
4
|
+
#
|
5
|
+
# The server handles all message formatting, traffic routing, and connection tracking
|
6
|
+
# Client managers handle connection handshaking as well as low-level network interaction
|
7
|
+
# The listener spawns new client managers for each incoming connection
|
8
|
+
#
|
9
|
+
|
10
|
+
require File.dirname(__FILE__) + '/../lib/revactor'
|
11
|
+
|
12
|
+
HOST = 'localhost'
|
13
|
+
PORT = 4321
|
14
|
+
|
15
|
+
# Open a listen socket. All traffic on new connections will be run through
|
16
|
+
# the "line" filter, so incoming messages are delimited by newlines.
|
17
|
+
|
18
|
+
listener = Actor::TCP.listen(HOST, PORT, :filter => :line)
|
19
|
+
puts "Listening on #{HOST}:#{PORT}"
|
20
|
+
|
21
|
+
# Spawn the server
|
22
|
+
server = Actor.spawn do
|
23
|
+
clients = {}
|
24
|
+
|
25
|
+
# A proc to broadcast a message to all connected clients. If the server
|
26
|
+
# were encapsulated into an object this could be a method
|
27
|
+
broadcast = proc do |msg|
|
28
|
+
clients.keys.each { |client| client << T[:write, msg] }
|
29
|
+
end
|
30
|
+
|
31
|
+
# Server's main loop. The server handles incoming messages from the
|
32
|
+
# client managers and dispatches them to other client managers.
|
33
|
+
loop do
|
34
|
+
Actor.receive do |filter|
|
35
|
+
filter.when(T[:register]) do |_, client, nickname|
|
36
|
+
clients[client] = nickname
|
37
|
+
broadcast.call "*** #{nickname} joined"
|
38
|
+
client << T[:write, "*** Users: " + clients.values.join(', ')]
|
39
|
+
end
|
40
|
+
|
41
|
+
filter.when(T[:say]) do |_, client, msg|
|
42
|
+
nickname = clients[client]
|
43
|
+
broadcast.call "<#{nickname}> #{msg}"
|
44
|
+
end
|
45
|
+
|
46
|
+
filter.when(T[:disconnected]) do |_, client|
|
47
|
+
nickname = clients.delete client
|
48
|
+
broadcast.call "*** #{nickname} left" if nickname
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# The main loop handles incoming connections
|
55
|
+
loop do
|
56
|
+
# Spawn a new actor for each incoming connection
|
57
|
+
Actor.spawn(listener.accept) do |sock|
|
58
|
+
puts "#{sock.remote_addr}:#{sock.remote_port} connected"
|
59
|
+
|
60
|
+
# Connection handshaking
|
61
|
+
sock.write "Please enter a nickname:"
|
62
|
+
nickname = sock.read
|
63
|
+
|
64
|
+
server << T[:register, Actor.current, nickname]
|
65
|
+
|
66
|
+
# Flip the socket into asynchronous "active" mode
|
67
|
+
# This means the Actor can receive messages from
|
68
|
+
# the socket alongside other events.
|
69
|
+
sock.controller = Actor.current
|
70
|
+
sock.active = :once
|
71
|
+
|
72
|
+
# Main message loop
|
73
|
+
loop do
|
74
|
+
Actor.receive do |filter|
|
75
|
+
filter.when(T[:tcp, sock]) do |_, _, message|
|
76
|
+
server << T[:say, Actor.current, message]
|
77
|
+
sock.active = :once
|
78
|
+
end
|
79
|
+
|
80
|
+
filter.when(T[:write]) do |_, message|
|
81
|
+
sock.write message
|
82
|
+
end
|
83
|
+
|
84
|
+
filter.when(T[:tcp_closed, sock]) do
|
85
|
+
raise EOFError
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
rescue EOFError
|
90
|
+
puts "#{sock.remote_addr}:#{sock.remote_port} disconnected"
|
91
|
+
server << T[:disconnected, Actor.current]
|
92
|
+
end
|
93
|
+
end
|
data/examples/mongrel.rb
CHANGED
@@ -5,10 +5,8 @@ require File.dirname(__FILE__) + '/../lib/revactor/mongrel'
|
|
5
5
|
ADDR = '127.0.0.1'
|
6
6
|
PORT = 8080
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
server.register '/', Mongrel::DirHandler.new(".")
|
11
|
-
server.run
|
8
|
+
server = Mongrel::HttpServer.new(ADDR, PORT)
|
9
|
+
server.register '/', Mongrel::DirHandler.new(".")
|
12
10
|
|
13
|
-
|
14
|
-
|
11
|
+
puts "Running on #{ADDR}:#{PORT}"
|
12
|
+
server.start
|
data/lib/revactor.rb
CHANGED
@@ -5,7 +5,6 @@
|
|
5
5
|
#++
|
6
6
|
|
7
7
|
require 'rev'
|
8
|
-
require 'case'
|
9
8
|
|
10
9
|
# This is mostly in hopes of a bright future with Rubinius
|
11
10
|
# The recommended container for all datagrams sent between
|
@@ -14,18 +13,27 @@ unless defined? Tuple
|
|
14
13
|
# A Tuple class. Will (eventually) be a subset of Array
|
15
14
|
# with fixed size and faster performance, at least that's
|
16
15
|
# the hope with Rubinius...
|
17
|
-
class Tuple < Array
|
16
|
+
class Tuple < Array
|
17
|
+
def ===(obj)
|
18
|
+
return false unless obj.is_a? Array
|
19
|
+
size.times { |n| return false unless self[n] === obj[n] }
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|
18
23
|
end
|
19
24
|
|
20
25
|
# Shortcut Tuple as T
|
21
26
|
T = Tuple unless defined? T
|
22
27
|
|
23
28
|
module Revactor
|
24
|
-
Revactor::VERSION = '0.1.
|
29
|
+
Revactor::VERSION = '0.1.3' unless defined? Revactor::VERSION
|
25
30
|
def self.version() VERSION end
|
26
31
|
end
|
27
32
|
|
28
|
-
%w{
|
33
|
+
%w{
|
34
|
+
actor scheduler mailbox delegator tcp http_client
|
35
|
+
filters/line filters/packet
|
36
|
+
}.each do |file|
|
29
37
|
require File.dirname(__FILE__) + '/revactor/' + file
|
30
38
|
end
|
31
39
|
|
@@ -34,4 +42,5 @@ class Actor
|
|
34
42
|
Actor::TCP = Revactor::TCP unless defined? Actor::TCP
|
35
43
|
Actor::Filter = Revactor::Filter unless defined? Actor::Filter
|
36
44
|
Actor::Delegator = Revactor::Delegator unless defined? Actor::Delegator
|
45
|
+
Actor::HttpClient = Revactor::HttpClient unless defined? Actor::HttpClient
|
37
46
|
end
|
data/lib/revactor/actor.rb
CHANGED
@@ -99,6 +99,11 @@ class Actor
|
|
99
99
|
receive { |filter| filter.after(seconds) }
|
100
100
|
end
|
101
101
|
|
102
|
+
# Run the event loop and return after processing all outstanding messages
|
103
|
+
def tick
|
104
|
+
sleep 0
|
105
|
+
end
|
106
|
+
|
102
107
|
# Wait for messages matching a given filter. The filter object is yielded
|
103
108
|
# to be block passed to receive. You can then invoke the when argument
|
104
109
|
# which takes a parameter and a block. Messages are compared (using ===)
|
@@ -140,7 +145,7 @@ class Actor
|
|
140
145
|
current.instance_eval { @dead = true }
|
141
146
|
end
|
142
147
|
|
143
|
-
actor =
|
148
|
+
actor = new(fiber)
|
144
149
|
fiber.instance_eval { @_actor = actor }
|
145
150
|
end
|
146
151
|
end
|
@@ -181,7 +186,11 @@ class Actor
|
|
181
186
|
|
182
187
|
# Send a message to an actor
|
183
188
|
def <<(message)
|
184
|
-
|
189
|
+
# Use the scheduler mailbox to send messages across threads
|
190
|
+
unless @thread == Thread.current
|
191
|
+
scheduler.mailbox.send(self, message)
|
192
|
+
return message
|
193
|
+
end
|
185
194
|
|
186
195
|
# Erlang discards messages sent to dead actors, and if Erlang does it,
|
187
196
|
# it must be the right thing to do, right? Hooray for the Erlang
|
@@ -14,7 +14,7 @@ module Revactor
|
|
14
14
|
# length prefix followed by a message body, such as DRb. Either 16-bit
|
15
15
|
# or 32-bit prefixes are supported.
|
16
16
|
class Packet
|
17
|
-
def initialize(size =
|
17
|
+
def initialize(size = 4)
|
18
18
|
unless size == 2 or size == 4
|
19
19
|
raise ArgumentError, 'only 2 or 4 byte prefixes are supported'
|
20
20
|
end
|
@@ -0,0 +1,349 @@
|
|
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
|
+
require 'uri'
|
9
|
+
|
10
|
+
|
11
|
+
module Revactor
|
12
|
+
# Thrown for all HTTP-specific errors
|
13
|
+
class HttpClientError < StandardError; end
|
14
|
+
|
15
|
+
# A high performance HTTP client which wraps the asynchronous client in Rev
|
16
|
+
class HttpClient < Rev::HttpClient
|
17
|
+
# Default timeout for HTTP requests (until the response header is received)
|
18
|
+
REQUEST_TIMEOUT = 60
|
19
|
+
|
20
|
+
# Read timeout for responses from the server
|
21
|
+
READ_TIMEOUT = 30
|
22
|
+
|
23
|
+
# Maximum number of HTTP redirects to follow
|
24
|
+
MAX_REDIRECTS = 10
|
25
|
+
|
26
|
+
# Statuses which indicate the request was redirected
|
27
|
+
REDIRECT_STATUSES = [301, 302, 303, 307]
|
28
|
+
|
29
|
+
class << self
|
30
|
+
def connect(host, port = 80)
|
31
|
+
client = super
|
32
|
+
client.instance_eval { @receiver = Actor.current }
|
33
|
+
client.attach Rev::Loop.default
|
34
|
+
|
35
|
+
Actor.receive do |filter|
|
36
|
+
filter.when(T[Object, client]) do |message, _|
|
37
|
+
case message
|
38
|
+
when :http_connected
|
39
|
+
client.disable
|
40
|
+
return client
|
41
|
+
when :http_connect_failed
|
42
|
+
raise TCP::ConnectError, "connection refused"
|
43
|
+
when :http_resolve_failed
|
44
|
+
raise TCP::ResolveError, "couldn't resolve #{host}"
|
45
|
+
else raise "unexpected message for #{client.inspect}: #{message.inspect}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
filter.after(TCP::CONNECT_TIMEOUT) do
|
50
|
+
raise TCP::ConnectError, "connection timed out"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Perform an HTTP request for the given method and return a response object
|
56
|
+
def request(method, uri, options = {}, &block)
|
57
|
+
follow_redirects = options.has_key?(:follow_redirects) ? options[:follow_redirects] : true
|
58
|
+
uri = URI.parse(uri)
|
59
|
+
|
60
|
+
MAX_REDIRECTS.times do
|
61
|
+
raise URI::InvalidURIError, "invalid HTTP URI: #{uri}" unless uri.is_a? URI::HTTP
|
62
|
+
request_options = uri.is_a?(URI::HTTPS) ? options.merge(:ssl => true) : options
|
63
|
+
|
64
|
+
client = connect(uri.host, uri.port)
|
65
|
+
response = client.request(method, uri.request_uri, request_options, &block)
|
66
|
+
|
67
|
+
return response unless follow_redirects and REDIRECT_STATUSES.include? response.status
|
68
|
+
response.close
|
69
|
+
|
70
|
+
location = response.headers['location']
|
71
|
+
raise "redirect with no location header: #{uri}" if location.nil?
|
72
|
+
|
73
|
+
# Append host to relative URLs
|
74
|
+
if location[0] == '/'
|
75
|
+
location = "#{uri.scheme}://#{uri.host}" << location
|
76
|
+
end
|
77
|
+
|
78
|
+
uri = URI.parse(location)
|
79
|
+
end
|
80
|
+
|
81
|
+
raise HttpClientError, "exceeded maximum of #{MAX_REDIRECTS} redirects"
|
82
|
+
end
|
83
|
+
|
84
|
+
Rev::HttpClient::ALLOWED_METHODS.each do |meth|
|
85
|
+
module_eval <<-EOD
|
86
|
+
def #{meth}(uri, options = {}, &block)
|
87
|
+
request(:#{meth}, uri, options, &block)
|
88
|
+
end
|
89
|
+
EOD
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def initialize(socket)
|
94
|
+
super
|
95
|
+
@controller = @receiver ||= Actor.current
|
96
|
+
end
|
97
|
+
|
98
|
+
# Change the controlling Actor for active mode reception
|
99
|
+
# Set the controlling actor
|
100
|
+
def controller=(controller)
|
101
|
+
raise ArgumentError, "controller must be an actor" unless controller.is_a? Actor
|
102
|
+
|
103
|
+
@receiver = controller if @receiver == @controller
|
104
|
+
@controller = controller
|
105
|
+
end
|
106
|
+
|
107
|
+
# Initiate an HTTP request for the given path using the given method
|
108
|
+
# Supports the following options:
|
109
|
+
#
|
110
|
+
# ssl: Boolean
|
111
|
+
# If true, an HTTPS request will be made
|
112
|
+
#
|
113
|
+
# head: {Key: Value, Key2: Value2}
|
114
|
+
# Specify HTTP headers, e.g. {'Connection': 'close'}
|
115
|
+
#
|
116
|
+
# query: {Key: Value}
|
117
|
+
# Specify query string parameters (auto-escaped)
|
118
|
+
#
|
119
|
+
# cookies: {Key: Value}
|
120
|
+
# Specify hash of cookies (auto-escaped)
|
121
|
+
#
|
122
|
+
# body: String
|
123
|
+
# Specify the request body (you must encode it for now)
|
124
|
+
#
|
125
|
+
def request(method, path, options = {})
|
126
|
+
if options[:ssl]
|
127
|
+
ssl_handshake
|
128
|
+
|
129
|
+
Actor.receive do |filter|
|
130
|
+
filter.when(T[:https_connected, self]) do
|
131
|
+
disable
|
132
|
+
end
|
133
|
+
|
134
|
+
filter.when(T[:http_closed, self]) do
|
135
|
+
raise EOFError, "SSL handshake failed"
|
136
|
+
end
|
137
|
+
|
138
|
+
filter.after(TCP::CONNECT_TIMEOUT) do
|
139
|
+
close unless closed?
|
140
|
+
raise TCP::ConnectError, "SSL handshake timed out"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
super
|
146
|
+
enable
|
147
|
+
|
148
|
+
Actor.receive do |filter|
|
149
|
+
filter.when(T[:http_response_header, self]) do |_, _, response_header|
|
150
|
+
return HttpResponse.new(self, response_header)
|
151
|
+
end
|
152
|
+
|
153
|
+
filter.when(T[:http_error, self, Object]) do |_, _, reason|
|
154
|
+
close unless closed?
|
155
|
+
raise HttpClientError, reason
|
156
|
+
end
|
157
|
+
|
158
|
+
filter.when(T[:http_closed, self]) do
|
159
|
+
raise EOFError, "connection closed unexpectedly"
|
160
|
+
end
|
161
|
+
|
162
|
+
filter.after(REQUEST_TIMEOUT) do
|
163
|
+
@finished = true
|
164
|
+
close unless closed?
|
165
|
+
|
166
|
+
raise HttpClientError, "request timed out"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
#########
|
172
|
+
protected
|
173
|
+
#########
|
174
|
+
|
175
|
+
def ssl_handshake
|
176
|
+
require 'rev/ssl'
|
177
|
+
extend Rev::SSL
|
178
|
+
ssl_client_start
|
179
|
+
end
|
180
|
+
|
181
|
+
def on_connect
|
182
|
+
super
|
183
|
+
@receiver << T[:http_connected, self]
|
184
|
+
end
|
185
|
+
|
186
|
+
def on_ssl_connect
|
187
|
+
@receiver << [:https_connected, self]
|
188
|
+
end
|
189
|
+
|
190
|
+
def on_connect_failed
|
191
|
+
super
|
192
|
+
@receiver << T[:http_connect_failed, self]
|
193
|
+
end
|
194
|
+
|
195
|
+
def on_resolve_failed
|
196
|
+
super
|
197
|
+
@receiver << T[:http_resolve_failed, self]
|
198
|
+
end
|
199
|
+
|
200
|
+
def on_response_header(response_header)
|
201
|
+
disable
|
202
|
+
@receiver << T[:http_response_header, self, response_header]
|
203
|
+
end
|
204
|
+
|
205
|
+
def on_body_data(data)
|
206
|
+
disable if enabled? and not @active
|
207
|
+
@receiver << T[:http, self, data]
|
208
|
+
end
|
209
|
+
|
210
|
+
def on_request_complete
|
211
|
+
@finished = true
|
212
|
+
@receiver << T[:http_request_complete, self]
|
213
|
+
close
|
214
|
+
end
|
215
|
+
|
216
|
+
def on_close
|
217
|
+
@receiver << T[:http_closed, self] unless @finished
|
218
|
+
end
|
219
|
+
|
220
|
+
def on_error(reason)
|
221
|
+
@finished = true
|
222
|
+
@receiver << T[:http_error, self, reason]
|
223
|
+
close
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# An object representing a response to an HTTP request
|
228
|
+
class HttpResponse
|
229
|
+
def initialize(client, response_header)
|
230
|
+
@client = client
|
231
|
+
|
232
|
+
# Copy these out of the original Rev response object, then discard it
|
233
|
+
@status = response_header.status
|
234
|
+
@reason = response_header.http_reason
|
235
|
+
@version = response_header.http_version
|
236
|
+
@content_length = response_header.content_length
|
237
|
+
@chunked_encoding = response_header.chunked_encoding?
|
238
|
+
|
239
|
+
# Convert header fields hash from LIKE_THIS to like-this
|
240
|
+
@headers = response_header.reduce({}) { |h, (k, v)| h[k.split('_').map(&:downcase).join('-')] = v; h }
|
241
|
+
|
242
|
+
# Extract Transfer-Encoding if available
|
243
|
+
@transfer_encoding = @headers.delete('transfer-encoding')
|
244
|
+
|
245
|
+
# Extract Content-Type if available
|
246
|
+
@content_type = @headers.delete('content-type')
|
247
|
+
|
248
|
+
# Extract Content-Encoding if available
|
249
|
+
@content_encoding = @headers.delete('content-encoding') || 'identity'
|
250
|
+
end
|
251
|
+
|
252
|
+
# The response status as an integer (e.g. 200)
|
253
|
+
attr_reader :status
|
254
|
+
|
255
|
+
# The reason returned in the http response (e.g "OK", "File not found", etc.)
|
256
|
+
attr_reader :reason
|
257
|
+
|
258
|
+
# The HTTP version returned (e.g. "HTTP/1.1")
|
259
|
+
attr_reader :version
|
260
|
+
|
261
|
+
# The encoding of the transfer
|
262
|
+
attr_reader :transfer_encoding
|
263
|
+
|
264
|
+
# The encoding of the content. Gzip encoding will be processed automatically
|
265
|
+
attr_reader :content_encoding
|
266
|
+
|
267
|
+
# The MIME type of the response's content
|
268
|
+
attr_reader :content_type
|
269
|
+
|
270
|
+
# The content length as an integer, or nil if the length is unspecified or
|
271
|
+
# the response is using chunked transfer encoding
|
272
|
+
attr_reader :content_length
|
273
|
+
|
274
|
+
# Access to the raw header fields from the request
|
275
|
+
attr_reader :headers
|
276
|
+
|
277
|
+
# Is the request encoding chunked?
|
278
|
+
def chunked_encoding?; @chunked_encoding; end
|
279
|
+
|
280
|
+
# Incrementally read the response body
|
281
|
+
def read_body
|
282
|
+
@client.controller = Actor.current
|
283
|
+
@client.enable if @client.attached? and not @client.enabled?
|
284
|
+
|
285
|
+
Actor.receive do |filter|
|
286
|
+
filter.when(T[:http, @client]) do |_, _, data|
|
287
|
+
return data
|
288
|
+
end
|
289
|
+
|
290
|
+
filter.when(T[:http_request_complete, @client]) do
|
291
|
+
return nil
|
292
|
+
end
|
293
|
+
|
294
|
+
filter.when(T[:http_error, @client]) do |_, _, reason|
|
295
|
+
raise HttpClientError, reason
|
296
|
+
end
|
297
|
+
|
298
|
+
filter.when(T[:http_closed, @client]) do
|
299
|
+
raise EOFError, "connection closed unexpectedly"
|
300
|
+
end
|
301
|
+
|
302
|
+
filter.after(HttpClient::READ_TIMEOUT) do
|
303
|
+
@finished = true
|
304
|
+
@client.close unless @client.closed?
|
305
|
+
raise HttpClientError, "read timed out"
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
# Consume the entire response body and return it as a string.
|
311
|
+
# The body is stored for subsequent access.
|
312
|
+
# A maximum body length may optionally be specified
|
313
|
+
def body(maxlength = nil)
|
314
|
+
return @body if @body
|
315
|
+
@body = ""
|
316
|
+
|
317
|
+
begin
|
318
|
+
while (data = read_body)
|
319
|
+
@body << data
|
320
|
+
|
321
|
+
if maxlength and @body.size > maxlength
|
322
|
+
raise HttpClientError, "overlength body"
|
323
|
+
end
|
324
|
+
end
|
325
|
+
rescue EOFError => ex
|
326
|
+
# If we didn't get a Content-Length and encoding isn't chunked
|
327
|
+
# we have to depend on the socket closing to detect end-of-body
|
328
|
+
# Otherwise the EOFError was unexpected and should be raised
|
329
|
+
unless (content_length.nil? or content_length.zero?) and not chunked_encoding?
|
330
|
+
raise ex
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
if content_length and body.size != content_length
|
335
|
+
raise HttpClientError, "body size does not match Content-Length (#{body.size} of #{content_length})"
|
336
|
+
end
|
337
|
+
|
338
|
+
@body
|
339
|
+
end
|
340
|
+
|
341
|
+
# Explicitly close the connection
|
342
|
+
def close
|
343
|
+
return if @client.closed?
|
344
|
+
@finished = true
|
345
|
+
@client.controller = Actor.current
|
346
|
+
@client.close
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
data/lib/revactor/mailbox.rb
CHANGED
@@ -30,7 +30,7 @@ class Actor
|
|
30
30
|
|
31
31
|
# Clear mailbox processing variables
|
32
32
|
action = matched_index = nil
|
33
|
-
|
33
|
+
at = 0
|
34
34
|
|
35
35
|
# Clear timeout variables
|
36
36
|
@timed_out = false
|
@@ -43,19 +43,13 @@ class Actor
|
|
43
43
|
|
44
44
|
# Process incoming messages
|
45
45
|
while action.nil?
|
46
|
-
@queue
|
47
|
-
unless
|
48
|
-
|
49
|
-
# Keep track of which messages we've ran the filter across so it
|
50
|
-
# isn't re-run against messages it already failed to match
|
51
|
-
processed_upto += 1
|
52
|
-
next
|
53
|
-
end
|
54
|
-
|
55
|
-
# We've found a matching action, so break out of the loop
|
56
|
-
matched_index = processed_upto + index
|
46
|
+
at.upto(@queue.size - 1) do |i|
|
47
|
+
next unless action = filter.match(@queue[i])
|
48
|
+
matched_index = i
|
57
49
|
break
|
58
50
|
end
|
51
|
+
|
52
|
+
at = @queue.size
|
59
53
|
|
60
54
|
# Ignore timeouts if we've matched a message
|
61
55
|
if action
|
@@ -91,6 +85,11 @@ class Actor
|
|
91
85
|
def empty?
|
92
86
|
@queue.empty?
|
93
87
|
end
|
88
|
+
|
89
|
+
# Clear the mailbox
|
90
|
+
def clear
|
91
|
+
@queue.clear
|
92
|
+
end
|
94
93
|
|
95
94
|
#######
|
96
95
|
private
|
@@ -121,7 +120,8 @@ class Actor
|
|
121
120
|
# Provide a pattern to match against with === and a block to call
|
122
121
|
# when the pattern is matched.
|
123
122
|
def when(pattern, &action)
|
124
|
-
|
123
|
+
# Don't explicitly require an action to be specified
|
124
|
+
action ||= proc {}
|
125
125
|
@ruleset << [pattern, action]
|
126
126
|
end
|
127
127
|
|
@@ -131,7 +131,7 @@ class Actor
|
|
131
131
|
raise ArgumentError, "timeout already specified" if @mailbox.timer
|
132
132
|
raise ArgumentError, "must be zero or positive" if seconds < 0
|
133
133
|
|
134
|
-
# Don't explicitly require an action to be specified
|
134
|
+
# Don't explicitly require an action to be specified
|
135
135
|
@mailbox.timeout_action = action || proc {}
|
136
136
|
@mailbox.timer = Timer.new(seconds, Actor.current).attach(Rev::Loop.default)
|
137
137
|
end
|
data/lib/revactor/mongrel.rb
CHANGED
@@ -29,33 +29,41 @@ module Mongrel
|
|
29
29
|
@timeout = timeout
|
30
30
|
end
|
31
31
|
|
32
|
-
#
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
end
|
32
|
+
# Start Mongrel. This method executes the Mongrel event loop, and will
|
33
|
+
# not return until interrupted or explicitly stopped.
|
34
|
+
def start
|
35
|
+
begin
|
36
|
+
while true
|
37
|
+
begin
|
38
|
+
client = @socket.accept
|
39
|
+
actor = Actor.spawn client, &method(:process_client)
|
40
|
+
actor[:started_on] = Time.now
|
41
|
+
rescue Interrupt, StopServer
|
42
|
+
break
|
43
|
+
rescue Errno::ECONNABORTED
|
44
|
+
# client closed the socket even before accept
|
45
|
+
client.close rescue nil
|
46
|
+
rescue Object => e
|
47
|
+
STDERR.puts "#{Time.now}: Unhandled listen loop exception #{e.inspect}."
|
48
|
+
STDERR.puts e.backtrace.join("\n")
|
50
49
|
end
|
51
|
-
graceful_shutdown
|
52
|
-
ensure
|
53
|
-
@socket.close
|
54
|
-
# STDERR.puts "#{Time.now}: Closed socket."
|
55
50
|
end
|
51
|
+
graceful_shutdown
|
52
|
+
ensure
|
53
|
+
@socket.close
|
54
|
+
# STDERR.puts "#{Time.now}: Closed socket."
|
56
55
|
end
|
56
|
+
end
|
57
57
|
|
58
|
-
|
58
|
+
# Runs the thing. Returns the Thread the server is running in.
|
59
|
+
def run
|
60
|
+
@acceptor = Thread.new { start }
|
61
|
+
end
|
62
|
+
|
63
|
+
# Clean up after any dead workers
|
64
|
+
def reap_dead_workers(reason = 'unknown')
|
65
|
+
# FIXME This should signal all workers to die
|
66
|
+
0
|
59
67
|
end
|
60
68
|
end
|
61
69
|
end
|
data/lib/revactor/scheduler.rb
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
# See file LICENSE for details
|
5
5
|
#++
|
6
6
|
|
7
|
+
require 'thread'
|
7
8
|
require File.dirname(__FILE__) + '/../revactor'
|
8
9
|
|
9
10
|
class Actor
|
@@ -12,9 +13,13 @@ class Actor
|
|
12
13
|
# processed their mailboxes then the scheduler waits for any outstanding
|
13
14
|
# Rev events. If there are no active Rev watchers then the scheduler exits.
|
14
15
|
class Scheduler
|
16
|
+
attr_reader :mailbox
|
17
|
+
|
15
18
|
def initialize
|
16
19
|
@queue = []
|
17
20
|
@running = false
|
21
|
+
@mailbox = Mailbox.new
|
22
|
+
@mailbox.attach Rev::Loop.default
|
18
23
|
end
|
19
24
|
|
20
25
|
# Schedule an Actor to be executed, and run the scheduler if it isn't
|
@@ -25,16 +30,21 @@ class Actor
|
|
25
30
|
@queue << actor unless @queue.last == actor
|
26
31
|
|
27
32
|
unless @running
|
28
|
-
#
|
29
|
-
@
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
# Resume the scheduler
|
34
|
-
@fiber.resume
|
33
|
+
# Reschedule the current Actor for execution
|
34
|
+
@queue << Actor.current
|
35
|
+
|
36
|
+
# Start the scheduler
|
37
|
+
Fiber.new { run }.resume
|
35
38
|
end
|
36
39
|
end
|
37
|
-
|
40
|
+
|
41
|
+
# Is the scheduler running?
|
42
|
+
def running?; @running; end
|
43
|
+
|
44
|
+
#########
|
45
|
+
protected
|
46
|
+
#########
|
47
|
+
|
38
48
|
# Run the scheduler
|
39
49
|
def run
|
40
50
|
return if @running
|
@@ -42,35 +52,24 @@ class Actor
|
|
42
52
|
@running = true
|
43
53
|
default_loop = Rev::Loop.default
|
44
54
|
|
45
|
-
|
46
|
-
@queue.
|
55
|
+
while true
|
56
|
+
while actor = @queue.shift
|
47
57
|
begin
|
48
58
|
actor.fiber.resume
|
49
59
|
handle_exit(actor) if actor.dead?
|
50
60
|
rescue FiberError
|
51
61
|
# Handle Actors whose Fibers died after being scheduled
|
62
|
+
actor.instance_eval { @dead = true }
|
52
63
|
handle_exit(actor)
|
53
64
|
rescue => ex
|
54
65
|
handle_exit(actor, ex)
|
55
66
|
end
|
56
67
|
end
|
57
68
|
|
58
|
-
|
59
|
-
default_loop.run_once if default_loop.has_active_watchers?
|
69
|
+
default_loop.run_once
|
60
70
|
end
|
61
|
-
|
62
|
-
@running = false
|
63
|
-
end
|
64
|
-
|
65
|
-
# Is the scheduler running?
|
66
|
-
def running?
|
67
|
-
@running
|
68
71
|
end
|
69
|
-
|
70
|
-
#########
|
71
|
-
protected
|
72
|
-
#########
|
73
|
-
|
72
|
+
|
74
73
|
def handle_exit(actor, ex = nil)
|
75
74
|
actor.instance_eval do
|
76
75
|
# Mark Actor as dead
|
@@ -92,5 +91,33 @@ class Actor
|
|
92
91
|
# FIXME this should go to a real logger
|
93
92
|
STDERR.puts "#{ex.class}: #{[ex, *ex.backtrace].join("\n\t")}"
|
94
93
|
end
|
94
|
+
|
95
|
+
# The Scheduler Mailbox allows messages to be safely delivered across
|
96
|
+
# threads. If a thread is sleeping sending it a message will wake
|
97
|
+
# it up.
|
98
|
+
class Mailbox < Rev::AsyncWatcher
|
99
|
+
def initialize
|
100
|
+
super
|
101
|
+
|
102
|
+
@queue = []
|
103
|
+
@lock = Mutex.new
|
104
|
+
end
|
105
|
+
|
106
|
+
def send(actor, message)
|
107
|
+
@lock.synchronize { @queue << T[actor, message] }
|
108
|
+
signal
|
109
|
+
end
|
110
|
+
|
111
|
+
#########
|
112
|
+
protected
|
113
|
+
#########
|
114
|
+
|
115
|
+
def on_signal
|
116
|
+
@lock.synchronize do
|
117
|
+
@queue.each { |actor, message| actor << message }
|
118
|
+
@queue.clear
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
95
122
|
end
|
96
123
|
end
|
data/lib/revactor/tcp.rb
CHANGED
@@ -31,7 +31,7 @@ module Revactor
|
|
31
31
|
socket.attach Rev::Loop.default
|
32
32
|
|
33
33
|
Actor.receive do |filter|
|
34
|
-
filter.when(
|
34
|
+
filter.when(T[Object, socket]) do |message, _|
|
35
35
|
case message
|
36
36
|
when :tcp_connected
|
37
37
|
return socket
|
@@ -56,6 +56,11 @@ module Revactor
|
|
56
56
|
#
|
57
57
|
# :controller - The controlling actor, default Actor.current
|
58
58
|
#
|
59
|
+
# :filter - An symbol/class or array of symbols/classes which implement
|
60
|
+
# #encode and #decode methods to transform data sent and
|
61
|
+
# received data respectively via Revactor::TCP::Socket.
|
62
|
+
# See the "Filters" section in the README for more information
|
63
|
+
#
|
59
64
|
def self.listen(addr, port, options = {})
|
60
65
|
Listener.new(addr, port, options).attach(Rev::Loop.default).disable
|
61
66
|
end
|
@@ -77,12 +82,13 @@ module Revactor
|
|
77
82
|
# :filter - An symbol/class or array of symbols/classes which implement
|
78
83
|
# #encode and #decode methods to transform data sent and
|
79
84
|
# received data respectively via Revactor::TCP::Socket.
|
85
|
+
# See the "Filters" section in the README for more information
|
80
86
|
#
|
81
87
|
def connect(host, port, options = {})
|
82
88
|
options[:active] ||= false
|
83
89
|
options[:controller] ||= Actor.current
|
84
90
|
|
85
|
-
super
|
91
|
+
super.instance_eval {
|
86
92
|
@active, @controller = options[:active], options[:controller]
|
87
93
|
@filterset = initialize_filter(*options[:filter])
|
88
94
|
self
|
@@ -112,6 +118,7 @@ module Revactor
|
|
112
118
|
# false - Receiving data is disabled
|
113
119
|
# :once - A single message will be sent to the controlling actor
|
114
120
|
# then active mode will be disabled
|
121
|
+
#
|
115
122
|
def active=(state)
|
116
123
|
unless @receiver == @controller
|
117
124
|
raise "cannot change active state during a synchronous call"
|
@@ -162,7 +169,7 @@ module Revactor
|
|
162
169
|
|
163
170
|
loop do
|
164
171
|
Actor.receive do |filter|
|
165
|
-
filter.when(
|
172
|
+
filter.when(T[:tcp, self]) do |_, _, data|
|
166
173
|
if length.nil?
|
167
174
|
@receiver = @controller
|
168
175
|
@active = active
|
@@ -182,7 +189,7 @@ module Revactor
|
|
182
189
|
end
|
183
190
|
end
|
184
191
|
|
185
|
-
filter.when(
|
192
|
+
filter.when(T[:tcp_closed, self]) do
|
186
193
|
unless @receiver == @controller
|
187
194
|
@receiver = @controller
|
188
195
|
@receiver << T[:tcp_closed, self]
|
@@ -207,19 +214,16 @@ module Revactor
|
|
207
214
|
super(encode(data))
|
208
215
|
|
209
216
|
Actor.receive do |filter|
|
210
|
-
filter.when(
|
217
|
+
filter.when(T[:tcp_write_complete, self]) do
|
211
218
|
@receiver = @controller
|
212
219
|
@active = active
|
213
|
-
enable if @active
|
220
|
+
enable if @active and not enabled?
|
214
221
|
|
215
222
|
return data.size
|
216
223
|
end
|
217
224
|
|
218
|
-
filter.when(
|
219
|
-
@
|
220
|
-
@active = active
|
221
|
-
enable if @active
|
222
|
-
|
225
|
+
filter.when(T[:tcp_closed, self]) do
|
226
|
+
@active = false
|
223
227
|
raise EOFError, "connection closed"
|
224
228
|
end
|
225
229
|
end
|
@@ -307,7 +311,7 @@ module Revactor
|
|
307
311
|
|
308
312
|
if message.is_a?(Array) and not message.empty?
|
309
313
|
message.each { |msg| @receiver << T[:tcp, self, msg] }
|
310
|
-
elsif message
|
314
|
+
elsif message and not message.empty?
|
311
315
|
@receiver << T[:tcp, self, message]
|
312
316
|
else return
|
313
317
|
end
|
@@ -334,6 +338,11 @@ module Revactor
|
|
334
338
|
#
|
335
339
|
# :controller - The controlling actor, default Actor.current
|
336
340
|
#
|
341
|
+
# :filter - An symbol/class or array of symbols/classes which implement
|
342
|
+
# #encode and #decode methods to transform data sent and
|
343
|
+
# received data respectively via Revactor::TCP::Socket.
|
344
|
+
# See the "Filters" section in the README for more information
|
345
|
+
#
|
337
346
|
def initialize(host, port, options = {})
|
338
347
|
super(host, port)
|
339
348
|
opts = {
|
@@ -378,7 +387,7 @@ module Revactor
|
|
378
387
|
enable
|
379
388
|
|
380
389
|
Actor.receive do |filter|
|
381
|
-
filter.when(
|
390
|
+
filter.when(T[:tcp_connection, self]) do |_, _, sock|
|
382
391
|
@accepting = false
|
383
392
|
return sock
|
384
393
|
end
|
data/revactor.gemspec
CHANGED
@@ -2,10 +2,10 @@ require 'rubygems'
|
|
2
2
|
|
3
3
|
GEMSPEC = Gem::Specification.new do |s|
|
4
4
|
s.name = "revactor"
|
5
|
-
s.version = "0.1.
|
5
|
+
s.version = "0.1.3"
|
6
6
|
s.authors = "Tony Arcieri"
|
7
7
|
s.email = "tony@medioh.com"
|
8
|
-
s.date = "2008-1-
|
8
|
+
s.date = "2008-1-29"
|
9
9
|
s.summary = "Revactor is an Actor implementation for writing high performance concurrent programs"
|
10
10
|
s.platform = Gem::Platform::RUBY
|
11
11
|
s.required_ruby_version = '>= 1.9.0'
|
@@ -14,8 +14,7 @@ GEMSPEC = Gem::Specification.new do |s|
|
|
14
14
|
s.files = Dir.glob("{lib,examples,tools,spec}/**/*") + ['Rakefile', 'revactor.gemspec']
|
15
15
|
|
16
16
|
# Dependencies
|
17
|
-
s.add_dependency("rev", ">= 0.
|
18
|
-
s.add_dependency("case", ">= 0.4")
|
17
|
+
s.add_dependency("rev", ">= 0.2.0")
|
19
18
|
|
20
19
|
# RubyForge info
|
21
20
|
s.homepage = "http://revactor.org"
|
data/spec/actor_spec.rb
CHANGED
@@ -44,6 +44,9 @@ describe Actor do
|
|
44
44
|
end
|
45
45
|
|
46
46
|
actor << :foo
|
47
|
+
|
48
|
+
# Hack to run the Actor scheduler once
|
49
|
+
Actor.sleep 0
|
47
50
|
@actor_run.should be_true
|
48
51
|
end
|
49
52
|
|
@@ -62,6 +65,9 @@ describe Actor do
|
|
62
65
|
end
|
63
66
|
|
64
67
|
['first message', 'second message', 'third message'].each { |m| actor << m }
|
68
|
+
|
69
|
+
# Hack to run the Actor scheduler once
|
70
|
+
Actor.sleep 0
|
65
71
|
@actor_run.should be_true
|
66
72
|
end
|
67
73
|
|
@@ -73,6 +79,9 @@ describe Actor do
|
|
73
79
|
end.should == :right
|
74
80
|
@actor_run = true
|
75
81
|
end
|
82
|
+
|
83
|
+
# Hack to run the Actor scheduler
|
84
|
+
Actor.sleep 0.02
|
76
85
|
@actor_run.should be_true
|
77
86
|
end
|
78
87
|
|
@@ -90,6 +99,9 @@ describe Actor do
|
|
90
99
|
end
|
91
100
|
|
92
101
|
[:foo, :bar, :baz].each { |m| actor << m }
|
102
|
+
|
103
|
+
# Hack to run the Actor scheduler once
|
104
|
+
Actor.sleep 0
|
93
105
|
@actor_run.should be_true
|
94
106
|
end
|
95
107
|
end
|
@@ -139,6 +151,9 @@ describe Actor do
|
|
139
151
|
|
140
152
|
actor.dead?.should be_false
|
141
153
|
actor << :foobar
|
154
|
+
|
155
|
+
# Hack to run the Actor scheduler once
|
156
|
+
Actor.sleep 0
|
142
157
|
actor.dead?.should be_true
|
143
158
|
end
|
144
159
|
end
|
metadata
CHANGED
@@ -1,33 +1,36 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.4
|
3
|
-
specification_version: 1
|
4
2
|
name: revactor
|
5
3
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.1.
|
7
|
-
date: 2008-01-28 00:00:00 -07:00
|
8
|
-
summary: Revactor is an Actor implementation for writing high performance concurrent programs
|
9
|
-
require_paths:
|
10
|
-
- lib
|
11
|
-
email: tony@medioh.com
|
12
|
-
homepage: http://revactor.org
|
13
|
-
rubyforge_project: revactor
|
14
|
-
description:
|
15
|
-
autorequire:
|
16
|
-
default_executable:
|
17
|
-
bindir: bin
|
18
|
-
has_rdoc: true
|
19
|
-
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
-
requirements:
|
21
|
-
- - ">="
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version: 1.9.0
|
24
|
-
version:
|
4
|
+
version: 0.1.3
|
25
5
|
platform: ruby
|
26
|
-
signing_key:
|
27
|
-
cert_chain:
|
28
|
-
post_install_message:
|
29
6
|
authors:
|
30
7
|
- Tony Arcieri
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-01-29 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rev
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.2.0
|
23
|
+
version:
|
24
|
+
description:
|
25
|
+
email: tony@medioh.com
|
26
|
+
executables: []
|
27
|
+
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files:
|
31
|
+
- LICENSE
|
32
|
+
- README
|
33
|
+
- CHANGES
|
31
34
|
files:
|
32
35
|
- lib/revactor
|
33
36
|
- lib/revactor/actor.rb
|
@@ -35,15 +38,16 @@ files:
|
|
35
38
|
- lib/revactor/filters
|
36
39
|
- lib/revactor/filters/line.rb
|
37
40
|
- lib/revactor/filters/packet.rb
|
41
|
+
- lib/revactor/http_client.rb
|
38
42
|
- lib/revactor/mailbox.rb
|
39
43
|
- lib/revactor/mongrel.rb
|
40
44
|
- lib/revactor/scheduler.rb
|
41
45
|
- lib/revactor/tcp.rb
|
42
46
|
- lib/revactor.rb
|
47
|
+
- examples/chat_server.rb
|
43
48
|
- examples/echo_server.rb
|
44
49
|
- examples/google.rb
|
45
50
|
- examples/mongrel.rb
|
46
|
-
- tools/messaging_throughput.rb
|
47
51
|
- spec/actor_spec.rb
|
48
52
|
- spec/delegator_spec.rb
|
49
53
|
- spec/line_filter_spec.rb
|
@@ -54,40 +58,35 @@ files:
|
|
54
58
|
- LICENSE
|
55
59
|
- README
|
56
60
|
- CHANGES
|
57
|
-
|
58
|
-
|
61
|
+
has_rdoc: true
|
62
|
+
homepage: http://revactor.org
|
63
|
+
post_install_message:
|
59
64
|
rdoc_options:
|
60
65
|
- --title
|
61
66
|
- Revactor
|
62
67
|
- --main
|
63
68
|
- README
|
64
69
|
- --line-numbers
|
65
|
-
|
66
|
-
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 1.9.0
|
77
|
+
version:
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: "0"
|
83
|
+
version:
|
73
84
|
requirements: []
|
74
85
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
- !ruby/object:Gem::Version
|
83
|
-
version: 0.1.4
|
84
|
-
version:
|
85
|
-
- !ruby/object:Gem::Dependency
|
86
|
-
name: case
|
87
|
-
version_requirement:
|
88
|
-
version_requirements: !ruby/object:Gem::Version::Requirement
|
89
|
-
requirements:
|
90
|
-
- - ">="
|
91
|
-
- !ruby/object:Gem::Version
|
92
|
-
version: "0.4"
|
93
|
-
version:
|
86
|
+
rubyforge_project: revactor
|
87
|
+
rubygems_version: 1.0.1
|
88
|
+
signing_key:
|
89
|
+
specification_version: 2
|
90
|
+
summary: Revactor is an Actor implementation for writing high performance concurrent programs
|
91
|
+
test_files: []
|
92
|
+
|
@@ -1,31 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/../lib/revactor'
|
2
|
-
|
3
|
-
NTIMES=100000
|
4
|
-
|
5
|
-
begin_time = Time.now
|
6
|
-
|
7
|
-
puts "#{begin_time.strftime('%H:%M:%S')} -- Sending #{NTIMES} messages"
|
8
|
-
|
9
|
-
parent = Actor.current
|
10
|
-
child = Actor.spawn do
|
11
|
-
(NTIMES / 2).times do
|
12
|
-
Actor.receive do |f|
|
13
|
-
f.when(:foo) { parent << :bar }
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
child << :foo
|
19
|
-
(NTIMES / 2).times do
|
20
|
-
Actor.receive do |f|
|
21
|
-
f.when(:bar) { child << :foo }
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
end_time = Time.now
|
26
|
-
duration = end_time - begin_time
|
27
|
-
throughput = NTIMES / duration
|
28
|
-
|
29
|
-
puts "#{end_time.strftime('%H:%M:%S')} -- Finished"
|
30
|
-
puts "Duration: #{sprintf("%0.2f", duration)} seconds"
|
31
|
-
puts "Throughput: #{sprintf("%0.2f", throughput)} messages per second"
|