zk 0.8.9 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.dotfiles/rvmrc +1 -1
- data/.yardopts +1 -0
- data/Gemfile +11 -1
- data/README.markdown +19 -10
- data/RELEASES.markdown +14 -0
- data/lib/z_k/client/base.rb +171 -46
- data/lib/z_k/client/continuation_proxy.rb +99 -0
- data/lib/z_k/client/conveniences.rb +10 -13
- data/lib/z_k/client/drop_box.rb +98 -0
- data/lib/z_k/client/multiplexed.rb +28 -0
- data/lib/z_k/client/threaded.rb +43 -13
- data/lib/z_k/client/unixisms.rb +74 -14
- data/lib/z_k/client.rb +3 -0
- data/lib/z_k/event_handler.rb +5 -45
- data/lib/z_k/event_handler_subscription.rb +13 -6
- data/lib/z_k/exceptions.rb +22 -2
- data/lib/z_k/extensions.rb +10 -2
- data/lib/z_k/find.rb +5 -2
- data/lib/z_k/locker.rb +9 -4
- data/lib/z_k/pool.rb +24 -6
- data/lib/z_k/version.rb +1 -1
- data/lib/z_k.rb +1 -2
- data/spec/shared/client_contexts.rb +15 -0
- data/spec/shared/client_examples.rb +155 -0
- data/spec/spec_helper.rb +9 -53
- data/spec/support/logging.rb +27 -0
- data/spec/support/special_happy_funtime_error.rb +6 -0
- data/spec/support/wait_watchers.rb +48 -0
- data/spec/watch_spec.rb +19 -4
- data/spec/z_k/client/drop_box_spec.rb +90 -0
- data/spec/z_k/client/locking_and_session_death_spec.rb +108 -0
- data/spec/z_k/client/multiplexed_spec.rb +20 -0
- data/spec/z_k/client_spec.rb +3 -231
- data/spec/z_k/election_spec.rb +12 -11
- data/spec/z_k/locker_spec.rb +69 -3
- data/spec/zookeeper_spec.rb +3 -3
- data/zk.gemspec +1 -2
- metadata +28 -8
data/lib/z_k/pool.rb
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
module ZK
|
2
2
|
module Pool
|
3
|
+
# Base class for a ZK connection pool. There are some applications that may
|
4
|
+
# require high synchronous throughput, which would be a suitable use for a
|
5
|
+
# connection pool. The ZK::Client::Threaded class is threadsafe, so it's
|
6
|
+
# not a problem accessing it from multiple threads, but it is limited to
|
7
|
+
# one outgoing synchronous request at a time, which could cause throughput
|
8
|
+
# issues for apps that are making very heavy use of zookeeper.
|
9
|
+
#
|
10
|
+
# The problem with using a connection pool is the added complexity when you
|
11
|
+
# try to use watchers. It may be possible to register a watch with one
|
12
|
+
# connection, and then call `:watch => true` on a different connection if
|
13
|
+
# you're not careful. Events delivered as part of an event handler have a
|
14
|
+
# `zk` attribute which can be used to access the connection that the
|
15
|
+
# callback is registered with.
|
16
|
+
#
|
17
|
+
# Unless you're sure you *need* a connection pool, then avoid the added
|
18
|
+
# complexity.
|
19
|
+
#
|
3
20
|
class Base
|
4
21
|
include Logging
|
5
22
|
|
@@ -40,7 +57,6 @@ module ZK
|
|
40
57
|
end
|
41
58
|
|
42
59
|
# close all the connections on the pool
|
43
|
-
# @param optional Boolean graceful allow the checked out connections to come back first?
|
44
60
|
def close_all!
|
45
61
|
@mutex.synchronize do
|
46
62
|
return unless open?
|
@@ -54,7 +70,8 @@ module ZK
|
|
54
70
|
|
55
71
|
# calls close! on all connection objects, whether or not they're back in the pool
|
56
72
|
# this is DANGEROUS!
|
57
|
-
|
73
|
+
# @private
|
74
|
+
def force_close!
|
58
75
|
@mutex.synchronize do
|
59
76
|
return if (closed? or forced?)
|
60
77
|
@state = :forced
|
@@ -273,17 +290,18 @@ module ZK
|
|
273
290
|
end
|
274
291
|
|
275
292
|
def create_connection
|
276
|
-
ZK.new(@host, @
|
293
|
+
ZK.new(@host, @connection_args.merge(:timeout => @connection_timeout))
|
277
294
|
end
|
278
295
|
end # Bounded
|
279
296
|
|
280
297
|
# create a connection pool useful for multithreaded applications
|
281
298
|
#
|
282
|
-
# Will spin up
|
299
|
+
# Will spin up `number_of_connections` at creation time and remain fixed at
|
283
300
|
# that number for the life of the pool.
|
284
301
|
#
|
285
|
-
#
|
286
|
-
#
|
302
|
+
# @example
|
303
|
+
#
|
304
|
+
# pool = ZK::Pool::Simple.new("localhost:2181", :timeout => 10)
|
287
305
|
# pool.checkout do |zk|
|
288
306
|
# zk.create("/mynew_path")
|
289
307
|
# end
|
data/lib/z_k/version.rb
CHANGED
data/lib/z_k.rb
CHANGED
@@ -25,8 +25,7 @@ require 'z_k/find'
|
|
25
25
|
module ZK
|
26
26
|
ZK_ROOT = File.expand_path('../..', __FILE__)
|
27
27
|
|
28
|
-
KILL_TOKEN =
|
29
|
-
|
28
|
+
KILL_TOKEN = Object.new unless defined?(KILL_TOKEN)
|
30
29
|
|
31
30
|
# The logger used by the ZK library. uses a Logger to +/dev/null+ by default
|
32
31
|
#
|
@@ -0,0 +1,15 @@
|
|
1
|
+
shared_context 'threaded client connection' do
|
2
|
+
before do
|
3
|
+
@connection_string = "localhost:#{ZK_TEST_PORT}"
|
4
|
+
@zk = ZK::Client::Threaded.new(@connection_string).tap { |z| wait_until { z.connected? } }
|
5
|
+
@zk.rm_rf('/test')
|
6
|
+
end
|
7
|
+
|
8
|
+
after do
|
9
|
+
@zk.rm_rf('/test')
|
10
|
+
@zk.close!
|
11
|
+
|
12
|
+
wait_until(2) { @zk.closed? }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
@@ -0,0 +1,155 @@
|
|
1
|
+
shared_examples_for 'client' do
|
2
|
+
describe :mkdir_p do
|
3
|
+
before(:each) do
|
4
|
+
@path_ary = %w[test mkdir_p path creation]
|
5
|
+
@bogus_path = File.join('/', *@path_ary)
|
6
|
+
end
|
7
|
+
|
8
|
+
it %[should create all intermediate paths for the path givem] do
|
9
|
+
@zk.should_not be_exists(@bogus_path)
|
10
|
+
@zk.should_not be_exists(File.dirname(@bogus_path))
|
11
|
+
@zk.mkdir_p(@bogus_path)
|
12
|
+
@zk.should be_exists(@bogus_path)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe :stat do
|
17
|
+
describe 'for a missing node' do
|
18
|
+
before do
|
19
|
+
@missing_path = '/thispathdoesnotexist'
|
20
|
+
@zk.delete(@missing_path) rescue ZK::Exceptions::NoNode
|
21
|
+
end
|
22
|
+
|
23
|
+
it %[should not raise any error] do
|
24
|
+
lambda { @zk.stat(@missing_path) }.should_not raise_error
|
25
|
+
end
|
26
|
+
|
27
|
+
it %[should return a Stat object] do
|
28
|
+
@zk.stat(@missing_path).should be_kind_of(ZookeeperStat::Stat)
|
29
|
+
end
|
30
|
+
|
31
|
+
it %[should return a stat that not exists?] do
|
32
|
+
@zk.stat(@missing_path).should_not be_exists
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe :block_until_node_deleted do
|
38
|
+
before do
|
39
|
+
@path = '/_bogualkjdhsna'
|
40
|
+
end
|
41
|
+
|
42
|
+
describe 'no node initially' do
|
43
|
+
before do
|
44
|
+
@zk.exists?(@path).should be_false
|
45
|
+
end
|
46
|
+
|
47
|
+
it %[should not block] do
|
48
|
+
@a = false
|
49
|
+
|
50
|
+
th = Thread.new do
|
51
|
+
@zk.block_until_node_deleted(@path)
|
52
|
+
@a = true
|
53
|
+
end
|
54
|
+
|
55
|
+
th.join(2)
|
56
|
+
@a.should be_true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe 'node exists initially' do
|
61
|
+
before do
|
62
|
+
@zk.create(@path, '', :mode => :ephemeral)
|
63
|
+
@zk.exists?(@path).should be_true
|
64
|
+
end
|
65
|
+
|
66
|
+
it %[should block until the node is deleted] do
|
67
|
+
@a = false
|
68
|
+
|
69
|
+
th = Thread.new do
|
70
|
+
@zk.block_until_node_deleted(@path)
|
71
|
+
@a = true
|
72
|
+
end
|
73
|
+
|
74
|
+
Thread.pass
|
75
|
+
@a.should be_false
|
76
|
+
|
77
|
+
@zk.delete(@path)
|
78
|
+
|
79
|
+
wait_until(2) { @a }
|
80
|
+
@a.should be_true
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe 'session_id and session_passwd' do
|
86
|
+
it %[should expose the underlying session_id] do
|
87
|
+
@zk.session_id.should be_kind_of(Fixnum)
|
88
|
+
end
|
89
|
+
|
90
|
+
it %[should expose the underlying session_passwd] do
|
91
|
+
@zk.session_passwd.should be_kind_of(String)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe 'reopen' do
|
96
|
+
describe 'watchers' do
|
97
|
+
before do
|
98
|
+
@path = '/testwatchers'
|
99
|
+
@queue = Queue.new
|
100
|
+
@zk.delete(@path) rescue ZK::Exceptions::NoNode
|
101
|
+
end
|
102
|
+
|
103
|
+
# after do
|
104
|
+
# logger.info { "AFTER EACH" }
|
105
|
+
# @zk.delete(@path)
|
106
|
+
# end
|
107
|
+
|
108
|
+
def ensure_event_delivery!
|
109
|
+
@sub ||= @zk.event_handler.register(@path) do |event|
|
110
|
+
logger.debug { "got event: #{event.inspect}" }
|
111
|
+
@queue << event
|
112
|
+
end
|
113
|
+
|
114
|
+
@zk.exists?(@path, :watch => true).should be_false
|
115
|
+
@zk.create(@path, '')
|
116
|
+
|
117
|
+
logger.debug { "waiting for event delivery" }
|
118
|
+
|
119
|
+
wait_until(2) do
|
120
|
+
begin
|
121
|
+
@events << @queue.pop(true)
|
122
|
+
true
|
123
|
+
rescue ThreadError
|
124
|
+
false
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# first watch delivered correctly
|
129
|
+
@events.length.should > 0
|
130
|
+
end
|
131
|
+
|
132
|
+
it %[should fire re-registered watchers after reopen (#9)] do
|
133
|
+
@events = []
|
134
|
+
|
135
|
+
logger.debug { "ensure event delivery" }
|
136
|
+
ensure_event_delivery!
|
137
|
+
|
138
|
+
logger.debug { "reopening connection" }
|
139
|
+
@zk.reopen
|
140
|
+
|
141
|
+
wait_until(2) { @zk.connected? }
|
142
|
+
|
143
|
+
logger.debug { "deleting path" }
|
144
|
+
@zk.delete(@path)
|
145
|
+
|
146
|
+
logger.debug { "clearing events" }
|
147
|
+
@events.clear
|
148
|
+
|
149
|
+
logger.debug { "taunt them a second time" }
|
150
|
+
ensure_event_delivery!
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
data/spec/spec_helper.rb
CHANGED
@@ -8,65 +8,25 @@ require 'benchmark'
|
|
8
8
|
|
9
9
|
ZK_TEST_PORT = 2181
|
10
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
|
-
ZK.logger.debug { "LOG OPEN" }
|
17
|
-
|
18
11
|
# Requires supporting ruby files with custom matchers and macros, etc,
|
19
12
|
# in spec/support/ and its subdirectories.
|
20
|
-
Dir[File.expand_path("../support/**/*.rb", __FILE__)].each {|f| require f}
|
13
|
+
Dir[File.expand_path("../{support,shared}/**/*.rb", __FILE__)].each {|f| require f}
|
21
14
|
|
22
15
|
$stderr.sync = true
|
23
16
|
|
24
|
-
# COMMENT THESE LINES FOR REMOTE DEBUGGING
|
25
|
-
# require 'ruby-debug'
|
26
17
|
require 'flexmock'
|
27
18
|
|
28
19
|
RSpec.configure do |config|
|
29
20
|
config.mock_with :flexmock
|
30
21
|
config.include(FlexMock::ArgumentTypes)
|
31
22
|
|
32
|
-
|
33
|
-
|
34
|
-
# $did_debug = true
|
35
|
-
# $stderr.puts "debugger started? is #{Debugger.started?.inspect}"
|
23
|
+
config.include(WaitWatchers)
|
24
|
+
config.extend(WaitWatchers)
|
36
25
|
|
37
|
-
|
38
|
-
|
39
|
-
# Debugger.start_remote
|
40
|
-
|
41
|
-
# config.debug = true
|
42
|
-
# end
|
43
|
-
# end
|
26
|
+
config.include(SpecGlobalLogger)
|
27
|
+
config.extend(SpecGlobalLogger)
|
44
28
|
end
|
45
29
|
|
46
|
-
def logger
|
47
|
-
ZK.logger
|
48
|
-
end
|
49
|
-
|
50
|
-
# method to wait until block passed returns true or timeout (default is 2 seconds) is reached
|
51
|
-
def wait_until(timeout=2)
|
52
|
-
time_to_stop = Time.now + timeout
|
53
|
-
|
54
|
-
until yield
|
55
|
-
break if Time.now > time_to_stop
|
56
|
-
Thread.pass
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def wait_while(timeout=2)
|
61
|
-
time_to_stop = Time.now + timeout
|
62
|
-
|
63
|
-
while yield
|
64
|
-
break if Time.now > time_to_stop
|
65
|
-
Thread.pass
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
|
70
30
|
class ::Thread
|
71
31
|
# join with thread until given block is true, the thread joins successfully,
|
72
32
|
# or timeout seconds have passed
|
@@ -76,7 +36,8 @@ class ::Thread
|
|
76
36
|
|
77
37
|
until yield
|
78
38
|
break if Time.now > time_to_stop
|
79
|
-
break if join(0
|
39
|
+
break if join(0)
|
40
|
+
Thread.pass
|
80
41
|
end
|
81
42
|
end
|
82
43
|
|
@@ -85,15 +46,10 @@ class ::Thread
|
|
85
46
|
|
86
47
|
while yield
|
87
48
|
break if Time.now > time_to_stop
|
88
|
-
break if join(0
|
49
|
+
break if join(0)
|
50
|
+
Thread.pass
|
89
51
|
end
|
90
52
|
end
|
91
53
|
end
|
92
54
|
|
93
|
-
def report_realtime(what)
|
94
|
-
return yield
|
95
|
-
t = Benchmark.realtime { yield }
|
96
|
-
$stderr.puts "#{what}: %0.3f" % [t.to_f]
|
97
|
-
end
|
98
|
-
|
99
55
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module ZK
|
2
|
+
LOG_FILE = File.open(File.join(ZK::ZK_ROOT, 'test.log'), 'a').tap { |f| f.sync = true }
|
3
|
+
# LOG_FILE = $stderr
|
4
|
+
end
|
5
|
+
|
6
|
+
ZK.logger = Logger.new(ZK::LOG_FILE).tap { |log| log.level = Logger::DEBUG }
|
7
|
+
|
8
|
+
# Zookeeper.logger = ZK.logger
|
9
|
+
# Zookeeper.set_debug_level(4)
|
10
|
+
|
11
|
+
ZK.logger.debug { "LOG OPEN" }
|
12
|
+
|
13
|
+
module SpecGlobalLogger
|
14
|
+
def logger
|
15
|
+
ZK.logger
|
16
|
+
end
|
17
|
+
|
18
|
+
# sets the log level to FATAL for the duration of the block
|
19
|
+
def mute_logger
|
20
|
+
orig_level, ZK.logger.level = ZK.logger.level, Logger::FATAL
|
21
|
+
orig_zk_level, Zookeeper.debug_level = Zookeeper.debug_level, ZookeeperConstants::ZOO_LOG_LEVEL_ERROR
|
22
|
+
yield
|
23
|
+
ensure
|
24
|
+
ZK.logger.level = orig_level
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module WaitWatchers
|
2
|
+
class TimeoutError < StandardError; end
|
3
|
+
|
4
|
+
# method to wait until block passed returns truthy (false will not work) or
|
5
|
+
# timeout (default is 2 seconds) is reached raises TiemoutError on timeout
|
6
|
+
#
|
7
|
+
# @returns the truthy value
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
#
|
11
|
+
# @a = nil
|
12
|
+
#
|
13
|
+
# th = Thread.new do
|
14
|
+
# sleep(1)
|
15
|
+
# @a = :fudge
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# wait_until(2) { @a }.should == :fudge
|
19
|
+
#
|
20
|
+
def wait_until(timeout=2)
|
21
|
+
time_to_stop = Time.now + timeout
|
22
|
+
while true
|
23
|
+
rval = yield
|
24
|
+
return rval if rval
|
25
|
+
raise TimeoutError, "timeout of #{timeout}s exceeded" if Time.now > time_to_stop
|
26
|
+
Thread.pass
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# inverse of wait_until
|
31
|
+
def wait_while(timeout=2)
|
32
|
+
time_to_stop = Time.now + timeout
|
33
|
+
while true
|
34
|
+
rval = yield
|
35
|
+
return rval unless rval
|
36
|
+
raise TimeoutError, "timeout of #{timeout}s exceeded" if Time.now > time_to_stop
|
37
|
+
Thread.pass
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def report_realtime(what)
|
42
|
+
return yield
|
43
|
+
t = Benchmark.realtime { yield }
|
44
|
+
$stderr.puts "#{what}: %0.3f" % [t.to_f]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
data/spec/watch_spec.rb
CHANGED
@@ -16,7 +16,9 @@ describe ZK do
|
|
16
16
|
wait_until { !@zk.connected? }
|
17
17
|
end
|
18
18
|
|
19
|
-
|
19
|
+
mute_logger do
|
20
|
+
ZK.open(@cnx_str) { |zk| zk.rm_rf(@path) }
|
21
|
+
end
|
20
22
|
end
|
21
23
|
|
22
24
|
it "should call back to path registers" do
|
@@ -37,6 +39,16 @@ describe ZK do
|
|
37
39
|
callback_called.should be_true
|
38
40
|
end
|
39
41
|
|
42
|
+
# this is stupid, and a bad test, but we have to check that events
|
43
|
+
# don't get re-delivered to a single registered callback just because
|
44
|
+
# :watch => true was called twice
|
45
|
+
#
|
46
|
+
# again, we're testing a negative here, so consider this a regression check
|
47
|
+
#
|
48
|
+
def wait_for_events_to_not_be_delivered(events)
|
49
|
+
lambda { wait_until(0.2) { events.length >= 2 } }.should raise_error(WaitWatchers::TimeoutError)
|
50
|
+
end
|
51
|
+
|
40
52
|
it %[should only deliver an event once to each watcher registered for exists?] do
|
41
53
|
events = []
|
42
54
|
|
@@ -51,7 +63,8 @@ describe ZK do
|
|
51
63
|
|
52
64
|
@zk.create(@path, '', :mode => :ephemeral)
|
53
65
|
|
54
|
-
|
66
|
+
wait_for_events_to_not_be_delivered(events)
|
67
|
+
|
55
68
|
events.length.should == 1
|
56
69
|
end
|
57
70
|
|
@@ -72,7 +85,8 @@ describe ZK do
|
|
72
85
|
|
73
86
|
@zk.set(@path, 'two')
|
74
87
|
|
75
|
-
|
88
|
+
wait_for_events_to_not_be_delivered(events)
|
89
|
+
|
76
90
|
events.length.should == 1
|
77
91
|
end
|
78
92
|
|
@@ -94,7 +108,8 @@ describe ZK do
|
|
94
108
|
|
95
109
|
@zk.create("#{@path}/pfx", '', :mode => :ephemeral_sequential)
|
96
110
|
|
97
|
-
|
111
|
+
wait_for_events_to_not_be_delivered(events)
|
112
|
+
|
98
113
|
events.length.should == 1
|
99
114
|
end
|
100
115
|
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module ZK
|
4
|
+
module Client
|
5
|
+
describe 'ZK::Client::DropBox' do
|
6
|
+
let(:drop_box) { DropBox.new }
|
7
|
+
|
8
|
+
after do
|
9
|
+
DropBox.remove_current
|
10
|
+
end
|
11
|
+
|
12
|
+
it %[should start out with an undefined value] do
|
13
|
+
drop_box.value.should == DropBox::UNDEFINED
|
14
|
+
end
|
15
|
+
|
16
|
+
it %[should block the caller waiting for a response] do
|
17
|
+
@rv = nil
|
18
|
+
|
19
|
+
th1 = Thread.new do
|
20
|
+
Thread.current.abort_on_exception = true
|
21
|
+
@rv = drop_box.pop
|
22
|
+
end
|
23
|
+
|
24
|
+
wait_until(2) { th1.status == 'sleep' }
|
25
|
+
|
26
|
+
th1.status.should == 'sleep'
|
27
|
+
|
28
|
+
th2 = Thread.new do
|
29
|
+
drop_box.push :result
|
30
|
+
end
|
31
|
+
|
32
|
+
th2.join(2).should == th2
|
33
|
+
th1.join(2).should == th1
|
34
|
+
|
35
|
+
@rv.should == :result
|
36
|
+
end
|
37
|
+
|
38
|
+
describe :oh_noes! do
|
39
|
+
let(:error_class) { SpecialHappyFuntimeError }
|
40
|
+
let(:error_msg) { 'this is a unique message' }
|
41
|
+
|
42
|
+
it %[should wake the caller by raising the exception class and message given] do
|
43
|
+
drop_box.should_not be_done # sanity check
|
44
|
+
|
45
|
+
th1 = Thread.new do
|
46
|
+
drop_box.pop
|
47
|
+
end
|
48
|
+
|
49
|
+
wait_until(2) { th1.status == 'sleep' }
|
50
|
+
|
51
|
+
th1.status.should == 'sleep'
|
52
|
+
|
53
|
+
drop_box.oh_noes!(error_class, error_msg).should_not be_nil
|
54
|
+
|
55
|
+
lambda { th1.join(2) }.should raise_error(error_class, error_msg)
|
56
|
+
|
57
|
+
drop_box.should be_done
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe :done? do
|
62
|
+
it %[should be done if the value is defined] do
|
63
|
+
drop_box.should_not be_done
|
64
|
+
drop_box.push :defined
|
65
|
+
drop_box.should be_done
|
66
|
+
end
|
67
|
+
|
68
|
+
it %[should not be done once cleared] do
|
69
|
+
drop_box.push :defined
|
70
|
+
drop_box.should be_done
|
71
|
+
drop_box.clear
|
72
|
+
drop_box.should_not be_done
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe :with_current do
|
77
|
+
it %[should clear the current thread's drop_box once the block exits] do
|
78
|
+
DropBox.with_current do |c|
|
79
|
+
c.should_not be_done
|
80
|
+
c.push 'yo_mama'
|
81
|
+
c.should be_done
|
82
|
+
end
|
83
|
+
|
84
|
+
DropBox.current.should_not be_done
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
shared_examples_for 'session death' do
|
4
|
+
def deliver_session_event_to(event_num, zk)
|
5
|
+
# jeez, Zookeeper callbacks are so frustratingly stupid
|
6
|
+
bogus_event = ZookeeperCallbacks::WatcherCallback.new
|
7
|
+
bogus_event.initialize_context(:type => -1, :state => event_num, :path => '', :context => 'bogustestevent')
|
8
|
+
# XXX: this is bad because we're in the wrong thread, but we'll fix this after the next Zookeeper release
|
9
|
+
zk.event_handler.process(bogus_event)
|
10
|
+
end
|
11
|
+
|
12
|
+
before do
|
13
|
+
@other_zk = ZK.new(@connection_string)
|
14
|
+
end
|
15
|
+
|
16
|
+
after do
|
17
|
+
@other_zk.close! unless @other_zk.closed?
|
18
|
+
end
|
19
|
+
|
20
|
+
it %[should wake up in the case of an expired session and throw an exception] do
|
21
|
+
@a = false
|
22
|
+
|
23
|
+
@other_zk.event_handler.register_state_handler(zoo_state) do |event|
|
24
|
+
@a = event
|
25
|
+
end
|
26
|
+
|
27
|
+
th = Thread.new do
|
28
|
+
@other_zk.block_until_node_deleted(@path)
|
29
|
+
end
|
30
|
+
|
31
|
+
# we don't expect an exception yet, so warn us if there is on while this
|
32
|
+
# thread is on its way to sleep
|
33
|
+
th.abort_on_exception = true
|
34
|
+
|
35
|
+
wait_until(2) { th.status == 'sleep' }
|
36
|
+
|
37
|
+
th.abort_on_exception = false # after here, we're raising an exception on purpose
|
38
|
+
|
39
|
+
th.join if th.status.nil? # this indicates an exception happened...already
|
40
|
+
|
41
|
+
# not on the other thread, this may be bad
|
42
|
+
deliver_session_event_to(zoo_state, @other_zk)
|
43
|
+
|
44
|
+
# ditto, this is probably happening synchrnously
|
45
|
+
wait_until(2) { @a }.should be_true
|
46
|
+
|
47
|
+
lambda { th.join(2) }.should raise_error(zoo_error_class)
|
48
|
+
end
|
49
|
+
end # session death
|
50
|
+
|
51
|
+
shared_examples_for 'locking and session death' do
|
52
|
+
describe 'exceptional conditions' do
|
53
|
+
describe 'ZOO_EXPIRED_SESSION_STATE' do
|
54
|
+
let(:zoo_state) { ZookeeperConstants::ZOO_EXPIRED_SESSION_STATE }
|
55
|
+
let(:zoo_error_class) { ZookeeperExceptions::ZookeeperException::SessionExpired }
|
56
|
+
|
57
|
+
it_behaves_like 'session death'
|
58
|
+
end
|
59
|
+
|
60
|
+
describe 'ZOO_CONNECTING_STATE' do
|
61
|
+
let(:zoo_state) { ZookeeperConstants::ZOO_CONNECTING_STATE }
|
62
|
+
let(:zoo_error_class) { ZookeeperExceptions::ZookeeperException::NotConnected }
|
63
|
+
|
64
|
+
it_behaves_like 'session death'
|
65
|
+
end
|
66
|
+
|
67
|
+
describe 'ZOO_CLOSED_STATE' do
|
68
|
+
let(:zoo_state) { ZookeeperConstants::ZOO_CLOSED_STATE }
|
69
|
+
let(:zoo_error_class) { ZookeeperExceptions::ZookeeperException::ConnectionClosed }
|
70
|
+
|
71
|
+
it_behaves_like 'session death'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe 'threaded client and locking behavior' do
|
77
|
+
include_context 'threaded client connection'
|
78
|
+
|
79
|
+
before do
|
80
|
+
@path = '/test'
|
81
|
+
@zk.create('/test') rescue ZK::Exceptions::NodeExists
|
82
|
+
end
|
83
|
+
|
84
|
+
describe 'block_until_node_deleted' do
|
85
|
+
it %[should raise an EventDispatchThreadException if called in the context of the event dispatch thread] do
|
86
|
+
@exception = nil
|
87
|
+
|
88
|
+
@zk.register(@path) do |event|
|
89
|
+
@zk.event_dispatch_thread?.should be_true
|
90
|
+
|
91
|
+
begin
|
92
|
+
@zk.block_until_node_deleted(@path)
|
93
|
+
rescue Exception => e
|
94
|
+
@exception = e
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
@zk.exists?(@path, :watch => true)
|
99
|
+
|
100
|
+
@zk.set(@path, 'blah')
|
101
|
+
|
102
|
+
wait_until(2) { @exception }.should be_kind_of(ZK::Exceptions::EventDispatchThreadException)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
it_should_behave_like 'locking and session death'
|
107
|
+
end
|
108
|
+
|