arya-pandemic 0.2.3 → 0.3.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/Manifest +6 -1
- data/README.markdown +18 -1
- data/Rakefile +1 -1
- 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 +3 -2
- data/lib/pandemic/mutex_counter.rb +21 -0
- data/lib/pandemic/server_side/config.rb +4 -2
- data/lib/pandemic/server_side/handler.rb +4 -0
- data/lib/pandemic/server_side/processor.rb +90 -0
- data/lib/pandemic/server_side/server.rb +31 -12
- data/pandemic.gemspec +5 -5
- data/test/functional_test.rb +2 -2
- data/test/mutex_counter_test.rb +27 -0
- data/test/processor_test.rb +33 -0
- data/test/server_test.rb +11 -9
- metadata +12 -3
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'pandemic'
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
class WordCounter < Pandemic::ServerSide::Handler
|
|
6
|
+
def map(request, servers)
|
|
7
|
+
# select only the alive servers (non-disconnected)
|
|
8
|
+
only_alive = servers.keys.select{|k| servers[k] != :disconnected}
|
|
9
|
+
|
|
10
|
+
mapping = {}
|
|
11
|
+
intervals = (request.body.size / only_alive.size.to_f).floor
|
|
12
|
+
|
|
13
|
+
pos = 0
|
|
14
|
+
only_alive.size.times do |i|
|
|
15
|
+
if i == only_alive.size - 1 # last peer gets the rest
|
|
16
|
+
mapping[only_alive[i]] = request.body[pos..-1]
|
|
17
|
+
else
|
|
18
|
+
next_pos = request.body[(pos + intervals)..-1].index(/ /) + pos + intervals
|
|
19
|
+
mapping[only_alive[i]] = request.body[pos...next_pos]
|
|
20
|
+
pos = next_pos
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
mapping
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def process(text)
|
|
27
|
+
counts = Hash.new(0)
|
|
28
|
+
text.scan(/\w+/) do |word|
|
|
29
|
+
counts[word.strip.downcase] += 1
|
|
30
|
+
end
|
|
31
|
+
counts.to_json
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def reduce(request)
|
|
35
|
+
total_counts = Hash.new(0)
|
|
36
|
+
request.responses.each do |counts|
|
|
37
|
+
JSON.parse(counts).each do |word, count|
|
|
38
|
+
total_counts[word] += count
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
total_counts.to_json
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
server = epidemic!
|
|
46
|
+
server.handler = WordCounter
|
|
47
|
+
server.start.join
|
data/lib/pandemic.rb
CHANGED
|
@@ -17,6 +17,7 @@ require 'pandemic/server_side/server'
|
|
|
17
17
|
require 'pandemic/server_side/peer'
|
|
18
18
|
require 'pandemic/server_side/request'
|
|
19
19
|
require 'pandemic/server_side/handler'
|
|
20
|
+
require 'pandemic/server_side/processor'
|
|
20
21
|
|
|
21
22
|
require 'pandemic/client_side/config'
|
|
22
23
|
require 'pandemic/client_side/cluster_connection'
|
|
@@ -33,13 +34,13 @@ require 'pandemic/client_side/pandemize'
|
|
|
33
34
|
TCP_NO_DELAY_AVAILABLE =
|
|
34
35
|
RUBY_VERSION < '1.9' ? Socket.constants.include?('TCP_NODELAY') : Socket.constants.include?(:TCP_NODELAY)
|
|
35
36
|
|
|
36
|
-
def epidemic!
|
|
37
|
+
def epidemic!(bind_to = nil)
|
|
37
38
|
if $pandemic_logger.nil?
|
|
38
39
|
$pandemic_logger = Logger.new("pandemic.log")
|
|
39
40
|
$pandemic_logger.level = Logger::INFO
|
|
40
41
|
$pandemic_logger.datetime_format = "%Y-%m-%d %H:%M:%S "
|
|
41
42
|
end
|
|
42
|
-
Pandemic::ServerSide::Server.boot
|
|
43
|
+
Pandemic::ServerSide::Server.boot(bind_to)
|
|
43
44
|
end
|
|
44
45
|
|
|
45
46
|
::Pandemize = Pandemic::ClientSide::Pandemize
|
|
@@ -11,6 +11,7 @@ module Pandemic
|
|
|
11
11
|
def real_total
|
|
12
12
|
@mutex.synchronize { (@resets * @max) + @counter }
|
|
13
13
|
end
|
|
14
|
+
alias_method :to_i, :real_total
|
|
14
15
|
|
|
15
16
|
def value
|
|
16
17
|
@mutex.synchronize { @counter }
|
|
@@ -25,5 +26,25 @@ module Pandemic
|
|
|
25
26
|
@counter += 1
|
|
26
27
|
end
|
|
27
28
|
end
|
|
29
|
+
alias_method :next, :inc
|
|
30
|
+
alias_method :succ, :inc
|
|
31
|
+
|
|
32
|
+
# decr only to zero
|
|
33
|
+
def decr
|
|
34
|
+
@mutex.synchronize do
|
|
35
|
+
if @counter > 0
|
|
36
|
+
@counter -= 1
|
|
37
|
+
else
|
|
38
|
+
if @resets > 1
|
|
39
|
+
@resets -= 1
|
|
40
|
+
@counter = @max
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
@counter
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
alias_method :pred, :decr
|
|
47
|
+
alias_method :prev, :decr
|
|
48
|
+
|
|
28
49
|
end
|
|
29
50
|
end
|
|
@@ -2,7 +2,7 @@ module Pandemic
|
|
|
2
2
|
module ServerSide
|
|
3
3
|
class Config
|
|
4
4
|
class << self
|
|
5
|
-
attr_accessor :bind_to, :servers, :response_timeout
|
|
5
|
+
attr_accessor :bind_to, :servers, :response_timeout, :fork_for_processor
|
|
6
6
|
def load
|
|
7
7
|
path = extract_config_path
|
|
8
8
|
yaml = YAML.load_file(path)
|
|
@@ -10,9 +10,11 @@ module Pandemic
|
|
|
10
10
|
@server_map = yaml['servers'] || []
|
|
11
11
|
@servers = @server_map.is_a?(Hash) ? @server_map.values : @server_map
|
|
12
12
|
@servers = @servers.collect { |s| s.is_a?(Hash) ? s.keys.first : s }
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
@response_timeout = (yaml['response_timeout'] || 1).to_f
|
|
15
15
|
@bind_to = extract_bind_to
|
|
16
|
+
@fork_for_processor = yaml['fork_for_processor']
|
|
17
|
+
|
|
16
18
|
raise "Interface to bind to is nil." unless @bind_to
|
|
17
19
|
end
|
|
18
20
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
module Pandemic
|
|
2
|
+
module ServerSide
|
|
3
|
+
class Processor
|
|
4
|
+
def initialize(handler)
|
|
5
|
+
read_from_parent, write_to_child = IO.pipe
|
|
6
|
+
read_from_child, write_to_parent = IO.pipe
|
|
7
|
+
|
|
8
|
+
@child_process_id = fork
|
|
9
|
+
if @child_process_id
|
|
10
|
+
# I'm the parent
|
|
11
|
+
write_to_parent.close
|
|
12
|
+
read_from_parent.close
|
|
13
|
+
@out = write_to_child
|
|
14
|
+
@in = read_from_child
|
|
15
|
+
else
|
|
16
|
+
# I'm the child
|
|
17
|
+
write_to_child.close
|
|
18
|
+
read_from_child.close
|
|
19
|
+
@out = write_to_parent
|
|
20
|
+
@in = read_from_parent
|
|
21
|
+
@handler = handler.new
|
|
22
|
+
wait_for_jobs
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def process(body)
|
|
27
|
+
if parent?
|
|
28
|
+
@out.puts(body.size.to_s)
|
|
29
|
+
@out.write(body)
|
|
30
|
+
ready, = IO.select([@in], nil, nil)
|
|
31
|
+
if ready
|
|
32
|
+
size = @in.gets.to_i
|
|
33
|
+
result = @in.read(size)
|
|
34
|
+
return result
|
|
35
|
+
end
|
|
36
|
+
else
|
|
37
|
+
return @handler.process(body)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def close(status = 0)
|
|
42
|
+
if parent? && child_alive?
|
|
43
|
+
Process.detach(@child_process_id)
|
|
44
|
+
@out.puts(status.to_s)
|
|
45
|
+
@out.close
|
|
46
|
+
@in.close
|
|
47
|
+
else
|
|
48
|
+
Process.exit!(status)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def closed?
|
|
53
|
+
!child_alive?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
def wait_for_jobs
|
|
58
|
+
if child?
|
|
59
|
+
while true
|
|
60
|
+
ready, = IO.select([@in], nil, nil)
|
|
61
|
+
if ready
|
|
62
|
+
size = @in.gets.to_i
|
|
63
|
+
if size > 0
|
|
64
|
+
body = @in.read(size)
|
|
65
|
+
result = process(body)
|
|
66
|
+
@out.puts(result.size.to_s)
|
|
67
|
+
@out.write(result)
|
|
68
|
+
else
|
|
69
|
+
self.close(size)
|
|
70
|
+
break
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def parent?
|
|
78
|
+
!!@child_process_id
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def child?
|
|
82
|
+
!parent?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def child_alive?
|
|
86
|
+
parent? && !@in.closed?
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -4,10 +4,10 @@ module Pandemic
|
|
|
4
4
|
include Util
|
|
5
5
|
class StopServer < Exception; end
|
|
6
6
|
class << self
|
|
7
|
-
def boot
|
|
7
|
+
def boot(bind_to = nil)
|
|
8
8
|
Config.load
|
|
9
9
|
# Process.setrlimit(Process::RLIMIT_NOFILE, 4096) # arbitrary high number of max file descriptors.
|
|
10
|
-
server = self.new
|
|
10
|
+
server = self.new(bind_to || Config.bind_to)
|
|
11
11
|
set_signal_traps(server)
|
|
12
12
|
server
|
|
13
13
|
end
|
|
@@ -26,23 +26,25 @@ module Pandemic
|
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
attr_reader :host, :port, :running
|
|
29
|
-
def initialize
|
|
30
|
-
@host, @port = host_port(
|
|
31
|
-
|
|
29
|
+
def initialize(bind_to)
|
|
30
|
+
@host, @port = host_port(bind_to)
|
|
32
31
|
@clients = []
|
|
33
32
|
@total_clients = 0
|
|
34
33
|
@clients_mutex = Mutex.new
|
|
34
|
+
@num_jobs_processed = MutexCounter.new
|
|
35
|
+
@num_jobs_entered = MutexCounter.new
|
|
35
36
|
|
|
36
37
|
@peers = {}
|
|
37
38
|
@servers = Config.servers
|
|
38
39
|
@servers.each do |peer|
|
|
39
|
-
next if peer ==
|
|
40
|
+
next if peer == bind_to # not a peer, it's itself
|
|
40
41
|
@peers[peer] = Peer.new(peer, self)
|
|
41
42
|
end
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
def handler=(handler)
|
|
45
46
|
@handler = handler
|
|
47
|
+
@handler_instance = handler.new
|
|
46
48
|
end
|
|
47
49
|
|
|
48
50
|
def start
|
|
@@ -113,7 +115,7 @@ module Pandemic
|
|
|
113
115
|
|
|
114
116
|
def handle_client_request(request)
|
|
115
117
|
info("Handling client request")
|
|
116
|
-
map = @
|
|
118
|
+
map = @handler_instance.map(request, connection_statuses)
|
|
117
119
|
request.max_responses = map.size
|
|
118
120
|
debug("Sending client request to #{map.size} handlers (#{request.hash})")
|
|
119
121
|
|
|
@@ -129,7 +131,7 @@ module Pandemic
|
|
|
129
131
|
begin
|
|
130
132
|
request.add_response(self.process(map[signature]))
|
|
131
133
|
rescue Exception => e
|
|
132
|
-
warn("Unhandled exception in local processing: #{e.inspect}")
|
|
134
|
+
warn("Unhandled exception in local processing: #{e.inspect}#{e.backtrace.join("\n")}}")
|
|
133
135
|
end
|
|
134
136
|
end
|
|
135
137
|
end
|
|
@@ -138,11 +140,18 @@ module Pandemic
|
|
|
138
140
|
request.wait_for_responses
|
|
139
141
|
|
|
140
142
|
debug("Done waiting for responses, calling reduce")
|
|
141
|
-
@
|
|
143
|
+
@handler_instance.reduce(request)
|
|
142
144
|
end
|
|
143
145
|
|
|
144
146
|
def process(body)
|
|
145
|
-
@
|
|
147
|
+
@num_jobs_entered.inc
|
|
148
|
+
response = if Config.fork_for_processor
|
|
149
|
+
self.processor.with_connection {|con| con.process(body) }
|
|
150
|
+
else
|
|
151
|
+
@handler_instance.process(body)
|
|
152
|
+
end
|
|
153
|
+
@num_jobs_processed.inc
|
|
154
|
+
response
|
|
146
155
|
end
|
|
147
156
|
|
|
148
157
|
def signature
|
|
@@ -166,6 +175,14 @@ module Pandemic
|
|
|
166
175
|
end
|
|
167
176
|
end
|
|
168
177
|
|
|
178
|
+
def processor
|
|
179
|
+
@processor ||= begin
|
|
180
|
+
processor = ConnectionPool.new
|
|
181
|
+
processor.create_connection { Processor.new(@handler) }
|
|
182
|
+
processor
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
169
186
|
private
|
|
170
187
|
def print_stats(connection)
|
|
171
188
|
begin
|
|
@@ -180,6 +197,8 @@ module Pandemic
|
|
|
180
197
|
str << "Total Requests: #{stats[:num_requests]}"
|
|
181
198
|
str << "Pending Requests: #{stats[:pending_requests]}"
|
|
182
199
|
str << "Late Responses: #{stats[:late_responses]}"
|
|
200
|
+
str << "Total Jobs Processed: #{stats[:total_jobs_processed]}"
|
|
201
|
+
str << "Pending Jobs: #{stats[:jobs_pending]}"
|
|
183
202
|
connection.puts(str.join("\n"))
|
|
184
203
|
end while (s = connection.gets) && (s.strip == "stats" || s.strip == "")
|
|
185
204
|
connection.close if connection && !connection.closed?
|
|
@@ -212,10 +231,10 @@ module Pandemic
|
|
|
212
231
|
end
|
|
213
232
|
counts
|
|
214
233
|
end
|
|
215
|
-
|
|
234
|
+
results[:total_jobs_processed] = @num_jobs_processed.to_i
|
|
235
|
+
results[:jobs_pending] = @num_jobs_entered.to_i - results[:total_jobs_processed]
|
|
216
236
|
results[:num_requests] = Request.total_request_count
|
|
217
237
|
results[:late_responses] = Request.total_late_responses
|
|
218
|
-
|
|
219
238
|
results[:pending_requests] = @clients_mutex.synchronize do
|
|
220
239
|
@clients.inject(0) do |pending, client|
|
|
221
240
|
pending + (client.received_requests - client.responded_requests)
|
data/pandemic.gemspec
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Gem::Specification.new do |s|
|
|
4
4
|
s.name = %q{pandemic}
|
|
5
|
-
s.version = "0.
|
|
5
|
+
s.version = "0.3.0"
|
|
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-
|
|
9
|
+
s.date = %q{2009-05-27}
|
|
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
|
-
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", "Manifest", "MIT-LICENSE", "
|
|
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/processor.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 = ["examples/client/client.rb", "examples/client/constitution.txt", "examples/client/pandemic_client.yml", "examples/server/pandemic_server.yml", "examples/server/word_count_server.rb", "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/processor.rb", "lib/pandemic/server_side/request.rb", "lib/pandemic/server_side/server.rb", "lib/pandemic/util.rb", "lib/pandemic.rb", "Manifest", "MIT-LICENSE", "Rakefile", "README.markdown", "pandemic.gemspec", "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/processor_test.rb", "test/server_test.rb", "test/test_helper.rb", "test/util_test.rb"]
|
|
14
14
|
s.has_rdoc = true
|
|
15
15
|
s.homepage = %q{https://github.com/arya/pandemic/}
|
|
16
16
|
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Pandemic", "--main", "README.markdown"]
|
|
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
|
|
|
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
|
+
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/processor_test.rb", "test/server_test.rb", "test/test_helper.rb", "test/util_test.rb"]
|
|
22
22
|
|
|
23
23
|
if s.respond_to? :specification_version then
|
|
24
24
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
data/test/functional_test.rb
CHANGED
|
@@ -12,7 +12,7 @@ class FunctionalTest < Test::Unit::TestCase
|
|
|
12
12
|
def process(body)
|
|
13
13
|
body.reverse
|
|
14
14
|
end
|
|
15
|
-
end
|
|
15
|
+
end
|
|
16
16
|
server.start
|
|
17
17
|
|
|
18
18
|
client = Class.new do
|
|
@@ -30,7 +30,7 @@ class FunctionalTest < Test::Unit::TestCase
|
|
|
30
30
|
def process(body)
|
|
31
31
|
body.reverse
|
|
32
32
|
end
|
|
33
|
-
end
|
|
33
|
+
end
|
|
34
34
|
|
|
35
35
|
ARGV.replace(["-i", "0", "-c", "test/pandemic_server.yml"]) # :(
|
|
36
36
|
server = epidemic!
|
data/test/mutex_counter_test.rb
CHANGED
|
@@ -16,6 +16,24 @@ class MutexCounterTest < Test::Unit::TestCase
|
|
|
16
16
|
assert_equal 1, @counter.value
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
should "decrement to 0 after one call to inc" do
|
|
20
|
+
assert_equal 0, @counter.value
|
|
21
|
+
assert_equal 1, @counter.inc
|
|
22
|
+
assert_equal 1, @counter.value
|
|
23
|
+
assert_equal 0, @counter.decr
|
|
24
|
+
assert_equal 0, @counter.value
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
should "only decrement to 0" do
|
|
28
|
+
assert_equal 0, @counter.value
|
|
29
|
+
assert_equal 1, @counter.inc
|
|
30
|
+
assert_equal 1, @counter.value
|
|
31
|
+
assert_equal 0, @counter.decr
|
|
32
|
+
assert_equal 0, @counter.value
|
|
33
|
+
assert_equal 0, @counter.decr
|
|
34
|
+
assert_equal 0, @counter.value
|
|
35
|
+
end
|
|
36
|
+
|
|
19
37
|
should "be thread safe" do
|
|
20
38
|
# Not exactly a perfect test, but I'm not sure how to actually test
|
|
21
39
|
# this without putting some code in the counter for this reason.
|
|
@@ -42,5 +60,14 @@ class MutexCounterTest < Test::Unit::TestCase
|
|
|
42
60
|
assert_equal 1, @counter.value
|
|
43
61
|
assert_equal 11, @counter.real_total
|
|
44
62
|
end
|
|
63
|
+
|
|
64
|
+
should "maintain correct 'real_total' with decrement" do
|
|
65
|
+
11.times { @counter.inc }
|
|
66
|
+
assert_equal 1, @counter.value
|
|
67
|
+
assert_equal 11, @counter.real_total
|
|
68
|
+
assert_equal 0, @counter.decr
|
|
69
|
+
assert_equal 10, @counter.real_total
|
|
70
|
+
end
|
|
71
|
+
|
|
45
72
|
end
|
|
46
73
|
end
|