revactor 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|