zk 1.4.2 → 1.5.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.
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