arya-pandemic 0.2 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +22 -0
- data/Rakefile +5 -5
- data/lib/pandemic.rb +6 -4
- data/lib/pandemic/client_side/cluster_connection.rb +6 -1
- data/lib/pandemic/client_side/config.rb +2 -1
- data/lib/pandemic/connection_pool.rb +92 -16
- data/lib/pandemic/mutex_counter.rb +8 -3
- data/lib/pandemic/server_side/client.rb +18 -2
- data/lib/pandemic/server_side/peer.rb +12 -4
- data/lib/pandemic/server_side/request.rb +17 -2
- data/lib/pandemic/server_side/server.rb +1 -1
- data/lib/pandemic/util.rb +1 -12
- data/pandemic.gemspec +11 -4
- data/test/client_test.rb +80 -0
- data/test/connection_pool_test.rb +133 -0
- data/test/functional_test.rb +56 -0
- data/test/handler_test.rb +31 -0
- data/test/mutex_counter_test.rb +46 -0
- data/test/peer_test.rb +48 -0
- data/test/server_test.rb +111 -0
- data/test/test_helper.rb +24 -0
- data/test/util_test.rb +21 -0
- metadata +46 -9
data/MIT-LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2009 Arya Asemanfar
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
CHANGED
@@ -2,13 +2,13 @@ require 'rubygems'
|
|
2
2
|
require 'rake'
|
3
3
|
require 'echoe'
|
4
4
|
|
5
|
-
Echoe.new('pandemic', '0.2') do |p|
|
5
|
+
Echoe.new('pandemic', '0.2.1') do |p|
|
6
6
|
p.description = "Distribute MapReduce to any of the workers and it will spread, like a pandemic."
|
7
|
-
p.url = ""
|
7
|
+
p.url = "https://github.com/arya/pandemic/"
|
8
8
|
p.author = "Arya Asemanfar"
|
9
9
|
p.email = "aryaasemanfar@gmail.com"
|
10
|
-
p.ignore_pattern = ["tmp/*", "script/*", '
|
11
|
-
p.development_dependencies = []
|
10
|
+
p.ignore_pattern = ["tmp/*", "script/*", 'test/*']
|
11
|
+
p.development_dependencies = ["shoulda", "mocha"]
|
12
12
|
end
|
13
13
|
|
14
|
-
Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
|
14
|
+
Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
|
data/lib/pandemic.rb
CHANGED
@@ -25,15 +25,17 @@ require 'pandemic/client_side/connection_proxy'
|
|
25
25
|
require 'pandemic/client_side/pandemize'
|
26
26
|
|
27
27
|
# TODO:
|
28
|
+
# - tests
|
28
29
|
# - IO timeouts/robustness
|
29
30
|
# - documentation
|
30
31
|
# - PING/PONG?
|
31
32
|
|
32
|
-
$logger = Logger.new(STDOUT)
|
33
|
-
$logger.level = Logger::DEBUG
|
34
|
-
$logger.datetime_format = "%Y-%m-%d %H:%M:%S "
|
35
|
-
|
36
33
|
def epidemic!
|
34
|
+
if $pandemic_logger.nil?
|
35
|
+
$pandemic_logger = Logger.new("pandemic.log")
|
36
|
+
$pandemic_logger.level = Logger::INFO
|
37
|
+
$pandemic_logger.datetime_format = "%Y-%m-%d %H:%M:%S "
|
38
|
+
end
|
37
39
|
Pandemic::ServerSide::Server.boot
|
38
40
|
end
|
39
41
|
|
@@ -4,6 +4,7 @@ module Pandemic
|
|
4
4
|
class NotEnoughConnectionsTimeout < Exception; end
|
5
5
|
class NoNodesAvailable < Exception; end
|
6
6
|
class LostConnectionToNode < Exception; end
|
7
|
+
class NodeTimedOut < Exception; end
|
7
8
|
|
8
9
|
include Util
|
9
10
|
def initialize
|
@@ -16,6 +17,9 @@ module Pandemic
|
|
16
17
|
@connection_proxies = {}
|
17
18
|
@queue = @mutex.new_cond # TODO: there should be a queue for each group
|
18
19
|
|
20
|
+
@response_timeout = Config.response_timeout
|
21
|
+
@response_timeout = nil if @response_timeout <= 0
|
22
|
+
|
19
23
|
Config.servers.each_with_index do |server_addr, key|
|
20
24
|
@connection_proxies[key] = ConnectionProxy.new(key, self)
|
21
25
|
host, port = host_port(server_addr)
|
@@ -41,7 +45,8 @@ module Pandemic
|
|
41
45
|
begin
|
42
46
|
socket.write("#{body.size}\n#{body}")
|
43
47
|
socket.flush
|
44
|
-
|
48
|
+
is_ready = IO.select([socket], nil, nil, @response_timeout)
|
49
|
+
raise NodeTimedOut if is_ready.nil?
|
45
50
|
response_size = socket.gets
|
46
51
|
if response_size
|
47
52
|
socket.read(response_size.strip.to_i)
|
@@ -5,7 +5,7 @@ module Pandemic
|
|
5
5
|
@@load_mutex = Mutex.new
|
6
6
|
attr_accessor :config_path, :loaded
|
7
7
|
attr_accessor :servers, :max_connections_per_server, :min_connections_per_server,
|
8
|
-
:connection_wait_timeout
|
8
|
+
:connection_wait_timeout, :response_timeout
|
9
9
|
def load
|
10
10
|
@@load_mutex.synchronize do
|
11
11
|
return if self.loaded
|
@@ -20,6 +20,7 @@ module Pandemic
|
|
20
20
|
@max_connections_per_server = (yaml['max_connections_per_server'] || 1).to_i
|
21
21
|
@min_connections_per_server = (yaml['min_connections_per_server'] || 1).to_i
|
22
22
|
@connection_wait_timeout = (yaml['connection_wait_timeout'] || 1).to_f
|
23
|
+
@response_timeout = (yaml['response_timeout'] || 1).to_f
|
23
24
|
self.loaded = true
|
24
25
|
end
|
25
26
|
end
|
@@ -4,34 +4,39 @@ module Pandemic
|
|
4
4
|
class CreateConnectionUndefinedException < Exception; end
|
5
5
|
include Util
|
6
6
|
def initialize(options = {})
|
7
|
+
@connected = false
|
7
8
|
@mutex = Monitor.new
|
8
9
|
@queue = @mutex.new_cond
|
9
10
|
@available = []
|
10
11
|
@connections = []
|
11
12
|
@max_connections = options[:max_connections] || 10
|
13
|
+
@min_connections = options[:min_connections] || 1
|
12
14
|
@timeout = options[:timeout] || 3
|
13
15
|
end
|
14
16
|
|
15
17
|
def add_connection!
|
16
|
-
# bang because we're
|
17
|
-
|
18
|
-
|
19
|
-
@
|
20
|
-
@connections << conn
|
21
|
-
@available << conn
|
22
|
-
end
|
18
|
+
# bang because we're ignoring the max connections
|
19
|
+
@mutex.synchronize do
|
20
|
+
conn = create_connection
|
21
|
+
@available << conn if conn && !conn.closed?
|
23
22
|
end
|
24
23
|
end
|
25
24
|
|
26
25
|
def create_connection(&block)
|
27
26
|
if block.nil?
|
28
27
|
if @create_connection
|
29
|
-
@create_connection.call
|
28
|
+
conn = @create_connection.call
|
29
|
+
if conn && !conn.closed?
|
30
|
+
@connections << conn
|
31
|
+
@connected = true
|
32
|
+
conn
|
33
|
+
end
|
30
34
|
else
|
31
35
|
raise CreateConnectionUndefinedException.new("You must specify a block to create connections")
|
32
36
|
end
|
33
37
|
else
|
34
38
|
@create_connection = block
|
39
|
+
connect
|
35
40
|
end
|
36
41
|
end
|
37
42
|
|
@@ -45,30 +50,61 @@ module Pandemic
|
|
45
50
|
connection.close
|
46
51
|
end
|
47
52
|
end
|
53
|
+
@connections.delete(connection)
|
54
|
+
@available.delete(connection)
|
55
|
+
# this is within the mutex of the caller
|
56
|
+
@connected = false if @connections.empty?
|
48
57
|
else
|
49
58
|
@destroy_connection = block
|
50
59
|
end
|
51
60
|
end
|
52
61
|
|
62
|
+
def status_check(connection = nil, &block)
|
63
|
+
if block.nil?
|
64
|
+
if @status_check
|
65
|
+
@status_check.call(connection)
|
66
|
+
else
|
67
|
+
connection && !connection.closed?
|
68
|
+
end
|
69
|
+
else
|
70
|
+
@status_check = block
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
53
74
|
def connected?
|
54
|
-
@
|
75
|
+
@connected
|
76
|
+
end
|
77
|
+
|
78
|
+
def connect
|
79
|
+
if !connected?
|
80
|
+
@min_connections.times { add_connection! }
|
81
|
+
grim_reaper
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def available_count
|
86
|
+
@available.size
|
87
|
+
end
|
88
|
+
|
89
|
+
def connections_count
|
90
|
+
@connections.size
|
55
91
|
end
|
56
92
|
|
57
93
|
def disconnect
|
58
94
|
@mutex.synchronize do
|
59
95
|
return if @disconnecting
|
60
96
|
@disconnecting = true
|
61
|
-
@
|
97
|
+
@connected = false # we don't want anyone thinking they can use this connection
|
98
|
+
@grim_reaper.kill if @grim_reaper && @grim_reaper.alive?
|
99
|
+
|
100
|
+
@available.dup.each do |conn|
|
62
101
|
destroy_connection(conn)
|
63
|
-
@connections.delete(conn)
|
64
102
|
end
|
65
|
-
|
103
|
+
|
66
104
|
while @connections.size > 0 && @queue.wait
|
67
|
-
@available.each do |conn|
|
105
|
+
@available.dup.each do |conn|
|
68
106
|
destroy_connection(conn)
|
69
|
-
@connections.delete(conn)
|
70
107
|
end
|
71
|
-
@available = []
|
72
108
|
end
|
73
109
|
@disconnecting = false
|
74
110
|
end
|
@@ -94,7 +130,6 @@ module Pandemic
|
|
94
130
|
connection = @available.pop
|
95
131
|
break
|
96
132
|
elsif @connections.size < @max_connections && (connection = create_connection)
|
97
|
-
@connections << connection
|
98
133
|
break
|
99
134
|
elsif @queue.wait(@timeout)
|
100
135
|
next
|
@@ -113,5 +148,46 @@ module Pandemic
|
|
113
148
|
end
|
114
149
|
end
|
115
150
|
|
151
|
+
def grim_reaper
|
152
|
+
@grim_reaper.kill if @grim_reaper && @grim_reaper.alive?
|
153
|
+
@grim_reaper = Thread.new do
|
154
|
+
usage_history = []
|
155
|
+
loop do
|
156
|
+
if connected?
|
157
|
+
@mutex.synchronize do
|
158
|
+
dead = []
|
159
|
+
|
160
|
+
@connections.each do |conn|
|
161
|
+
dead << conn if !status_check(conn)
|
162
|
+
end
|
163
|
+
|
164
|
+
dead.each { |c| destroy_connection(c) }
|
165
|
+
|
166
|
+
# restore to minimum number of connections if it's too low
|
167
|
+
[@min_connections - @connections.size, 0].max.times do
|
168
|
+
add_connection!
|
169
|
+
end
|
170
|
+
|
171
|
+
usage_history.push(@available.size)
|
172
|
+
if usage_history.size >= 10
|
173
|
+
# kill the minimum number of available connections over the last 10 checks
|
174
|
+
# or the total connections minux the min connections, whichever is lower.
|
175
|
+
# this ensures that you never go below min connections
|
176
|
+
to_kill = [usage_history.min, @connections.size - @min_connections].min
|
177
|
+
[to_kill, 0].max.times do
|
178
|
+
destroy_connection(@connections.last)
|
179
|
+
end
|
180
|
+
usage_history = []
|
181
|
+
end
|
182
|
+
|
183
|
+
end # end mutex
|
184
|
+
sleep 30
|
185
|
+
else
|
186
|
+
break
|
187
|
+
end # end if connected
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
116
192
|
end
|
117
193
|
end
|
@@ -1,19 +1,24 @@
|
|
1
1
|
module Pandemic
|
2
2
|
class MutexCounter
|
3
3
|
MAX = (2 ** 30) - 1
|
4
|
-
def initialize
|
4
|
+
def initialize(max = MAX)
|
5
5
|
@mutex = Mutex.new
|
6
6
|
@counter = 0
|
7
7
|
@resets = 0
|
8
|
+
@max = max
|
8
9
|
end
|
9
10
|
|
10
11
|
def real_total
|
11
|
-
@mutex.synchronize { (@resets *
|
12
|
+
@mutex.synchronize { (@resets * @max) + @counter }
|
13
|
+
end
|
14
|
+
|
15
|
+
def value
|
16
|
+
@mutex.synchronize { @counter }
|
12
17
|
end
|
13
18
|
|
14
19
|
def inc
|
15
20
|
@mutex.synchronize do
|
16
|
-
if @counter >=
|
21
|
+
if @counter >= @max
|
17
22
|
@counter = 0 # to avoid Bignum, it's about 4x slower
|
18
23
|
@resets += 1
|
19
24
|
end
|
@@ -11,6 +11,7 @@ module Pandemic
|
|
11
11
|
@server = server
|
12
12
|
@received_requests = 0
|
13
13
|
@responded_requests = 0
|
14
|
+
@current_request = nil
|
14
15
|
end
|
15
16
|
|
16
17
|
def listen
|
@@ -38,6 +39,8 @@ module Pandemic
|
|
38
39
|
response = handle_request(body)
|
39
40
|
|
40
41
|
debug("Writing response to client")
|
42
|
+
|
43
|
+
# the connection could be closed, we'll let it be rescued if it is.
|
41
44
|
@connection.write("#{response.size}\n#{response}")
|
42
45
|
@connection.flush
|
43
46
|
@responded_requests += 1
|
@@ -46,10 +49,14 @@ module Pandemic
|
|
46
49
|
end
|
47
50
|
rescue DisconnectClient
|
48
51
|
info("Closing client connection")
|
49
|
-
|
52
|
+
close_connection
|
53
|
+
rescue Errno::EPIPE
|
54
|
+
info("Connection to client lost")
|
55
|
+
close_connection
|
50
56
|
rescue Exception => e
|
51
57
|
warn("Unhandled exception in client listen thread: #{e.inspect}")
|
52
58
|
ensure
|
59
|
+
@current_request.cancel! if @current_request
|
53
60
|
@server.client_closed(self)
|
54
61
|
end
|
55
62
|
end
|
@@ -60,12 +67,21 @@ module Pandemic
|
|
60
67
|
def close
|
61
68
|
@listener_thread.raise(DisconnectClient)
|
62
69
|
end
|
70
|
+
|
71
|
+
|
63
72
|
|
64
73
|
def handle_request(request)
|
65
|
-
@
|
74
|
+
@current_request = Request.new(request)
|
75
|
+
response = @server.handle_client_request(@current_request)
|
76
|
+
@current_request = nil
|
77
|
+
return response
|
66
78
|
end
|
67
79
|
|
68
80
|
private
|
81
|
+
def close_connection
|
82
|
+
@connection.close unless @connection.nil? || @connection.closed?
|
83
|
+
end
|
84
|
+
|
69
85
|
def signature
|
70
86
|
@signature ||= @connection.peeraddr.values_at(3,1).join(":")
|
71
87
|
end
|
@@ -15,9 +15,8 @@ module Pandemic
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def connect
|
18
|
-
return if connected?
|
19
18
|
debug("Forced connection to peer")
|
20
|
-
|
19
|
+
@connection_pool.connect
|
21
20
|
end
|
22
21
|
|
23
22
|
def disconnect
|
@@ -31,6 +30,7 @@ module Pandemic
|
|
31
30
|
|
32
31
|
def client_request(request, body)
|
33
32
|
debug("Sending client's request to peer")
|
33
|
+
debug("Connection pool has #{@connection_pool.available_count} of #{@connection_pool.connections_count} connections available")
|
34
34
|
# TODO: Consider adding back threads here if it will be faster that way in Ruby 1.9
|
35
35
|
@connection_pool.with_connection do |connection|
|
36
36
|
if connection && !connection.closed?
|
@@ -49,6 +49,7 @@ module Pandemic
|
|
49
49
|
debug("Adding incoming connection")
|
50
50
|
|
51
51
|
connect # if we're not connected, we should be
|
52
|
+
|
52
53
|
|
53
54
|
thread = Thread.new(conn) do |connection|
|
54
55
|
begin
|
@@ -89,10 +90,18 @@ module Pandemic
|
|
89
90
|
|
90
91
|
@connection_pool.create_connection do
|
91
92
|
connection = nil
|
93
|
+
retries = 0
|
92
94
|
begin
|
93
95
|
connection = TCPSocket.new(@host, @port)
|
94
|
-
rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED
|
96
|
+
rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED => e
|
95
97
|
connection = nil
|
98
|
+
debug("Connection timeout or refused: #{e.inspect}")
|
99
|
+
if retries == 0
|
100
|
+
debug("Retrying connection")
|
101
|
+
retries += 1
|
102
|
+
sleep 0.01
|
103
|
+
retry
|
104
|
+
end
|
96
105
|
rescue Exception => e
|
97
106
|
warn("Unhandled exception in create connection block: #{e.inspect}")
|
98
107
|
end
|
@@ -102,7 +111,6 @@ module Pandemic
|
|
102
111
|
end
|
103
112
|
connection
|
104
113
|
end
|
105
|
-
|
106
114
|
end
|
107
115
|
|
108
116
|
def handle_incoming_request(request, connection)
|
@@ -34,20 +34,35 @@ module Pandemic
|
|
34
34
|
@responses << response
|
35
35
|
if @max_responses && @responses.size >= @max_responses
|
36
36
|
debug("Hit max responses, waking up waiting thread")
|
37
|
-
|
37
|
+
wakeup_waiting_thread
|
38
38
|
@complete = true
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
42
|
+
|
43
|
+
def wakeup_waiting_thread
|
44
|
+
@waiting_thread.wakeup if @waiting_thread && @waiting_thread.status == "sleep"
|
45
|
+
end
|
42
46
|
|
43
47
|
def responses
|
44
48
|
@responses # its frozen so we don't have to worry about mutex
|
45
49
|
end
|
50
|
+
|
51
|
+
def cancel!
|
52
|
+
# consider telling peers that they should stop, but for now this is fine.
|
53
|
+
@responses_mutex.synchronize do
|
54
|
+
wakeup_waiting_thread
|
55
|
+
end
|
56
|
+
end
|
46
57
|
|
47
58
|
def wait_for_responses
|
48
59
|
return if @complete
|
49
60
|
@waiting_thread = Thread.current
|
50
|
-
|
61
|
+
if Config.response_timeout <= 0
|
62
|
+
Thread.stop
|
63
|
+
else
|
64
|
+
sleep Config.response_timeout
|
65
|
+
end
|
51
66
|
# there is a race case where if the sleep finishes,
|
52
67
|
# and response comes in and has the mutex, and then array is frozen
|
53
68
|
# it would be ideal to use monitor wait/signal here but the monitor implementation is currently flawed
|
data/lib/pandemic/util.rb
CHANGED
@@ -5,18 +5,7 @@ module Pandemic
|
|
5
5
|
end
|
6
6
|
|
7
7
|
def logger
|
8
|
-
$
|
9
|
-
end
|
10
|
-
|
11
|
-
def bm(title, &block)
|
12
|
-
@times ||= Hash.new(0)
|
13
|
-
begin
|
14
|
-
start = Time.now.to_f
|
15
|
-
yield block
|
16
|
-
ensure
|
17
|
-
@times[title] += (Time.now.to_f - start)
|
18
|
-
$stdout.puts("#{title} #{@times[title]}")
|
19
|
-
end
|
8
|
+
$pandemic_logger
|
20
9
|
end
|
21
10
|
|
22
11
|
def with_mutex(obj)
|
data/pandemic.gemspec
CHANGED
@@ -2,30 +2,37 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{pandemic}
|
5
|
-
s.version = "0.2"
|
5
|
+
s.version = "0.2.1"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Arya Asemanfar"]
|
9
|
-
s.date = %q{2009-03-
|
9
|
+
s.date = %q{2009-03-26}
|
10
10
|
s.description = %q{Distribute MapReduce to any of the workers and it will spread, like a pandemic.}
|
11
11
|
s.email = %q{aryaasemanfar@gmail.com}
|
12
12
|
s.extra_rdoc_files = ["lib/pandemic/client_side/cluster_connection.rb", "lib/pandemic/client_side/config.rb", "lib/pandemic/client_side/connection.rb", "lib/pandemic/client_side/connection_proxy.rb", "lib/pandemic/client_side/pandemize.rb", "lib/pandemic/connection_pool.rb", "lib/pandemic/mutex_counter.rb", "lib/pandemic/server_side/client.rb", "lib/pandemic/server_side/config.rb", "lib/pandemic/server_side/handler.rb", "lib/pandemic/server_side/peer.rb", "lib/pandemic/server_side/request.rb", "lib/pandemic/server_side/server.rb", "lib/pandemic/util.rb", "lib/pandemic.rb", "README.markdown"]
|
13
|
-
s.files = ["lib/pandemic/client_side/cluster_connection.rb", "lib/pandemic/client_side/config.rb", "lib/pandemic/client_side/connection.rb", "lib/pandemic/client_side/connection_proxy.rb", "lib/pandemic/client_side/pandemize.rb", "lib/pandemic/connection_pool.rb", "lib/pandemic/mutex_counter.rb", "lib/pandemic/server_side/client.rb", "lib/pandemic/server_side/config.rb", "lib/pandemic/server_side/handler.rb", "lib/pandemic/server_side/peer.rb", "lib/pandemic/server_side/request.rb", "lib/pandemic/server_side/server.rb", "lib/pandemic/util.rb", "lib/pandemic.rb", "Rakefile", "README.markdown", "
|
13
|
+
s.files = ["lib/pandemic/client_side/cluster_connection.rb", "lib/pandemic/client_side/config.rb", "lib/pandemic/client_side/connection.rb", "lib/pandemic/client_side/connection_proxy.rb", "lib/pandemic/client_side/pandemize.rb", "lib/pandemic/connection_pool.rb", "lib/pandemic/mutex_counter.rb", "lib/pandemic/server_side/client.rb", "lib/pandemic/server_side/config.rb", "lib/pandemic/server_side/handler.rb", "lib/pandemic/server_side/peer.rb", "lib/pandemic/server_side/request.rb", "lib/pandemic/server_side/server.rb", "lib/pandemic/util.rb", "lib/pandemic.rb", "Manifest", "MIT-LICENSE", "pandemic.gemspec", "Rakefile", "README.markdown", "test/client_test.rb", "test/connection_pool_test.rb", "test/functional_test.rb", "test/handler_test.rb", "test/mutex_counter_test.rb", "test/peer_test.rb", "test/server_test.rb", "test/test_helper.rb", "test/util_test.rb"]
|
14
14
|
s.has_rdoc = true
|
15
|
-
s.homepage = %q{}
|
15
|
+
s.homepage = %q{https://github.com/arya/pandemic/}
|
16
16
|
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Pandemic", "--main", "README.markdown"]
|
17
17
|
s.require_paths = ["lib"]
|
18
18
|
s.rubyforge_project = %q{pandemic}
|
19
19
|
s.rubygems_version = %q{1.3.1}
|
20
20
|
s.summary = %q{Distribute MapReduce to any of the workers and it will spread, like a pandemic.}
|
21
|
+
s.test_files = ["test/client_test.rb", "test/connection_pool_test.rb", "test/functional_test.rb", "test/handler_test.rb", "test/mutex_counter_test.rb", "test/peer_test.rb", "test/server_test.rb", "test/test_helper.rb", "test/util_test.rb"]
|
21
22
|
|
22
23
|
if s.respond_to? :specification_version then
|
23
24
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
24
25
|
s.specification_version = 2
|
25
26
|
|
26
27
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
28
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
29
|
+
s.add_development_dependency(%q<mocha>, [">= 0"])
|
27
30
|
else
|
31
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
32
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
28
33
|
end
|
29
34
|
else
|
35
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
36
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
30
37
|
end
|
31
38
|
end
|
data/test/client_test.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ClientTest < Test::Unit::TestCase
|
4
|
+
include TestHelper
|
5
|
+
|
6
|
+
context "with a client object" do
|
7
|
+
setup do
|
8
|
+
@server = mock()
|
9
|
+
@server.expects(:running).returns(true).then.returns(false)
|
10
|
+
@connection = mock()
|
11
|
+
@connection.expects(:peeraddr).returns(['','','',''])
|
12
|
+
@connection.expects(:nil?).returns(false).at_least_once
|
13
|
+
@client = Pandemic::ServerSide::Client.new(@connection, @server)
|
14
|
+
end
|
15
|
+
|
16
|
+
should "read size from the connection" do
|
17
|
+
@connection.expects(:gets).returns("5\n")
|
18
|
+
@server.expects(:client_closed).with(@client)
|
19
|
+
@client.listen
|
20
|
+
wait_for_threads
|
21
|
+
end
|
22
|
+
|
23
|
+
should "read body from the connection" do
|
24
|
+
@connection.expects(:gets).returns("5\n")
|
25
|
+
@connection.expects(:read).with(5).returns("hello")
|
26
|
+
@server.expects(:client_closed).with(@client)
|
27
|
+
@client.listen
|
28
|
+
wait_for_threads
|
29
|
+
end
|
30
|
+
|
31
|
+
should "call handle request body on server" do
|
32
|
+
@connection.expects(:gets).returns("5\n")
|
33
|
+
@connection.expects(:read).with(5).returns("hello")
|
34
|
+
|
35
|
+
request = mock()
|
36
|
+
Pandemic::ServerSide::Request.expects(:new).returns(request)
|
37
|
+
@server.expects(:handle_client_request).with(request)
|
38
|
+
|
39
|
+
@server.expects(:client_closed).with(@client)
|
40
|
+
@client.listen
|
41
|
+
wait_for_threads
|
42
|
+
end
|
43
|
+
|
44
|
+
should "write response back to client" do
|
45
|
+
@connection.expects(:gets).returns("5\n")
|
46
|
+
@connection.expects(:read).with(5).returns("hello")
|
47
|
+
|
48
|
+
request = mock()
|
49
|
+
response = "olleh"
|
50
|
+
Pandemic::ServerSide::Request.expects(:new).returns(request)
|
51
|
+
@server.expects(:handle_client_request).with(request).returns(response)
|
52
|
+
|
53
|
+
@connection.expects(:write).with("5\n#{response}")
|
54
|
+
@connection.expects(:flush)
|
55
|
+
|
56
|
+
@server.expects(:client_closed).with(@client)
|
57
|
+
@client.listen
|
58
|
+
wait_for_threads
|
59
|
+
end
|
60
|
+
|
61
|
+
should "close the connection on nil response" do
|
62
|
+
@connection.expects(:gets).returns(nil)
|
63
|
+
@connection.expects(:close)
|
64
|
+
|
65
|
+
@server.expects(:client_closed).with(@client)
|
66
|
+
@client.listen
|
67
|
+
wait_for_threads
|
68
|
+
end
|
69
|
+
|
70
|
+
should "close the connection on disconnect" do
|
71
|
+
@connection.expects(:gets).raises(Pandemic::ServerSide::Client::DisconnectClient)
|
72
|
+
@connection.expects(:closed?).returns(false)
|
73
|
+
@connection.expects(:close)
|
74
|
+
|
75
|
+
@server.expects(:client_closed).with(@client)
|
76
|
+
@client.listen
|
77
|
+
wait_for_threads
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ConnectionPoolTest < Test::Unit::TestCase
|
4
|
+
include TestHelper
|
5
|
+
context "without a create connection block" do
|
6
|
+
setup do
|
7
|
+
@connection_pool = Pandemic::ConnectionPool.new
|
8
|
+
end
|
9
|
+
|
10
|
+
should "raise an exception when trying to connect" do
|
11
|
+
assert_raises Pandemic::ConnectionPool::CreateConnectionUndefinedException do
|
12
|
+
@connection_pool.connect
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "with a connection pool" do
|
18
|
+
setup do
|
19
|
+
@connection_pool = Pandemic::ConnectionPool.new
|
20
|
+
@connection_creator = mock()
|
21
|
+
@connection_destroyer = mock()
|
22
|
+
end
|
23
|
+
|
24
|
+
should "call create connection after its defined" do
|
25
|
+
@connection_creator.expects(:create).once
|
26
|
+
@connection_pool.create_connection do
|
27
|
+
@connection_creator.create
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
should "call destroyer on disconnect" do
|
32
|
+
@connection_creator.expects(:create).once
|
33
|
+
@connection_destroyer.expects(:destroy).once
|
34
|
+
@connection_pool.create_connection do
|
35
|
+
@connection_creator.create
|
36
|
+
conn = mock()
|
37
|
+
conn.expects(:closed?).returns(false).at_least(0)
|
38
|
+
conn
|
39
|
+
end
|
40
|
+
|
41
|
+
@connection_pool.destroy_connection do
|
42
|
+
@connection_destroyer.destroy
|
43
|
+
end
|
44
|
+
|
45
|
+
@connection_pool.disconnect
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
context "with a max connections of 2" do
|
51
|
+
setup do
|
52
|
+
@connection_pool = Pandemic::ConnectionPool.new(:max_connections => 2, :timeout => 0.01)
|
53
|
+
@connection_creator = mock()
|
54
|
+
end
|
55
|
+
|
56
|
+
should "raise timeout exception when no connections available" do
|
57
|
+
@connection_creator.expects(:create).twice
|
58
|
+
@connection_pool.create_connection do
|
59
|
+
@connection_creator.create
|
60
|
+
conn = mock()
|
61
|
+
conn.expects(:closed?).returns(false).at_least(0)
|
62
|
+
conn
|
63
|
+
end
|
64
|
+
|
65
|
+
assert_raises Pandemic::ConnectionPool::TimedOutWaitingForConnectionException do
|
66
|
+
@connection_pool.with_connection do |conn1|
|
67
|
+
@connection_pool.with_connection do |conn2|
|
68
|
+
@connection_pool.with_connection do |conn3|
|
69
|
+
fail("there should only be two connections")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
should "should checkin a connection and allow someone check the same one out" do
|
77
|
+
@connection_creator.expects(:create).twice
|
78
|
+
@connection_pool.create_connection do
|
79
|
+
@connection_creator.create
|
80
|
+
conn = mock()
|
81
|
+
conn.expects(:closed?).returns(false).at_least(0)
|
82
|
+
conn
|
83
|
+
end
|
84
|
+
|
85
|
+
@connection_pool.with_connection do |conn1|
|
86
|
+
conn2, conn3 = nil, nil
|
87
|
+
|
88
|
+
@connection_pool.with_connection do |conn|
|
89
|
+
conn2 = conn
|
90
|
+
end
|
91
|
+
@connection_pool.with_connection do |conn|
|
92
|
+
conn3 = conn
|
93
|
+
end
|
94
|
+
|
95
|
+
assert_equal conn2, conn3
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
should "should checkin connection even if there is an exception" do
|
100
|
+
@connection_creator.expects(:create).once
|
101
|
+
@connection_pool.create_connection do
|
102
|
+
@connection_creator.create
|
103
|
+
conn = mock()
|
104
|
+
conn.expects(:closed?).returns(false).at_least(0)
|
105
|
+
conn
|
106
|
+
end
|
107
|
+
before = @connection_pool.available_count
|
108
|
+
begin
|
109
|
+
@connection_pool.with_connection do |conn1|
|
110
|
+
raise TestException
|
111
|
+
end
|
112
|
+
rescue TestException
|
113
|
+
end
|
114
|
+
after = @connection_pool.available_count
|
115
|
+
|
116
|
+
assert_equal before, after
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context "with a min connections of 2" do
|
121
|
+
setup do
|
122
|
+
@connection_pool = Pandemic::ConnectionPool.new(:min_connections => 2)
|
123
|
+
@connection_creator = mock()
|
124
|
+
end
|
125
|
+
|
126
|
+
should "call create connection twice" do
|
127
|
+
@connection_creator.expects(:create).twice
|
128
|
+
@connection_pool.create_connection do
|
129
|
+
@connection_creator.create
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class FunctionalTest < Test::Unit::TestCase
|
4
|
+
include TestHelper
|
5
|
+
should "work" do
|
6
|
+
ignore_threads = Thread.list
|
7
|
+
ARGV.replace(["-i", "0", "-c", "test/pandemic_server.yml"]) # :(
|
8
|
+
Pandemic::ClientSide::Config.config_path = "test/pandemic_client.yml"
|
9
|
+
|
10
|
+
server = epidemic!
|
11
|
+
server.handler = Class.new(Pandemic::ServerSide::Handler) do
|
12
|
+
def process(body)
|
13
|
+
body.reverse
|
14
|
+
end
|
15
|
+
end.new
|
16
|
+
server.start
|
17
|
+
|
18
|
+
client = Class.new do
|
19
|
+
include Pandemize
|
20
|
+
end.new
|
21
|
+
client.extend(Pandemize)
|
22
|
+
assert_equal "dlrow olleh", client.pandemic.request("hello world")
|
23
|
+
server.stop
|
24
|
+
wait_for_threads(ignore_threads)
|
25
|
+
end
|
26
|
+
|
27
|
+
should "work with multiple peers" do
|
28
|
+
ignore_threads = Thread.list
|
29
|
+
handler = Class.new(Pandemic::ServerSide::Handler) do
|
30
|
+
def process(body)
|
31
|
+
body.reverse
|
32
|
+
end
|
33
|
+
end.new
|
34
|
+
|
35
|
+
ARGV.replace(["-i", "0", "-c", "test/pandemic_server.yml"]) # :(
|
36
|
+
server = epidemic!
|
37
|
+
server.handler = handler
|
38
|
+
server.start
|
39
|
+
|
40
|
+
ARGV.replace(["-i", "1", "-c", "test/pandemic_server.yml"]) # :(
|
41
|
+
server2 = epidemic!
|
42
|
+
server2.handler = handler
|
43
|
+
server2.start
|
44
|
+
|
45
|
+
Pandemic::ClientSide::Config.config_path = "test/pandemic_client.yml"
|
46
|
+
|
47
|
+
client = Class.new do
|
48
|
+
include Pandemize
|
49
|
+
end.new
|
50
|
+
client.extend(Pandemize)
|
51
|
+
assert_equal "raboofraboof", client.pandemic.request("foobar")
|
52
|
+
server.stop
|
53
|
+
server2.stop
|
54
|
+
wait_for_threads(ignore_threads)
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class HandlerTest < Test::Unit::TestCase
|
4
|
+
include TestHelper
|
5
|
+
|
6
|
+
context "with a request object" do
|
7
|
+
setup do
|
8
|
+
@request = mock()
|
9
|
+
@servers = {
|
10
|
+
1 => :self,
|
11
|
+
2 => :disconnected,
|
12
|
+
3 => :connected
|
13
|
+
}
|
14
|
+
@handler = Pandemic::ServerSide::Handler.new
|
15
|
+
end
|
16
|
+
|
17
|
+
should "concatenate all the results" do
|
18
|
+
@request.expects(:responses).once.returns(%w{1 2 3})
|
19
|
+
assert_equal "123", @handler.reduce(@request)
|
20
|
+
end
|
21
|
+
|
22
|
+
should "map to all non-disconnected nodes" do
|
23
|
+
@request.expects(:body).twice.returns("123")
|
24
|
+
map = @handler.map(@request, @servers)
|
25
|
+
# see setup for @servers
|
26
|
+
assert_equal 2, map.size
|
27
|
+
assert_equal "123", map[1]
|
28
|
+
assert_equal "123", map[3]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class MutexCounterTest < Test::Unit::TestCase
|
4
|
+
context "with a new counter" do
|
5
|
+
setup do
|
6
|
+
@counter = Pandemic::MutexCounter.new
|
7
|
+
end
|
8
|
+
|
9
|
+
should "start at 0" do
|
10
|
+
assert_equal 0, @counter.value
|
11
|
+
end
|
12
|
+
|
13
|
+
should "increment to 1 after one call to inc" do
|
14
|
+
assert_equal 0, @counter.value
|
15
|
+
assert_equal 1, @counter.inc
|
16
|
+
assert_equal 1, @counter.value
|
17
|
+
end
|
18
|
+
|
19
|
+
should "be thread safe" do
|
20
|
+
# Not exactly a perfect test, but I'm not sure how to actually test
|
21
|
+
# this without putting some code in the counter for this reason.
|
22
|
+
threads = []
|
23
|
+
5.times { threads << Thread.new { 100.times { @counter.inc }}}
|
24
|
+
threads.each {|t| t.join } # make sure they're all done
|
25
|
+
assert_equal 500, @counter.value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "with a max of 10" do
|
30
|
+
setup do
|
31
|
+
@counter = Pandemic::MutexCounter.new(10)
|
32
|
+
end
|
33
|
+
|
34
|
+
should "cycle from 1 to 10" do
|
35
|
+
expected = (1..10).to_a + [1]
|
36
|
+
actual = (1..11).collect { @counter.inc }
|
37
|
+
assert_equal expected, actual
|
38
|
+
end
|
39
|
+
|
40
|
+
should "have the correct 'real_total'" do
|
41
|
+
11.times { @counter.inc }
|
42
|
+
assert_equal 1, @counter.value
|
43
|
+
assert_equal 11, @counter.real_total
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/test/peer_test.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class PeerTest < Test::Unit::TestCase
|
4
|
+
include TestHelper
|
5
|
+
|
6
|
+
should "initialize a new connection pool" do
|
7
|
+
connection_pool = mock()
|
8
|
+
Pandemic::ConnectionPool.expects(:new).returns(connection_pool)
|
9
|
+
connection_pool.expects(:create_connection)
|
10
|
+
|
11
|
+
server = mock()
|
12
|
+
peer = Pandemic::ServerSide::Peer.new("localhost:4000", server)
|
13
|
+
end
|
14
|
+
|
15
|
+
should "create a tcp socket" do
|
16
|
+
connection_pool = mock()
|
17
|
+
Pandemic::ConnectionPool.expects(:new).returns(connection_pool)
|
18
|
+
connection_pool.expects(:create_connection).yields
|
19
|
+
TCPSocket.expects(:new).with("localhost", 4000)
|
20
|
+
|
21
|
+
server = mock()
|
22
|
+
peer = Pandemic::ServerSide::Peer.new("localhost:4000", server)
|
23
|
+
end
|
24
|
+
|
25
|
+
context "with conn. pool" do
|
26
|
+
setup do
|
27
|
+
@connection_pool = mock()
|
28
|
+
Pandemic::ConnectionPool.expects(:new).returns(@connection_pool)
|
29
|
+
@connection_pool.expects(:create_connection)
|
30
|
+
|
31
|
+
@server = mock()
|
32
|
+
@peer = Pandemic::ServerSide::Peer.new("localhost:4000", @server)
|
33
|
+
end
|
34
|
+
|
35
|
+
should "send client request to peer connection" do
|
36
|
+
request, body = stub(:hash => "asdasdfadsf"), "hello world"
|
37
|
+
@connection_pool.expects(:available_count => 1, :connections_count => 1)
|
38
|
+
conn = mock()
|
39
|
+
@connection_pool.expects(:with_connection).yields(conn)
|
40
|
+
|
41
|
+
conn.stubs(:closed? => false)
|
42
|
+
conn.expects(:write).with("PROCESS #{request.hash} #{body.size}\n#{body}")
|
43
|
+
conn.expects(:flush)
|
44
|
+
|
45
|
+
@peer.client_request(request, body)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/test/server_test.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ServerTest < Test::Unit::TestCase
|
4
|
+
include TestHelper
|
5
|
+
|
6
|
+
should "initialize peers" do
|
7
|
+
Pandemic::ServerSide::Config.expects(:bind_to).at_least_once.returns("localhost:4000")
|
8
|
+
Pandemic::ServerSide::Config.expects(:servers).returns(["localhost:4000", "localhost:4001"])
|
9
|
+
Pandemic::ServerSide::Peer.expects(:new).with("localhost:4001", is_a(Pandemic::ServerSide::Server))
|
10
|
+
@server = Pandemic::ServerSide::Server.new
|
11
|
+
end
|
12
|
+
|
13
|
+
context "with a server" do
|
14
|
+
setup do
|
15
|
+
Pandemic::ServerSide::Config.expects(:bind_to).at_least_once.returns("localhost:4000")
|
16
|
+
Pandemic::ServerSide::Config.expects(:servers).returns(["localhost:4000", "localhost:4001"])
|
17
|
+
@peer = mock()
|
18
|
+
Pandemic::ServerSide::Peer.expects(:new).with("localhost:4001", is_a(Pandemic::ServerSide::Server)).returns(@peer)
|
19
|
+
@server = Pandemic::ServerSide::Server.new
|
20
|
+
end
|
21
|
+
|
22
|
+
should "start a TCPServer, and connect to peers" do
|
23
|
+
ignore_threads = Thread.list
|
24
|
+
@tcpserver = mock()
|
25
|
+
TCPServer.expects(:new).with("localhost", 4000).returns(@tcpserver)
|
26
|
+
@peer.expects(:connect).once
|
27
|
+
@tcpserver.expects(:accept).twice.returns(nil).then.raises(Pandemic::ServerSide::Server::StopServer)
|
28
|
+
@tcpserver.expects(:close)
|
29
|
+
@peer.expects(:disconnect)
|
30
|
+
@server.handler = mock()
|
31
|
+
@server.start
|
32
|
+
wait_for_threads(ignore_threads)
|
33
|
+
end
|
34
|
+
|
35
|
+
should "create a new client object for a client connection" do
|
36
|
+
ignore_threads = Thread.list
|
37
|
+
|
38
|
+
@tcpserver = mock()
|
39
|
+
TCPServer.expects(:new).with("localhost", 4000).returns(@tcpserver)
|
40
|
+
@peer.expects(:connect).once
|
41
|
+
|
42
|
+
@conn = mock(:peeraddr => ['','','',''])
|
43
|
+
@tcpserver.expects(:accept).twice.returns(@conn).then.raises(Pandemic::ServerSide::Server::StopServer)
|
44
|
+
client = mock()
|
45
|
+
@conn.expects(:setsockopt).with(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
46
|
+
|
47
|
+
Pandemic::ServerSide::Client.expects(:new).with(@conn, @server).returns(client)
|
48
|
+
|
49
|
+
|
50
|
+
@conn.expects(:gets).returns("CLIENT\n")
|
51
|
+
@tcpserver.expects(:close)
|
52
|
+
@peer.expects(:disconnect)
|
53
|
+
client.expects(:listen).returns(client)
|
54
|
+
client.expects(:close).at_most_once # optional due to threaded nature, this may not actually happen
|
55
|
+
@server.handler = mock()
|
56
|
+
@server.start
|
57
|
+
wait_for_threads(ignore_threads)
|
58
|
+
end
|
59
|
+
|
60
|
+
should "connect with the corresponding peer object" do
|
61
|
+
ignore_threads = Thread.list
|
62
|
+
@tcpserver = mock()
|
63
|
+
TCPServer.expects(:new).with("localhost", 4000).returns(@tcpserver)
|
64
|
+
@peer.expects(:connect).once
|
65
|
+
|
66
|
+
@conn = mock(:peeraddr => ['','','',''])
|
67
|
+
@tcpserver.expects(:accept).twice.returns(@conn).then.raises(Pandemic::ServerSide::Server::StopServer)
|
68
|
+
@tcpserver.expects(:close)
|
69
|
+
@peer.expects(:disconnect)
|
70
|
+
|
71
|
+
@conn.expects(:setsockopt).with(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
72
|
+
@conn.expects(:gets).returns("SERVER localhost:4001\n")
|
73
|
+
|
74
|
+
@peer.expects(:host).returns("localhost")
|
75
|
+
@peer.expects(:port).returns(4001)
|
76
|
+
@peer.expects(:add_incoming_connection).with(@conn)
|
77
|
+
@server.handler = mock()
|
78
|
+
|
79
|
+
@server.start
|
80
|
+
wait_for_threads(ignore_threads)
|
81
|
+
end
|
82
|
+
|
83
|
+
should "call process on handler" do
|
84
|
+
handler = mock()
|
85
|
+
handler.expects(:process).with("body")
|
86
|
+
@server.handler = handler
|
87
|
+
@server.process("body")
|
88
|
+
end
|
89
|
+
|
90
|
+
should "map request, distribute to peers, and reduce" do
|
91
|
+
handler = mock()
|
92
|
+
request = mock()
|
93
|
+
request.expects(:hash).at_least_once.returns("abcddef134123")
|
94
|
+
@peer.expects(:connected?).returns(true)
|
95
|
+
handler.expects(:map).with(request, is_a(Hash)).returns({"localhost:4000" => "1", "localhost:4001" => "2"})
|
96
|
+
request.expects(:max_responses=).with(2)
|
97
|
+
@peer.expects(:client_request).with(request, "2")
|
98
|
+
|
99
|
+
handler.expects(:process).with("1").returns("2")
|
100
|
+
request.expects(:add_response).with("2")
|
101
|
+
|
102
|
+
request.expects(:wait_for_responses).once
|
103
|
+
handler.expects(:reduce).with(request)
|
104
|
+
|
105
|
+
@server.handler = handler
|
106
|
+
|
107
|
+
@server.handle_client_request(request)
|
108
|
+
# wait_for_threads
|
109
|
+
end
|
110
|
+
end
|
111
|
+
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 'test/unit'
|
7
|
+
require 'pandemic'
|
8
|
+
require 'rubygems'
|
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
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: arya-pandemic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Arya Asemanfar
|
@@ -9,10 +9,29 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-03-
|
12
|
+
date: 2009-03-26 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
|
-
dependencies:
|
15
|
-
|
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:
|
16
35
|
description: Distribute MapReduce to any of the workers and it will spread, like a pandemic.
|
17
36
|
email: aryaasemanfar@gmail.com
|
18
37
|
executables: []
|
@@ -52,12 +71,22 @@ files:
|
|
52
71
|
- lib/pandemic/server_side/server.rb
|
53
72
|
- lib/pandemic/util.rb
|
54
73
|
- lib/pandemic.rb
|
55
|
-
- Rakefile
|
56
|
-
- README.markdown
|
57
74
|
- Manifest
|
75
|
+
- MIT-LICENSE
|
58
76
|
- pandemic.gemspec
|
77
|
+
- Rakefile
|
78
|
+
- README.markdown
|
79
|
+
- test/client_test.rb
|
80
|
+
- test/connection_pool_test.rb
|
81
|
+
- test/functional_test.rb
|
82
|
+
- test/handler_test.rb
|
83
|
+
- test/mutex_counter_test.rb
|
84
|
+
- test/peer_test.rb
|
85
|
+
- test/server_test.rb
|
86
|
+
- test/test_helper.rb
|
87
|
+
- test/util_test.rb
|
59
88
|
has_rdoc: true
|
60
|
-
homepage:
|
89
|
+
homepage: https://github.com/arya/pandemic/
|
61
90
|
post_install_message:
|
62
91
|
rdoc_options:
|
63
92
|
- --line-numbers
|
@@ -87,5 +116,13 @@ rubygems_version: 1.2.0
|
|
87
116
|
signing_key:
|
88
117
|
specification_version: 2
|
89
118
|
summary: Distribute MapReduce to any of the workers and it will spread, like a pandemic.
|
90
|
-
test_files:
|
91
|
-
|
119
|
+
test_files:
|
120
|
+
- test/client_test.rb
|
121
|
+
- test/connection_pool_test.rb
|
122
|
+
- test/functional_test.rb
|
123
|
+
- test/handler_test.rb
|
124
|
+
- test/mutex_counter_test.rb
|
125
|
+
- test/peer_test.rb
|
126
|
+
- test/server_test.rb
|
127
|
+
- test/test_helper.rb
|
128
|
+
- test/util_test.rb
|