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.
- data/.gitignore +7 -0
- data/Gemfile +10 -0
- data/Rakefile +13 -0
- data/lib/z_k/client.rb +906 -0
- data/lib/z_k/election.rb +411 -0
- data/lib/z_k/event_handler.rb +202 -0
- data/lib/z_k/event_handler_subscription.rb +29 -0
- data/lib/z_k/exceptions.rb +101 -0
- data/lib/z_k/extensions.rb +144 -0
- data/lib/z_k/locker.rb +254 -0
- data/lib/z_k/logging.rb +15 -0
- data/lib/z_k/message_queue.rb +143 -0
- data/lib/z_k/mongoid.rb +172 -0
- data/lib/z_k/pool.rb +254 -0
- data/lib/z_k/threadpool.rb +109 -0
- data/lib/z_k/version.rb +3 -0
- data/lib/z_k.rb +73 -0
- data/lib/zk.rb +2 -0
- data/spec/client_pool_spec.rb +329 -0
- data/spec/client_spec.rb +102 -0
- data/spec/election_spec.rb +301 -0
- data/spec/locker_spec.rb +386 -0
- data/spec/log4j.properties +17 -0
- data/spec/message_queue_spec.rb +55 -0
- data/spec/mongoid_spec.rb +330 -0
- data/spec/spec_helper.rb +96 -0
- data/spec/support/bogus_mongoid.rb +11 -0
- data/spec/support/queuey_thread.rb +11 -0
- data/spec/test_file.txt +4 -0
- data/spec/threadpool_spec.rb +71 -0
- data/spec/watch_spec.rb +118 -0
- data/spec/zookeeper_spec.rb +176 -0
- data/zk.gemspec +24 -0
- metadata +176 -0
@@ -0,0 +1,329 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), %w[spec_helper])
|
2
|
+
|
3
|
+
require 'tracer'
|
4
|
+
|
5
|
+
describe ZK::Pool do
|
6
|
+
describe :Simple do
|
7
|
+
|
8
|
+
before do
|
9
|
+
report_realtime('opening pool') do
|
10
|
+
@pool_size = 2
|
11
|
+
@connection_pool = ZK::Pool::Simple.new("localhost:#{ZK_TEST_PORT}", @pool_size, :watcher => :default)
|
12
|
+
@connection_pool.should be_open
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
after do
|
17
|
+
report_realtime("close_all!") do
|
18
|
+
unless @connection_pool.closed?
|
19
|
+
th = Thread.new do
|
20
|
+
@connection_pool.close_all!
|
21
|
+
end
|
22
|
+
|
23
|
+
unless th.join(5) == th
|
24
|
+
logger.warn { "Forcing pool closed!" }
|
25
|
+
@connection_pool.force_close!
|
26
|
+
th.join(5).should == th
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
report_realtime("closing") do
|
32
|
+
ZK.open("localhost:#{ZK_TEST_PORT}") do |zk|
|
33
|
+
zk.delete('/test_pool') rescue ZK::Exceptions::NoNode
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should allow you to execute commands on a connection" do
|
39
|
+
@connection_pool.with_connection do |zk|
|
40
|
+
zk.create("/test_pool", "", :mode => :ephemeral)
|
41
|
+
zk.exists?("/test_pool").should be_true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe :method_missing do
|
46
|
+
it %[should allow you to execute commands on the connection pool itself] do
|
47
|
+
@connection_pool.create('/test_pool', '', :mode => :persistent)
|
48
|
+
wait_until(2) { @connection_pool.exists?('/test_pool') }
|
49
|
+
@connection_pool.exists?('/test_pool').should be_true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe :close_all! do
|
54
|
+
it %[should shutdown gracefully] do
|
55
|
+
release_q = Queue.new
|
56
|
+
|
57
|
+
@about_to_block = false
|
58
|
+
|
59
|
+
open_th = Thread.new do
|
60
|
+
@connection_pool.with_connection do |cnx|
|
61
|
+
@about_to_block = true
|
62
|
+
# wait for signal to release our connection
|
63
|
+
release_q.pop
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
wait_until(2) { @about_to_block }
|
68
|
+
@about_to_block.should be_true
|
69
|
+
|
70
|
+
release_q.num_waiting.should == 1
|
71
|
+
|
72
|
+
closing_th = Thread.new do
|
73
|
+
@connection_pool.close_all!
|
74
|
+
end
|
75
|
+
|
76
|
+
wait_until(2) { @connection_pool.closing? }
|
77
|
+
@connection_pool.should be_closing
|
78
|
+
|
79
|
+
lambda { @connection_pool.with_connection { |c| } }.should raise_error(ZK::Exceptions::PoolIsShuttingDownException)
|
80
|
+
|
81
|
+
release_q << :ok_let_go
|
82
|
+
|
83
|
+
open_th.join(2).should == open_th
|
84
|
+
|
85
|
+
wait_until(2) { @connection_pool.closed? }
|
86
|
+
$stderr.puts "@connection_pool.pool_state: #{@connection_pool.pool_state.inspect}"
|
87
|
+
@connection_pool.should be_closed
|
88
|
+
|
89
|
+
lambda do
|
90
|
+
closing_th.join(1).should == closing_th
|
91
|
+
open_th.join(1).should == open_th
|
92
|
+
end.should_not raise_error
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe :force_close! do
|
97
|
+
it %[should raise PoolIsShuttingDownException in a thread blocked waiting for a connection] do
|
98
|
+
@cnx = []
|
99
|
+
|
100
|
+
until @connection_pool.available_size <= 0
|
101
|
+
@cnx << @connection_pool.checkout
|
102
|
+
end
|
103
|
+
|
104
|
+
@cnx.length.should_not be_zero
|
105
|
+
|
106
|
+
th = Thread.new do
|
107
|
+
@connection_pool.checkout(true)
|
108
|
+
end
|
109
|
+
|
110
|
+
th.join_until { @connection_pool.count_waiters > 0 }
|
111
|
+
@connection_pool.count_waiters.should > 0
|
112
|
+
|
113
|
+
@connection_pool.force_close!
|
114
|
+
|
115
|
+
lambda { th.join(2) }.should raise_error(ZK::Exceptions::PoolIsShuttingDownException)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should allow watchers still" do
|
120
|
+
@callback_called = false
|
121
|
+
|
122
|
+
@path = '/_testWatch'
|
123
|
+
|
124
|
+
@connection_pool.with_connection do |zk|
|
125
|
+
zk.delete(@path) rescue ZK::Exceptions::NoNode
|
126
|
+
end
|
127
|
+
|
128
|
+
@connection_pool.with_connection do |zk|
|
129
|
+
$stderr.puts "registering callback"
|
130
|
+
zk.watcher.register(@path) do |event|
|
131
|
+
$stderr.puts "callback fired! event: #{event.inspect}"
|
132
|
+
|
133
|
+
@callback_called = true
|
134
|
+
event.path.should == @path
|
135
|
+
$stderr.puts "signaling other waiters"
|
136
|
+
end
|
137
|
+
|
138
|
+
$stderr.puts "setting up watcher"
|
139
|
+
zk.exists?(@path, :watch => true).should be_false
|
140
|
+
end
|
141
|
+
|
142
|
+
@connection_pool.with_connection do |zk|
|
143
|
+
$stderr.puts "creating path"
|
144
|
+
zk.create(@path, "", :mode => :ephemeral).should == @path
|
145
|
+
end
|
146
|
+
|
147
|
+
wait_until(1) { @callback_called }
|
148
|
+
|
149
|
+
@callback_called.should be_true
|
150
|
+
end
|
151
|
+
end # Client
|
152
|
+
|
153
|
+
describe :Bounded do
|
154
|
+
before do
|
155
|
+
@min_clients = 1
|
156
|
+
@max_clients = 2
|
157
|
+
@connection_pool = ZK::Pool::Bounded.new("localhost:#{ZK_TEST_PORT}", :min_clients => @min_clients, :max_clients => @max_clients, :timeout => @timeout)
|
158
|
+
@connection_pool.should be_open
|
159
|
+
end
|
160
|
+
|
161
|
+
after do
|
162
|
+
@connection_pool.force_close! unless @connection_pool.closed?
|
163
|
+
@connection_pool.should be_closed
|
164
|
+
end
|
165
|
+
|
166
|
+
describe 'initial state' do
|
167
|
+
it %[should have initialized the minimum number of clients] do
|
168
|
+
@connection_pool.size.should == @min_clients
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe 'should grow to max_clients' do
|
173
|
+
# before do
|
174
|
+
# require 'tracer'
|
175
|
+
# Tracer.on
|
176
|
+
# end
|
177
|
+
|
178
|
+
# after do
|
179
|
+
# Tracer.off
|
180
|
+
# end
|
181
|
+
|
182
|
+
it %[should grow if it can] do
|
183
|
+
q1 = Queue.new
|
184
|
+
|
185
|
+
@connection_pool.size.should == 1
|
186
|
+
|
187
|
+
th1 = Thread.new do
|
188
|
+
@connection_pool.with_connection do |cnx|
|
189
|
+
Thread.current[:cnx] = cnx
|
190
|
+
q1.pop # block here
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
th1.join_until(2) { th1[:cnx] }
|
195
|
+
th1[:cnx].should_not be_nil
|
196
|
+
|
197
|
+
th2 = Thread.new do
|
198
|
+
@connection_pool.with_connection do |cnx|
|
199
|
+
Thread.current[:cnx] = cnx
|
200
|
+
q1.pop
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
th2.join_until(2) { th2[:cnx] }
|
205
|
+
|
206
|
+
th2[:cnx].should_not be_nil
|
207
|
+
th2[:cnx].should be_connected
|
208
|
+
|
209
|
+
@connection_pool.size.should == 2
|
210
|
+
@connection_pool.available_size.should be_zero
|
211
|
+
|
212
|
+
2.times { q1.enq(:release_cnx) }
|
213
|
+
|
214
|
+
lambda do
|
215
|
+
th1.join(1).should == th1
|
216
|
+
th2.join(1).should == th2
|
217
|
+
end.should_not raise_error
|
218
|
+
|
219
|
+
@connection_pool.size.should == 2
|
220
|
+
@connection_pool.available_size.should == 2
|
221
|
+
end
|
222
|
+
|
223
|
+
it %[should not grow past max_clients and block] do
|
224
|
+
win_q = Queue.new
|
225
|
+
lose_q = Queue.new
|
226
|
+
|
227
|
+
threads = []
|
228
|
+
|
229
|
+
2.times do
|
230
|
+
threads << Thread.new do
|
231
|
+
@connection_pool.with_connection do |cnx|
|
232
|
+
Thread.current[:cnx] = cnx
|
233
|
+
win_q.pop
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
wait_until(2) { threads.all? { |th| th[:cnx] } }
|
239
|
+
threads.each { |th| th[:cnx].should_not be_nil }
|
240
|
+
|
241
|
+
loser = Thread.new do
|
242
|
+
@connection_pool.with_connection do |cnx|
|
243
|
+
Thread.current[:cnx] = cnx
|
244
|
+
lose_q.pop
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
wait_until(2) { @connection_pool.count_waiters > 0 }
|
249
|
+
@connection_pool.count_waiters.should == 1
|
250
|
+
|
251
|
+
loser[:cnx].should be_nil
|
252
|
+
|
253
|
+
2.times { win_q.enq(:release) }
|
254
|
+
|
255
|
+
lambda { threads.each { |th| th.join(2).should == th } }.should_not raise_error
|
256
|
+
|
257
|
+
loser.join_until(2) { loser[:cnx] }
|
258
|
+
|
259
|
+
loser[:cnx].should_not be_nil
|
260
|
+
|
261
|
+
lose_q.enq(:release)
|
262
|
+
|
263
|
+
lambda { loser.join(2).should == loser }.should_not raise_error
|
264
|
+
|
265
|
+
@connection_pool.count_waiters.should be_zero
|
266
|
+
@connection_pool.available_size.should == 2
|
267
|
+
@connection_pool.size.should == 2
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
describe 'health checking' do
|
272
|
+
before do
|
273
|
+
@connections = @connection_pool.connections
|
274
|
+
@connections.length.should == 1
|
275
|
+
|
276
|
+
@cnx1 = @connections.first
|
277
|
+
end
|
278
|
+
|
279
|
+
describe 'disconnected client' do
|
280
|
+
before do
|
281
|
+
flexmock(@cnx1) do |m|
|
282
|
+
m.should_receive(:connected?).and_return(false)
|
283
|
+
end
|
284
|
+
|
285
|
+
@cnx2 = nil
|
286
|
+
|
287
|
+
th = Thread.new do
|
288
|
+
@connection_pool.with_connection do |cnx|
|
289
|
+
@cnx2 = cnx
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
th.join_while { @cnx2.nil? }
|
294
|
+
end
|
295
|
+
|
296
|
+
it %[should create a new client and return it] do
|
297
|
+
@cnx2.should_not be_nil
|
298
|
+
@cnx2.should_not == @cnx1
|
299
|
+
end
|
300
|
+
|
301
|
+
it %[should remove the disconnected client from the pool] do
|
302
|
+
@connection_pool.available_size.should == 1
|
303
|
+
end
|
304
|
+
|
305
|
+
it %[should still have the original client in its array of all connections] do
|
306
|
+
@connections.should include(@cnx1)
|
307
|
+
end
|
308
|
+
|
309
|
+
describe 'when connected event fires' do
|
310
|
+
before do
|
311
|
+
@event = flexmock(:event) do |m|
|
312
|
+
m.should_receive(:type).and_return(-1)
|
313
|
+
m.should_receive(:zk=).with(any())
|
314
|
+
m.should_receive(:node_event?).and_return(false)
|
315
|
+
m.should_receive(:state_event?).and_return(true)
|
316
|
+
m.should_receive(:state).and_return(ZookeeperConstants::ZOO_CONNECTED_STATE)
|
317
|
+
end
|
318
|
+
|
319
|
+
@cnx1.watcher.process(@event)
|
320
|
+
end
|
321
|
+
|
322
|
+
it %[should add the connection back into the pool] do
|
323
|
+
@connection_pool.available_size.should == 2
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), %w[spec_helper])
|
2
|
+
|
3
|
+
describe ZK::Client do
|
4
|
+
before do
|
5
|
+
@zk = ZK.new("localhost:#{ZK_TEST_PORT}")
|
6
|
+
wait_until{ @zk.connected? }
|
7
|
+
@zk.rm_rf('/test')
|
8
|
+
end
|
9
|
+
|
10
|
+
after do
|
11
|
+
@zk.rm_rf('/test')
|
12
|
+
@zk.close!
|
13
|
+
|
14
|
+
wait_until(2) { @zk.closed? }
|
15
|
+
end
|
16
|
+
|
17
|
+
describe :mkdir_p do
|
18
|
+
before(:each) do
|
19
|
+
@path_ary = %w[test mkdir_p path creation]
|
20
|
+
@bogus_path = File.join('/', *@path_ary)
|
21
|
+
end
|
22
|
+
|
23
|
+
it %[should create all intermediate paths for the path givem] do
|
24
|
+
@zk.should_not be_exists(@bogus_path)
|
25
|
+
@zk.should_not be_exists(File.dirname(@bogus_path))
|
26
|
+
@zk.mkdir_p(@bogus_path)
|
27
|
+
@zk.should be_exists(@bogus_path)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe :stat do
|
32
|
+
describe 'for a missing node' do
|
33
|
+
before do
|
34
|
+
@missing_path = '/thispathdoesnotexist'
|
35
|
+
@zk.delete(@missing_path) rescue ZK::Exceptions::NoNode
|
36
|
+
end
|
37
|
+
|
38
|
+
it %[should not raise any error] do
|
39
|
+
lambda { @zk.stat(@missing_path) }.should_not raise_error
|
40
|
+
end
|
41
|
+
|
42
|
+
it %[should return a Stat object] do
|
43
|
+
@zk.stat(@missing_path).should be_kind_of(ZookeeperStat::Stat)
|
44
|
+
end
|
45
|
+
|
46
|
+
it %[should return a stat that not exists?] do
|
47
|
+
@zk.stat(@missing_path).should_not be_exists
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe :block_until_node_deleted do
|
53
|
+
before do
|
54
|
+
@path = '/_bogualkjdhsna'
|
55
|
+
end
|
56
|
+
|
57
|
+
describe 'no node initially' do
|
58
|
+
before do
|
59
|
+
@zk.exists?(@path).should be_false
|
60
|
+
end
|
61
|
+
|
62
|
+
it %[should not block] do
|
63
|
+
@a = false
|
64
|
+
|
65
|
+
th = Thread.new do
|
66
|
+
@zk.block_until_node_deleted(@path)
|
67
|
+
@a = true
|
68
|
+
end
|
69
|
+
|
70
|
+
th.join(2)
|
71
|
+
@a.should be_true
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe 'node exists initially' do
|
76
|
+
before do
|
77
|
+
@zk.create(@path, '', :mode => :ephemeral)
|
78
|
+
@zk.exists?(@path).should be_true
|
79
|
+
end
|
80
|
+
|
81
|
+
it %[should block until the node is deleted] do
|
82
|
+
@a = false
|
83
|
+
|
84
|
+
th = Thread.new do
|
85
|
+
@zk.block_until_node_deleted(@path)
|
86
|
+
@a = true
|
87
|
+
end
|
88
|
+
|
89
|
+
Thread.pass
|
90
|
+
@a.should be_false
|
91
|
+
|
92
|
+
@zk.delete(@path)
|
93
|
+
|
94
|
+
wait_until(2) { @a }
|
95
|
+
@a.should be_true
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
|