infopark-politics 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- OGYyMmNhMjBlZDFiMTIzZTJjZWU0ODg2NjgyYmM4ZTE2NTdiNDZhNw==
4
+ NmUwNzYzMGNmM2M2MmJkOTliNjFlOTYwY2I1ZmI1NzYxOTA1ZTg1NA==
5
5
  data.tar.gz: !binary |-
6
- ZTU5ZGU0MDI2YjRiM2FiNjlmNWJlNDU1YzA1NDYxZGQ5NDU1MTg5OA==
6
+ YTFjZGEzODU3Y2NhOWU1YzQ2NGY0NTRiMTQzZDdlZGRlYjQzZmVlOQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- NzYzZjk0MWVmYzZhNzhmZTFkYjQzM2I2NTViMGUzMWFlN2ZlZTFmNDdlMTQ2
10
- Y2RlMTc5ZmU4YzAxNTAxOGNkYjc4N2I0NDM0MDhmYTEzZTg4YmYzYTY5YWZm
11
- NjJmMzFjYTkzYzVjMTE0NTZkNTY0NzE1NTZhNzM1MjAzYjk2NTA=
9
+ Zjk3ZDA3NzU0MmUyMmNkNzEyNjgzN2UzYmQ2ZTkyNThkNjEwNjM4ZTc5MTY4
10
+ MjA4ZjIxNzgxMjA2ZjRlMGYwYmU1ODAxZTQ4M2Q2YTRjNzAyODlkYWIwOTlm
11
+ NGQxYzVjYTg4NGY3YWQyNmYyNDYwNmZhNGMwZjYxODIzZjQ3MDI=
12
12
  data.tar.gz: !binary |-
13
- YTEwM2MyOGEwMjIxNGI1ZmJlMThjY2MxNWJlNzYyZjQ4ZDEwNmVkM2I2NDE5
14
- MDQ3OWUxNTUzNzgwZTJjYmVkYTMyNmJjZGU3ZTM2Yzk4NGI4MWMwYWUyYWRl
15
- ZDJhM2JlZmY1NzhhOWI0YTI2OWFmMDFiMDMzMTJkZDkyYTFmOGE=
13
+ YjcyMDNlZTBjNzRlYTcyNDcyZmU5M2U3ODdlN2E3ODM2MjRiZTY3YWI2MzBm
14
+ Mzk3NDZiZDE5NmNlM2U2N2UxZDcwNDdhYTFlNDk4YzdjMmM1NmY4YTgzMWZh
15
+ NDVmOGQ4NzAzNjg3ODg0NzUwZTM5Zjg2Y2ExOGFlZjlmNjU0ZTQ=
data/README.md CHANGED
@@ -3,7 +3,7 @@ Infopark-Politics
3
3
 
4
4
  Infopark-Politics was derived from mperham-politics. All parts except of the StaticQueueWorker had
5
5
  eventually been dropped. The StaticQueueWorker remained for breaking up work into a large amount of
6
- pieces (called buckets) which then are distributed on n-1 worker nodes where n ist the total number
6
+ pieces (called buckets) which then are distributed on n worker nodes where n ist the total number
7
7
  of nodes.
8
8
 
9
9
  Actually there is no further documentation available…
@@ -6,7 +6,7 @@ end
6
6
 
7
7
  Gem::Specification.new do |gem|
8
8
  gem.name = "infopark-politics"
9
- gem.version = "0.7.0"
9
+ gem.version = "0.8.0"
10
10
  gem.summary = "Algorithms and Tools for Distributed Computing in Ruby."
11
11
  gem.description = ""
12
12
  gem.authors = ["Mike Perham", "Tilo Prütz"]
@@ -29,7 +29,6 @@ Gem::Specification.new do |gem|
29
29
  gem.add_development_dependency "rdoc", ">= 3.9"
30
30
  gem.add_development_dependency "rubygems-tasks", ">=0.2"
31
31
 
32
- gem.add_runtime_dependency "memcache-client", ">= 1.5.0"
33
- gem.add_runtime_dependency "starling", ">= 0.9.8"
32
+ gem.add_runtime_dependency "dalli"
34
33
  end
35
34
 
@@ -5,13 +5,7 @@ require 'uri'
5
5
  require 'drb'
6
6
  require 'set'
7
7
  require 'logger'
8
-
9
- begin
10
- require 'memcache'
11
- rescue LoadError => e
12
- puts "Unable to load memcache client, please run `sudo gem install memcache-client`: #{e.message}"
13
- exit(1)
14
- end
8
+ require 'dalli'
15
9
 
16
10
  module Politics
17
11
  module StaticQueueWorker
@@ -26,7 +20,6 @@ module Politics
26
20
  @group_name = name
27
21
  @iteration_length = options[:iteration_length]
28
22
  @memcache_client = client_for(Array(options[:servers]))
29
- @nominated_at = Time.now
30
23
  @dictatorship_length = options[:dictatorship_length]
31
24
 
32
25
  @buckets = []
@@ -46,7 +39,7 @@ module Politics
46
39
  def process_bucket(&block)
47
40
  log.debug "start bucket processing"
48
41
  raise ArgumentError, "process_bucket requires a block!" unless block_given?
49
- unless @memcache_client
42
+ unless memcache_client
50
43
  raise ArgumentError, "You must call register_worker before processing!"
51
44
  end
52
45
 
@@ -58,21 +51,25 @@ module Politics
58
51
  end
59
52
  begin
60
53
  nominate
61
- if leader?
62
- log.info { "has been elected leader" }
63
- perform_leader_duties
64
- else
65
- # Get a bucket from the leader and process it
66
- begin
67
- log.debug "getting bucket request from leader (#{leader_uri}) and processing it"
68
- bucket_process(*leader.bucket_request(uri, bucket_request_context), &block)
69
- rescue DRb::DRbError => dre
70
- log.error { "Error talking to leader: #{dre.message}" }
71
- relax until_next_iteration
54
+
55
+ if leader? && !(@leader_thread && @leader_thread.alive?)
56
+ unless (@leader_thread && @leader_thread.alive?)
57
+ @leader_thread = Thread.new do
58
+ perform_leader_duties
59
+ end
72
60
  end
73
61
  end
74
- rescue MemCache::MemCacheError => e
75
- log.error { "Unexpected MemCacheError: #{e.message}" }
62
+
63
+ # Get a bucket from the leader and process it
64
+ begin
65
+ log.debug "getting bucket request from leader (#{leader_uri}) and processing it"
66
+ bucket_process(*leader.bucket_request(uri, bucket_request_context), &block)
67
+ rescue DRb::DRbError => dre
68
+ log.error { "Error talking to leader: #{dre.message}" }
69
+ relax until_next_iteration
70
+ end
71
+ rescue Dalli::DalliError => e
72
+ log.error { "Unexpected DalliError: #{e.message}" }
76
73
  relax until_next_iteration
77
74
  end
78
75
  end while loop?
@@ -87,34 +84,33 @@ module Politics
87
84
  seize_leadership
88
85
  end
89
86
 
90
- def seize_leadership(duration = iteration_length)
91
- @memcache_client.set(token, uri, duration)
92
- @nominated_at = Time.now + duration - iteration_length
87
+ def seize_leadership(*args)
88
+ start_iteration(*args) {|duration| memcache_client.set(token, uri, duration) }
93
89
  end
94
90
 
95
91
  def perform_leader_duties
92
+ # The DRb thread handles the requests to the leader.
93
+ # This method performs the bucket managing.
94
+ log.info { "has been elected leader" }
96
95
  before_perform_leader_duties
97
- # Drb thread handles requests to leader
98
- as_dictator do
99
- initialize_buckets
96
+ # keeping leader state as long as buckets are being initialized
97
+ as_dictator { initialize_buckets }
98
+
99
+ while !buckets.empty?
100
+ # keeping leader state as long as buckets are available by renominating before
101
+ # nomination times out
102
+ as_dictator { update_buckets } unless restart_wanted?
100
103
  end
101
- # keeping leader state as long as buckets are available by renominating before nomination
102
- # times out
103
- while !buckets.empty? do
104
- log.debug { "relaxes half the time until next iteration" }
105
- relax(until_next_iteration / 2)
106
- as_dictator do
107
- update_buckets unless restart_wanted?
104
+
105
+ if restart_wanted?
106
+ as_dictator { populate_followers_to_stop }
107
+ # keeping leader state as long as there are followers to stop
108
+ while !followers_to_stop.empty?
109
+ relax(until_next_iteration / 2)
110
+ seize_leadership
108
111
  end
112
+ exit 0
109
113
  end
110
- as_dictator() {populate_followers_to_stop} if restart_wanted?
111
- # keeping leader state as long as there are followers to stop
112
- while !followers_to_stop.empty? do
113
- relax(until_next_iteration / 2)
114
- seize_leadership
115
- end
116
- exit 0 if restart_wanted?
117
- relax until_next_iteration
118
114
  end
119
115
 
120
116
  def populate_followers_to_stop
@@ -149,8 +145,7 @@ module Politics
149
145
  end
150
146
 
151
147
  def until_next_iteration
152
- left = iteration_length - (Time.now - @nominated_at)
153
- left > 0 ? left : 0
148
+ [(iteration_end || Time.at(0)) - Time.now, 0].max
154
149
  end
155
150
 
156
151
  def alive?
@@ -173,11 +168,17 @@ module Politics
173
168
 
174
169
  private
175
170
 
171
+ attr_reader :iteration_end, :memcache_client
172
+
173
+ def set_iteration_end(interval = iteration_length)
174
+ @iteration_end = Time.now + interval
175
+ end
176
+
176
177
  def before_perform_leader_duties
177
178
  end
178
179
 
179
180
  def restart_wanted?
180
- @memcache_client.get(restart_flag)
181
+ memcache_client.get(restart_flag)
181
182
  end
182
183
 
183
184
  def bucket_process(bucket, sleep_time)
@@ -210,6 +211,8 @@ module Politics
210
211
  end
211
212
 
212
213
  def update_buckets
214
+ log.debug { "relaxes half the time until next iteration" }
215
+ relax(until_next_iteration / 2)
213
216
  end
214
217
 
215
218
  def loop?
@@ -227,8 +230,8 @@ module Politics
227
230
  def internal_cleanup
228
231
  log.debug("uri: #{uri}, leader_uri: #{leader_uri}, until_next_iteration: #{until_next_iteration}")
229
232
  if leader?
230
- @memcache_client.delete(token)
231
- @memcache_client.delete(restart_flag)
233
+ memcache_client.delete(token)
234
+ memcache_client.delete(restart_flag)
232
235
  end
233
236
  end
234
237
 
@@ -236,16 +239,6 @@ module Politics
236
239
  # for custom use
237
240
  end
238
241
 
239
- def pause_until_expiry(elapsed)
240
- pause_time = (iteration_length - elapsed).to_f
241
- if pause_time > 0
242
- relax(pause_time)
243
- else
244
- raise ArgumentError, "Negative iteration time left. " +
245
- "Assuming the worst and exiting… #{iteration_length}/#{elapsed}"
246
- end
247
- end
248
-
249
242
  def relax(time)
250
243
  sleep time
251
244
  end
@@ -254,13 +247,17 @@ module Politics
254
247
  # and attempting to add the token with our name attached.
255
248
  def nominate
256
249
  log.debug("try to nominate")
257
- @nominated_at = Time.now
258
- @memcache_client.add(token, uri, iteration_length)
250
+ start_iteration {|duration| memcache_client.add(token, uri, duration) }
251
+ end
252
+
253
+ def start_iteration(duration = iteration_length)
254
+ yield duration
255
+ set_iteration_end(duration)
259
256
  @leader_uri = nil
260
257
  end
261
258
 
262
259
  def leader_uri
263
- @leader_uri ||= @memcache_client.get(token)
260
+ @leader_uri ||= memcache_client.get(token)
264
261
  end
265
262
 
266
263
  # Check to see if we are leader by looking at the process name
@@ -271,7 +268,7 @@ module Politics
271
268
 
272
269
  # Easy to mock or monkey-patch if another MemCache client is preferred.
273
270
  def client_for(servers)
274
- MemCache.new(servers)
271
+ Dalli::Client.new(servers)
275
272
  end
276
273
 
277
274
  def time_for(&block)
@@ -290,9 +287,7 @@ module Politics
290
287
  end
291
288
 
292
289
  def start_druby_service
293
- server = DRb.start_service("druby://#{hostname || ""}:0", self)
294
- @uri = DRb.uri
295
- register_service
290
+ @uri = DRb.start_service("druby://#{hostname || ""}:0", self).uri
296
291
  end
297
292
  end
298
293
  end
data/spec/spec_helper.rb CHANGED
@@ -14,6 +14,7 @@ RSpec.configure do |config|
14
14
  # the seed, which is printed after each run.
15
15
  # --seed 1234
16
16
  config.order = 'random'
17
+ config.mock_with(:rspec) {|mocks| mocks.verify_partial_doubles = true }
17
18
  end
18
19
 
19
20
  require_relative 'politics'
@@ -94,8 +94,7 @@ describe Worker do
94
94
  end
95
95
 
96
96
  it "should return time to next iteration even if nominate was not completed" do
97
- worker.until_next_iteration.should > 0
98
- worker.until_next_iteration.should <= 10
97
+ worker.until_next_iteration.should == 0
99
98
  end
100
99
 
101
100
  it "should give access to the uri" do
@@ -127,14 +126,14 @@ describe Worker do
127
126
  end
128
127
 
129
128
  it "should relax until next iteration on MemCache errors during nomination" do
130
- worker.should_receive(:nominate).exactly(4).and_raise MemCache::MemCacheError.new("Buh!")
129
+ worker.should_receive(:nominate).exactly(4).and_raise Dalli::DalliError.new("Buh!")
131
130
  worker.should_receive(:relax).with(666).exactly(4).times
132
131
 
133
132
  worker.start
134
133
  end
135
134
 
136
135
  it "should relax until next iteration on MemCache errors during request for leader" do
137
- worker.should_receive(:leader_uri).exactly(4).and_raise(MemCache::MemCacheError.new("Buh"))
136
+ worker.should_receive(:leader_uri).exactly(4).and_raise(Dalli::DalliError.new("Buh"))
138
137
  worker.should_receive(:relax).with(666).exactly(4).times
139
138
 
140
139
  worker.start
@@ -143,10 +142,14 @@ describe Worker do
143
142
  describe "as leader" do
144
143
  before do
145
144
  worker.stub(:leader?).and_return true
145
+ allow(worker).to receive(:bucket_process) { sleep 0.5 }
146
+ allow(worker).to receive(:relax)
147
+ allow(worker).to receive(:leader).and_return(double('leader', bucket_request: nil))
146
148
  end
147
149
 
148
- it "should do leader duties" do
149
- worker.should_receive(:perform_leader_duties).exactly(4).times
150
+ it "performs leader duties in a separate thread" do
151
+ # four iterations of 0.5 seconds → two leader threads of one second
152
+ worker.should_receive(:perform_leader_duties).exactly(2).times { sleep 1 }
150
153
  worker.start
151
154
  end
152
155
  end
@@ -310,52 +313,56 @@ describe Worker do
310
313
  worker.stub(:buckets).and_return([])
311
314
  end
312
315
 
313
- it "should populate the followers_to_stop list before evaluating it if restart is wanted" do
314
- worker.stub(:restart_wanted?).and_return true
315
- worker.stub(:exit)
316
- worker.should_receive(:populate_followers_to_stop).ordered.once
317
- worker.should_receive(:followers_to_stop).ordered.and_return []
318
- worker.perform_leader_duties
319
- end
320
-
321
- it "should not populate the followers_to_stop list if restart is not wanted" do
322
- worker.stub(:restart_wanted?).and_return false
323
- worker.should_not_receive(:populate_followers_to_stop)
324
- worker.perform_leader_duties
325
- end
326
-
327
- describe "as long as there are followers to stop" do
316
+ context "when restart is wanted" do
328
317
  before do
329
- worker.stub(:followers_to_stop).and_return([1], [2], [3], [4], [])
330
- worker.stub(:relax)
318
+ allow(worker).to receive(:restart_wanted?).and_return true
319
+ allow(worker).to receive(:exit)
331
320
  end
332
321
 
333
- it "should relax half of the time to the next iteration" do
334
- worker.stub(:until_next_iteration).and_return(6)
335
- worker.should_receive(:relax).with(3).exactly(4).times
322
+ it "populates the followers_to_stop list before evaluating it" do
323
+ worker.should_receive(:populate_followers_to_stop).ordered.once
324
+ worker.should_receive(:followers_to_stop).ordered.and_return []
336
325
  worker.perform_leader_duties
337
326
  end
338
327
 
339
- it "should seize the leadership periodically" do
340
- worker.should_receive(:seize_leadership).at_least(4).times
341
- worker.perform_leader_duties
328
+ context "as long as there are followers to stop" do
329
+ before do
330
+ worker.stub(:followers_to_stop).and_return([1], [2], [3], [4], [])
331
+ worker.stub(:relax)
332
+ end
333
+
334
+ it "relaxes half of the time to the next iteration" do
335
+ worker.stub(:until_next_iteration).and_return(6)
336
+ worker.should_receive(:relax).with(3).exactly(4).times
337
+ worker.perform_leader_duties
338
+ end
339
+
340
+ it "seizes the leadership periodically" do
341
+ worker.should_receive(:seize_leadership).at_least(4).times
342
+ worker.perform_leader_duties
343
+ end
342
344
  end
343
- end
344
345
 
345
- describe "if there are no more followers to stop" do
346
- before do
347
- worker.stub(:followers_to_stop).and_return([])
346
+ context "if there are no more followers to stop" do
347
+ before do
348
+ worker.stub(:followers_to_stop).and_return([])
349
+ end
350
+
351
+ it "exits" do
352
+ worker.should_receive(:exit).with(0)
353
+ worker.perform_leader_duties
354
+ end
348
355
  end
356
+ end
349
357
 
350
- it "should relax until next iteration" do
351
- worker.stub(:until_next_iteration).and_return(6)
352
- worker.should_receive(:relax).with(6).once
353
- worker.perform_leader_duties
358
+ context "when restart is wanted" do
359
+ before do
360
+ allow(worker).to receive(:restart_wanted?).and_return false
354
361
  end
355
362
 
356
- it "should exit if restart is wanted" do
357
- worker.stub(:restart_wanted?).and_return true
358
- worker.should_receive(:exit).with(0)
363
+ it "does not populate the followers_to_stop list if restart is not wanted" do
364
+ worker.stub(:restart_wanted?).and_return false
365
+ worker.should_not_receive(:populate_followers_to_stop)
359
366
  worker.perform_leader_duties
360
367
  end
361
368
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: infopark-politics
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Perham
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-08-13 00:00:00.000000000 Z
12
+ date: 2015-03-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -68,33 +68,19 @@ dependencies:
68
68
  - !ruby/object:Gem::Version
69
69
  version: '0.2'
70
70
  - !ruby/object:Gem::Dependency
71
- name: memcache-client
71
+ name: dalli
72
72
  requirement: !ruby/object:Gem::Requirement
73
73
  requirements:
74
74
  - - ! '>='
75
75
  - !ruby/object:Gem::Version
76
- version: 1.5.0
76
+ version: '0'
77
77
  type: :runtime
78
78
  prerelease: false
79
79
  version_requirements: !ruby/object:Gem::Requirement
80
80
  requirements:
81
81
  - - ! '>='
82
82
  - !ruby/object:Gem::Version
83
- version: 1.5.0
84
- - !ruby/object:Gem::Dependency
85
- name: starling
86
- requirement: !ruby/object:Gem::Requirement
87
- requirements:
88
- - - ! '>='
89
- - !ruby/object:Gem::Version
90
- version: 0.9.8
91
- type: :runtime
92
- prerelease: false
93
- version_requirements: !ruby/object:Gem::Requirement
94
- requirements:
95
- - - ! '>='
96
- - !ruby/object:Gem::Version
97
- version: 0.9.8
83
+ version: '0'
98
84
  description: ''
99
85
  email: tilo@infopark.de
100
86
  executables: []