revactor 0.1.0 → 0.1.1
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 +22 -0
- data/README +13 -49
- data/examples/echo_server.rb +19 -23
- data/examples/google.rb +13 -15
- data/examples/mongrel.rb +1 -1
- data/lib/revactor.rb +15 -7
- data/lib/revactor/actor.rb +75 -225
- data/lib/revactor/delegator.rb +70 -0
- data/lib/revactor/mailbox.rb +159 -0
- data/lib/revactor/mongrel.rb +3 -3
- data/lib/revactor/scheduler.rb +65 -0
- data/lib/revactor/tcp.rb +13 -5
- data/revactor.gemspec +4 -4
- data/spec/actor_spec.rb +13 -32
- data/spec/delegator_spec.rb +46 -0
- data/spec/tcp_spec.rb +24 -44
- data/tools/messaging_throughput.rb +10 -12
- metadata +8 -7
- data/lib/revactor/behaviors/server.rb +0 -87
- data/lib/revactor/server.rb +0 -153
@@ -0,0 +1,46 @@
|
|
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__) + '/../lib/revactor'
|
8
|
+
|
9
|
+
describe Actor::Delegator do
|
10
|
+
before :each do
|
11
|
+
@obj = mock(:obj)
|
12
|
+
@delegator = Actor::Delegator.new(@obj)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "delegates calls to the given object" do
|
16
|
+
@obj.should_receive(:foo).with(1)
|
17
|
+
@obj.should_receive(:bar).with(2)
|
18
|
+
@obj.should_receive(:baz).with(3)
|
19
|
+
|
20
|
+
@delegator.foo(1)
|
21
|
+
@delegator.bar(2)
|
22
|
+
@delegator.baz(3)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "returns the value from calls to the delegate object" do
|
26
|
+
input_value = 42
|
27
|
+
output_value = 420
|
28
|
+
|
29
|
+
@obj.should_receive(:spiffy).with(input_value).and_return(input_value * 10)
|
30
|
+
@delegator.spiffy(input_value).should == output_value
|
31
|
+
end
|
32
|
+
|
33
|
+
it "captures exceptions in the delegate and raises them for the caller" do
|
34
|
+
ex = "crash!"
|
35
|
+
|
36
|
+
@obj.should_receive(:crashy_method).and_raise(ex)
|
37
|
+
proc { @delegator.crashy_method }.should raise_error(ex)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "passes blocks along to the delegate" do
|
41
|
+
prc = proc { "yay" }
|
42
|
+
|
43
|
+
@obj.should_receive(:blocky).with(&prc)
|
44
|
+
@delegator.blocky(&prc)
|
45
|
+
end
|
46
|
+
end
|
data/spec/tcp_spec.rb
CHANGED
@@ -22,63 +22,43 @@ describe Revactor::TCP do
|
|
22
22
|
end
|
23
23
|
|
24
24
|
it "connects to remote servers" do
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
@server.accept.should be_an_instance_of(TCPSocket)
|
29
|
-
|
30
|
-
sock.close
|
31
|
-
@actor_run = true
|
32
|
-
end
|
25
|
+
sock = Revactor::TCP.connect(TEST_HOST, RANDOM_PORT)
|
26
|
+
sock.should be_an_instance_of(Revactor::TCP::Socket)
|
27
|
+
@server.accept.should be_an_instance_of(TCPSocket)
|
33
28
|
|
34
|
-
|
29
|
+
sock.close
|
35
30
|
end
|
36
31
|
|
37
32
|
it "listens for remote connections" do
|
38
|
-
@server.close # Don't use
|
33
|
+
@server.close # Don't use their server for this one...
|
39
34
|
|
40
|
-
|
41
|
-
|
42
|
-
server.should be_an_instance_of(Revactor::TCP::Listener)
|
43
|
-
|
44
|
-
s1 = TCPSocket.open(TEST_HOST, RANDOM_PORT)
|
45
|
-
s2 = server.accept
|
46
|
-
|
47
|
-
server.close
|
48
|
-
s2.close
|
49
|
-
@actor_run = true
|
50
|
-
end
|
35
|
+
server = Revactor::TCP.listen(TEST_HOST, RANDOM_PORT)
|
36
|
+
server.should be_an_instance_of(Revactor::TCP::Listener)
|
51
37
|
|
52
|
-
|
38
|
+
s1 = TCPSocket.open(TEST_HOST, RANDOM_PORT)
|
39
|
+
s2 = server.accept
|
40
|
+
|
41
|
+
server.close
|
42
|
+
s2.close
|
53
43
|
end
|
54
44
|
|
55
45
|
it "reads data" do
|
56
|
-
|
57
|
-
|
58
|
-
s2 = @server.accept
|
59
|
-
|
60
|
-
s2.write 'foobar'
|
61
|
-
s1.read(6).should == 'foobar'
|
62
|
-
|
63
|
-
s1.close
|
64
|
-
@actor_run = true
|
65
|
-
end
|
46
|
+
s1 = Revactor::TCP.connect(TEST_HOST, RANDOM_PORT)
|
47
|
+
s2 = @server.accept
|
66
48
|
|
67
|
-
|
49
|
+
s2.write 'foobar'
|
50
|
+
s1.read(6).should == 'foobar'
|
51
|
+
|
52
|
+
s1.close
|
68
53
|
end
|
69
54
|
|
70
55
|
it "writes data" do
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
s2.read(6).should == 'foobar'
|
77
|
-
|
78
|
-
s1.close
|
79
|
-
@actor_run = true
|
80
|
-
end
|
56
|
+
s1 = Revactor::TCP.connect(TEST_HOST, RANDOM_PORT)
|
57
|
+
s2 = @server.accept
|
58
|
+
|
59
|
+
s1.write 'foobar'
|
60
|
+
s2.read(6).should == 'foobar'
|
81
61
|
|
82
|
-
|
62
|
+
s1.close
|
83
63
|
end
|
84
64
|
end
|
@@ -6,24 +6,22 @@ begin_time = Time.now
|
|
6
6
|
|
7
7
|
puts "#{begin_time.strftime('%H:%M:%S')} -- Sending #{NTIMES} messages"
|
8
8
|
|
9
|
-
Actor.
|
10
|
-
|
11
|
-
child = Actor.spawn do
|
12
|
-
(NTIMES / 2).times do
|
13
|
-
Actor.receive do |f|
|
14
|
-
f.when(:foo) { parent << :bar }
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
child << :foo
|
9
|
+
parent = Actor.current
|
10
|
+
child = Actor.spawn do
|
20
11
|
(NTIMES / 2).times do
|
21
12
|
Actor.receive do |f|
|
22
|
-
f.when(:
|
13
|
+
f.when(:foo) { parent << :bar }
|
23
14
|
end
|
24
15
|
end
|
25
16
|
end
|
26
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
|
+
|
27
25
|
end_time = Time.now
|
28
26
|
duration = end_time - begin_time
|
29
27
|
throughput = NTIMES / duration
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.4
|
|
3
3
|
specification_version: 1
|
4
4
|
name: revactor
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.1.
|
7
|
-
date: 2008-01-
|
6
|
+
version: 0.1.1
|
7
|
+
date: 2008-01-28 00:00:00 -07:00
|
8
8
|
summary: Revactor is an Actor implementation for writing high performance concurrent programs
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -31,13 +31,13 @@ authors:
|
|
31
31
|
files:
|
32
32
|
- lib/revactor
|
33
33
|
- lib/revactor/actor.rb
|
34
|
-
- lib/revactor/
|
35
|
-
- lib/revactor/behaviors/server.rb
|
34
|
+
- lib/revactor/delegator.rb
|
36
35
|
- lib/revactor/filters
|
37
36
|
- lib/revactor/filters/line.rb
|
38
37
|
- lib/revactor/filters/packet.rb
|
38
|
+
- lib/revactor/mailbox.rb
|
39
39
|
- lib/revactor/mongrel.rb
|
40
|
-
- lib/revactor/
|
40
|
+
- lib/revactor/scheduler.rb
|
41
41
|
- lib/revactor/tcp.rb
|
42
42
|
- lib/revactor.rb
|
43
43
|
- examples/echo_server.rb
|
@@ -45,6 +45,7 @@ files:
|
|
45
45
|
- examples/mongrel.rb
|
46
46
|
- tools/messaging_throughput.rb
|
47
47
|
- spec/actor_spec.rb
|
48
|
+
- spec/delegator_spec.rb
|
48
49
|
- spec/line_filter_spec.rb
|
49
50
|
- spec/packet_filter_spec.rb
|
50
51
|
- spec/tcp_spec.rb
|
@@ -79,7 +80,7 @@ dependencies:
|
|
79
80
|
requirements:
|
80
81
|
- - ">="
|
81
82
|
- !ruby/object:Gem::Version
|
82
|
-
version: 0.1.
|
83
|
+
version: 0.1.4
|
83
84
|
version:
|
84
85
|
- !ruby/object:Gem::Dependency
|
85
86
|
name: case
|
@@ -88,5 +89,5 @@ dependencies:
|
|
88
89
|
requirements:
|
89
90
|
- - ">="
|
90
91
|
- !ruby/object:Gem::Version
|
91
|
-
version: "0.
|
92
|
+
version: "0.4"
|
92
93
|
version:
|
@@ -1,87 +0,0 @@
|
|
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
|
-
module Revactor
|
8
|
-
module Behavior
|
9
|
-
# The Server behavior provides a callback-driven class which eases the
|
10
|
-
# creation of standard synchronous "blocking" calls by abstracting away
|
11
|
-
# inter-Actor communication and also providing baked-in state management.
|
12
|
-
#
|
13
|
-
# This behavior module provides the base set of callbacks necessary
|
14
|
-
# to implement the behavior. It also provides descriptions for what
|
15
|
-
# certain callback methods should do.
|
16
|
-
#
|
17
|
-
# When used properly, the server behavior can implement transactional
|
18
|
-
# semantics, ensuring only successful calls mutate the previous state
|
19
|
-
# and erroneous/exception-raising ones do not.
|
20
|
-
#
|
21
|
-
# The design is modeled off Erlang/OTP's gen_server
|
22
|
-
module Server
|
23
|
-
# Initialize the server state. Can return:
|
24
|
-
#
|
25
|
-
# start(*args)
|
26
|
-
# -> [:ok, state]
|
27
|
-
# -> [:ok, state, timeout]
|
28
|
-
# -> [:stop, reason]
|
29
|
-
#
|
30
|
-
# The state variable allows you to provide a set of state whose mutation
|
31
|
-
# can be controlled a lot more closely than is possible with standard
|
32
|
-
# object oriented behavior. The latest version of state is passed
|
33
|
-
# to all Revactor::Server callbacks and is only mutated upon a
|
34
|
-
# successful return (unless an exception was raised)
|
35
|
-
#
|
36
|
-
def start(*args)
|
37
|
-
return :ok
|
38
|
-
end
|
39
|
-
|
40
|
-
# Handle any calls made to a Reactor::Server object, which are captured
|
41
|
-
# via method_missing and dispatched here. Calls provide synchronous
|
42
|
-
# behavior: the callee will block until this method completss and a
|
43
|
-
# reply is sent back to them. Can return:
|
44
|
-
#
|
45
|
-
# handle_call(message, from, state)
|
46
|
-
# -> [:reply, reply, new_state]
|
47
|
-
# -> [:reply, reply, new_state, timeout]
|
48
|
-
# -> [:noreply, new_state]
|
49
|
-
# -> [:noreply, new_state, timeout]
|
50
|
-
# -> [:stop, reason, reply, new_state]
|
51
|
-
#
|
52
|
-
def handle_call(message, from, state)
|
53
|
-
return :reply, :ok, state
|
54
|
-
end
|
55
|
-
|
56
|
-
# Handle calls without return values
|
57
|
-
#
|
58
|
-
# handle_cast(message, state)
|
59
|
-
# -> [:noreply, new_state]
|
60
|
-
# -> [:noreply, new_state, timeout]
|
61
|
-
# -> [:stop, reason, new_state]
|
62
|
-
#
|
63
|
-
def handle_cast(message, state)
|
64
|
-
return :noreply, state
|
65
|
-
end
|
66
|
-
|
67
|
-
# Handle any spontaneous messages to the server which are not calls
|
68
|
-
# or casts made from Rev::Server. Can return:
|
69
|
-
#
|
70
|
-
# handle_info(info, state)
|
71
|
-
# -> [:noreply, new_state]
|
72
|
-
# -> [:noreply, new_state, timeout]
|
73
|
-
# -> [:stop, reason, new_state]
|
74
|
-
#
|
75
|
-
def handle_info(info, state)
|
76
|
-
return :noreply, state
|
77
|
-
end
|
78
|
-
|
79
|
-
# Method called when the server is about to terminate, for example when
|
80
|
-
# any of the handle_* routines above return :stop. The return value of
|
81
|
-
# terminate is discarded.
|
82
|
-
#
|
83
|
-
def terminate(reason, state)
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
data/lib/revactor/server.rb
DELETED
@@ -1,153 +0,0 @@
|
|
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
|