revactor 0.1.0
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 +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:
|