slyphon-zookeeper 0.3.0 → 0.8.0.rc.1
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/Rakefile +25 -5
- data/ext/c_zookeeper.rb +214 -0
- data/ext/dbg.h +18 -2
- data/ext/depend +1 -2
- data/ext/zookeeper_base.rb +71 -100
- data/ext/zookeeper_c.c +52 -41
- data/ext/zookeeper_lib.c +90 -78
- data/ext/zookeeper_lib.h +0 -1
- data/java/zookeeper_base.rb +72 -154
- data/lib/zookeeper.rb +41 -15
- data/lib/zookeeper/common.rb +69 -9
- data/lib/zookeeper/common/queue_with_pipe.rb +78 -0
- data/lib/zookeeper/constants.rb +28 -0
- data/lib/zookeeper/em_client.rb +11 -144
- data/lib/zookeeper/exceptions.rb +4 -1
- data/slyphon-zookeeper.gemspec +1 -1
- data/spec/c_zookeeper_spec.rb +50 -0
- data/spec/chrooted_connection_spec.rb +121 -0
- data/spec/em_spec.rb +0 -61
- data/spec/shared/all_success_return_values.rb +10 -0
- data/spec/shared/connection_examples.rb +990 -0
- data/spec/spec_helper.rb +42 -19
- data/spec/zookeeper_spec.rb +8 -1033
- metadata +25 -11
data/lib/zookeeper/common.rb
CHANGED
@@ -4,15 +4,18 @@ module ZookeeperCommon
|
|
4
4
|
# sigh, i guess define this here?
|
5
5
|
ZKRB_GLOBAL_CB_REQ = -1
|
6
6
|
|
7
|
+
protected
|
7
8
|
def get_next_event(blocking=true)
|
8
|
-
|
9
|
-
|
9
|
+
@event_queue.pop(!blocking).tap do |event|
|
10
|
+
logger.debug { "#{self.class}##{__method__} delivering event #{event.inspect}" }
|
11
|
+
end
|
12
|
+
rescue ThreadError
|
13
|
+
nil
|
10
14
|
end
|
11
15
|
|
12
|
-
protected
|
13
16
|
def setup_call(meth_name, opts)
|
14
17
|
req_id = nil
|
15
|
-
@
|
18
|
+
@mutex.synchronize {
|
16
19
|
req_id = @current_req_id
|
17
20
|
@current_req_id += 1
|
18
21
|
setup_completion(req_id, meth_name, opts) if opts[:callback]
|
@@ -38,20 +41,65 @@ protected
|
|
38
41
|
end
|
39
42
|
|
40
43
|
def get_watcher(req_id)
|
41
|
-
@
|
44
|
+
@mutex.synchronize {
|
42
45
|
(req_id == ZKRB_GLOBAL_CB_REQ) ? @watcher_reqs[req_id] : @watcher_reqs.delete(req_id)
|
43
46
|
}
|
44
47
|
end
|
45
48
|
|
46
49
|
def get_completion(req_id)
|
47
|
-
@
|
50
|
+
@mutex.synchronize { @completion_reqs.delete(req_id) }
|
51
|
+
end
|
52
|
+
|
53
|
+
def setup_dispatch_thread!
|
54
|
+
logger.debug { "starting dispatch thread" }
|
55
|
+
@dispatcher ||= Thread.new do
|
56
|
+
while true
|
57
|
+
begin
|
58
|
+
dispatch_next_callback(get_next_event(true))
|
59
|
+
rescue QueueWithPipe::ShutdownException
|
60
|
+
logger.info { "dispatch thread exiting, got shutdown exception" }
|
61
|
+
break
|
62
|
+
rescue Exception => e
|
63
|
+
$stderr.puts ["#{e.class}: #{e.message}", e.backtrace.map { |n| "\t#{n}" }.join("\n")].join("\n")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
signal_dispatch_thread_exit!
|
67
|
+
end
|
48
68
|
end
|
69
|
+
|
70
|
+
# this method is part of the reopen/close code, and is responsible for
|
71
|
+
# shutting down the dispatch thread.
|
72
|
+
#
|
73
|
+
# @dispatcher will be nil when this method exits
|
74
|
+
#
|
75
|
+
def stop_dispatch_thread!
|
76
|
+
logger.debug { "#{self.class}##{__method__}" }
|
77
|
+
|
78
|
+
if @dispatcher
|
79
|
+
@mutex.synchronize do
|
80
|
+
event_queue.graceful_close!
|
49
81
|
|
50
|
-
|
51
|
-
|
52
|
-
|
82
|
+
# we now release the mutex so that dispatch_next_callback can grab it
|
83
|
+
# to do what it needs to do while delivering events
|
84
|
+
@dispatch_shutdown_cond.wait
|
53
85
|
|
86
|
+
@dispatcher.join
|
87
|
+
@dispatcher = nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def signal_dispatch_thread_exit!
|
93
|
+
@mutex.synchronize do
|
94
|
+
logger.debug { "dispatch thread exiting!" }
|
95
|
+
@dispatch_shutdown_cond.broadcast
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def dispatch_next_callback(hash)
|
54
100
|
return nil unless hash
|
101
|
+
|
102
|
+
Zookeeper.logger.debug { "get_next_event returned: #{prettify_event(hash).inspect}" }
|
55
103
|
|
56
104
|
is_completion = hash.has_key?(:rc)
|
57
105
|
|
@@ -96,4 +144,16 @@ protected
|
|
96
144
|
"Required arguments are: #{required.inspect}, but only the arguments #{args.keys.inspect} were supplied."
|
97
145
|
end
|
98
146
|
end
|
147
|
+
|
148
|
+
private
|
149
|
+
def prettify_event(hash)
|
150
|
+
hash.dup.tap do |h|
|
151
|
+
# pretty up the event display
|
152
|
+
h[:type] = ZookeeperConstants::EVENT_TYPE_NAMES.fetch(h[:type]) if h[:type]
|
153
|
+
h[:state] = ZookeeperConstants::STATE_NAMES.fetch(h[:state]) if h[:state]
|
154
|
+
h[:req_id] = :global_session if h[:req_id] == -1
|
155
|
+
end
|
156
|
+
end
|
99
157
|
end
|
158
|
+
|
159
|
+
require 'zookeeper/common/queue_with_pipe'
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module ZookeeperCommon
|
2
|
+
# Ceci n'est pas une pipe
|
3
|
+
class QueueWithPipe
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
def_delegators :@queue, :clear
|
7
|
+
|
8
|
+
# raised when close has been called, and pop() is performed
|
9
|
+
#
|
10
|
+
class ShutdownException < StandardError; end
|
11
|
+
|
12
|
+
# @private
|
13
|
+
KILL_TOKEN = Object.new unless defined?(KILL_TOKEN)
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
# r, w = IO.pipe
|
17
|
+
# @pipe = { :read => r, :write => w }
|
18
|
+
@queue = Queue.new
|
19
|
+
|
20
|
+
# with the EventMachine client, we want to let EM handle clearing the
|
21
|
+
# event pipe, so we set this to false
|
22
|
+
# @clear_reads_on_pop = true
|
23
|
+
|
24
|
+
@mutex = Mutex.new
|
25
|
+
@closed = false
|
26
|
+
@graceful = false
|
27
|
+
end
|
28
|
+
|
29
|
+
def push(obj)
|
30
|
+
logger.debug { "#{self.class}##{__method__} obj: #{obj.inspect}, kill_token? #{obj == KILL_TOKEN}" }
|
31
|
+
@queue.push(obj)
|
32
|
+
end
|
33
|
+
|
34
|
+
def pop(non_blocking=false)
|
35
|
+
raise ShutdownException if closed? # this may get us in trouble
|
36
|
+
|
37
|
+
rv = @queue.pop(non_blocking)
|
38
|
+
|
39
|
+
if rv == KILL_TOKEN
|
40
|
+
close
|
41
|
+
raise ShutdownException
|
42
|
+
end
|
43
|
+
|
44
|
+
rv
|
45
|
+
end
|
46
|
+
|
47
|
+
# close the queue and causes ShutdownException to be raised on waiting threads
|
48
|
+
def graceful_close!
|
49
|
+
@mutex.synchronize do
|
50
|
+
return if @graceful or @closed
|
51
|
+
logger.debug { "#{self.class}##{__method__} gracefully closing" }
|
52
|
+
@graceful = true
|
53
|
+
push(KILL_TOKEN)
|
54
|
+
end
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def close
|
59
|
+
@mutex.synchronize do
|
60
|
+
return if @closed
|
61
|
+
@closed = true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def closed?
|
66
|
+
@mutex.synchronize { !!@closed }
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
def clear_reads_on_pop?
|
71
|
+
@clear_reads_on_pop
|
72
|
+
end
|
73
|
+
|
74
|
+
def logger
|
75
|
+
Zookeeper.logger
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/zookeeper/constants.rb
CHANGED
@@ -18,6 +18,34 @@ module ZookeeperConstants
|
|
18
18
|
ZOO_CHILD_EVENT = 4
|
19
19
|
ZOO_SESSION_EVENT = -1
|
20
20
|
ZOO_NOTWATCHING_EVENT = -2
|
21
|
+
|
22
|
+
# only used by the C extension
|
23
|
+
ZOO_LOG_LEVEL_ERROR = 1
|
24
|
+
ZOO_LOG_LEVEL_WARN = 2
|
25
|
+
ZOO_LOG_LEVEL_INFO = 3
|
26
|
+
ZOO_LOG_LEVEL_DEBUG = 4
|
27
|
+
|
28
|
+
# used to find the name for a numeric event
|
29
|
+
# @private
|
30
|
+
EVENT_TYPE_NAMES = {
|
31
|
+
1 => :created,
|
32
|
+
2 => :deleted,
|
33
|
+
3 => :changed,
|
34
|
+
4 => :child,
|
35
|
+
-1 => :session,
|
36
|
+
-2 => :notwatching,
|
37
|
+
}
|
38
|
+
|
39
|
+
# used to pretty print the state name
|
40
|
+
# @private
|
41
|
+
STATE_NAMES = {
|
42
|
+
-112 => :expired_session,
|
43
|
+
-113 => :auth_failed,
|
44
|
+
0 => :closed,
|
45
|
+
1 => :connecting,
|
46
|
+
2 => :associating,
|
47
|
+
3 => :connected,
|
48
|
+
}
|
21
49
|
|
22
50
|
def print_events
|
23
51
|
puts "ZK events:"
|
data/lib/zookeeper/em_client.rb
CHANGED
@@ -13,6 +13,7 @@ module ZookeeperEM
|
|
13
13
|
@em_connection = nil
|
14
14
|
logger.debug { "ZookeeperEM::Client obj_id %x: init" % [object_id] }
|
15
15
|
super(*a, &b)
|
16
|
+
on_attached.succeed
|
16
17
|
end
|
17
18
|
|
18
19
|
# EM::DefaultDeferrable that will be called back when our em_connection has been detached
|
@@ -29,152 +30,18 @@ module ZookeeperEM
|
|
29
30
|
@on_attached
|
30
31
|
end
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
#
|
35
|
-
# if a block is given, it will be registered as a callback when the
|
36
|
-
# connection has been closed
|
37
|
-
#
|
38
|
-
def close(&block)
|
39
|
-
on_close(&block)
|
40
|
-
|
41
|
-
logger.debug { "#{self.class.name}: close called, closed? #{closed?} running? #{running?}" }
|
42
|
-
|
43
|
-
if @_running
|
44
|
-
@start_stop_mutex.synchronize do
|
45
|
-
@_running = false
|
46
|
-
end
|
47
|
-
|
48
|
-
if @em_connection
|
49
|
-
EM.next_tick do
|
50
|
-
@em_connection.detach do
|
51
|
-
logger.debug { "#{self.class.name}: connection unbound, continuing with shutdown" }
|
52
|
-
finish_closing
|
53
|
-
end
|
54
|
-
end
|
55
|
-
else
|
56
|
-
logger.debug { "#{self.class.name}: em_connection was never set up, finish closing" }
|
57
|
-
finish_closing
|
58
|
-
end
|
59
|
-
else
|
60
|
-
logger.debug { "#{self.class.name}: we are not running, so returning on_close deferred" }
|
61
|
-
end
|
62
|
-
|
63
|
-
on_close
|
64
|
-
end
|
65
|
-
|
66
|
-
# make this public as the ZKConnection object needs to call it
|
67
|
-
public :dispatch_next_callback
|
68
|
-
|
69
|
-
protected
|
70
|
-
# instead of setting up a dispatch thread here, we instead attach
|
71
|
-
# the #selectable_io to the event loop
|
72
|
-
def setup_dispatch_thread!
|
73
|
-
EM.schedule do
|
74
|
-
if running? and not closed?
|
75
|
-
begin
|
76
|
-
logger.debug { "adding EM.watch(#{selectable_io.inspect})" }
|
77
|
-
@em_connection = EM.watch(selectable_io, ZKConnection, self) { |cnx| cnx.notify_readable = true }
|
78
|
-
rescue Exception => e
|
79
|
-
$stderr.puts "caught exception from EM.watch(): #{e.inspect}"
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
33
|
+
def dispatch_next_callback(hash)
|
34
|
+
EM.schedule { super(hash) }
|
83
35
|
end
|
84
36
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
unless selectable_io.closed?
|
93
|
-
logger.debug { "calling close on selectable_io: #{selectable_io.inspect}" }
|
94
|
-
selectable_io.close
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
on_close.succeed
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
# this class is handed to EventMachine.watch to handle event dispatching
|
103
|
-
# when the queue has a message waiting. There's a pipe shared between
|
104
|
-
# the event thread managed by the queue implementation in C. It's made
|
105
|
-
# available to the ruby-space through the Zookeeper#selectable_io method.
|
106
|
-
# When the pipe is readable, that means there's an event waiting. We call
|
107
|
-
# dispatch_next_event and read a single byte off the pipe.
|
108
|
-
#
|
109
|
-
class ZKConnection < EM::Connection
|
110
|
-
|
111
|
-
def initialize(zk_client)
|
112
|
-
@zk_client = zk_client
|
113
|
-
end
|
114
|
-
|
115
|
-
def post_init
|
116
|
-
logger.debug { "post_init called" }
|
117
|
-
@attached = true
|
118
|
-
|
119
|
-
@on_unbind = EM::DefaultDeferrable.new.tap do |d|
|
120
|
-
d.callback do
|
121
|
-
logger.debug { "on_unbind deferred fired" }
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
# probably because of the way post_init works, unless we fire this
|
126
|
-
# callback in next_tick @em_connection in the client may not be set
|
127
|
-
# (which on_attached callbacks may be relying on)
|
128
|
-
EM.next_tick do
|
129
|
-
logger.debug { "firing on_attached callback" }
|
130
|
-
@zk_client.on_attached.succeed
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
# EM::DefaultDeferrable that will be called back when our em_connection has been detached
|
135
|
-
# and we've completed the close operation
|
136
|
-
def on_unbind(&block)
|
137
|
-
@on_unbind.callback(&block) if block
|
138
|
-
@on_unbind
|
139
|
-
end
|
140
|
-
|
141
|
-
def attached?
|
142
|
-
@attached
|
143
|
-
end
|
144
|
-
|
145
|
-
def unbind
|
146
|
-
on_unbind.succeed
|
147
|
-
end
|
148
|
-
|
149
|
-
def detach(&blk)
|
150
|
-
on_unbind(&blk)
|
151
|
-
return unless @attached
|
152
|
-
@attached = false
|
153
|
-
rval = super()
|
154
|
-
logger.debug { "#{self.class.name}: detached, rval: #{rval.inspect}" }
|
155
|
-
end
|
156
|
-
|
157
|
-
# we have an event waiting
|
158
|
-
def notify_readable
|
159
|
-
if @zk_client.running?
|
160
|
-
|
161
|
-
read_io_nb if @zk_client.dispatch_next_callback(false)
|
162
|
-
|
163
|
-
elsif attached?
|
164
|
-
logger.debug { "#{self.class.name}: @zk_client was not running? and attached? #{attached?}, detaching!" }
|
165
|
-
detach
|
37
|
+
# this is synchronous, but since the API still allows attaching to on_close,
|
38
|
+
# we just fake it here
|
39
|
+
def close(&block)
|
40
|
+
on_close(&block).tap do |d|
|
41
|
+
super()
|
42
|
+
d.succeed
|
166
43
|
end
|
167
44
|
end
|
168
|
-
|
169
|
-
|
170
|
-
def read_io_nb(size=1)
|
171
|
-
@io.read_nonblock(1)
|
172
|
-
rescue Errno::EWOULDBLOCK, Errno::EAGAIN, IOError
|
173
|
-
end
|
174
|
-
|
175
|
-
def logger
|
176
|
-
Zookeeper.logger
|
177
|
-
end
|
178
|
-
end
|
179
|
-
end
|
45
|
+
end # Client
|
46
|
+
end # ZookeeperEM
|
180
47
|
|
data/lib/zookeeper/exceptions.rb
CHANGED
@@ -27,7 +27,7 @@ module ZookeeperExceptions
|
|
27
27
|
ZNOTHING = -117
|
28
28
|
ZSESSIONMOVED = -118
|
29
29
|
|
30
|
-
class ZookeeperException <
|
30
|
+
class ZookeeperException < StandardError
|
31
31
|
class EverythingOk < ZookeeperException; end
|
32
32
|
class SystemError < ZookeeperException; end
|
33
33
|
class RunTimeInconsistency < ZookeeperException; end
|
@@ -58,6 +58,9 @@ module ZookeeperExceptions
|
|
58
58
|
class NotConnected < ZookeeperException; end
|
59
59
|
class ShuttingDownException < ZookeeperException; end
|
60
60
|
class DataTooLargeException < ZookeeperException; end
|
61
|
+
|
62
|
+
# yes, make an alias, this is the way zookeeper refers to it
|
63
|
+
ExpiredSession = SessionExpired
|
61
64
|
|
62
65
|
def self.by_code(code)
|
63
66
|
case code
|
data/slyphon-zookeeper.gemspec
CHANGED
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "slyphon-zookeeper"
|
6
|
-
s.version = '0.
|
6
|
+
s.version = '0.8.0.rc.1'
|
7
7
|
|
8
8
|
s.authors = ["Phillip Pearson", "Eric Maland", "Evan Weaver", "Brian Wickman", "Neil Conway", "Jonathan D. Simms"]
|
9
9
|
s.email = ["slyphon@gmail.com"]
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# tests the CZookeeper, obviously only available when running under MRI
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
if Module.const_defined?(:CZookeeper)
|
5
|
+
describe CZookeeper do
|
6
|
+
def pop_all_events
|
7
|
+
[].tap do |rv|
|
8
|
+
begin
|
9
|
+
rv << @event_queue.pop(non_blocking=true)
|
10
|
+
rescue ThreadError
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def wait_until_connected(timeout=2)
|
16
|
+
wait_until(timeout) { @czk.state == ZookeeperConstants::ZOO_CONNECTED_STATE }
|
17
|
+
end
|
18
|
+
|
19
|
+
describe do
|
20
|
+
before do
|
21
|
+
@event_queue = ZookeeperCommon::QueueWithPipe.new
|
22
|
+
@czk = CZookeeper.new('localhost:2181', @event_queue)
|
23
|
+
end
|
24
|
+
|
25
|
+
after do
|
26
|
+
@czk.close rescue Exception
|
27
|
+
@event_queue.close rescue Exception
|
28
|
+
end
|
29
|
+
|
30
|
+
it %[should be in connected state within a reasonable amount of time] do
|
31
|
+
wait_until_connected.should be_true
|
32
|
+
end
|
33
|
+
|
34
|
+
describe :after_connected do
|
35
|
+
before do
|
36
|
+
wait_until_connected.should be_true
|
37
|
+
end
|
38
|
+
|
39
|
+
it %[should have a connection event after being connected] do
|
40
|
+
event = wait_until(2) { @event_queue.pop }
|
41
|
+
event.should be
|
42
|
+
event[:req_id].should == ZookeeperCommon::ZKRB_GLOBAL_CB_REQ
|
43
|
+
event[:type].should == ZookeeperConstants::ZOO_SESSION_EVENT
|
44
|
+
event[:state].should == ZookeeperConstants::ZOO_CONNECTED_STATE
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|