zk 0.6.4

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.
@@ -0,0 +1,55 @@
1
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
2
+
3
+ describe ZK::MessageQueue do
4
+
5
+ before(:each) do
6
+ @zk = ZK.new("localhost:#{ZK_TEST_PORT}", :watcher => :default)
7
+ @zk2 = ZK.new("localhost:#{ZK_TEST_PORT}", :watcher => :default)
8
+ wait_until{ @zk.connected? && @zk2.connected? }
9
+ @queue_name = "_specQueue"
10
+ @consume_queue = @zk.queue(@queue_name)
11
+ @publish_queue = @zk2.queue(@queue_name)
12
+ end
13
+
14
+ after(:each) do
15
+ @consume_queue.destroy!
16
+ @zk.close!
17
+ @zk2.close!
18
+ wait_until{ !@zk.connected? && !@zk2.connected? }
19
+ end
20
+
21
+ it "should be able to receive a published message" do
22
+ message_received = false
23
+ @consume_queue.subscribe do |title, data|
24
+ data.should == 'mydata'
25
+ message_received = true
26
+ end
27
+ @publish_queue.publish("mydata")
28
+ wait_until {message_received }
29
+ message_received.should be_true
30
+ end
31
+
32
+ it "should be able to receive a custom message title" do
33
+ message_title = false
34
+ @consume_queue.subscribe do |title, data|
35
+ title.should == 'title'
36
+ message_title = true
37
+ end
38
+ @publish_queue.publish("data", "title")
39
+ wait_until { message_title }
40
+ message_title.should be_true
41
+ end
42
+
43
+ it "should work even after processing a message from before" do
44
+ @publish_queue.publish("data1", "title")
45
+ message_times = 0
46
+ @consume_queue.subscribe do |title, data|
47
+ title.should == "title"
48
+ message_times += 1
49
+ end
50
+
51
+ @publish_queue.publish("data2", "title")
52
+ wait_until { message_times == 2 }
53
+ message_times.should == 2
54
+ end
55
+ end
@@ -0,0 +1,330 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ require 'tracer'
4
+
5
+ describe ZK::Mongoid::Locking do
6
+ before do
7
+ ZK::Mongoid::Locking.zk_lock_pool = ZK.new_pool('localhost:2181', :min_clients => 1, :max_clients => 5)
8
+
9
+ @doc = BogusMongoid.new
10
+ @other_doc = BogusMongoid.new
11
+ end
12
+
13
+ after do
14
+ th = Thread.new do
15
+ ZK::Mongoid::Locking.zk_lock_pool.close_all!
16
+ end
17
+
18
+ unless th.join(5) == th
19
+ logger.warn { "Forcing pool closed!" }
20
+ ZK::Mongoid::Locking.zk_lock_pool.force_close!
21
+ th.join(5).should == th
22
+ end
23
+
24
+ ZK::Mongoid::Locking.zk_lock_pool = nil
25
+ end
26
+
27
+ describe :with_shared_lock do
28
+ it %[should grab a shared lock] do
29
+ @lock_state = nil
30
+
31
+ th = Thread.new do
32
+ @doc.with_shared_lock do
33
+ @lock_state = @doc.locked_for_share?
34
+ end
35
+ end
36
+
37
+ th.join_until { !@lock_state.nil? }
38
+ @lock_state.should_not be_nil
39
+ @lock_state.should be_true
40
+ end
41
+
42
+ it %[should allow another thread to enter the shared lock] do
43
+ @counter = 0
44
+ @queue = Queue.new
45
+
46
+ begin
47
+ @th1 = Thread.new do
48
+ @doc.with_shared_lock do
49
+ @counter += 1
50
+ @queue.pop
51
+ end
52
+ end
53
+
54
+ @th1.join_until { @counter > 0 }
55
+ @counter.should > 0
56
+
57
+ @th1.join_until { @queue.num_waiting > 0 }
58
+ @queue.num_waiting.should > 0
59
+
60
+ @th2 = Thread.new do
61
+ @other_doc.with_shared_lock do
62
+ @counter += 1
63
+ end
64
+ end
65
+
66
+ @th2.join_until { @counter == 2 }
67
+ @counter.should == 2
68
+
69
+ @th2.join(2).should == @th2
70
+ ensure
71
+ @queue << :unlock
72
+
73
+ unless @th1.join(2)
74
+ $stderr.puts "UH OH! @th1 IS HUNG!!"
75
+ end
76
+ end
77
+ end
78
+
79
+ it %[should block an exclusive lock from entering] do
80
+ begin
81
+ q1 = Queue.new
82
+ q2 = Queue.new
83
+
84
+ @got_exclusive_lock = nil
85
+
86
+ @th1 = Thread.new do
87
+ @doc.with_shared_lock do
88
+ q1 << :have_shared_lock
89
+ q2.pop
90
+ logger.debug { "@th1 releasing shared lock" }
91
+ end
92
+ end
93
+
94
+ @th2 = Thread.new do
95
+ q1.pop
96
+ logger.debug { "@th1 has the shared lock" }
97
+
98
+ @other_doc.lock_for_update do
99
+ logger.debug { "@th2 got an exclusive lock" }
100
+ @got_exclusive_lock = true
101
+ end
102
+ end
103
+
104
+ @th1.join_until { q2.num_waiting >= 1 }
105
+ q2.num_waiting.should >= 1
106
+
107
+ @th2.join_until { q1.size == 0 }
108
+ q1.size.should be_zero
109
+
110
+ @got_exclusive_lock.should_not be_true
111
+
112
+ q2.enq(:release)
113
+
114
+ @th1.join_until { q2.size == 0 }
115
+ q2.size.should be_zero
116
+
117
+ @th2.join_until(5) { @got_exclusive_lock }
118
+ @got_exclusive_lock.should be_true
119
+
120
+ rescue Exception => e
121
+ $stderr.puts e.to_std_format
122
+ raise e
123
+ ensure
124
+ q2 << :release
125
+
126
+ unless @th1.join(2)
127
+ $stderr.puts "UH OH! @th1 IS HUNG!!"
128
+ end
129
+
130
+ unless @th2.join(2)
131
+ $stderr.puts "UH OH! @th2 IS HUNG!!"
132
+ end
133
+ end
134
+ end
135
+ end
136
+
137
+ describe :lock_for_update do
138
+ it %[should be locked_for_update? inside the block] do
139
+ @lock_state = nil
140
+
141
+ th = Thread.new do
142
+ @doc.lock_for_update do
143
+ @lock_state = @doc.locked_for_update?
144
+ end
145
+ end
146
+
147
+ th.join_until { !@lock_state.nil? }
148
+ @lock_state.should_not be_nil
149
+ @lock_state.should be_true
150
+ end
151
+
152
+ it %[should allow the same thread to re-enter the lock] do
153
+ @counter = 0
154
+
155
+ th = Thread.new do
156
+ @doc.lock_for_update do
157
+ @counter += 1
158
+ logger.debug { "we are locked for update, trying to lock again" }
159
+
160
+ @doc.lock_for_update do
161
+ logger.debug { "locked again" }
162
+ @counter += 1
163
+ end
164
+ end
165
+ end
166
+
167
+ th.join_until { @counter >= 2 }
168
+ @counter.should == 2
169
+ end
170
+
171
+ it %[should block another thread from entering the lock] do
172
+ @counter = 0
173
+ queue = Queue.new
174
+ @other_doc_got_lock = false
175
+
176
+ th1 = Thread.new do
177
+ @doc.lock_for_update do
178
+ @counter += 1
179
+ queue.pop
180
+ end
181
+ end
182
+
183
+ th1.join_until { @counter == 1 }
184
+ @counter.should == 1
185
+
186
+ th1.zk_mongoid_lock_registry[:exclusive].should include(@doc.zk_lock_name)
187
+
188
+ th2 = Thread.new do
189
+ @other_doc.lock_for_update do
190
+ @other_doc_got_lock = true
191
+ @counter += 1
192
+ end
193
+ end
194
+
195
+ th2.join(0.1)
196
+
197
+ # this is not a deterministic check of whether or not th2 ran and did not
198
+ # get the lock but probably close enough
199
+
200
+ @counter.should == 1
201
+ @other_doc_got_lock.should == false
202
+ th2.zk_mongoid_lock_registry[:exclusive].should_not include(@other_doc.zk_lock_name)
203
+
204
+ queue << :release_lock
205
+ th1.join(5).should == th1
206
+
207
+ th2.join_until { @counter == 2 }
208
+ @counter.should == 2
209
+ @other_doc_got_lock.should be_true
210
+ end
211
+
212
+ describe :with_name do
213
+ before do
214
+ @queue = Queue.new
215
+ end
216
+
217
+ after do
218
+ if @queue.num_waiting > 0
219
+ @queue << :bogus
220
+ @th1.join(5).should == @th1
221
+ end
222
+ end
223
+
224
+ it %[should block another thread using the same name] do
225
+ @counter = 0
226
+ @queue = Queue.new
227
+ @other_doc_got_lock = false
228
+ @name = 'peanuts'
229
+
230
+ @th1 = Thread.new do
231
+ @doc.lock_for_update(@name) do
232
+ @counter += 1
233
+ @queue.pop
234
+ end
235
+ end
236
+
237
+ @th1.join_until { @counter == 1 }
238
+ @counter.should == 1
239
+
240
+ @th1.zk_mongoid_lock_registry[:exclusive].should include(@doc.zk_lock_name(@name))
241
+
242
+ @th2 = Thread.new do
243
+ @other_doc.lock_for_update(@name) do
244
+ @other_doc_got_lock = true
245
+ @counter += 1
246
+ end
247
+ end
248
+
249
+ @th2.join(0.1)
250
+
251
+ # this is not a deterministic check of whether or not @th2 ran and did not
252
+ # get the lock but probably close enough
253
+
254
+ @counter.should == 1
255
+ @other_doc_got_lock.should == false
256
+
257
+ @queue << :release_lock
258
+ @th1.join(5).should == @th1
259
+
260
+ @th2.join_until { @counter == 2 }
261
+ @counter.should == 2
262
+ @other_doc_got_lock.should be_true
263
+ end
264
+
265
+ it %[should not affect another thread using a different name] do
266
+ @counter = 0
267
+ @queue = Queue.new
268
+ @other_doc_got_lock = false
269
+ @name = 'peanuts'
270
+
271
+ @th1 = Thread.new do
272
+ @doc.lock_for_update(@name) do
273
+ @counter += 1
274
+ @queue.pop
275
+ end
276
+ end
277
+
278
+ @th1.join_until { @counter == 1 }
279
+ @counter.should == 1
280
+
281
+ @th1.zk_mongoid_lock_registry[:exclusive].should include(@doc.zk_lock_name(@name))
282
+
283
+ @th2 = Thread.new do
284
+ @other_doc.lock_for_update do
285
+ @other_doc_got_lock = true
286
+ @counter += 1
287
+ end
288
+ end
289
+
290
+ @th2.join_until { @other_doc_got_lock }
291
+ @other_doc_got_lock.should be_true
292
+
293
+ @counter.should == 2
294
+
295
+ @queue << :release_lock
296
+ @th1.join(2).should == @th1
297
+ end
298
+ end
299
+ end
300
+
301
+ describe :assert_locked_for_update! do
302
+ it %[should raise MustBeExclusivelyLockedException if the current thread does not hold the lock] do
303
+ lambda { @doc.assert_locked_for_update! }.should raise_error(ZK::Exceptions::MustBeExclusivelyLockedException)
304
+ end
305
+
306
+ it %[should not raise an exception if the current thread holds the lock] do
307
+ lambda do
308
+ @doc.lock_for_update do
309
+ @doc.assert_locked_for_update!
310
+ end
311
+ end.should_not raise_error
312
+ end
313
+ end
314
+
315
+ describe :assert_locked_for_share! do
316
+ it %[should raise MustBeShareLockedException if the current thread does not hold a shared lock] do
317
+ lambda { @doc.assert_locked_for_share! }.should raise_error(ZK::Exceptions::MustBeShareLockedException)
318
+ end
319
+
320
+ it %[should not raise an exception if the current thread holds a shared lock] do
321
+ lambda do
322
+ @doc.with_shared_lock do
323
+ @doc.assert_locked_for_share!
324
+ end
325
+ end.should_not raise_error
326
+ end
327
+ end
328
+ end
329
+
330
+
@@ -0,0 +1,96 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
5
+
6
+ require 'zk'
7
+ require 'benchmark'
8
+
9
+ ZK_TEST_PORT = 2181
10
+
11
+ LOG_FILE = File.open(File.join(ZK::ZK_ROOT, 'test.log'), 'a').tap { |f| f.sync = true }
12
+
13
+ ZK.logger = Logger.new(LOG_FILE).tap { |log| log.level = Logger::DEBUG }
14
+ #Zookeeper.logger = ZK.logger
15
+
16
+ # Requires supporting ruby files with custom matchers and macros, etc,
17
+ # in spec/support/ and its subdirectories.
18
+ Dir[File.expand_path("../support/**/*.rb", __FILE__)].each {|f| require f}
19
+
20
+ $stderr.sync = true
21
+
22
+ # COMMENT THESE LINES FOR REMOTE DEBUGGING
23
+ require 'ruby-debug'
24
+ require 'flexmock'
25
+
26
+ RSpec.configure do |config|
27
+ config.mock_with :flexmock
28
+ config.include(FlexMock::ArgumentTypes)
29
+
30
+ # config.before(:all) do
31
+ # unless $did_debug
32
+ # $did_debug = true
33
+ # $stderr.puts "debugger started? is #{Debugger.started?.inspect}"
34
+
35
+ # Debugger.wait_connection = true
36
+ # $stderr.puts "run 'rdebug -c -p #{Debugger::PORT}'"
37
+ # Debugger.start_remote
38
+
39
+ # config.debug = true
40
+ # end
41
+ # end
42
+ end
43
+
44
+ def logger
45
+ ZK.logger
46
+ end
47
+
48
+ # method to wait until block passed returns true or timeout (default is 2 seconds) is reached
49
+ def wait_until(timeout=2)
50
+ time_to_stop = Time.now + timeout
51
+
52
+ until yield
53
+ break if Time.now > time_to_stop
54
+ Thread.pass
55
+ end
56
+ end
57
+
58
+ def wait_while(timeout=2)
59
+ time_to_stop = Time.now + timeout
60
+
61
+ while yield
62
+ break if Time.now > time_to_stop
63
+ Thread.pass
64
+ end
65
+ end
66
+
67
+
68
+ class ::Thread
69
+ # join with thread until given block is true, the thread joins successfully,
70
+ # or timeout seconds have passed
71
+ #
72
+ def join_until(timeout=2)
73
+ time_to_stop = Time.now + timeout
74
+
75
+ until yield
76
+ break if Time.now > time_to_stop
77
+ break if join(0.1)
78
+ end
79
+ end
80
+
81
+ def join_while(timeout=2)
82
+ time_to_stop = Time.now + timeout
83
+
84
+ while yield
85
+ break if Time.now > time_to_stop
86
+ break if join(0.1)
87
+ end
88
+ end
89
+ end
90
+
91
+ def report_realtime(what)
92
+ t = Benchmark.realtime { yield }
93
+ $stderr.puts "#{what}: %0.3f" % [t.to_f]
94
+ end
95
+
96
+
@@ -0,0 +1,11 @@
1
+ class BogusMongoid
2
+ include ZK::Mongoid::Locking
3
+ include ZK::Logging
4
+
5
+ attr_reader :id
6
+
7
+ def initialize(opts={})
8
+ @id = opts[:id] || 42
9
+ end
10
+ end
11
+
@@ -0,0 +1,11 @@
1
+ class QueueyThread < ::Thread
2
+ attr_reader :input, :output
3
+
4
+ def initialize(*args, &block)
5
+ @output = Queue.new
6
+ @input = Queue.new
7
+
8
+ super(*args, &block)
9
+ end
10
+ end
11
+
@@ -0,0 +1,4 @@
1
+ a
2
+ b
3
+ c
4
+ d
@@ -0,0 +1,71 @@
1
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
2
+
3
+ describe ZK::Threadpool do
4
+
5
+ before do
6
+ @threadpool = ZK::Threadpool.new
7
+ end
8
+
9
+ after do
10
+ @threadpool.shutdown
11
+ end
12
+
13
+ describe :new do
14
+ it %[should be running] do
15
+ @threadpool.should be_running
16
+ end
17
+
18
+ it %[should use the default size] do
19
+ @threadpool.size.should == ZK::Threadpool.default_size
20
+ end
21
+ end
22
+
23
+
24
+ describe :defer do
25
+ it %[should run the given block on a thread in the threadpool] do
26
+ @th = nil
27
+
28
+ @threadpool.defer { @th = Thread.current }
29
+
30
+ wait_until(2) { @th }
31
+
32
+ @th.should_not == Thread.current
33
+ end
34
+
35
+ it %[should barf if the argument is not callable] do
36
+ bad_obj = mock(:not_callable)
37
+ bad_obj.should_not respond_to(:call)
38
+
39
+ lambda { @threadpool.defer(bad_obj) }.should raise_error(ArgumentError)
40
+ end
41
+
42
+ it %[should barf if the threadpool is not running] do
43
+ @threadpool.shutdown
44
+ lambda { @threadpool.defer { "hai!" } }.should raise_error(ZK::Exceptions::ThreadpoolIsNotRunningException)
45
+ end
46
+ end
47
+
48
+ describe :shutdown do
49
+ it %[should set running to false] do
50
+ @threadpool.shutdown
51
+ @threadpool.should_not be_running
52
+ end
53
+ end
54
+
55
+ describe :start! do
56
+ it %[should be able to start a threadpool that had previously been shutdown (reuse)] do
57
+ @threadpool.shutdown
58
+ @threadpool.start!
59
+
60
+ @threadpool.should be_running
61
+
62
+ @rval = nil
63
+
64
+ @threadpool.defer { @rval = true }
65
+ wait_until(2) { @rval }
66
+ @rval.should be_true
67
+ end
68
+ end
69
+
70
+ end
71
+
@@ -0,0 +1,118 @@
1
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
2
+
3
+ describe ZK do
4
+ describe do
5
+ before do
6
+ @cnx_str = "localhost:#{ZK_TEST_PORT}"
7
+ @zk = ZK.new(@cnx_str)
8
+
9
+ @path = "/_testWatch"
10
+ wait_until { @zk.connected? }
11
+ end
12
+
13
+ after do
14
+ if @zk.connected?
15
+ @zk.close!
16
+ wait_until { !@zk.connected? }
17
+ end
18
+
19
+ ZK.open(@cnx_str) { |zk| zk.rm_rf(@path) }
20
+ end
21
+
22
+ it "should call back to path registers" do
23
+ locker = Mutex.new
24
+ callback_called = false
25
+
26
+ @zk.watcher.register(@path) do |event|
27
+ locker.synchronize do
28
+ callback_called = true
29
+ end
30
+ event.path.should == @path
31
+ end
32
+
33
+ @zk.exists?(@path, :watch => true)
34
+ @zk.create(@path, "", :mode => :ephemeral)
35
+
36
+ wait_until(5) { locker.synchronize { callback_called } }
37
+ callback_called.should be_true
38
+ end
39
+
40
+ it %[should only deliver an event once to each watcher registered for exists?] do
41
+ events = []
42
+
43
+ sub = @zk.watcher.register(@path) do |ev|
44
+ logger.debug "got event #{ev}"
45
+ events << ev
46
+ end
47
+
48
+ 2.times do
49
+ @zk.exists?(@path, :watch => true).should_not be_true
50
+ end
51
+
52
+ @zk.create(@path, '', :mode => :ephemeral)
53
+
54
+ wait_until { events.length >= 2 }
55
+ events.length.should == 1
56
+ end
57
+
58
+ it %[should only deliver an event once to each watcher registered for get] do
59
+ events = []
60
+
61
+ @zk.create(@path, 'one', :mode => :ephemeral)
62
+
63
+ sub = @zk.watcher.register(@path) do |ev|
64
+ logger.debug "got event #{ev}"
65
+ events << ev
66
+ end
67
+
68
+ 2.times do
69
+ data, stat = @zk.get(@path, :watch => true)
70
+ data.should == 'one'
71
+ end
72
+
73
+ @zk.set(@path, 'two')
74
+
75
+ wait_until { events.length >= 2 }
76
+ events.length.should == 1
77
+ end
78
+
79
+
80
+ it %[should only deliver an event once to each watcher registered for children] do
81
+ events = []
82
+
83
+ @zk.create(@path, '')
84
+
85
+ sub = @zk.watcher.register(@path) do |ev|
86
+ logger.debug "got event #{ev}"
87
+ events << ev
88
+ end
89
+
90
+ 2.times do
91
+ children = @zk.children(@path, :watch => true)
92
+ children.should be_empty
93
+ end
94
+
95
+ @zk.create("#{@path}/pfx", '', :mode => :ephemeral_sequential)
96
+
97
+ wait_until { events.length >= 2 }
98
+ events.length.should == 1
99
+ end
100
+ end
101
+
102
+ describe 'state watcher' do
103
+ before do
104
+ @event = nil
105
+ @cnx_str = "localhost:#{ZK_TEST_PORT}"
106
+
107
+ @zk = ZK.new(@cnx_str) do |zk|
108
+ @cnx_reg = zk.on_connected { |event| @event = event }
109
+ end
110
+ end
111
+
112
+ it %[should fire the registered callback] do
113
+ wait_while { @event.nil? }
114
+ @event.should_not be_nil
115
+ end
116
+ end
117
+ end
118
+