revactor 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +3 -0
- data/LICENSE +58 -0
- data/README +338 -0
- data/Rakefile +48 -0
- data/examples/echo_server.rb +39 -0
- data/examples/google.rb +24 -0
- data/examples/mongrel.rb +14 -0
- data/lib/revactor/actor.rb +316 -0
- data/lib/revactor/behaviors/server.rb +87 -0
- data/lib/revactor/filters/line.rb +53 -0
- data/lib/revactor/filters/packet.rb +59 -0
- data/lib/revactor/mongrel.rb +62 -0
- data/lib/revactor/server.rb +153 -0
- data/lib/revactor/tcp.rb +397 -0
- data/lib/revactor.rb +29 -0
- data/revactor.gemspec +28 -0
- data/spec/actor_spec.rb +127 -0
- data/spec/line_filter_spec.rb +36 -0
- data/spec/packet_filter_spec.rb +59 -0
- data/spec/tcp_spec.rb +84 -0
- data/tools/messaging_throughput.rb +33 -0
- metadata +92 -0
data/spec/actor_spec.rb
ADDED
@@ -0,0 +1,127 @@
|
|
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/actor'
|
8
|
+
|
9
|
+
describe Actor do
|
10
|
+
describe "creation" do
|
11
|
+
it "creates a base Actor with Actor.start" do
|
12
|
+
Actor.start do
|
13
|
+
Fiber.current.should be_an_instance_of(Actor)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it "allows creation of new Actors with Actor.new" do
|
18
|
+
Actor.new do
|
19
|
+
Fiber.current.should be_an_instance_of(Actor)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it "allows creation of new Actors with Actor.spawn" do
|
24
|
+
Actor.spawn do
|
25
|
+
Fiber.current.should be_an_instance_of(Actor)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it "allows arguments to be passed when an Actor is created" do
|
30
|
+
[:new, :spawn].each do |meth|
|
31
|
+
Actor.send(meth, 1, 2, 3) do |foo, bar, baz|
|
32
|
+
[foo, bar, baz].should == [1, 2, 3]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "current" do
|
39
|
+
it "allows the current Actor to be retrieved" do
|
40
|
+
Actor.new do
|
41
|
+
Actor.current.should be_an_instance_of(Actor)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it "disallows retrieving the current Actor unless the Actor environment is started" do
|
46
|
+
proc { Actor.current }.should raise_error(ActorError)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "receive" do
|
51
|
+
before :each do
|
52
|
+
@actor_run = false
|
53
|
+
end
|
54
|
+
|
55
|
+
it "returns the value of the matching filter action" do
|
56
|
+
actor = Actor.new do
|
57
|
+
Actor.receive do |filter|
|
58
|
+
filter.when(:foo) { |message| :bar }
|
59
|
+
end.should == :bar
|
60
|
+
|
61
|
+
# Make sure the spec actually ran the actor
|
62
|
+
@actor_run = true
|
63
|
+
end
|
64
|
+
|
65
|
+
actor << :foo
|
66
|
+
@actor_run.should be_true
|
67
|
+
end
|
68
|
+
|
69
|
+
it "filters messages with ===" do
|
70
|
+
actor = Actor.new do
|
71
|
+
results = []
|
72
|
+
3.times do
|
73
|
+
results << Actor.receive do |filter|
|
74
|
+
filter.when(/third/) { |m| m }
|
75
|
+
filter.when(/first/) { |m| m }
|
76
|
+
filter.when(/second/) { |m| m }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
results.should == ['first message', 'second message', 'third message']
|
80
|
+
@actor_run = true
|
81
|
+
end
|
82
|
+
|
83
|
+
['first message', 'second message', 'third message'].each { |m| actor << m }
|
84
|
+
@actor_run.should be_true
|
85
|
+
end
|
86
|
+
|
87
|
+
it "times out if a message isn't received after the specifed interval" do
|
88
|
+
actor = Actor.new do
|
89
|
+
Actor.receive do |filter|
|
90
|
+
filter.when(:foo) { :wrong }
|
91
|
+
filter.after(0.01) { :right }
|
92
|
+
end.should == :right
|
93
|
+
@actor_run = true
|
94
|
+
end
|
95
|
+
@actor_run.should be_true
|
96
|
+
end
|
97
|
+
|
98
|
+
it "matches any message with Object" do
|
99
|
+
actor = Actor.new do
|
100
|
+
result = []
|
101
|
+
3.times do
|
102
|
+
result << Actor.receive do |filter|
|
103
|
+
filter.when(Object) { |m| m }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
result.should == [:foo, :bar, :baz]
|
108
|
+
@actor_run = true
|
109
|
+
end
|
110
|
+
|
111
|
+
[:foo, :bar, :baz].each { |m| actor << m }
|
112
|
+
@actor_run.should be_true
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it "detects dead actors" do
|
117
|
+
actor = Actor.new do
|
118
|
+
Actor.receive do |filter|
|
119
|
+
filter.when(Object) {}
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
actor.dead?.should be_false
|
124
|
+
actor << :foobar
|
125
|
+
actor.dead?.should be_true
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,36 @@
|
|
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/filters/line'
|
8
|
+
|
9
|
+
describe Revactor::Filter::Line do
|
10
|
+
before(:each) do
|
11
|
+
@payload = "foo\nbar\r\nbaz\n"
|
12
|
+
@filter = Revactor::Filter::Line.new
|
13
|
+
end
|
14
|
+
|
15
|
+
it "decodes lines from an input buffer" do
|
16
|
+
@filter.decode(@payload).should == %w{foo bar baz}
|
17
|
+
end
|
18
|
+
|
19
|
+
it "encodes lines" do
|
20
|
+
@filter.encode(*%w{foo bar baz}).should == "foo\nbar\nbaz\n"
|
21
|
+
end
|
22
|
+
|
23
|
+
it "reassembles fragmented lines" do
|
24
|
+
msg1 = "foobar\r\n"
|
25
|
+
msg2 = "baz\n"
|
26
|
+
msg3 = "quux\r\n"
|
27
|
+
|
28
|
+
chunks = []
|
29
|
+
chunks[0] = msg1.slice(0, 1)
|
30
|
+
chunks[1] = msg1.slice(1, msg1.size) << msg2.slice(0, 1)
|
31
|
+
chunks[2] = msg2.slice(1, msg2.size - 1)
|
32
|
+
chunks[3] = msg2.slice(msg2.size, 1) << msg3
|
33
|
+
|
34
|
+
chunks.reduce([]) { |a, chunk| a + @filter.decode(chunk) }.should == %w{foobar baz quux}
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,59 @@
|
|
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/filters/packet'
|
8
|
+
|
9
|
+
describe Revactor::Filter::Packet do
|
10
|
+
before(:each) do
|
11
|
+
@payload = "A test string"
|
12
|
+
end
|
13
|
+
|
14
|
+
it "decodes frames with 2-byte prefixes" do
|
15
|
+
filter = Revactor::Filter::Packet.new(2)
|
16
|
+
filter.decode([@payload.length].pack('n') << @payload).should == [@payload]
|
17
|
+
end
|
18
|
+
|
19
|
+
it "encodes frames with 2-byte prefixes" do
|
20
|
+
filter = Revactor::Filter::Packet.new(2)
|
21
|
+
filter.encode(@payload).should == [@payload.length].pack('n') << @payload
|
22
|
+
end
|
23
|
+
|
24
|
+
it "decodes frames with 4-byte prefixes" do
|
25
|
+
@filter = Revactor::Filter::Packet.new(4)
|
26
|
+
@filter.decode([@payload.length].pack('N') << @payload).should == [@payload]
|
27
|
+
end
|
28
|
+
|
29
|
+
it "encodes frames with 4-byte prefixes" do
|
30
|
+
@filter = Revactor::Filter::Packet.new(4)
|
31
|
+
@filter.encode(@payload).should == [@payload.length].pack('N') << @payload
|
32
|
+
end
|
33
|
+
|
34
|
+
it "reassembles fragmented frames" do
|
35
|
+
filter = Revactor::Filter::Packet.new(2)
|
36
|
+
|
37
|
+
msg1 = 'foobar'
|
38
|
+
msg2 = 'baz'
|
39
|
+
msg3 = 'quux'
|
40
|
+
|
41
|
+
packet1 = [msg1.length].pack('n') << msg1
|
42
|
+
packet2 = [msg2.length].pack('n') << msg2
|
43
|
+
packet3 = [msg3.length].pack('n') << msg3
|
44
|
+
|
45
|
+
chunks = []
|
46
|
+
chunks[0] = packet1.slice(0, 1)
|
47
|
+
chunks[1] = packet1.slice(1, packet1.size) << packet2.slice(0, 1)
|
48
|
+
chunks[2] = packet2.slice(1, packet2.size - 1)
|
49
|
+
chunks[3] = packet2.slice(packet2.size, 1) << packet3
|
50
|
+
|
51
|
+
chunks.reduce([]) { |a, chunk| a + filter.decode(chunk) }.should == [msg1, msg2, msg3]
|
52
|
+
end
|
53
|
+
|
54
|
+
it "raises an exception for overlength frames" do
|
55
|
+
filter = Revactor::Filter::Packet.new(2)
|
56
|
+
payload = 'X' * 65537
|
57
|
+
proc { filter.encode payload }.should raise_error(ArgumentError)
|
58
|
+
end
|
59
|
+
end
|
data/spec/tcp_spec.rb
ADDED
@@ -0,0 +1,84 @@
|
|
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/tcp'
|
8
|
+
|
9
|
+
TEST_HOST = '127.0.0.1'
|
10
|
+
|
11
|
+
# Chosen with dice, guaranteed to be random!
|
12
|
+
RANDOM_PORT = 10103
|
13
|
+
|
14
|
+
describe Revactor::TCP do
|
15
|
+
before :each do
|
16
|
+
@actor_run = false
|
17
|
+
@server = TCPServer.new(TEST_HOST, RANDOM_PORT)
|
18
|
+
end
|
19
|
+
|
20
|
+
after :each do
|
21
|
+
@server.close unless @server.closed?
|
22
|
+
end
|
23
|
+
|
24
|
+
it "connects to remote servers" do
|
25
|
+
Actor.start do
|
26
|
+
sock = Revactor::TCP.connect(TEST_HOST, RANDOM_PORT)
|
27
|
+
sock.should be_an_instance_of(Revactor::TCP::Socket)
|
28
|
+
@server.accept.should be_an_instance_of(TCPSocket)
|
29
|
+
|
30
|
+
sock.close
|
31
|
+
@actor_run = true
|
32
|
+
end
|
33
|
+
|
34
|
+
@actor_run.should be_true
|
35
|
+
end
|
36
|
+
|
37
|
+
it "listens for remote connections" do
|
38
|
+
@server.close # Don't use it for this one...
|
39
|
+
|
40
|
+
Actor.start do
|
41
|
+
server = Revactor::TCP.listen(TEST_HOST, RANDOM_PORT)
|
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
|
51
|
+
|
52
|
+
@actor_run.should be_true
|
53
|
+
end
|
54
|
+
|
55
|
+
it "reads data" do
|
56
|
+
Actor.start do
|
57
|
+
s1 = Revactor::TCP.connect(TEST_HOST, RANDOM_PORT)
|
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
|
66
|
+
|
67
|
+
@actor_run.should be_true
|
68
|
+
end
|
69
|
+
|
70
|
+
it "writes data" do
|
71
|
+
Actor.start do
|
72
|
+
s1 = Revactor::TCP.connect(TEST_HOST, RANDOM_PORT)
|
73
|
+
s2 = @server.accept
|
74
|
+
|
75
|
+
s1.write 'foobar'
|
76
|
+
s2.read(6).should == 'foobar'
|
77
|
+
|
78
|
+
s1.close
|
79
|
+
@actor_run = true
|
80
|
+
end
|
81
|
+
|
82
|
+
@actor_run.should be_true
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,33 @@
|
|
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
|
+
Actor.start do
|
10
|
+
parent = Actor.current
|
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
|
20
|
+
(NTIMES / 2).times do
|
21
|
+
Actor.receive do |f|
|
22
|
+
f.when(:bar) { child << :foo }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end_time = Time.now
|
28
|
+
duration = end_time - begin_time
|
29
|
+
throughput = NTIMES / duration
|
30
|
+
|
31
|
+
puts "#{end_time.strftime('%H:%M:%S')} -- Finished"
|
32
|
+
puts "Duration: #{sprintf("%0.2f", duration)} seconds"
|
33
|
+
puts "Throughput: #{sprintf("%0.2f", throughput)} messages per second"
|
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.4
|
3
|
+
specification_version: 1
|
4
|
+
name: revactor
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.1.0
|
7
|
+
date: 2008-01-15 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:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Tony Arcieri
|
31
|
+
files:
|
32
|
+
- lib/revactor
|
33
|
+
- lib/revactor/actor.rb
|
34
|
+
- lib/revactor/behaviors
|
35
|
+
- lib/revactor/behaviors/server.rb
|
36
|
+
- lib/revactor/filters
|
37
|
+
- lib/revactor/filters/line.rb
|
38
|
+
- lib/revactor/filters/packet.rb
|
39
|
+
- lib/revactor/mongrel.rb
|
40
|
+
- lib/revactor/server.rb
|
41
|
+
- lib/revactor/tcp.rb
|
42
|
+
- lib/revactor.rb
|
43
|
+
- examples/echo_server.rb
|
44
|
+
- examples/google.rb
|
45
|
+
- examples/mongrel.rb
|
46
|
+
- tools/messaging_throughput.rb
|
47
|
+
- spec/actor_spec.rb
|
48
|
+
- spec/line_filter_spec.rb
|
49
|
+
- spec/packet_filter_spec.rb
|
50
|
+
- spec/tcp_spec.rb
|
51
|
+
- Rakefile
|
52
|
+
- revactor.gemspec
|
53
|
+
- LICENSE
|
54
|
+
- README
|
55
|
+
- CHANGES
|
56
|
+
test_files: []
|
57
|
+
|
58
|
+
rdoc_options:
|
59
|
+
- --title
|
60
|
+
- Revactor
|
61
|
+
- --main
|
62
|
+
- README
|
63
|
+
- --line-numbers
|
64
|
+
extra_rdoc_files:
|
65
|
+
- LICENSE
|
66
|
+
- README
|
67
|
+
- CHANGES
|
68
|
+
executables: []
|
69
|
+
|
70
|
+
extensions: []
|
71
|
+
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
dependencies:
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rev
|
77
|
+
version_requirement:
|
78
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.1.3
|
83
|
+
version:
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: case
|
86
|
+
version_requirement:
|
87
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: "0.3"
|
92
|
+
version:
|