zookeeper-ng 1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.ctags_paths +1 -0
- data/.dotfiles/ruby-gemset +1 -0
- data/.dotfiles/ruby-version +1 -0
- data/.dotfiles/rvmrc +2 -0
- data/.gitignore +19 -0
- data/.gitmodules +3 -0
- data/.travis.yml +25 -0
- data/CHANGELOG +395 -0
- data/Gemfile +30 -0
- data/Guardfile +8 -0
- data/LICENSE +23 -0
- data/Manifest +29 -0
- data/README.markdown +85 -0
- data/Rakefile +121 -0
- data/cause-abort.rb +117 -0
- data/ext/.gitignore +6 -0
- data/ext/Rakefile +41 -0
- data/ext/c_zookeeper.rb +398 -0
- data/ext/common.h +17 -0
- data/ext/dbg.h +53 -0
- data/ext/depend +5 -0
- data/ext/event_lib.c +740 -0
- data/ext/event_lib.h +175 -0
- data/ext/extconf.rb +103 -0
- data/ext/generate_gvl_code.rb +321 -0
- data/ext/patches/zkc-3.3.5-network.patch +24 -0
- data/ext/patches/zkc-3.4.5-fetch-and-add.patch +16 -0
- data/ext/patches/zkc-3.4.5-logging.patch +41 -0
- data/ext/patches/zkc-3.4.5-out-of-order-ping.patch +163 -0
- data/ext/patches/zkc-3.4.5-overflow.patch +11 -0
- data/ext/patches/zkc-3.4.5-yosemite-htonl-fix.patch +102 -0
- data/ext/zkc-3.4.5.tar.gz +0 -0
- data/ext/zkrb.c +1075 -0
- data/ext/zkrb_wrapper.c +775 -0
- data/ext/zkrb_wrapper.h +350 -0
- data/ext/zkrb_wrapper_compat.c +15 -0
- data/ext/zkrb_wrapper_compat.h +11 -0
- data/ext/zookeeper_base.rb +256 -0
- data/java/java_base.rb +503 -0
- data/lib/zookeeper.rb +115 -0
- data/lib/zookeeper/acls.rb +44 -0
- data/lib/zookeeper/callbacks.rb +108 -0
- data/lib/zookeeper/client.rb +30 -0
- data/lib/zookeeper/client_methods.rb +282 -0
- data/lib/zookeeper/common.rb +122 -0
- data/lib/zookeeper/common/queue_with_pipe.rb +110 -0
- data/lib/zookeeper/compatibility.rb +138 -0
- data/lib/zookeeper/constants.rb +97 -0
- data/lib/zookeeper/continuation.rb +223 -0
- data/lib/zookeeper/core_ext.rb +58 -0
- data/lib/zookeeper/em_client.rb +55 -0
- data/lib/zookeeper/exceptions.rb +135 -0
- data/lib/zookeeper/forked.rb +19 -0
- data/lib/zookeeper/latch.rb +34 -0
- data/lib/zookeeper/logger.rb +39 -0
- data/lib/zookeeper/logger/forwarding_logger.rb +84 -0
- data/lib/zookeeper/monitor.rb +19 -0
- data/lib/zookeeper/rake_tasks.rb +165 -0
- data/lib/zookeeper/request_registry.rb +153 -0
- data/lib/zookeeper/stat.rb +21 -0
- data/lib/zookeeper/version.rb +4 -0
- data/notes.txt +14 -0
- data/scripts/upgrade-1.0-sed-alike.rb +46 -0
- data/spec/c_zookeeper_spec.rb +51 -0
- data/spec/chrooted_connection_spec.rb +83 -0
- data/spec/compatibilty_spec.rb +8 -0
- data/spec/default_watcher_spec.rb +41 -0
- data/spec/em_spec.rb +51 -0
- data/spec/ext/zookeeper_base_spec.rb +19 -0
- data/spec/forked_connection_spec.rb +124 -0
- data/spec/latch_spec.rb +24 -0
- data/spec/log4j.properties +17 -0
- data/spec/shared/all_success_return_values.rb +10 -0
- data/spec/shared/connection_examples.rb +1077 -0
- data/spec/spec_helper.rb +61 -0
- data/spec/support/00_logging.rb +38 -0
- data/spec/support/10_spawn_zookeeper.rb +24 -0
- data/spec/support/progress_formatter.rb +15 -0
- data/spec/support/zookeeper_spec_helpers.rb +96 -0
- data/spec/zookeeper_spec.rb +24 -0
- data/zookeeper.gemspec +38 -0
- data/zoomonkey/duplicates +3 -0
- data/zoomonkey/zoomonkey.rb +194 -0
- metadata +157 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
module Zookeeper
|
2
|
+
class Stat
|
3
|
+
attr_reader :version, :exists, :czxid, :mzxid, :ctime, :mtime, :cversion, :aversion, :ephemeralOwner, :dataLength, :numChildren, :pzxid
|
4
|
+
|
5
|
+
alias :ephemeral_owner :ephemeralOwner
|
6
|
+
alias :num_children :numChildren
|
7
|
+
alias :data_length :dataLength
|
8
|
+
|
9
|
+
def initialize(val)
|
10
|
+
@exists = !!val
|
11
|
+
@czxid, @mzxid, @ctime, @mtime, @version, @cversion, @aversion,
|
12
|
+
@ephemeralOwner, @dataLength, @numChildren, @pzxid = val if val.is_a?(Array)
|
13
|
+
val.each { |k,v| instance_variable_set "@#{k}", v } if val.is_a?(Hash)
|
14
|
+
raise ArgumentError unless (val.is_a?(Hash) or val.is_a?(Array) or val.nil?)
|
15
|
+
end
|
16
|
+
|
17
|
+
def exists?
|
18
|
+
@exists
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/notes.txt
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
Notes on this fork of http://github.com/twitter/zookeeper
|
2
|
+
|
3
|
+
The main purpose of this fork is to provide JRuby compatibility with the MRI driver of the original. We have been using an early fork with an incomplete binding to the C client, and had recently bumped up against a fairly serious bug with handling events. The twitter/zookeeper driver solved this problem but was lacking JRuby support (which is a core piece of our environment).
|
4
|
+
|
5
|
+
I've packaged the Java client (and its dependency on log4j) as gems that are installed separately from this codebase to cut down on the size of the zookeeper gem. If this poses a problem (for instance, if the original goal was to have an all-in-one install with no external dependencies), it would be trivial to package those jars along with this code.
|
6
|
+
|
7
|
+
In the course of writing the wrapper for JRuby, I've written nearly-complete specs for all of the public API methods: get, set, get_children, stat, create, delete, get_acl, and set_acl. The reason I say they're "nearly" complete is that I have no use for set_acl, and quite honestly, couldn't figure out how to make it work.
|
8
|
+
|
9
|
+
I'm planning on writing a companion gem, 'ZK', that would take some of the higher-level constructs that we'd written around the lower-level driver to implement features like locking and queues, and also to provide a more ruby-like interface.
|
10
|
+
|
11
|
+
I'd like to reorganize this codebase in a number of ways, most of all to move all of the various classes and modules under a common "Zookeeper" namespace, however I thought that would be a radical enough change where I'd want to discuss/coordinate with you on how exactly to do it.
|
12
|
+
|
13
|
+
|
14
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
if ARGV.empty?
|
4
|
+
$stderr.puts <<-EOS
|
5
|
+
Usage: #{File.basename(__FILE__)} file1 file2 file3
|
6
|
+
|
7
|
+
Fix references to Zookeeper classes and modules.
|
8
|
+
|
9
|
+
This script acts like sed and edits files in place (not saving backups,
|
10
|
+
as you *are* using source control and aren't a complete tool).
|
11
|
+
|
12
|
+
if you have any doubts, *read the script*:
|
13
|
+
----------------------------------------------------------------------
|
14
|
+
|
15
|
+
#{File.read(__FILE__)}
|
16
|
+
|
17
|
+
EOS
|
18
|
+
|
19
|
+
exit 1
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
require 'tempfile'
|
24
|
+
require 'fileutils'
|
25
|
+
|
26
|
+
ARGV.each do |path|
|
27
|
+
Tempfile.open(File.basename(path)) do |tmp|
|
28
|
+
File.open(path) do |input|
|
29
|
+
while line = input.gets
|
30
|
+
tmp.puts line.gsub(/\bZookeeperStat::Stat\b/, 'Zookeeper::Stat').
|
31
|
+
gsub(/\bZookeeper::(\w+)Callback\b/, 'Zookeeper::Callbacks::\1Callback').
|
32
|
+
gsub(/\bZookeeperACLs::(ZOO_\w+)\b/, 'Zookeeper::Constants::\1').
|
33
|
+
gsub(/\bZookeeperExceptions::ZookeeperException::(\w+)\b/, 'Zookeeper::Exceptions::\1').
|
34
|
+
gsub(/\bZookeeper(Constants|Exceptions|Common|ACLs|Callbacks)\b/, 'Zookeeper::\1').
|
35
|
+
gsub(/\bZookeeperException::(\w+)\b/, 'Exceptions::\1')
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
tmp.fsync
|
41
|
+
tmp.close
|
42
|
+
|
43
|
+
FileUtils.mv(tmp.path, path)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# tests the CZookeeper, obviously only available when running under MRI
|
2
|
+
|
3
|
+
unless defined?(::JRUBY_VERSION)
|
4
|
+
require 'spec_helper'
|
5
|
+
|
6
|
+
describe Zookeeper::CZookeeper do
|
7
|
+
def pop_all_events
|
8
|
+
[].tap do |rv|
|
9
|
+
begin
|
10
|
+
rv << @event_queue.pop(non_blocking=true)
|
11
|
+
rescue ThreadError
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def wait_until_connected(timeout=10)
|
17
|
+
wait_until(timeout) { @czk.state == Zookeeper::Constants::ZOO_CONNECTED_STATE }
|
18
|
+
end
|
19
|
+
|
20
|
+
describe do
|
21
|
+
before do
|
22
|
+
@event_queue = Zookeeper::Common::QueueWithPipe.new
|
23
|
+
@czk = Zookeeper::CZookeeper.new(Zookeeper.default_cnx_str, @event_queue)
|
24
|
+
end
|
25
|
+
|
26
|
+
after do
|
27
|
+
@czk.close rescue Exception
|
28
|
+
@event_queue.close rescue Exception
|
29
|
+
end
|
30
|
+
|
31
|
+
it %[should be in connected state within a reasonable amount of time] do
|
32
|
+
wait_until_connected.should be_true
|
33
|
+
end
|
34
|
+
|
35
|
+
describe :after_connected do
|
36
|
+
before do
|
37
|
+
wait_until_connected.should be_true
|
38
|
+
end
|
39
|
+
|
40
|
+
it %[should have a connection event after being connected] do
|
41
|
+
event = wait_until(10) { @event_queue.pop }
|
42
|
+
event.should be
|
43
|
+
event[:req_id].should == Zookeeper::Constants::ZKRB_GLOBAL_CB_REQ
|
44
|
+
event[:type].should == Zookeeper::Constants::ZOO_SESSION_EVENT
|
45
|
+
event[:state].should == Zookeeper::Constants::ZOO_CONNECTED_STATE
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'shared/connection_examples'
|
3
|
+
|
4
|
+
describe 'Zookeeper chrooted' do
|
5
|
+
let(:path) { "/_zkchroottest_" }
|
6
|
+
let(:data) { "underpants" }
|
7
|
+
let(:chroot_path) { '/slyphon-zookeeper-chroot' }
|
8
|
+
|
9
|
+
let(:connection_string) { "#{Zookeeper.default_cnx_str}#{chroot_path}" }
|
10
|
+
|
11
|
+
before do
|
12
|
+
@zk = Zookeeper.new(connection_string)
|
13
|
+
end
|
14
|
+
|
15
|
+
after do
|
16
|
+
@zk and @zk.close
|
17
|
+
end
|
18
|
+
|
19
|
+
def zk
|
20
|
+
@zk
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'non-existent' do
|
24
|
+
describe 'with existing parent' do
|
25
|
+
let(:chroot_path) { '/one-level' }
|
26
|
+
|
27
|
+
describe 'create' do
|
28
|
+
before do
|
29
|
+
with_open_zk { |z| rm_rf(z, chroot_path) }
|
30
|
+
end
|
31
|
+
|
32
|
+
after do
|
33
|
+
with_open_zk { |z| rm_rf(z, chroot_path) }
|
34
|
+
end
|
35
|
+
|
36
|
+
it %[should successfully create the path] do
|
37
|
+
rv = zk.create(:path => '/', :data => '')
|
38
|
+
rv[:rc].should be_zero
|
39
|
+
rv[:path].should == ''
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe 'with missing parent' do
|
45
|
+
let(:chroot_path) { '/deeply/nested/path' }
|
46
|
+
|
47
|
+
describe 'create' do
|
48
|
+
before do
|
49
|
+
with_open_zk do |z|
|
50
|
+
rm_rf(z, chroot_path)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it %[should return ZNONODE] do
|
55
|
+
rv = zk.create(:path => '/', :data => '')
|
56
|
+
rv[:rc].should_not be_zero
|
57
|
+
rv[:rc].should == Zookeeper::Exceptions::ZNONODE
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
describe do
|
65
|
+
before :all do
|
66
|
+
logger.warn "running before :all"
|
67
|
+
|
68
|
+
with_open_zk do |z|
|
69
|
+
z.create(:path => chroot_path, :data => '')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
after :all do
|
74
|
+
with_open_zk do |z|
|
75
|
+
rm_rf(z, chroot_path)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it_should_behave_like "connection"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Zookeeper do
|
4
|
+
describe :initialize, 'with watcher block' do
|
5
|
+
before do
|
6
|
+
@events = []
|
7
|
+
@watch_block = lambda do |hash|
|
8
|
+
logger.debug "watch_block: #{hash.inspect}"
|
9
|
+
@events << hash
|
10
|
+
end
|
11
|
+
|
12
|
+
@zk = Zookeeper.new(Zookeeper.default_cnx_str, 10, @watch_block)
|
13
|
+
|
14
|
+
wait_until(2) { @zk.connected? }
|
15
|
+
@zk.should be_connected
|
16
|
+
logger.debug "connected!"
|
17
|
+
|
18
|
+
wait_until(2) { !@events.empty? }
|
19
|
+
logger.debug "got events!"
|
20
|
+
end
|
21
|
+
|
22
|
+
after do
|
23
|
+
@zk.close if @zk.connected?
|
24
|
+
end
|
25
|
+
|
26
|
+
it %[should receive initial connection state events] do
|
27
|
+
@events.should_not be_empty
|
28
|
+
@events.length.should == 1
|
29
|
+
@events.first[:state].should == Zookeeper::ZOO_CONNECTED_STATE
|
30
|
+
end
|
31
|
+
|
32
|
+
it %[should receive disconnection events] do
|
33
|
+
pending "the C driver doesn't appear to deliver disconnection events (?)"
|
34
|
+
@events.clear
|
35
|
+
@zk.close
|
36
|
+
wait_until(2) { !@events.empty? }
|
37
|
+
@events.should_not be_empty
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
data/spec/em_spec.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
require 'zookeeper/em_client'
|
3
|
+
|
4
|
+
gem 'evented-spec', '~> 0.9.0'
|
5
|
+
require 'evented-spec'
|
6
|
+
|
7
|
+
|
8
|
+
describe 'ZookeeperEM' do
|
9
|
+
describe 'Client' do
|
10
|
+
include EventedSpec::SpecHelper
|
11
|
+
default_timeout 3.0
|
12
|
+
|
13
|
+
def setup_zk
|
14
|
+
@zk = ZookeeperEM::Client.new(Zookeeper.default_cnx_str)
|
15
|
+
em do
|
16
|
+
@zk.on_attached do
|
17
|
+
yield
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def teardown_and_done
|
23
|
+
@zk.close do
|
24
|
+
logger.debug { "TEST: about to call done" }
|
25
|
+
EM.next_tick do
|
26
|
+
done
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe 'callbacks' do
|
32
|
+
it %[should be called on the reactor thread] do
|
33
|
+
cb = lambda do |h|
|
34
|
+
EM.reactor_thread?.should be_true
|
35
|
+
logger.debug { "called back on the reactor thread? #{EM.reactor_thread?}" }
|
36
|
+
teardown_and_done
|
37
|
+
end
|
38
|
+
|
39
|
+
setup_zk do
|
40
|
+
@zk.on_attached do |*|
|
41
|
+
logger.debug { "on_attached called" }
|
42
|
+
rv = @zk.get(:path => '/', :callback => cb)
|
43
|
+
logger.debug { "rv from @zk.get: #{rv.inspect}" }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
unless defined?(::JRUBY_VERSION)
|
4
|
+
describe Zookeeper::ZookeeperBase do
|
5
|
+
before do
|
6
|
+
@zk = described_class.new(Zookeeper.default_cnx_str)
|
7
|
+
end
|
8
|
+
|
9
|
+
after do
|
10
|
+
@zk.close unless @zk.closed?
|
11
|
+
end
|
12
|
+
|
13
|
+
it %[should have an original_pid assigned] do
|
14
|
+
@zk.original_pid.should == Process.pid
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
unless defined?(::JRUBY_VERSION)
|
4
|
+
describe %[forked connection] do
|
5
|
+
let(:path) { "/_zktest_" }
|
6
|
+
let(:pids_root) { "#{path}/pids" }
|
7
|
+
let(:data) { "underpants" }
|
8
|
+
let(:connection_string) { Zookeeper.default_cnx_str }
|
9
|
+
|
10
|
+
def process_alive?(pid)
|
11
|
+
Process.kill(0, @pid)
|
12
|
+
true
|
13
|
+
rescue Errno::ESRCH
|
14
|
+
false
|
15
|
+
end
|
16
|
+
|
17
|
+
LBORDER = ('-' * 35) << '< '
|
18
|
+
RBORDER = ' >' << ('-' * 35)
|
19
|
+
|
20
|
+
def mark(thing)
|
21
|
+
logger << "\n#{LBORDER}#{thing}#{RBORDER}\n\n"
|
22
|
+
end
|
23
|
+
|
24
|
+
before do
|
25
|
+
mark "BEFORE: START"
|
26
|
+
if defined?(::Rubinius)
|
27
|
+
pending("this test is currently broken in rbx")
|
28
|
+
# elsif ENV['TRAVIS']
|
29
|
+
# pending("this test is currently hanging in travis")
|
30
|
+
else
|
31
|
+
@zk = Zookeeper.new(connection_string)
|
32
|
+
rm_rf(@zk, path)
|
33
|
+
end
|
34
|
+
mark "BEFORE: END"
|
35
|
+
end
|
36
|
+
|
37
|
+
after do
|
38
|
+
mark "AFTER: START"
|
39
|
+
|
40
|
+
if @pid and process_alive?(@pid)
|
41
|
+
begin
|
42
|
+
Process.kill('KILL', @pid)
|
43
|
+
p Process.wait2(@pid)
|
44
|
+
rescue Errno::ESRCH
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
@zk.close if @zk and !@zk.closed?
|
49
|
+
with_open_zk(connection_string) { |z| rm_rf(z, path) }
|
50
|
+
|
51
|
+
mark "AFTER: END"
|
52
|
+
end
|
53
|
+
|
54
|
+
def wait_for_child_safely(pid, timeout=5)
|
55
|
+
time_to_stop = Time.now + timeout
|
56
|
+
|
57
|
+
until Time.now > time_to_stop
|
58
|
+
if a = Process.wait2(@pid, Process::WNOHANG)
|
59
|
+
return a.last
|
60
|
+
else
|
61
|
+
sleep(0.01)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
it %[should do the right thing and not fail] do
|
69
|
+
mark "TEST: START"
|
70
|
+
|
71
|
+
@zk.wait_until_connected
|
72
|
+
|
73
|
+
mkdir_p(@zk, pids_root)
|
74
|
+
|
75
|
+
# the parent's pid path
|
76
|
+
@zk.create(:path => "#{pids_root}/#{$$}", :data => $$.to_s)
|
77
|
+
|
78
|
+
@latch = Zookeeper::Latch.new
|
79
|
+
@event = nil
|
80
|
+
|
81
|
+
cb = proc do |h|
|
82
|
+
logger.debug { "watcher called back: #{h.inspect}" }
|
83
|
+
@event = h
|
84
|
+
@latch.release
|
85
|
+
end
|
86
|
+
|
87
|
+
@zk.stat(:path => "#{pids_root}/child", :watcher => cb)
|
88
|
+
|
89
|
+
@zk.pause_before_fork_in_parent
|
90
|
+
|
91
|
+
mark "FORK"
|
92
|
+
|
93
|
+
@pid = fork do
|
94
|
+
logger.debug { "reopening connection in child: #{$$}" }
|
95
|
+
@zk.reopen
|
96
|
+
logger.debug { "creating path" }
|
97
|
+
rv = @zk.create(:path => "#{pids_root}/child", :data => $$.to_s)
|
98
|
+
logger.debug { "created path #{rv[:path]}" }
|
99
|
+
@zk.close
|
100
|
+
|
101
|
+
logger.debug { "close finished" }
|
102
|
+
exit!(0)
|
103
|
+
end
|
104
|
+
|
105
|
+
@zk.resume_after_fork_in_parent
|
106
|
+
|
107
|
+
event_waiter_th = Thread.new do
|
108
|
+
@latch.await(5) unless @event
|
109
|
+
@event
|
110
|
+
end
|
111
|
+
|
112
|
+
logger.debug { "waiting on child #{@pid}" }
|
113
|
+
|
114
|
+
status = wait_for_child_safely(@pid)
|
115
|
+
raise "Child process did not exit, likely hung" unless status
|
116
|
+
|
117
|
+
status.should_not be_signaled
|
118
|
+
status.should be_success
|
119
|
+
|
120
|
+
event_waiter_th.join(5).should == event_waiter_th
|
121
|
+
@event.should_not be_nil
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|