arya-pandemic 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ servers:
2
+ - localhost:4000
3
+ - localhost:4001
@@ -0,0 +1,3 @@
1
+ servers:
2
+ - localhost:4000
3
+ - localhost:4001
@@ -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
 
@@ -22,6 +22,10 @@ module Pandemic
22
22
  def process(body)
23
23
  body
24
24
  end
25
+
26
+ def filter_alive(servers)
27
+ servers.keys.select{|k| servers[k] != :disconnected}
28
+ end
25
29
  end
26
30
  end
27
31
  end
@@ -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(Config.bind_to)
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 == Config.bind_to # not a peer, it's itself
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 = @handler.map(request, connection_statuses)
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
- @handler.reduce(request)
143
+ @handler_instance.reduce(request)
142
144
  end
143
145
 
144
146
  def process(body)
145
- @handler.process(body)
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.2.3"
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-04-10}
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", "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"]
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
@@ -12,7 +12,7 @@ class FunctionalTest < Test::Unit::TestCase
12
12
  def process(body)
13
13
  body.reverse
14
14
  end
15
- end.new
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.new
33
+ end
34
34
 
35
35
  ARGV.replace(["-i", "0", "-c", "test/pandemic_server.yml"]) # :(
36
36
  server = epidemic!
@@ -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