revactor 0.1.2 → 0.1.3
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 +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"
|