zk 0.8.9 → 0.9.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.
- 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
|
+
|