zk 1.4.2 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.dotfiles/ctags_paths +1 -0
  2. data/.dotfiles/rspec-logging +2 -2
  3. data/.gitignore +1 -0
  4. data/Gemfile +9 -3
  5. data/Guardfile +36 -0
  6. data/README.markdown +21 -18
  7. data/RELEASES.markdown +10 -0
  8. data/Rakefile +1 -1
  9. data/lib/zk.rb +28 -21
  10. data/lib/zk/client/threaded.rb +107 -17
  11. data/lib/zk/client/unixisms.rb +1 -41
  12. data/lib/zk/core_ext.rb +28 -0
  13. data/lib/zk/election.rb +14 -3
  14. data/lib/zk/event_handler.rb +36 -37
  15. data/lib/zk/event_handler_subscription/actor.rb +37 -2
  16. data/lib/zk/event_handler_subscription/base.rb +9 -0
  17. data/lib/zk/exceptions.rb +5 -0
  18. data/lib/zk/fork_hook.rb +112 -0
  19. data/lib/zk/install_fork_hooks.rb +37 -0
  20. data/lib/zk/locker/exclusive_locker.rb +14 -10
  21. data/lib/zk/locker/locker_base.rb +43 -26
  22. data/lib/zk/locker/shared_locker.rb +9 -5
  23. data/lib/zk/logging.rb +29 -7
  24. data/lib/zk/node_deletion_watcher.rb +167 -0
  25. data/lib/zk/pool.rb +14 -4
  26. data/lib/zk/subscription.rb +15 -34
  27. data/lib/zk/threaded_callback.rb +113 -29
  28. data/lib/zk/threadpool.rb +136 -40
  29. data/lib/zk/version.rb +1 -1
  30. data/spec/logging_progress_bar_formatter.rb +12 -0
  31. data/spec/shared/client_contexts.rb +13 -1
  32. data/spec/shared/client_examples.rb +3 -1
  33. data/spec/spec_helper.rb +28 -3
  34. data/spec/support/client_forker.rb +49 -8
  35. data/spec/support/latch.rb +1 -19
  36. data/spec/support/logging.rb +26 -10
  37. data/spec/support/wait_watchers.rb +2 -2
  38. data/spec/zk/00_forked_client_integration_spec.rb +1 -1
  39. data/spec/zk/client_spec.rb +11 -2
  40. data/spec/zk/election_spec.rb +21 -7
  41. data/spec/zk/locker_spec.rb +42 -22
  42. data/spec/zk/node_deletion_watcher_spec.rb +69 -0
  43. data/spec/zk/pool_spec.rb +32 -18
  44. data/spec/zk/threaded_callback_spec.rb +78 -0
  45. data/spec/zk/threadpool_spec.rb +52 -0
  46. data/spec/zk/watch_spec.rb +4 -0
  47. data/zk.gemspec +2 -1
  48. metadata +36 -10
  49. data/spec/support/logging_progress_bar_formatter.rb +0 -14
@@ -12,6 +12,7 @@ describe 'ZK::Client#locker' do
12
12
  @zk3 = ZK.new("localhost:#{ZK.test_port}", connection_opts)
13
13
  @connections = [@zk, @zk2, @zk3]
14
14
  wait_until { @connections.all? { |c| c.connected? } }
15
+ logger.debug { "all connections connected" }
15
16
  @path_to_lock = "/lock_tester"
16
17
  end
17
18
 
@@ -19,7 +20,7 @@ describe 'ZK::Client#locker' do
19
20
  @zk.close!
20
21
  @zk2.close!
21
22
  @zk3.close!
22
- wait_until{ @connections.all? { |c| !c.connected? } }
23
+ wait_until { @connections.all? { |c| c.closed? } }
23
24
  end
24
25
 
25
26
  it "should be able to acquire the lock if no one else is locking it" do
@@ -194,14 +195,16 @@ shared_examples_for 'SharedLocker' do
194
195
  ary << :locked
195
196
  end
196
197
 
197
- ary.should be_empty
198
+ shared_locker.wait_until_blocked(5)
199
+ shared_locker.should be_waiting
198
200
  shared_locker.should_not be_locked
201
+ ary.should be_empty
199
202
 
200
203
  zk.delete(@write_lock_path)
201
204
 
202
- th.join(2)
205
+ th.join(2).should == th
203
206
 
204
- wait_until(2) { !ary.empty? }
207
+ ary.should_not be_empty
205
208
  ary.length.should == 1
206
209
 
207
210
  shared_locker.should be_locked
@@ -235,24 +238,32 @@ shared_examples_for 'ExclusiveLocker' do
235
238
 
236
239
  it %[should raise LockAssertionFailedError if there is an exclusive lock with a number lower than ours] do
237
240
  # this should *really* never happen
238
- ex_locker.lock.should be_true
239
- exl_path = ex_locker.lock_path
241
+
242
+ rlp = ex_locker.root_lock_path
243
+
244
+ zk.mkdir_p(rlp)
245
+
246
+ bogus_path = zk.create("#{rlp}/#{ZK::Locker::EXCLUSIVE_LOCK_PREFIX}", :sequential => true, :ephemeral => true)
240
247
 
241
248
  th = Thread.new do
242
249
  ex_locker2.lock(true)
243
250
  end
244
251
 
245
- wait_until { th.status == 'sleep' }
252
+ logger.debug { "calling wait_until_blocked" }
253
+ ex_locker2.wait_until_blocked(2)
254
+ ex_locker2.should be_waiting
246
255
 
247
- ex_locker.unlock.should be_true
248
- ex_locker.should_not be_locked
249
- zk.exists?(exl_path).should be_false
256
+ wait_until { zk.exists?(ex_locker2.lock_path) }
257
+
258
+ zk.exists?(ex_locker2.lock_path).should be_true
259
+
260
+ zk.delete(bogus_path)
250
261
 
251
262
  th.join(5).should == th
252
263
 
253
- ex_locker2.lock_path.should_not == exl_path
264
+ ex_locker2.lock_path.should_not == bogus_path
254
265
 
255
- zk.create(exl_path, :ephemeral => true)
266
+ zk.create(bogus_path, :ephemeral => true)
256
267
 
257
268
  lambda { ex_locker2.assert! }.should raise_error(ZK::Exceptions::LockAssertionFailedError)
258
269
  end
@@ -299,11 +310,11 @@ shared_examples_for 'ExclusiveLocker' do
299
310
  describe 'blocking' do
300
311
  before do
301
312
  zk.mkdir_p(root_lock_path)
302
- @read_lock_path = zk.create('/_zklocking/shlock/read', '', :mode => :ephemeral_sequential)
303
313
  end
304
314
 
305
315
  it %[should block waiting for the lock] do
306
316
  ary = []
317
+ read_lock_path = zk.create('/_zklocking/shlock/read', '', :mode => :ephemeral_sequential)
307
318
 
308
319
  ex_locker.lock.should be_false
309
320
 
@@ -312,20 +323,20 @@ shared_examples_for 'ExclusiveLocker' do
312
323
  ary << :locked
313
324
  end
314
325
 
315
- th.run
326
+ ex_locker.wait_until_blocked(5)
316
327
 
317
328
  ary.should be_empty
318
329
  ex_locker.should_not be_locked
319
330
 
320
- zk.delete(@read_lock_path)
331
+ zk.delete(read_lock_path)
321
332
 
322
- th.join(2)
333
+ th.join(2).should == th
323
334
 
324
335
  ary.length.should == 1
325
336
  ex_locker.should be_locked
326
337
  end
327
- end
328
- end
338
+ end # blocking
339
+ end # lock
329
340
  end # ExclusiveLocker
330
341
 
331
342
  shared_examples_for 'shared-exclusive interaction' do
@@ -428,8 +439,13 @@ shared_examples_for 'shared-exclusive interaction' do
428
439
  end
429
440
  end
430
441
 
431
- ex_th.join_until { @ex_lock.waiting? }
442
+ logger.debug { "about to wait for @ex_lock to be blocked" }
443
+
444
+ @ex_lock.wait_until_blocked(5)
432
445
  @ex_lock.should be_waiting
446
+
447
+ logger.debug { "@ex_lock is waiting" }
448
+
433
449
  @ex_lock.should_not be_locked
434
450
 
435
451
  # this is the important one, does the second shared lock get blocked by
@@ -446,15 +462,19 @@ shared_examples_for 'shared-exclusive interaction' do
446
462
  end
447
463
  end
448
464
 
449
- sh2_th.join_until { @sh_lock2.waiting? }
465
+ logger.debug { "about to wait for @sh_lock2 to be blocked" }
466
+
467
+ @sh_lock2.wait_until_blocked(5)
450
468
  @sh_lock2.should be_waiting
451
469
 
470
+ logger.debug { "@sh_lock2 is waiting" }
471
+
452
472
  @sh_lock.unlock.should be_true
453
473
 
454
- ex_th.join_until { ex_th[:got_lock] }
474
+ ex_th.join(5).should == ex_th
455
475
  ex_th[:got_lock].should be_true
456
476
 
457
- sh2_th.join_until { sh2_th[:got_lock] }
477
+ sh2_th.join(5).should == sh2_th
458
478
  sh2_th[:got_lock].should be_true
459
479
 
460
480
  @array.length.should == 2
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ describe ZK::NodeDeletionWatcher do
4
+ include_context 'threaded client connection'
5
+
6
+ before do
7
+ @path = "#{@base_path}/node_deleteion_watcher_victim"
8
+
9
+ @n = ZK::NodeDeletionWatcher.new(@zk, @path)
10
+ end
11
+
12
+ describe %[when the node already exists] do
13
+ it %[blocks the caller until the node is deleted] do
14
+ @zk.mkdir_p(@path)
15
+
16
+ th = Thread.new { @n.block_until_deleted }
17
+
18
+ @n.wait_until_blocked(5).should be_true
19
+
20
+ logger.debug { "wait_until_blocked returned" }
21
+
22
+ @n.should be_blocked
23
+
24
+ @zk.rm_rf(@path)
25
+
26
+ th.join(5).should == th
27
+ @n.should_not be_blocked
28
+ @n.should be_done
29
+ end
30
+
31
+ it %[should wake up if interrupt! is called] do
32
+ @zk.mkdir_p(@path)
33
+
34
+ @exc = nil
35
+
36
+ th = Thread.new do
37
+ begin
38
+ @n.block_until_deleted
39
+ rescue Exception => e
40
+ @exc = e
41
+ end
42
+ end
43
+
44
+ @n.wait_until_blocked(5)
45
+
46
+ @n.should be_blocked
47
+
48
+ @n.interrupt!
49
+ th.join(5).should == th
50
+
51
+ @exc.should be_kind_of(ZK::Exceptions::WakeUpException)
52
+ end
53
+ end
54
+
55
+ describe %[when the node doesn't exist] do
56
+ it %[should not block the caller and be done] do
57
+ @zk.exists?(@path).should be_false
58
+
59
+ th = Thread.new { @n.block_until_deleted }
60
+
61
+ @n.wait_until_blocked
62
+ @n.should_not be_blocked
63
+ th.join(5).should == th
64
+ @n.should be_done
65
+ end
66
+ end
67
+ end
68
+
69
+
@@ -55,17 +55,27 @@ describe ZK::Pool do
55
55
  it %[should shutdown gracefully] do
56
56
  release_q = Queue.new
57
57
 
58
+ latch = Latch.new
59
+
58
60
  @about_to_block = false
59
61
 
62
+ @mutex = Mutex.new
63
+ @cond = ConditionVariable.new
64
+ @cnx = nil
65
+
60
66
  open_th = Thread.new do
61
- @cnx = @connection_pool.checkout(true)
62
- @about_to_block = true
63
- # wait for signal to release our connection
64
- release_q.pop
67
+ @mutex.synchronize do
68
+ @cnx = @connection_pool.checkout(true)
69
+ @cond.broadcast
70
+ end
71
+ latch.await(30) # don't time out
72
+ end
73
+
74
+ @mutex.synchronize do
75
+ @cond.wait(@mutex) while @cnx.nil?
65
76
  end
66
77
 
67
- wait_until(5) { @about_to_block }
68
- @about_to_block.should be_true
78
+ @cnx.should_not be_nil
69
79
 
70
80
  closing_th = Thread.new do
71
81
  @connection_pool.close_all!
@@ -75,14 +85,13 @@ describe ZK::Pool do
75
85
  @connection_pool.should be_closing
76
86
  logger.debug { "connection pool is closing" }
77
87
 
78
- lambda { @connection_pool.with_connection { |c| } }.should raise_error(ZK::Exceptions::PoolIsShuttingDownException)
88
+ lambda { @connection_pool.with_connection { |c| c } }.should raise_error(ZK::Exceptions::PoolIsShuttingDownException)
79
89
 
80
- release_q << :ok_let_go
90
+ latch.release
81
91
 
82
92
  open_th.join(5).should == open_th
83
93
 
84
- wait_until(5) { @connection_pool.closed? }
85
- # $stderr.puts "@connection_pool.pool_state: #{@connection_pool.pool_state.inspect}"
94
+ @connection_pool.wait_until_closed
86
95
 
87
96
  @connection_pool.should be_closed
88
97
 
@@ -94,7 +103,7 @@ describe ZK::Pool do
94
103
  end
95
104
 
96
105
  describe :force_close! do
97
- it %[should raise PoolIsShuttingDownException in a thread blocked waiting for a connection] do
106
+ it %[should raise PoolIsShuttingDownException in a thread blocked waiting for a connection], :mri_187 => :broken do
98
107
  @cnx = []
99
108
 
100
109
  until @connection_pool.available_size <= 0
@@ -103,8 +112,15 @@ describe ZK::Pool do
103
112
 
104
113
  @cnx.length.should_not be_zero
105
114
 
115
+ # this exc nonsense is because 1.8.7's scheduler is broken
116
+ @exc = nil
117
+
106
118
  th = Thread.new do
107
- @connection_pool.checkout(true)
119
+ begin
120
+ @connection_pool.checkout(true)
121
+ rescue
122
+ @exc = $!
123
+ end
108
124
  end
109
125
 
110
126
  # th.join_until { @connection_pool.count_waiters > 0 }
@@ -112,7 +128,10 @@ describe ZK::Pool do
112
128
 
113
129
  @connection_pool.force_close!
114
130
 
115
- lambda { th.join(5) }.should raise_error(ZK::Exceptions::PoolIsShuttingDownException)
131
+ @connection_pool.wait_until_closed
132
+
133
+ th.join(5).should == th
134
+ @exc.should be_kind_of(ZK::Exceptions::PoolIsShuttingDownException)
116
135
  end
117
136
  end
118
137
 
@@ -129,21 +148,16 @@ describe ZK::Pool do
129
148
  end
130
149
 
131
150
  @connection_pool.with_connection do |zk|
132
- # $stderr.puts "registering callback"
133
151
  zk.watcher.register(@path) do |event|
134
- # $stderr.puts "callback fired! event: #{event.inspect}"
135
152
 
136
153
  @callback_called = true
137
154
  event.path.should == @path
138
- # $stderr.puts "signaling other waiters"
139
155
  end
140
156
 
141
- # $stderr.puts "setting up watcher"
142
157
  zk.exists?(@path, :watch => true).should be_false
143
158
  end
144
159
 
145
160
  @connection_pool.with_connection do |zk|
146
- # $stderr.puts "creating path"
147
161
  zk.create(@path, "", :mode => :ephemeral).should == @path
148
162
  end
149
163
 
@@ -0,0 +1,78 @@
1
+ require 'spec_helper'
2
+
3
+ describe ZK::ThreadedCallback do
4
+ before do
5
+ @called = []
6
+ @called.extend(MonitorMixin)
7
+ @cond = @called.new_cond
8
+
9
+ @callback = proc do |*a|
10
+ @called.synchronize do
11
+ @called << a
12
+ @cond.broadcast
13
+ end
14
+ end
15
+
16
+ @tcb = ZK::ThreadedCallback.new(@callback)
17
+ end
18
+
19
+ after do
20
+ @tcb.shutdown.should be_true
21
+ end
22
+
23
+ it %[should have started the thread] do
24
+ @tcb.should be_alive
25
+ end
26
+
27
+ describe %[pausing for fork] do
28
+ describe %[when running] do
29
+ before do
30
+ @tcb.pause_before_fork_in_parent
31
+ end
32
+
33
+ it %[should stop the thread] do
34
+ @tcb.should_not be_alive
35
+ end
36
+
37
+ it %[should allow calls] do
38
+ lambda { @tcb.call(:a) }.should_not raise_error
39
+ end
40
+ end
41
+
42
+ describe %[when not running] do
43
+ it %[should barf with InvalidStateError] do
44
+ @tcb.shutdown.should be_true
45
+ @tcb.should_not be_alive
46
+ lambda { @tcb.pause_before_fork_in_parent }.should raise_error(ZK::Exceptions::InvalidStateError)
47
+ end
48
+ end
49
+ end
50
+
51
+ describe %[resuming] do
52
+ describe %[after pause] do
53
+ before do
54
+ @tcb.pause_before_fork_in_parent
55
+ @tcb.should_not be_alive
56
+ end
57
+
58
+ it %[should deliver any calls on resume] do
59
+ @tcb.call(:a)
60
+ @tcb.call(:b)
61
+
62
+ @tcb.resume_after_fork_in_parent
63
+
64
+ start = Time.now
65
+
66
+ wait_until { @called.length >= 2 }
67
+
68
+ @called.length.should >= 2
69
+ end
70
+ end
71
+
72
+ describe %[if not paused] do
73
+ it %[should barf with InvalidStateError] do
74
+ lambda { @tcb.resume_after_fork_in_parent }.should raise_error(ZK::Exceptions::InvalidStateError)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -98,5 +98,57 @@ describe ZK::Threadpool do
98
98
  @a.first.should be_true
99
99
  end
100
100
  end
101
+
102
+ describe :pause_before_fork_in_parent do
103
+ it %[should stop all running threads] do
104
+ @threadpool.should be_running
105
+ @threadpool.should be_alive
106
+ @threadpool.pause_before_fork_in_parent
107
+
108
+ @threadpool.should_not be_alive
109
+ end
110
+
111
+ it %[should raise InvalidStateError if already paused] do
112
+ @threadpool.pause_before_fork_in_parent
113
+ lambda { @threadpool.pause_before_fork_in_parent }.should raise_error(ZK::Exceptions::InvalidStateError)
114
+ end
115
+ end
116
+
117
+ describe :resume_after_fork_in_parent do
118
+ before do
119
+ @threadpool.pause_before_fork_in_parent
120
+ end
121
+
122
+ it %[should start all threads running again] do
123
+ @threadpool.resume_after_fork_in_parent
124
+ @threadpool.should be_alive
125
+ end
126
+
127
+ it %[should raise InvalidStateError if not in paused state] do
128
+ @threadpool.shutdown
129
+ lambda { @threadpool.resume_after_fork_in_parent }.should raise_error(ZK::Exceptions::InvalidStateError)
130
+ end
131
+
132
+ it %[should run callbacks deferred while paused] do
133
+ calls = []
134
+
135
+ num = 5
136
+
137
+ latch = Latch.new(num)
138
+
139
+ num.times do |n|
140
+ @threadpool.defer do
141
+ calls << n
142
+ latch.release
143
+ end
144
+ end
145
+
146
+ @threadpool.resume_after_fork_in_parent
147
+
148
+ latch.await(2)
149
+
150
+ calls.should_not be_empty
151
+ end
152
+ end
101
153
  end
102
154