infopark-politics 0.2.15 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/politics/static_queue_worker.rb +63 -30
- data/spec/static_queue_worker_spec.rb +179 -12
- metadata +1 -1
@@ -2,6 +2,7 @@ require 'socket'
|
|
2
2
|
require 'ipaddr'
|
3
3
|
require 'uri'
|
4
4
|
require 'drb'
|
5
|
+
require 'set'
|
5
6
|
|
6
7
|
begin
|
7
8
|
require 'net/dns/mdns-sd'
|
@@ -76,6 +77,7 @@ module Politics
|
|
76
77
|
|
77
78
|
@buckets = []
|
78
79
|
@bucket_count = bucket_count
|
80
|
+
@followers_to_stop = Set.new
|
79
81
|
|
80
82
|
register_with_bonjour
|
81
83
|
log.progname = uri
|
@@ -89,7 +91,9 @@ module Politics
|
|
89
91
|
def process_bucket(&block)
|
90
92
|
log.debug "start bucket processing"
|
91
93
|
raise ArgumentError, "process_bucket requires a block!" unless block_given?
|
92
|
-
|
94
|
+
unless @memcache_client
|
95
|
+
raise ArgumentError, "You must call register_worker before processing!"
|
96
|
+
end
|
93
97
|
|
94
98
|
begin
|
95
99
|
begin
|
@@ -144,20 +148,36 @@ module Politics
|
|
144
148
|
log.debug { "relaxes half the time until next iteration" }
|
145
149
|
relax(until_next_iteration / 2)
|
146
150
|
as_dictator do
|
147
|
-
update_buckets
|
151
|
+
update_buckets unless restart_wanted?
|
148
152
|
end
|
149
153
|
end
|
150
|
-
|
154
|
+
as_dictator() {populate_followers_to_stop} if restart_wanted?
|
155
|
+
# keeping leader state as long as there are followers to stop
|
156
|
+
while !followers_to_stop.empty? do
|
157
|
+
relax(until_next_iteration / 2)
|
158
|
+
seize_leadership
|
159
|
+
end
|
160
|
+
exit 0 if restart_wanted?
|
151
161
|
relax until_next_iteration
|
152
162
|
end
|
153
163
|
|
154
|
-
def
|
164
|
+
def populate_followers_to_stop
|
165
|
+
@followers_to_stop.replace(find_workers)
|
166
|
+
end
|
167
|
+
|
168
|
+
def followers_to_stop
|
169
|
+
@followers_to_stop.select {|u| DRbObject.new(nil, u).alive? rescue DRb::DRbConnError && false}
|
155
170
|
end
|
156
171
|
|
157
172
|
def bucket_request(requestor_uri)
|
158
173
|
if leader?
|
159
174
|
log.debug "delivering bucket request"
|
160
|
-
next_bucket
|
175
|
+
bucket_spec = next_bucket(requestor_uri)
|
176
|
+
if !bucket_spec[0] && @followers_to_stop.include?(requestor_uri)
|
177
|
+
bucket_spec = [:stop, 0]
|
178
|
+
@followers_to_stop.delete(requestor_uri)
|
179
|
+
end
|
180
|
+
bucket_spec
|
161
181
|
else
|
162
182
|
log.debug "received request for bucket but am not leader - delivering :not_leader"
|
163
183
|
[:not_leader, 0]
|
@@ -191,8 +211,22 @@ module Politics
|
|
191
211
|
DRbObject.new(nil, leader_uri)
|
192
212
|
end
|
193
213
|
|
214
|
+
def find_workers
|
215
|
+
workers = []
|
216
|
+
browser = Net::DNS::MDNSSD.browse(mdns_type) do |reply|
|
217
|
+
workers << reply.name unless reply.name == uri
|
218
|
+
end
|
219
|
+
sleep 5
|
220
|
+
browser.stop
|
221
|
+
workers
|
222
|
+
end
|
223
|
+
|
194
224
|
private
|
195
225
|
|
226
|
+
def restart_wanted?
|
227
|
+
@memcache_client.get(restart_flag)
|
228
|
+
end
|
229
|
+
|
196
230
|
def bucket_process(bucket, sleep_time)
|
197
231
|
case bucket
|
198
232
|
when nil
|
@@ -204,6 +238,9 @@ module Politics
|
|
204
238
|
log.warn { "Recv'd NOT_LEADER from peer." }
|
205
239
|
relax 1
|
206
240
|
@leader_uri = nil
|
241
|
+
when :stop
|
242
|
+
log.info "Received STOP from leader … exiting."
|
243
|
+
exit 0
|
207
244
|
else
|
208
245
|
log.info { "processing #{bucket}" }
|
209
246
|
yield bucket
|
@@ -230,8 +267,15 @@ module Politics
|
|
230
267
|
"#{group_name}_token"
|
231
268
|
end
|
232
269
|
|
270
|
+
def restart_flag
|
271
|
+
"#{group_name}_restart"
|
272
|
+
end
|
273
|
+
|
233
274
|
def cleanup
|
234
|
-
|
275
|
+
if leader?
|
276
|
+
@memcache_client.delete(token)
|
277
|
+
@memcache_client.delete(restart_flag)
|
278
|
+
end
|
235
279
|
end
|
236
280
|
|
237
281
|
def pause_until_expiry(elapsed)
|
@@ -239,7 +283,8 @@ module Politics
|
|
239
283
|
if pause_time > 0
|
240
284
|
relax(pause_time)
|
241
285
|
else
|
242
|
-
raise ArgumentError, "Negative iteration time left.
|
286
|
+
raise ArgumentError, "Negative iteration time left. " +
|
287
|
+
"Assuming the worst and exiting… #{iteration_length}/#{elapsed}"
|
243
288
|
end
|
244
289
|
end
|
245
290
|
|
@@ -277,38 +322,26 @@ module Politics
|
|
277
322
|
Time.now - a
|
278
323
|
end
|
279
324
|
|
325
|
+
def mdns_type
|
326
|
+
"_#{group_name}._tcp"
|
327
|
+
end
|
328
|
+
|
280
329
|
def register_with_bonjour
|
281
330
|
server = DRb.start_service(nil, self)
|
282
331
|
@uri = DRb.uri
|
283
332
|
@port = URI.parse(DRb.uri).port
|
284
333
|
|
285
334
|
# Register our DRb server with Bonjour.
|
286
|
-
name =
|
287
|
-
type = "_#{group_name}._tcp"
|
335
|
+
name = @uri
|
288
336
|
domain = "local"
|
289
|
-
log.debug "register service #{name} of type #{
|
290
|
-
handle = Net::DNS::MDNSSD.register(name,
|
337
|
+
log.debug "register service #{name} of type #{mdns_type} within domain #{domain} at port #{@port}"
|
338
|
+
handle = Net::DNS::MDNSSD.register(name, mdns_type, domain, @port) do |reply|
|
291
339
|
log.debug "registered as #{reply.fullname}"
|
340
|
+
if reply.name != name
|
341
|
+
log.debug "Registered name #{reply.name} differs from requested name #{name} … exiting."
|
342
|
+
handle.stop
|
343
|
+
end
|
292
344
|
end
|
293
|
-
|
294
|
-
# ['INT', 'TERM'].each { |signal|
|
295
|
-
# trap(signal) do
|
296
|
-
# handle.stop
|
297
|
-
# server.stop_service
|
298
|
-
# end
|
299
|
-
# }
|
300
|
-
end
|
301
|
-
|
302
|
-
# http://coderrr.wordpress.com/2008/05/28/get-your-local-ip-address/
|
303
|
-
def local_ip
|
304
|
-
orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true # turn off reverse DNS resolution temporarily
|
305
|
-
|
306
|
-
UDPSocket.open do |s|
|
307
|
-
s.connect '64.233.187.99', 1
|
308
|
-
IPAddr.new(s.addr.last).to_i
|
309
|
-
end
|
310
|
-
ensure
|
311
|
-
Socket.do_not_reverse_lookup = orig
|
312
345
|
end
|
313
346
|
end
|
314
347
|
end
|
@@ -124,14 +124,22 @@ describe Worker do
|
|
124
124
|
before do
|
125
125
|
@worker.stub!(:leader?).and_return false
|
126
126
|
@worker.stub!(:leader_uri).and_return "the leader"
|
127
|
+
@worker.stub!(:leader).and_return(@leader = mock('leader'))
|
128
|
+
@leader.stub!(:bucket_request).and_return([1, 0])
|
127
129
|
end
|
128
130
|
|
129
131
|
it "should get the bucket to process from the leader at every iteration" do
|
130
|
-
@worker.should_receive(:leader).exactly(4).times.and_return
|
131
|
-
leader.should_receive(:bucket_request).with(@worker.uri).exactly(4).times.
|
132
|
+
@worker.should_receive(:leader).exactly(4).times.and_return @leader
|
133
|
+
@leader.should_receive(:bucket_request).with(@worker.uri).exactly(4).times.
|
132
134
|
and_return([1, 2])
|
133
135
|
@worker.start
|
134
136
|
end
|
137
|
+
|
138
|
+
it "should exit on :stop bucket" do
|
139
|
+
@leader.should_receive(:bucket_request).ordered.once.and_return([:stop, 0])
|
140
|
+
@worker.should_receive(:exit).with(0).ordered
|
141
|
+
@worker.start
|
142
|
+
end
|
135
143
|
end
|
136
144
|
end
|
137
145
|
end
|
@@ -146,6 +154,27 @@ describe Worker do
|
|
146
154
|
@worker.should_receive(:next_bucket).with("requestor").and_return "the bucket"
|
147
155
|
@worker.bucket_request("requestor").should == "the bucket"
|
148
156
|
end
|
157
|
+
|
158
|
+
describe "when no buckets are left" do
|
159
|
+
before do
|
160
|
+
@worker.stub!(:find_workers).and_return(%w(1 2 3))
|
161
|
+
@worker.populate_followers_to_stop
|
162
|
+
DRbObject.stub(:new).and_return(mock('o', :alive? => true))
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should deliver the :stop bucket if requestor is in followers_to_stop list" do
|
166
|
+
@worker.bucket_request("1").should == [:stop, 0]
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should not deliver the :stop bucket if requestor is not in followers_to_stop list" do
|
170
|
+
@worker.bucket_request("requestor")[0].should be_nil
|
171
|
+
end
|
172
|
+
|
173
|
+
it "should remove the requestor from the followers_to_stop list" do
|
174
|
+
@worker.bucket_request("2")
|
175
|
+
@worker.followers_to_stop.should == %w(1 3)
|
176
|
+
end
|
177
|
+
end
|
149
178
|
end
|
150
179
|
|
151
180
|
describe "as follower" do
|
@@ -159,12 +188,27 @@ describe Worker do
|
|
159
188
|
end
|
160
189
|
end
|
161
190
|
|
191
|
+
describe "when determining if restart is wanted" do
|
192
|
+
it "should return true if the restart flag is set in memcache" do
|
193
|
+
@@memcache_client.should_receive(:get).with('worker_restart').and_return true
|
194
|
+
@worker.should be_restart_wanted
|
195
|
+
end
|
196
|
+
|
197
|
+
it "should return false if the restart flag is not set in memcache" do
|
198
|
+
@@memcache_client.should_receive(:get).with('worker_restart').and_return false
|
199
|
+
@worker.should_not be_restart_wanted
|
200
|
+
@@memcache_client.should_receive(:get).with('worker_restart').and_return nil
|
201
|
+
@worker.should_not be_restart_wanted
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
162
205
|
describe "when performing leader duties" do
|
163
206
|
before do
|
164
207
|
@worker.stub!(:until_next_iteration).and_return 0
|
165
208
|
@worker.stub!(:leader?).and_return true
|
166
209
|
@worker.stub!(:dictatorship_length).and_return 666
|
167
210
|
@worker.stub!(:iteration_length).and_return 5
|
211
|
+
@worker.stub!(:find_workers).and_return []
|
168
212
|
end
|
169
213
|
|
170
214
|
it "should initialize buckets as dictator" do
|
@@ -195,6 +239,20 @@ describe Worker do
|
|
195
239
|
@worker.should_receive(:seize_leadership).at_least(4).times
|
196
240
|
@worker.perform_leader_duties
|
197
241
|
end
|
242
|
+
|
243
|
+
it "should seize the leadership periodically even if restart is wanted" do
|
244
|
+
@worker.stub!(:restart_wanted?).and_return true
|
245
|
+
@worker.stub!(:exit)
|
246
|
+
@worker.should_receive(:seize_leadership).at_least(4).times
|
247
|
+
@worker.perform_leader_duties
|
248
|
+
end
|
249
|
+
|
250
|
+
it "should not update buckets if restart is wanted" do
|
251
|
+
@worker.stub!(:restart_wanted?).and_return true
|
252
|
+
@worker.stub!(:exit)
|
253
|
+
@worker.should_not_receive(:update_buckets)
|
254
|
+
@worker.perform_leader_duties
|
255
|
+
end
|
198
256
|
end
|
199
257
|
|
200
258
|
describe "if there are no more buckets" do
|
@@ -202,17 +260,55 @@ describe Worker do
|
|
202
260
|
@worker.stub!(:buckets).and_return([])
|
203
261
|
end
|
204
262
|
|
205
|
-
it "should
|
206
|
-
@worker.stub!(:
|
207
|
-
@worker.
|
263
|
+
it "should populate the followers_to_stop list before evaluating it if restart is wanted" do
|
264
|
+
@worker.stub!(:restart_wanted?).and_return true
|
265
|
+
@worker.stub!(:exit)
|
266
|
+
@worker.should_receive(:populate_followers_to_stop).ordered.once
|
267
|
+
@worker.should_receive(:followers_to_stop).ordered.and_return []
|
208
268
|
@worker.perform_leader_duties
|
209
269
|
end
|
210
270
|
|
211
|
-
it "should
|
212
|
-
@worker.
|
213
|
-
@worker.
|
271
|
+
it "should not populate the followers_to_stop list if restart is not wanted" do
|
272
|
+
@worker.stub!(:restart_wanted?).and_return false
|
273
|
+
@worker.should_not_receive(:populate_followers_to_stop)
|
214
274
|
@worker.perform_leader_duties
|
215
275
|
end
|
276
|
+
|
277
|
+
describe "as long as there are followers to stop" do
|
278
|
+
before do
|
279
|
+
@worker.stub!(:followers_to_stop).and_return([1], [2], [3], [4], [])
|
280
|
+
@worker.stub!(:relax)
|
281
|
+
end
|
282
|
+
|
283
|
+
it "should relax half of the time to the next iteration" do
|
284
|
+
@worker.stub!(:until_next_iteration).and_return(6)
|
285
|
+
@worker.should_receive(:relax).with(3).exactly(4).times
|
286
|
+
@worker.perform_leader_duties
|
287
|
+
end
|
288
|
+
|
289
|
+
it "should seize the leadership periodically" do
|
290
|
+
@worker.should_receive(:seize_leadership).at_least(4).times
|
291
|
+
@worker.perform_leader_duties
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
describe "if there are no more followers to stop" do
|
296
|
+
before do
|
297
|
+
@worker.stub!(:followers_to_stop).and_return([])
|
298
|
+
end
|
299
|
+
|
300
|
+
it "should relax until next iteration" do
|
301
|
+
@worker.stub!(:until_next_iteration).and_return(6)
|
302
|
+
@worker.should_receive(:relax).with(6).once
|
303
|
+
@worker.perform_leader_duties
|
304
|
+
end
|
305
|
+
|
306
|
+
it "should exit if restart is wanted" do
|
307
|
+
@worker.stub!(:restart_wanted?).and_return true
|
308
|
+
@worker.should_receive(:exit).with(0)
|
309
|
+
@worker.perform_leader_duties
|
310
|
+
end
|
311
|
+
end
|
216
312
|
end
|
217
313
|
end
|
218
314
|
|
@@ -324,16 +420,22 @@ describe Worker do
|
|
324
420
|
|
325
421
|
describe "when cleaning up" do
|
326
422
|
before do
|
327
|
-
@worker.stub!(:
|
423
|
+
@worker.stub!(:group_name).and_return('the group')
|
328
424
|
end
|
329
425
|
|
330
426
|
describe "as leader" do
|
331
427
|
before do
|
332
428
|
@worker.stub!(:leader?).and_return true
|
429
|
+
@@memcache_client.stub!(:delete)
|
430
|
+
end
|
431
|
+
|
432
|
+
it "should remove the leadership token from memcache" do
|
433
|
+
@@memcache_client.should_receive(:delete).with('the group_token')
|
434
|
+
@worker.send(:cleanup)
|
333
435
|
end
|
334
436
|
|
335
|
-
it "should remove the
|
336
|
-
@@memcache_client.should_receive(:delete).with('
|
437
|
+
it "should remove the restart wanted flag from memcache" do
|
438
|
+
@@memcache_client.should_receive(:delete).with('the group_restart')
|
337
439
|
@worker.send(:cleanup)
|
338
440
|
end
|
339
441
|
end
|
@@ -343,10 +445,75 @@ describe Worker do
|
|
343
445
|
@worker.stub!(:leader?).and_return false
|
344
446
|
end
|
345
447
|
|
346
|
-
it "should not remove
|
448
|
+
it "should not remove anything from memcache" do
|
347
449
|
@@memcache_client.should_not_receive(:delete)
|
348
450
|
@worker.send(:cleanup)
|
349
451
|
end
|
350
452
|
end
|
351
453
|
end
|
454
|
+
|
455
|
+
describe "when finding workers" do
|
456
|
+
before do
|
457
|
+
Net::DNS::MDNSSD.stub(:browse).
|
458
|
+
and_yield(mock('response1', :name => 'w1')).
|
459
|
+
and_yield(mock('response2', :name => 'w2')).
|
460
|
+
and_yield(mock('response3', :name => 'w3')).
|
461
|
+
and_yield(mock('response4', :name => 'w4')).
|
462
|
+
and_return(@browser = mock('browser', :stop => nil))
|
463
|
+
@worker.stub!(:sleep)
|
464
|
+
end
|
465
|
+
|
466
|
+
it "should browse mdns group and return workers found" do
|
467
|
+
@worker.find_workers.should == %w(w1 w2 w3 w4)
|
468
|
+
end
|
469
|
+
|
470
|
+
it "should not add itself to the result list" do
|
471
|
+
@worker.stub!(:uri).and_return('w3')
|
472
|
+
@worker.find_workers.should_not include('w3')
|
473
|
+
end
|
474
|
+
|
475
|
+
it "should stop browser thread after five seconds" do
|
476
|
+
@worker.should_receive(:sleep).with(5).ordered
|
477
|
+
@browser.should_receive(:stop)
|
478
|
+
@worker.find_workers
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
describe "when populating followers_to_stop" do
|
483
|
+
before do
|
484
|
+
@worker.stub(:find_workers).and_return(%w(a b c))
|
485
|
+
DRbObject.stub(:new).and_return(mock('o', :alive? => true))
|
486
|
+
end
|
487
|
+
|
488
|
+
it "should add all visible workers" do
|
489
|
+
@worker.populate_followers_to_stop
|
490
|
+
@worker.followers_to_stop.should == %w(a b c)
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
describe "when delivering followers_to_stop" do
|
495
|
+
before do
|
496
|
+
@worker.stub(:find_workers).and_return(%w(a b c))
|
497
|
+
@worker.populate_followers_to_stop
|
498
|
+
DRbObject.stub(:new).and_return(mock('o', :alive? => true))
|
499
|
+
end
|
500
|
+
|
501
|
+
it "should return the actual followers_to_stop" do
|
502
|
+
@worker.followers_to_stop.should == %w(a b c)
|
503
|
+
end
|
504
|
+
|
505
|
+
it "should not deliver entries that are not reachable at the moment" do
|
506
|
+
DRbObject.stub!(:new).with(nil, 'a').and_return(mock('o', :alive? => false))
|
507
|
+
DRbObject.stub!(:new).with(nil, 'b').and_return(x = mock('o'))
|
508
|
+
x.stub(:alive?).and_raise DRb::DRbConnError.new('nix da')
|
509
|
+
@worker.followers_to_stop.should == %w(c)
|
510
|
+
end
|
511
|
+
|
512
|
+
it "should not remove unreachable entries from the list - maybe they reappear" do
|
513
|
+
DRbObject.stub!(:new).with(nil, 'a').and_return(mock('o', :alive? => false))
|
514
|
+
@worker.followers_to_stop.should == %w(b c)
|
515
|
+
DRbObject.stub!(:new).with(nil, 'a').and_return(mock('o', :alive? => true))
|
516
|
+
@worker.followers_to_stop.should == %w(a b c)
|
517
|
+
end
|
518
|
+
end
|
352
519
|
end
|