pandemic 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +22 -0
- data/Manifest +27 -0
- data/README.markdown +133 -0
- data/Rakefile +14 -0
- data/examples/client/client.rb +17 -0
- data/examples/client/constitution.txt +865 -0
- data/examples/client/pandemic_client.yml +3 -0
- data/examples/server/pandemic_server.yml +3 -0
- data/examples/server/word_count_server.rb +47 -0
- data/lib/pandemic.rb +42 -0
- data/lib/pandemic/client_side/cluster_connection.rb +194 -0
- data/lib/pandemic/client_side/config.rb +34 -0
- data/lib/pandemic/client_side/connection.rb +40 -0
- data/lib/pandemic/client_side/connection_proxy.rb +15 -0
- data/lib/pandemic/client_side/pandemize.rb +17 -0
- data/lib/pandemic/connection_pool.rb +194 -0
- data/lib/pandemic/mutex_counter.rb +50 -0
- data/lib/pandemic/requests_per_second.rb +31 -0
- data/lib/pandemic/server_side/client.rb +123 -0
- data/lib/pandemic/server_side/config.rb +74 -0
- data/lib/pandemic/server_side/handler.rb +31 -0
- data/lib/pandemic/server_side/peer.rb +211 -0
- data/lib/pandemic/server_side/processor.rb +90 -0
- data/lib/pandemic/server_side/request.rb +92 -0
- data/lib/pandemic/server_side/server.rb +285 -0
- data/lib/pandemic/util.rb +15 -0
- data/pandemic.gemspec +37 -0
- data/test/client_test.rb +87 -0
- data/test/connection_pool_test.rb +133 -0
- data/test/functional_test.rb +57 -0
- data/test/handler_test.rb +31 -0
- data/test/mutex_counter_test.rb +73 -0
- data/test/peer_test.rb +48 -0
- data/test/processor_test.rb +33 -0
- data/test/server_test.rb +171 -0
- data/test/test_helper.rb +24 -0
- data/test/util_test.rb +21 -0
- metadata +141 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class HandlerTest < Test::Unit::TestCase
|
4
|
+
include TestHelper
|
5
|
+
|
6
|
+
context "with a basic echo processor" do
|
7
|
+
setup do
|
8
|
+
echo = Class.new do
|
9
|
+
def process(body)
|
10
|
+
body.reverse
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
@processor = Pandemic::ServerSide::Processor.new(echo)
|
15
|
+
end
|
16
|
+
|
17
|
+
should "have a running child process" do
|
18
|
+
assert !@processor.closed?
|
19
|
+
end
|
20
|
+
|
21
|
+
should "close running child" do
|
22
|
+
assert !@processor.closed?
|
23
|
+
@processor.close
|
24
|
+
assert @processor.closed?
|
25
|
+
end
|
26
|
+
|
27
|
+
should "echo back" do
|
28
|
+
10.times do |i| # just to test the loop part
|
29
|
+
assert_equal "#{i} dlrow olleh", @processor.process("hello world #{i}")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/test/server_test.rb
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ServerTest < Test::Unit::TestCase
|
4
|
+
include TestHelper
|
5
|
+
|
6
|
+
context "without a peer list" do
|
7
|
+
setup do
|
8
|
+
Pandemic::ServerSide::Config.expects(:servers).returns(["localhost:4000"])
|
9
|
+
@server = Pandemic::ServerSide::Server.new("localhost:4000")
|
10
|
+
end
|
11
|
+
|
12
|
+
should "create a peer connection when a unknown peer tries to connect" do
|
13
|
+
ignore_threads = Thread.list
|
14
|
+
@tcpserver = mock()
|
15
|
+
TCPServer.expects(:new).with("localhost", 4000).returns(@tcpserver)
|
16
|
+
|
17
|
+
@conn = mock()
|
18
|
+
@conn.stubs(:peeraddr => ['','','',''])
|
19
|
+
@tcpserver.expects(:accept).twice.returns(@conn).then.raises(Pandemic::ServerSide::Server::StopServer)
|
20
|
+
peer = mock()
|
21
|
+
peer.stubs(:add_incoming_connection)
|
22
|
+
@conn.expects(:setsockopt).with(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
23
|
+
|
24
|
+
@conn.expects(:gets).returns("SERVER localhost:4001\n")
|
25
|
+
Pandemic::ServerSide::Peer.expects(:new).with("localhost:4001", is_a(Pandemic::ServerSide::Server)).returns(peer)
|
26
|
+
@tcpserver.expects(:close)
|
27
|
+
peer.stubs(:disconnect) # the StopServer signal could be called before the peer is added. race case not important for this test
|
28
|
+
|
29
|
+
@server.handler = mock(:new)
|
30
|
+
@server.start
|
31
|
+
wait_for_threads(ignore_threads)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
should "create a pid file" do
|
36
|
+
ignore_threads = Thread.list
|
37
|
+
|
38
|
+
Pandemic::ServerSide::Config.expects(:pid_file).at_least_once.returns("test/pandemic.pid")
|
39
|
+
Pandemic::ServerSide::Config.expects(:servers).at_least_once.returns([])
|
40
|
+
|
41
|
+
@tcpserver = mock()
|
42
|
+
TCPServer.expects(:new).with("localhost", 4000).returns(@tcpserver)
|
43
|
+
|
44
|
+
file = mock()
|
45
|
+
File.expects(:open).with("test/pandemic.pid", "w").yields(file)
|
46
|
+
file.expects(:write).with(Process.pid)
|
47
|
+
File.expects(:chmod)
|
48
|
+
File.expects(:exists?).with("test/pandemic.pid").returns(true)
|
49
|
+
File.expects(:unlink).with("test/pandemic.pid")
|
50
|
+
|
51
|
+
@tcpserver.expects(:accept).once.raises(Pandemic::ServerSide::Server::StopServer)
|
52
|
+
|
53
|
+
@tcpserver.expects(:close)
|
54
|
+
|
55
|
+
@server = Pandemic::ServerSide::Server.new("localhost:4000")
|
56
|
+
|
57
|
+
@server.handler = mock(:new)
|
58
|
+
@server.start
|
59
|
+
wait_for_threads(ignore_threads)
|
60
|
+
end
|
61
|
+
|
62
|
+
should "initialize peers" do
|
63
|
+
Pandemic::ServerSide::Config.expects(:servers).returns(["localhost:4000", "localhost:4001"])
|
64
|
+
Pandemic::ServerSide::Peer.expects(:new).with("localhost:4001", is_a(Pandemic::ServerSide::Server))
|
65
|
+
@server = Pandemic::ServerSide::Server.new("localhost:4000")
|
66
|
+
end
|
67
|
+
|
68
|
+
context "with a server" do
|
69
|
+
setup do
|
70
|
+
Pandemic::ServerSide::Config.expects(:servers).returns(["localhost:4000", "localhost:4001"])
|
71
|
+
@peer = mock()
|
72
|
+
Pandemic::ServerSide::Peer.expects(:new).with("localhost:4001", is_a(Pandemic::ServerSide::Server)).returns(@peer)
|
73
|
+
@server = Pandemic::ServerSide::Server.new("localhost:4000")
|
74
|
+
end
|
75
|
+
|
76
|
+
should "start a TCPServer, and connect to peers" do
|
77
|
+
ignore_threads = Thread.list
|
78
|
+
@tcpserver = mock()
|
79
|
+
TCPServer.expects(:new).with("localhost", 4000).returns(@tcpserver)
|
80
|
+
@peer.expects(:connect).once
|
81
|
+
@tcpserver.expects(:accept).twice.returns(nil).then.raises(Pandemic::ServerSide::Server::StopServer)
|
82
|
+
@tcpserver.expects(:close)
|
83
|
+
@peer.expects(:disconnect)
|
84
|
+
@server.handler = mock(:new)
|
85
|
+
@server.start
|
86
|
+
wait_for_threads(ignore_threads)
|
87
|
+
end
|
88
|
+
|
89
|
+
should "create a new client object for a client connection" do
|
90
|
+
ignore_threads = Thread.list
|
91
|
+
|
92
|
+
@tcpserver = mock()
|
93
|
+
TCPServer.expects(:new).with("localhost", 4000).returns(@tcpserver)
|
94
|
+
@peer.expects(:connect).once
|
95
|
+
|
96
|
+
@conn = mock()
|
97
|
+
@conn.stubs(:peeraddr => ['','','',''])
|
98
|
+
@tcpserver.expects(:accept).twice.returns(@conn).then.raises(Pandemic::ServerSide::Server::StopServer)
|
99
|
+
client = mock()
|
100
|
+
@conn.expects(:setsockopt).with(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
101
|
+
|
102
|
+
Pandemic::ServerSide::Client.expects(:new).with(@conn, @server).returns(client)
|
103
|
+
|
104
|
+
|
105
|
+
@conn.expects(:gets).returns("CLIENT\n")
|
106
|
+
@tcpserver.expects(:close)
|
107
|
+
@peer.expects(:disconnect)
|
108
|
+
client.expects(:listen).returns(client)
|
109
|
+
client.expects(:close).at_most_once # optional due to threaded nature, this may not actually happen
|
110
|
+
@server.handler = mock(:new)
|
111
|
+
@server.start
|
112
|
+
wait_for_threads(ignore_threads)
|
113
|
+
end
|
114
|
+
|
115
|
+
should "connect with the corresponding peer object" do
|
116
|
+
ignore_threads = Thread.list
|
117
|
+
@tcpserver = mock()
|
118
|
+
TCPServer.expects(:new).with("localhost", 4000).returns(@tcpserver)
|
119
|
+
@peer.expects(:connect).once
|
120
|
+
|
121
|
+
@conn = mock()
|
122
|
+
@conn.stubs(:peeraddr => ['','','',''])
|
123
|
+
@tcpserver.expects(:accept).twice.returns(@conn).then.raises(Pandemic::ServerSide::Server::StopServer)
|
124
|
+
@tcpserver.expects(:close)
|
125
|
+
@peer.expects(:disconnect)
|
126
|
+
|
127
|
+
@conn.expects(:setsockopt).with(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
128
|
+
@conn.expects(:gets).returns("SERVER localhost:4001\n")
|
129
|
+
|
130
|
+
@peer.expects(:host).returns("localhost")
|
131
|
+
@peer.expects(:port).returns(4001)
|
132
|
+
@peer.expects(:add_incoming_connection).with(@conn)
|
133
|
+
@server.handler = mock(:new)
|
134
|
+
|
135
|
+
@server.start
|
136
|
+
wait_for_threads(ignore_threads)
|
137
|
+
end
|
138
|
+
|
139
|
+
should "call process on handler" do
|
140
|
+
handler_class = mock()
|
141
|
+
handler = mock()
|
142
|
+
handler_class.expects(:new).once.returns(handler)
|
143
|
+
handler.expects(:process).with("body")
|
144
|
+
@server.handler = handler_class
|
145
|
+
@server.process("body")
|
146
|
+
end
|
147
|
+
|
148
|
+
should "map request, distribute to peers, and reduce" do
|
149
|
+
handler_class = mock()
|
150
|
+
handler = mock()
|
151
|
+
handler_class.expects(:new).once.returns(handler)
|
152
|
+
request = mock()
|
153
|
+
request.stubs(:hash => "abcddef134123")
|
154
|
+
@peer.expects(:connected?).returns(true)
|
155
|
+
handler.expects(:partition).with(request, is_a(Hash)).returns({"localhost:4000" => "1", "localhost:4001" => "2"})
|
156
|
+
request.expects(:max_responses=).with(2)
|
157
|
+
@peer.expects(:client_request).with(request, "2")
|
158
|
+
|
159
|
+
handler.expects(:process).with("1").returns("2")
|
160
|
+
request.expects(:add_response).with("2")
|
161
|
+
|
162
|
+
request.expects(:wait_for_responses).once
|
163
|
+
handler.expects(:reduce).with(request)
|
164
|
+
|
165
|
+
@server.handler = handler_class
|
166
|
+
|
167
|
+
@server.handle_client_request(request)
|
168
|
+
# wait_for_threads
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
TEST_DIR = File.dirname(__FILE__)
|
2
|
+
%w(lib test).each do |dir|
|
3
|
+
$LOAD_PATH.unshift "#{TEST_DIR}/../#{dir}"
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'test/unit'
|
8
|
+
require 'pandemic'
|
9
|
+
require 'shoulda'
|
10
|
+
require 'mocha'
|
11
|
+
|
12
|
+
|
13
|
+
blackhole = StringIO.new
|
14
|
+
$pandemic_logger = Logger.new(blackhole)
|
15
|
+
|
16
|
+
module TestHelper
|
17
|
+
class TestException < Exception; end
|
18
|
+
def wait_for_threads(ignore = [Thread.current])
|
19
|
+
Thread.list.each do |thread|
|
20
|
+
next if ignore.include?(thread)
|
21
|
+
thread.join
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/test/util_test.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class UtilTest < Test::Unit::TestCase
|
4
|
+
context "with the module methods" do
|
5
|
+
setup do
|
6
|
+
@util = Object.new
|
7
|
+
@util.extend(Pandemic::Util)
|
8
|
+
end
|
9
|
+
|
10
|
+
should "parse out host and port" do
|
11
|
+
assert_equal ["localhost", 4000], @util.host_port("localhost:4000")
|
12
|
+
end
|
13
|
+
|
14
|
+
should "include the monitor mixin" do
|
15
|
+
object = Object.new
|
16
|
+
assert !object.respond_to?(:synchronize)
|
17
|
+
@util.with_mutex(object)
|
18
|
+
assert object.respond_to?(:synchronize)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pandemic
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Arya Asemanfar
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-09-07 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: shoulda
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: mocha
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
description: A framework for distributing work for real-time services and offline tasks.
|
36
|
+
email: aryaasemanfar@gmail.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- lib/pandemic/client_side/cluster_connection.rb
|
43
|
+
- lib/pandemic/client_side/config.rb
|
44
|
+
- lib/pandemic/client_side/connection.rb
|
45
|
+
- lib/pandemic/client_side/connection_proxy.rb
|
46
|
+
- lib/pandemic/client_side/pandemize.rb
|
47
|
+
- lib/pandemic/connection_pool.rb
|
48
|
+
- lib/pandemic/mutex_counter.rb
|
49
|
+
- lib/pandemic/requests_per_second.rb
|
50
|
+
- lib/pandemic/server_side/client.rb
|
51
|
+
- lib/pandemic/server_side/config.rb
|
52
|
+
- lib/pandemic/server_side/handler.rb
|
53
|
+
- lib/pandemic/server_side/peer.rb
|
54
|
+
- lib/pandemic/server_side/processor.rb
|
55
|
+
- lib/pandemic/server_side/request.rb
|
56
|
+
- lib/pandemic/server_side/server.rb
|
57
|
+
- lib/pandemic/util.rb
|
58
|
+
- lib/pandemic.rb
|
59
|
+
- README.markdown
|
60
|
+
files:
|
61
|
+
- examples/client/client.rb
|
62
|
+
- examples/client/constitution.txt
|
63
|
+
- examples/client/pandemic_client.yml
|
64
|
+
- examples/server/pandemic_server.yml
|
65
|
+
- examples/server/word_count_server.rb
|
66
|
+
- lib/pandemic/client_side/cluster_connection.rb
|
67
|
+
- lib/pandemic/client_side/config.rb
|
68
|
+
- lib/pandemic/client_side/connection.rb
|
69
|
+
- lib/pandemic/client_side/connection_proxy.rb
|
70
|
+
- lib/pandemic/client_side/pandemize.rb
|
71
|
+
- lib/pandemic/connection_pool.rb
|
72
|
+
- lib/pandemic/mutex_counter.rb
|
73
|
+
- lib/pandemic/requests_per_second.rb
|
74
|
+
- lib/pandemic/server_side/client.rb
|
75
|
+
- lib/pandemic/server_side/config.rb
|
76
|
+
- lib/pandemic/server_side/handler.rb
|
77
|
+
- lib/pandemic/server_side/peer.rb
|
78
|
+
- lib/pandemic/server_side/processor.rb
|
79
|
+
- lib/pandemic/server_side/request.rb
|
80
|
+
- lib/pandemic/server_side/server.rb
|
81
|
+
- lib/pandemic/util.rb
|
82
|
+
- lib/pandemic.rb
|
83
|
+
- Manifest
|
84
|
+
- MIT-LICENSE
|
85
|
+
- pandemic.gemspec
|
86
|
+
- Rakefile
|
87
|
+
- README.markdown
|
88
|
+
- test/client_test.rb
|
89
|
+
- test/connection_pool_test.rb
|
90
|
+
- test/functional_test.rb
|
91
|
+
- test/handler_test.rb
|
92
|
+
- test/mutex_counter_test.rb
|
93
|
+
- test/peer_test.rb
|
94
|
+
- test/processor_test.rb
|
95
|
+
- test/server_test.rb
|
96
|
+
- test/test_helper.rb
|
97
|
+
- test/util_test.rb
|
98
|
+
has_rdoc: true
|
99
|
+
homepage: https://github.com/arya/pandemic/
|
100
|
+
licenses: []
|
101
|
+
|
102
|
+
post_install_message:
|
103
|
+
rdoc_options:
|
104
|
+
- --line-numbers
|
105
|
+
- --inline-source
|
106
|
+
- --title
|
107
|
+
- Pandemic
|
108
|
+
- --main
|
109
|
+
- README.markdown
|
110
|
+
require_paths:
|
111
|
+
- lib
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: "0"
|
117
|
+
version:
|
118
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: "1.2"
|
123
|
+
version:
|
124
|
+
requirements: []
|
125
|
+
|
126
|
+
rubyforge_project: pandemic
|
127
|
+
rubygems_version: 1.3.4
|
128
|
+
signing_key:
|
129
|
+
specification_version: 3
|
130
|
+
summary: A framework for distributing work for real-time services and offline tasks.
|
131
|
+
test_files:
|
132
|
+
- test/client_test.rb
|
133
|
+
- test/connection_pool_test.rb
|
134
|
+
- test/functional_test.rb
|
135
|
+
- test/handler_test.rb
|
136
|
+
- test/mutex_counter_test.rb
|
137
|
+
- test/peer_test.rb
|
138
|
+
- test/processor_test.rb
|
139
|
+
- test/server_test.rb
|
140
|
+
- test/test_helper.rb
|
141
|
+
- test/util_test.rb
|