zkruby 3.4.3 → 3.4.4.rc3
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 +11 -0
- data/.yardopts +2 -0
- data/Gemfile +4 -0
- data/README.rdoc +15 -12
- data/Rakefile +26 -31
- data/jute/lib/hoe/jute.rb +1 -1
- data/jute/lib/jute/task.rb +62 -0
- data/lib/em_zkruby.rb +1 -1
- data/lib/zkruby/async_op.rb +193 -0
- data/lib/zkruby/client.rb +61 -46
- data/lib/zkruby/enum.rb +2 -2
- data/lib/zkruby/eventmachine.rb +54 -131
- data/lib/zkruby/protocol.rb +37 -101
- data/lib/zkruby/rubyio.rb +94 -206
- data/lib/zkruby/session.rb +273 -137
- data/lib/zkruby/util.rb +80 -95
- data/lib/zkruby/version.rb +5 -0
- data/lib/zkruby/zkruby.rb +0 -6
- data/spec/eventmachine_spec.rb +9 -30
- data/spec/server_helper.rb +13 -9
- data/spec/shared/basic.rb +36 -2
- data/spec/shared/binding.rb +2 -0
- data/spec/shared/chroot.rb +30 -0
- data/spec/shared/multi.rb +1 -1
- data/spec/shared/performance.rb +50 -0
- data/spec/shared/watch.rb +2 -2
- data/spec/spec_helper.rb +3 -3
- data/zkruby.gemspec +38 -0
- metadata +109 -112
- data/.gemtest +0 -0
- data/Manifest.txt +0 -39
- data.tar.gz.sig +0 -0
- metadata.gz.sig +0 -0
data/lib/zkruby/session.rb
CHANGED
@@ -1,8 +1,29 @@
|
|
1
1
|
require 'set'
|
2
|
+
require 'strand'
|
3
|
+
require 'strand/monitor'
|
4
|
+
#TODO Move async_op into this file
|
5
|
+
require 'zkruby/async_op'
|
2
6
|
module ZooKeeper
|
3
7
|
|
4
|
-
# Represents
|
8
|
+
# Represents a session that may span connect/disconnect
|
9
|
+
#
|
10
|
+
# @note this is a private API not intended for client use
|
5
11
|
class Session
|
12
|
+
# TODO this class is too big
|
13
|
+
|
14
|
+
# There are multiple threads of execution involved in a session
|
15
|
+
# Client threads - send requests
|
16
|
+
# Connection/Read thread
|
17
|
+
# EventLoop - callback execution
|
18
|
+
#
|
19
|
+
# All client activity is synchronised on this session as a monitor
|
20
|
+
# The read thread synchronises with client activity
|
21
|
+
# only around connection/disconnection
|
22
|
+
# ie any changes to @session_state which always happens in
|
23
|
+
# conjunction with processing all entries in @pending_queue
|
24
|
+
#
|
25
|
+
# All interaction with the event loop occurs via a Queue
|
26
|
+
include Strand::MonitorMixin
|
6
27
|
|
7
28
|
DEFAULT_TIMEOUT = 4
|
8
29
|
DEFAULT_CONNECT_DELAY = 0.2
|
@@ -16,24 +37,30 @@ module ZooKeeper
|
|
16
37
|
attr_reader :conn
|
17
38
|
attr_accessor :watcher
|
18
39
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
40
|
+
# @api zookeeper
|
41
|
+
# See {ZooKeeper.connect}
|
42
|
+
def initialize(addresses,options=nil)
|
43
|
+
super()
|
23
44
|
@addresses = parse_addresses(addresses)
|
24
45
|
parse_options(options)
|
25
46
|
|
26
|
-
#
|
27
|
-
|
28
|
-
@keeper_state = nil
|
47
|
+
# our transaction id
|
48
|
+
@xid=0
|
29
49
|
|
30
|
-
#
|
31
|
-
|
32
|
-
@client_state = :ready
|
50
|
+
# The state of the connection, nil, :disconnected, :connected, :closed, :expired
|
51
|
+
@session_state = nil
|
33
52
|
|
34
|
-
|
53
|
+
# The connection we'll send packets to
|
54
|
+
@conn = nil
|
55
|
+
|
56
|
+
# The list of pending requests
|
57
|
+
# When disconnected this builds up a list to send through when we are connected
|
58
|
+
# When connected represents the order in which we expect to see responses
|
35
59
|
@pending_queue = []
|
36
60
|
|
61
|
+
# Client state is :ready, :closing, :closed
|
62
|
+
@client_state = :ready
|
63
|
+
|
37
64
|
# Create the watch list
|
38
65
|
# hash by watch type of hashes by path of set of watchers
|
39
66
|
@watches = [ :children, :data, :exists ].inject({}) do |ws,wtype|
|
@@ -41,33 +68,14 @@ module ZooKeeper
|
|
41
68
|
ws
|
42
69
|
end
|
43
70
|
|
71
|
+
# the default watcher
|
44
72
|
@watcher = nil
|
45
73
|
|
46
74
|
@ping_logger = Slf4r::LoggerFacade.new("ZooKeeper::Session::Ping")
|
47
|
-
|
48
|
-
end
|
49
|
-
|
50
|
-
def chroot(path)
|
51
|
-
return @chroot + path
|
52
75
|
end
|
53
76
|
|
54
|
-
|
55
|
-
|
56
|
-
path.slice(@chroot.length..-1)
|
57
|
-
end
|
58
|
-
|
59
|
-
# close won't run your block if the connection is
|
60
|
-
# already closed, so this is how you can check
|
61
|
-
def closed?
|
62
|
-
@client_state == :closed
|
63
|
-
end
|
64
|
-
|
65
|
-
# Connection API - testing whether to send a ping
|
66
|
-
def connected?()
|
67
|
-
@keeper_state == :connected
|
68
|
-
end
|
69
|
-
|
70
|
-
# Connection API - Injects a new connection that is ready to receive records
|
77
|
+
# @api connection
|
78
|
+
# Injects a new connection that is ready to receive records
|
71
79
|
# @param conn that responds to #send_records(record...) and #disconnect()
|
72
80
|
def prime_connection(conn)
|
73
81
|
@conn = conn
|
@@ -76,96 +84,157 @@ module ZooKeeper
|
|
76
84
|
reset_watches()
|
77
85
|
end
|
78
86
|
|
79
|
-
|
80
|
-
#
|
81
|
-
# @param
|
87
|
+
# @api connection
|
88
|
+
# called when data is available, reads and processes one packet/event
|
89
|
+
# @param [IO] io
|
82
90
|
def receive_records(io)
|
83
|
-
case @
|
91
|
+
case @session_state
|
84
92
|
when :disconnected
|
85
93
|
complete_connection(io)
|
86
94
|
when :connected
|
87
95
|
process_reply(io)
|
88
96
|
else
|
89
|
-
logger.warn { "Receive packet for closed session #{@
|
97
|
+
logger.warn { "Receive packet for closed session #{@session_state}" }
|
90
98
|
end
|
91
99
|
end
|
92
100
|
|
93
|
-
#
|
101
|
+
# @api connection
|
102
|
+
# Connection API - testing whether to send a ping
|
103
|
+
def connected?()
|
104
|
+
@session_state == :connected
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
# @api connection
|
109
|
+
# called when no data has been received for #ping_interval
|
94
110
|
def ping()
|
95
|
-
if
|
111
|
+
if connected?
|
96
112
|
ping_logger.debug { "Ping send" }
|
97
113
|
hdr = Proto::RequestHeader.new(:xid => -2, :_type => 11)
|
98
|
-
conn.send_records(hdr)
|
114
|
+
conn.send_records(hdr)
|
99
115
|
end
|
100
116
|
end
|
101
117
|
|
102
|
-
#
|
118
|
+
# @api connection
|
119
|
+
# called when the connection has dropped from either end
|
103
120
|
def disconnected()
|
104
|
-
@
|
105
|
-
logger.info { "Disconnected id=#{@session_id}, keeper=:#{@keeper_state}, client=:#{@client_state}" }
|
121
|
+
logger.info { "Disconnected id=#{@session_id}, keeper=:#{@session_state}, client=:#{@client_state}" }
|
106
122
|
|
107
123
|
# We keep trying to reconnect until the session expiration time is reached
|
108
|
-
@disconnect_time = Time.now if @
|
124
|
+
@disconnect_time = Time.now if @session_state == :connected
|
109
125
|
time_since_first_disconnect = (Time.now - @disconnect_time)
|
110
126
|
|
111
|
-
if @
|
112
|
-
|
127
|
+
if @session_state == :closing
|
128
|
+
#We were expecting this disconnect
|
129
|
+
session_expired(:closed)
|
130
|
+
elsif time_since_first_disconnect > timeout
|
131
|
+
session_expired(:expired)
|
113
132
|
else
|
114
|
-
|
115
|
-
|
116
|
-
# if not, then we'll keep them and hope the next reconnect works
|
117
|
-
if @keeper_state == :connected
|
133
|
+
if @session_state == :connected
|
134
|
+
#first disconnect
|
118
135
|
clear_pending_queue(:disconnected)
|
119
136
|
invoke_watch(@watcher,KeeperState::DISCONNECTED,nil,WatchEvent::NONE) if @watcher
|
137
|
+
@conn = nil
|
120
138
|
end
|
121
|
-
@keeper_state = :disconnected
|
122
|
-
reconnect()
|
123
139
|
end
|
124
140
|
end
|
125
141
|
|
126
|
-
#
|
127
|
-
|
128
|
-
|
129
|
-
|
142
|
+
# @api zookeeper
|
143
|
+
# See {ZooKeeper.connect}
|
144
|
+
def start(client)
|
145
|
+
raise ProtocolError, "Already started!" unless @session_state.nil?
|
146
|
+
@session_state = :disconnected
|
130
147
|
@disconnect_time = Time.now
|
131
148
|
logger.debug("Starting new zookeeper client session")
|
132
|
-
|
149
|
+
@event_loop = EventLoop.new(client)
|
150
|
+
Strand.new {
|
151
|
+
begin
|
152
|
+
reconnect()
|
153
|
+
while active?
|
154
|
+
delay = rand() * @max_connect_delay
|
155
|
+
Strand.sleep(delay)
|
156
|
+
reconnect()
|
157
|
+
end
|
158
|
+
rescue Exception => ex
|
159
|
+
logger.error("Exception in connect loop",ex)
|
160
|
+
end
|
161
|
+
logger.debug("Session complete")
|
162
|
+
}
|
133
163
|
end
|
134
164
|
|
135
|
-
def queue_request(request,op,opcode,response=nil,watch_type=nil,watcher=nil,ptype=Packet,&callback)
|
136
|
-
raise Error.SESSION_EXPIRED, "Session expired due to client state #{@client_state}" unless @client_state == :ready
|
137
|
-
watch_type, watcher = resolve_watcher(watch_type,watcher)
|
138
165
|
|
139
|
-
|
166
|
+
# @api client
|
167
|
+
def chroot(path)
|
168
|
+
return @chroot if path == "/"
|
169
|
+
return @chroot + path
|
170
|
+
end
|
140
171
|
|
141
|
-
|
172
|
+
# @api client
|
173
|
+
def unchroot(path)
|
174
|
+
return path unless path
|
175
|
+
path.slice(@chroot.length..-1)
|
176
|
+
end
|
142
177
|
|
143
|
-
|
178
|
+
# @api client
|
179
|
+
def request(*args,&callback)
|
180
|
+
AsyncOp.new(@event_loop,callback) do |op|
|
181
|
+
queue_request(*args) do |error,response|
|
182
|
+
op.resume(error,response)
|
183
|
+
end
|
184
|
+
end
|
144
185
|
end
|
145
186
|
|
187
|
+
# @api client
|
146
188
|
def close(&callback)
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
close_packet = @close_packet
|
152
|
-
@client_state = :closing
|
153
|
-
|
154
|
-
# If there are other requests in flight, then we wait for them to finish
|
155
|
-
# before sending the close packet since it immediately causes the socket
|
156
|
-
# to close.
|
157
|
-
queue_close_packet_if_necessary()
|
158
|
-
@close_packet
|
159
|
-
when :closed, :closing
|
160
|
-
raise ProtocolError, "Already closed"
|
161
|
-
else
|
162
|
-
raise ProtocolError, "Unexpected state #{@client_state}"
|
189
|
+
AsyncOp.new(@event_loop,callback) do |op|
|
190
|
+
close_session() do |error,response|
|
191
|
+
op.resume(error,response)
|
192
|
+
end
|
163
193
|
end
|
164
194
|
end
|
165
195
|
|
166
196
|
private
|
197
|
+
def active?
|
198
|
+
[:connected,:disconnected].include?(@session_state)
|
199
|
+
end
|
200
|
+
|
201
|
+
def queue_request(request,op,opcode,response=nil,watch_type=nil,watcher=nil,ptype=Packet,&callback)
|
202
|
+
synchronize do
|
203
|
+
raise ProtocolError, "Client closed #{@client_state}" unless @client_state == :ready
|
204
|
+
raise Error.SESSION_EXPIRED, "Session has expired #{@session_state}" unless active?
|
205
|
+
watch_type, watcher = resolve_watcher(watch_type,watcher)
|
206
|
+
|
207
|
+
xid = next_xid
|
208
|
+
|
209
|
+
packet = ptype.new(xid,op,opcode,request,response,watch_type,watcher, callback)
|
210
|
+
|
211
|
+
queue_packet(packet)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def close_session(&callback)
|
216
|
+
synchronize do
|
217
|
+
if @client_state == :ready
|
218
|
+
if active?
|
219
|
+
# we keep the requested block in a close packet but we don't send it
|
220
|
+
# until we've received all pending reponses
|
221
|
+
@close_packet = ClosePacket.new(next_xid(),:close,-11,nil,nil,nil,nil,callback)
|
222
|
+
|
223
|
+
# but we can force a response by sending a ping
|
224
|
+
ping()
|
225
|
+
|
226
|
+
else
|
227
|
+
# We've already expired put the close callback on the event loop
|
228
|
+
@event_loop.invoke_close(callback,nil,true)
|
229
|
+
end
|
230
|
+
@client_state = :closed
|
231
|
+
else
|
232
|
+
raise ProtocolError, "Client already #{@client_state}"
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
167
237
|
attr_reader :watches
|
168
|
-
attr_reader :binding
|
169
238
|
|
170
239
|
def parse_addresses(addresses)
|
171
240
|
case addresses
|
@@ -201,42 +270,36 @@ module ZooKeeper
|
|
201
270
|
end
|
202
271
|
|
203
272
|
def reconnect()
|
204
|
-
|
205
273
|
#Rotate address
|
206
274
|
host,port = @addresses.shift
|
207
275
|
@addresses.push([host,port])
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
logger.debug { "Connecting id=#{@session_id} to #{host}:#{port} with delay=#{delay}, timeout=#{@connect_timeout}" }
|
212
|
-
binding.connect(host,port,delay,@connect_timeout)
|
276
|
+
logger.debug { "Connecting id=#{@session_id} to #{host}:#{port} with timeout=#{@connect_timeout}" }
|
277
|
+
connect(host,port,@connect_timeout)
|
213
278
|
end
|
214
279
|
|
215
|
-
|
216
280
|
def session_expired(reason=:expired)
|
217
|
-
|
218
|
-
|
219
|
-
invoke_response(*@close_packet.error(reason)) if @close_packet
|
220
|
-
|
221
|
-
if @client_state == :closed
|
222
|
-
logger.info { "Session closed id=#{@session_id}, keeper=:#{@keeper_state}, client=:#{@client_state}" }
|
281
|
+
if reason == :closed
|
282
|
+
logger.info { "Session closed id=#{@session_id}, keeper=:#{@session_state}, client=:#{@client_state}" }
|
223
283
|
else
|
224
|
-
logger.warn { "Session expired id=#{@session_id}, keeper=:#{@
|
284
|
+
logger.warn { "Session expired reason=#{reason} id=#{@session_id}, keeper=:#{@session_state}, client=:#{@client_state}" }
|
225
285
|
end
|
226
286
|
|
287
|
+
clear_pending_queue(reason)
|
288
|
+
#TODO Clients will want to distinguish between EXPIRED and CLOSED
|
227
289
|
invoke_watch(@watcher,KeeperState::EXPIRED,nil,WatchEvent::NONE) if @watcher
|
228
|
-
@
|
229
|
-
@client_state = :closed
|
290
|
+
@event_loop.stop()
|
230
291
|
end
|
231
292
|
|
232
293
|
def complete_connection(response)
|
233
294
|
result = Proto::ConnectResponse.read(response)
|
234
295
|
if (result.time_out <= 0)
|
235
296
|
#We're dead!
|
236
|
-
session_expired()
|
297
|
+
session_expired()
|
237
298
|
else
|
238
299
|
@timeout = result.time_out.to_f / 1000.0
|
239
|
-
@
|
300
|
+
@session_id = result.session_id
|
301
|
+
@session_passwd = result.passwd
|
302
|
+
logger.info { "Connected session_id=#{@session_id}, timeout=#{@time_out}, ping=#{@ping_interval}" }
|
240
303
|
|
241
304
|
# Why 2 / 7 of the timeout?. If a binding sees no server response in this period it is required to
|
242
305
|
# generate a ping request
|
@@ -244,14 +307,14 @@ module ZooKeeper
|
|
244
307
|
# so we are already more than half way through the session timeout
|
245
308
|
# and we need to give ourselves time to reconnect to another server
|
246
309
|
@ping_interval = @timeout * 2.0 / 7.0
|
247
|
-
@session_id = result.session_id
|
248
|
-
@session_passwd = result.passwd
|
249
|
-
logger.info { "Connected session_id=#{@session_id}, timeout=#{@time_out}, ping=#{@ping_interval}" }
|
250
310
|
|
251
|
-
|
252
|
-
|
311
|
+
synchronize do
|
312
|
+
logger.debug { "Sending #{@pending_queue.length} queued packets" }
|
313
|
+
@session_state = :connected
|
314
|
+
@pending_queue.each { |p| send_packet(p) }
|
315
|
+
send_close_packet_if_necessary()
|
316
|
+
end
|
253
317
|
|
254
|
-
queue_close_packet_if_necessary()
|
255
318
|
invoke_watch(@watcher,KeeperState::CONNECTED,nil,WatchEvent::NONE) if @watcher
|
256
319
|
end
|
257
320
|
end
|
@@ -261,7 +324,6 @@ module ZooKeeper
|
|
261
324
|
req.last_zxid_seen = @last_zxid_seen if @last_zxid_seen
|
262
325
|
req.session_id = @session_id if @session_id
|
263
326
|
req.passwd = @session_passwd if @session_passwd
|
264
|
-
|
265
327
|
conn.send_records(req)
|
266
328
|
end
|
267
329
|
|
@@ -296,6 +358,7 @@ module ZooKeeper
|
|
296
358
|
case header.xid.to_i
|
297
359
|
when -2
|
298
360
|
ping_logger.debug { "Ping reply" }
|
361
|
+
send_close_packet_if_necessary()
|
299
362
|
when -4
|
300
363
|
logger.debug { "Auth reply" }
|
301
364
|
session_expired(:auth_failed) unless header.err.to_i == 0
|
@@ -312,6 +375,13 @@ module ZooKeeper
|
|
312
375
|
# A normal packet reply. They should come in the order we sent them
|
313
376
|
# so we just match it to the packet at the front of the queue
|
314
377
|
packet = @pending_queue.shift
|
378
|
+
|
379
|
+
if packet == nil && @close_packet
|
380
|
+
packet = @close_packet
|
381
|
+
@close_packet = nil
|
382
|
+
@session_state = :closing
|
383
|
+
end
|
384
|
+
|
315
385
|
logger.debug { "Packet reply: #{packet.inspect}" }
|
316
386
|
|
317
387
|
if (packet.xid.to_i != header.xid.to_i)
|
@@ -324,9 +394,8 @@ module ZooKeeper
|
|
324
394
|
invoke_response(*packet.error(:disconnected))
|
325
395
|
@conn.disconnect()
|
326
396
|
else
|
327
|
-
|
328
397
|
@last_zxid_seen = header.zxid
|
329
|
-
|
398
|
+
|
330
399
|
callback, error, response, watch_type = packet.result(header.err.to_i)
|
331
400
|
invoke_response(callback, error, response, packet_io)
|
332
401
|
|
@@ -334,11 +403,11 @@ module ZooKeeper
|
|
334
403
|
@watches[watch_type][packet.path] << packet.watcher
|
335
404
|
logger.debug { "Registered #{packet.watcher} for type=#{watch_type} at #{packet.path}" }
|
336
405
|
end
|
337
|
-
|
406
|
+
send_close_packet_if_necessary()
|
338
407
|
end
|
339
408
|
end
|
340
|
-
end
|
341
409
|
|
410
|
+
end
|
342
411
|
|
343
412
|
def process_watch_notification(state,path,event)
|
344
413
|
|
@@ -347,17 +416,16 @@ module ZooKeeper
|
|
347
416
|
|
348
417
|
keeper_state = KeeperState.fetch(state)
|
349
418
|
|
350
|
-
watches = watch_types.inject(Set.new()) do |
|
419
|
+
watches = watch_types.inject(Set.new()) do |result, watch_type|
|
351
420
|
more_watches = @watches[watch_type].delete(path)
|
352
|
-
|
353
|
-
|
421
|
+
result.merge(more_watches) if more_watches
|
422
|
+
result
|
354
423
|
end
|
355
424
|
|
356
425
|
if watches.empty?
|
357
426
|
logger.warn { "Received notification for unregistered watch #{state} #{path} #{event}" }
|
358
427
|
end
|
359
428
|
watches.each { | watch | invoke_watch(watch,keeper_state,path,watch_event) }
|
360
|
-
|
361
429
|
end
|
362
430
|
|
363
431
|
def invoke_watch(watch,state,path,event)
|
@@ -369,39 +437,48 @@ module ZooKeeper
|
|
369
437
|
else
|
370
438
|
raise ProtocolError("Bad watcher #{watch}")
|
371
439
|
end
|
372
|
-
|
373
|
-
binding.invoke(callback,state,unchroot(path),event)
|
440
|
+
@event_loop.invoke(callback,state,unchroot(path),event)
|
374
441
|
end
|
375
442
|
|
376
443
|
def clear_pending_queue(reason)
|
377
|
-
|
378
|
-
|
444
|
+
synchronize do
|
445
|
+
@session_state = reason
|
446
|
+
@pending_queue.each { |p| invoke_response(*p.error(reason)) }
|
447
|
+
@pending_queue.clear()
|
448
|
+
if @close_packet
|
449
|
+
invoke_response(*@close_packet.error(reason))
|
450
|
+
@close_packet = nil
|
451
|
+
end
|
452
|
+
end
|
379
453
|
end
|
380
454
|
|
381
|
-
def
|
382
|
-
|
455
|
+
def send_close_packet_if_necessary
|
456
|
+
# We don't need to synchronize this because the creation of
|
457
|
+
# the close packet was synchronized and after that all
|
458
|
+
# client requests are rejected
|
459
|
+
# we can receive watch and ping notifications after this
|
460
|
+
# but the server drops the connection as soon as this
|
461
|
+
# packet arrives
|
462
|
+
if @pending_queue.empty? && @session_state == :connected && @close_packet
|
383
463
|
logger.debug { "Sending close packet!" }
|
384
|
-
@
|
385
|
-
queue_packet(@close_packet)
|
386
|
-
@close_packet = nil
|
464
|
+
send_packet(@close_packet)
|
387
465
|
end
|
388
466
|
end
|
389
467
|
|
390
468
|
def invoke_response(callback,error,response,packet_io = nil)
|
391
469
|
if callback
|
392
|
-
|
393
470
|
result = if error
|
394
471
|
nil
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
472
|
+
elsif response.respond_to?(:read) && packet_io
|
473
|
+
response.read(packet_io)
|
474
|
+
elsif response
|
475
|
+
response
|
476
|
+
else
|
477
|
+
nil
|
478
|
+
end
|
402
479
|
|
403
480
|
logger.debug { "Invoking response cb=#{callback} err=#{error} resp=#{result}" }
|
404
|
-
|
481
|
+
@event_loop.invoke(callback,error,result)
|
405
482
|
end
|
406
483
|
end
|
407
484
|
|
@@ -420,12 +497,10 @@ module ZooKeeper
|
|
420
497
|
[watch_type,watcher]
|
421
498
|
end
|
422
499
|
|
423
|
-
|
424
500
|
def queue_packet(packet)
|
501
|
+
logger.debug { "Queuing: #{packet.inspect}" }
|
425
502
|
@pending_queue.push(packet)
|
426
|
-
|
427
|
-
|
428
|
-
if @keeper_state == :connected
|
503
|
+
if @session_state == :connected
|
429
504
|
send_packet(packet)
|
430
505
|
end
|
431
506
|
end
|
@@ -440,6 +515,67 @@ module ZooKeeper
|
|
440
515
|
conn.send_records(*records)
|
441
516
|
end
|
442
517
|
|
518
|
+
class EventLoop
|
519
|
+
include Slf4r::Logger
|
520
|
+
|
521
|
+
def initialize(client)
|
522
|
+
@event_queue = Strand::Queue.new()
|
523
|
+
|
524
|
+
@alive = true
|
525
|
+
@event_thread = Strand.new() do
|
526
|
+
logger.debug { "Starting event loop" }
|
527
|
+
Strand.current[:name] = "ZooKeeper::EventLoop"
|
528
|
+
Strand.current[CURRENT] = [ client ]
|
529
|
+
begin
|
530
|
+
pop_event_queue until dead?
|
531
|
+
logger.debug { "Finished event loop" }
|
532
|
+
rescue Exception => ex
|
533
|
+
logger.error("Uncaught exception in event loop",ex)
|
534
|
+
end
|
535
|
+
end
|
536
|
+
end
|
443
537
|
|
538
|
+
def dead?
|
539
|
+
!@alive
|
540
|
+
end
|
541
|
+
|
542
|
+
# @api async_op
|
543
|
+
def pop_event_queue
|
544
|
+
#We're alive until we get a nil result from #stop
|
545
|
+
queued = @event_queue.pop if @alive
|
546
|
+
if queued
|
547
|
+
begin
|
548
|
+
callback,*args = queued
|
549
|
+
callback.call(*args)
|
550
|
+
rescue StandardError => ex
|
551
|
+
logger.error("Uncaught error in async callback", ex)
|
552
|
+
end
|
553
|
+
else
|
554
|
+
@alive = false
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
558
|
+
# @api session
|
559
|
+
def invoke(*args)
|
560
|
+
@event_queue.push(args)
|
561
|
+
end
|
562
|
+
|
563
|
+
def invoke_close(callback,*args)
|
564
|
+
Strand.new do
|
565
|
+
@event_thread.join()
|
566
|
+
callback.call(*args)
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
# @api session
|
571
|
+
def stop
|
572
|
+
@event_queue.push(nil)
|
573
|
+
end
|
574
|
+
|
575
|
+
# @api async_op
|
576
|
+
def current?
|
577
|
+
Strand.current == @event_thread
|
578
|
+
end
|
579
|
+
end
|
444
580
|
end # Session
|
445
581
|
end
|