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/rubyio.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'socket'
|
2
2
|
require 'thread'
|
3
|
-
require 'monitor'
|
4
3
|
|
5
4
|
# Binding over standard ruby sockets
|
6
5
|
#
|
@@ -23,6 +22,16 @@ require 'monitor'
|
|
23
22
|
# All interaction with the session is synchronized
|
24
23
|
#
|
25
24
|
# Client synchronous code is implemented with a condition variable that waits on the callback/errback
|
25
|
+
#
|
26
|
+
|
27
|
+
# JRuby does not define Errno::NOERROR
|
28
|
+
unless defined? Errno::NOERROR
|
29
|
+
class Errno::NOERROR < SystemCallError
|
30
|
+
Errno = 0
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
26
35
|
module ZooKeeper::RubyIO
|
27
36
|
|
28
37
|
class Connection
|
@@ -30,56 +39,92 @@ module ZooKeeper::RubyIO
|
|
30
39
|
include Slf4r::Logger
|
31
40
|
include Socket::Constants
|
32
41
|
|
42
|
+
HAS_NONBLOCKING_CONNECT = RUBY_PLATFORM != "java"|| Gem::Version.new(JRUBY_VERSION.dup) >= Gem::Version.new("1.6.7")
|
43
|
+
SOL_TCP = IPPROTO_TCP unless defined? ::Socket::SOL_TCP
|
44
|
+
|
45
|
+
attr_reader :session
|
46
|
+
|
33
47
|
def initialize(host,port,timeout,session)
|
34
48
|
@session = session
|
35
49
|
@write_queue = Queue.new()
|
36
50
|
|
37
|
-
|
38
|
-
# no way to properly implement the connection-timeout
|
39
|
-
# See http://jira.codehaus.org/browse/JRUBY-5165
|
40
|
-
# In any case this should be encapsulated in TCPSocket.open(host,port,timeout)
|
41
|
-
if RUBY_PLATFORM == "java"
|
42
|
-
begin
|
43
|
-
sock = TCPSocket.new(host,port.to_i)
|
44
|
-
rescue Errno::ECONNREFUSED
|
45
|
-
logger.warn("TCP Connection refused to #{host}:#{port}")
|
46
|
-
sock = nil
|
47
|
-
end
|
48
|
-
else
|
51
|
+
if HAS_NONBLOCKING_CONNECT
|
49
52
|
addr = Socket.getaddrinfo(host, nil)
|
50
53
|
sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
|
51
54
|
sock.setsockopt(SOL_SOCKET, SO_LINGER, [0,-1].pack("ii"))
|
52
|
-
|
55
|
+
begin
|
56
|
+
sock.setsockopt(SOL_TCP, TCP_NODELAY,[0].pack("i_"))
|
57
|
+
rescue
|
58
|
+
# JRuby defines SOL_TCP, but it doesn't work
|
59
|
+
sock.setsockopt(IPPROTO_TCP, TCP_NODELAY,[0].pack("i_"))
|
60
|
+
end
|
61
|
+
|
53
62
|
sockaddr = Socket.pack_sockaddr_in(port, addr[0][3])
|
54
63
|
begin
|
55
64
|
sock.connect_nonblock(sockaddr)
|
56
65
|
rescue Errno::EINPROGRESS
|
57
|
-
|
66
|
+
|
58
67
|
begin
|
59
|
-
|
60
|
-
|
61
|
-
|
68
|
+
read,write,errors = IO.select(nil, [sock], nil, timeout)
|
69
|
+
optval = sock.getsockopt(Socket::SOL_SOCKET,Socket::SO_ERROR)
|
70
|
+
sockerr = (optval.unpack "i")[0]
|
71
|
+
rescue Exception => ex
|
72
|
+
#JRuby raises Connection Refused instead of populating error array
|
73
|
+
logger.warn( "Exception from non blocking select", ex )
|
74
|
+
sockerr=-1
|
75
|
+
end
|
76
|
+
|
77
|
+
if sockerr == Errno::NOERROR::Errno
|
78
|
+
begin
|
79
|
+
sock.connect_nonblock(sockaddr)
|
80
|
+
rescue Errno::EISCONN
|
81
|
+
#Woohoo! we're connected
|
82
|
+
rescue Exception => ex
|
83
|
+
logger.warn( "Exception after successful connect",ex )
|
84
|
+
sock = nil
|
85
|
+
end
|
86
|
+
else
|
87
|
+
if sockerr == Errno::ECONNREFUSED::Errno
|
88
|
+
logger.warn("Connection refused to #{ host }:#{ port }")
|
89
|
+
else
|
90
|
+
logger.warn("Connection to #{ host }:#{ port } failed: #{sockerr}")
|
91
|
+
end
|
62
92
|
sock = nil
|
63
|
-
rescue Errno::EISCONN
|
64
93
|
end
|
65
94
|
end
|
95
|
+
else
|
96
|
+
# JRuby prior to 1.6.7 cannot do non-blocking connects, which means there is
|
97
|
+
# no way to properly implement the connection-timeout
|
98
|
+
# See http://jira.codehaus.org/browse/JRUBY-5165
|
99
|
+
# In any case this should be encapsulated in TCPSocket.open(host,port,timeout)
|
100
|
+
logger.warn { "Using blocking connect (JRuby < 1.6.7)" }
|
101
|
+
begin
|
102
|
+
sock = TCPSocket.new(host,port.to_i)
|
103
|
+
rescue Errno::ECONNREFUSED
|
104
|
+
logger.warn("TCP Connection refused to #{host}:#{port}")
|
105
|
+
sock = nil
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
if sock
|
110
|
+
Thread.new(sock) { |sock| write_loop(sock) }
|
111
|
+
read_loop(sock)
|
112
|
+
disconnect(sock)
|
66
113
|
end
|
67
|
-
|
68
|
-
Thread.new(sock) { |sock| write_loop(sock) } if sock
|
114
|
+
session.disconnected()
|
69
115
|
end
|
70
116
|
|
71
|
-
# This is called from random client threads
|
72
|
-
# a @session.synchronized() block
|
117
|
+
# This is called from random client threads
|
73
118
|
def send_data(data)
|
74
119
|
@write_queue.push(data)
|
75
120
|
end
|
76
121
|
|
77
|
-
# Since this runs in its very own thread
|
122
|
+
# Since this runs in its very own thread and has no timers
|
78
123
|
# we can use boring blocking IO
|
79
124
|
def write_loop(socket)
|
80
125
|
Thread.current[:name] = "ZooKeeper::RubyIO::WriteLoop"
|
81
126
|
begin
|
82
|
-
|
127
|
+
until socket.closed?
|
83
128
|
data = @write_queue.pop()
|
84
129
|
if socket.write(data) != data.length()
|
85
130
|
#TODO - will this really ever happen
|
@@ -87,35 +132,39 @@ module ZooKeeper::RubyIO
|
|
87
132
|
end
|
88
133
|
logger.debug { "Sending: " + data.unpack("H*")[0] }
|
89
134
|
end
|
135
|
+
logger.debug { "Write loop finished" }
|
90
136
|
rescue Exception => ex
|
91
137
|
logger.warn("Exception in write loop",ex)
|
92
|
-
|
138
|
+
# Make sure we break out of the read loop
|
139
|
+
disconnect(socket)
|
93
140
|
end
|
94
|
-
|
95
141
|
end
|
96
142
|
|
97
|
-
def read_loop()
|
98
|
-
|
143
|
+
def read_loop(socket)
|
144
|
+
Thread.current[:name] = "ZooKeeper::RubyIO::ReadLoop"
|
145
|
+
session.prime_connection(self)
|
99
146
|
ping = 0
|
100
|
-
|
147
|
+
until socket.closed?
|
101
148
|
begin
|
102
149
|
data = socket.read_nonblock(1024)
|
103
150
|
logger.debug { "Received (#{data.length})" + data.unpack("H*")[0] }
|
104
151
|
receive_data(data)
|
105
152
|
ping = 0
|
106
153
|
rescue IO::WaitReadable
|
107
|
-
select_result = IO.select([socket],[],[]
|
154
|
+
select_result = IO.select([socket],[],[],session.ping_interval)
|
108
155
|
unless select_result
|
109
156
|
ping += 1
|
110
157
|
# two timeouts in a row mean we need to send a ping
|
111
158
|
case ping
|
112
|
-
when 1 ;
|
159
|
+
when 1 ; session.ping()
|
113
160
|
when 2
|
114
|
-
logger.
|
161
|
+
logger.warn{"No response to ping in #{session.ping_interval}*2"}
|
115
162
|
break
|
116
163
|
end
|
117
164
|
end
|
118
165
|
rescue EOFError
|
166
|
+
# This is how we expect to end - send a close packet and the
|
167
|
+
# server closes the socket
|
119
168
|
logger.debug { "EOF reading from socket" }
|
120
169
|
break
|
121
170
|
rescue Exception => ex
|
@@ -123,188 +172,27 @@ module ZooKeeper::RubyIO
|
|
123
172
|
break
|
124
173
|
end
|
125
174
|
end
|
126
|
-
disconnect()
|
127
175
|
end
|
128
176
|
|
129
|
-
|
130
|
-
socket = @socket
|
131
|
-
@socket = nil
|
132
|
-
socket.close if socket
|
133
|
-
rescue Exception => ex
|
134
|
-
#oh well
|
135
|
-
logger.debug("Exception closing socket",ex)
|
136
|
-
end
|
137
|
-
|
138
|
-
# Protocol requirement
|
177
|
+
# @api protocol
|
139
178
|
def receive_records(packet_io)
|
140
|
-
|
179
|
+
session.receive_records(packet_io)
|
141
180
|
end
|
142
181
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
include Slf4r::Logger
|
147
|
-
attr_reader :session
|
148
|
-
def self.available?
|
149
|
-
true
|
150
|
-
end
|
151
|
-
|
152
|
-
def self.context(&context_block)
|
153
|
-
yield Thread
|
154
|
-
end
|
155
|
-
|
156
|
-
def initialize()
|
157
|
-
@event_queue = Queue.new()
|
158
|
-
end
|
159
|
-
|
160
|
-
def pop_event_queue()
|
161
|
-
queued = @event_queue.pop()
|
162
|
-
return false unless queued
|
163
|
-
logger.debug { "Invoking #{queued[0]}" }
|
164
|
-
callback,*args = queued
|
165
|
-
callback.call(*args)
|
166
|
-
logger.debug { "Completed #{queued[0]}" }
|
167
|
-
return true
|
182
|
+
private
|
183
|
+
def disconnect(socket)
|
184
|
+
socket.close if socket and !socket.closed?
|
168
185
|
rescue Exception => ex
|
169
|
-
|
170
|
-
|
171
|
-
end
|
172
|
-
|
173
|
-
def start(client,session)
|
174
|
-
@session = session
|
175
|
-
@session.extend(MonitorMixin)
|
176
|
-
|
177
|
-
# start the event thread
|
178
|
-
@event_thread = Thread.new() do
|
179
|
-
Thread.current[:name] = "ZooKeeper::RubyIO::EventLoop"
|
180
|
-
|
181
|
-
# In this thread, the current client is always this client!
|
182
|
-
Thread.current[ZooKeeper::CURRENT] = [client]
|
183
|
-
loop do
|
184
|
-
break unless pop_event_queue()
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
# and the read thread
|
189
|
-
Thread.new() do
|
190
|
-
begin
|
191
|
-
Thread.current[:name] = "ZooKeeper::RubyIO::ReadLoop"
|
192
|
-
conn = session.synchronize { session.start(); session.conn() } # will invoke connect
|
193
|
-
loop do
|
194
|
-
break unless conn
|
195
|
-
conn.read_loop()
|
196
|
-
conn = session.synchronize { session.disconnected(); session.conn() }
|
197
|
-
end
|
198
|
-
#event of death
|
199
|
-
logger.debug("Pushing nil (event of death) to event queue")
|
200
|
-
@event_queue.push(nil)
|
201
|
-
rescue Exception => ex
|
202
|
-
logger.error( "Exception in session thread", ex )
|
203
|
-
end
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
# session callback, IO thread
|
208
|
-
def connect(host,port,delay,timeout)
|
209
|
-
sleep(delay)
|
210
|
-
conn = Connection.new(host,port,timeout,session)
|
211
|
-
session.synchronize() { session.prime_connection(conn) }
|
212
|
-
end
|
213
|
-
|
214
|
-
|
215
|
-
def close(&callback)
|
216
|
-
op = AsyncOp.new(self,&callback)
|
217
|
-
|
218
|
-
session.synchronize do
|
219
|
-
session.close() do |error,response|
|
220
|
-
op.resume(error,response)
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
op
|
225
|
-
|
226
|
-
end
|
227
|
-
|
228
|
-
def queue_request(*args,&callback)
|
229
|
-
|
230
|
-
op = AsyncOp.new(self,&callback)
|
231
|
-
|
232
|
-
session.synchronize do
|
233
|
-
session.queue_request(*args) do |error,response|
|
234
|
-
op.resume(error,response)
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
op
|
239
|
-
end
|
240
|
-
|
241
|
-
def event_thread?
|
242
|
-
Thread.current.equal?(@event_thread)
|
243
|
-
end
|
244
|
-
|
245
|
-
def invoke(*args)
|
246
|
-
@event_queue.push(args)
|
247
|
-
end
|
248
|
-
|
249
|
-
end #Binding
|
250
|
-
|
251
|
-
class AsyncOp < ::ZooKeeper::AsyncOp
|
252
|
-
|
253
|
-
def initialize(binding,&callback)
|
254
|
-
@mutex = Monitor.new
|
255
|
-
@cv = @mutex.new_cond()
|
256
|
-
@callback = callback
|
257
|
-
@rubyio = binding
|
258
|
-
end
|
259
|
-
|
260
|
-
def resume(error,response)
|
261
|
-
mutex.synchronize do
|
262
|
-
@error = error
|
263
|
-
@result = nil
|
264
|
-
begin
|
265
|
-
@result = @callback.call(response) unless error
|
266
|
-
rescue StandardError => ex
|
267
|
-
@error = ex
|
268
|
-
end
|
269
|
-
|
270
|
-
if @error && @errback
|
271
|
-
begin
|
272
|
-
@result = @errback.call(@error)
|
273
|
-
@error = nil
|
274
|
-
rescue StandardError => ex
|
275
|
-
@error = ex
|
276
|
-
end
|
277
|
-
end
|
278
|
-
|
279
|
-
cv.signal()
|
280
|
-
end
|
186
|
+
#oh well
|
187
|
+
logger.debug("Exception closing socket",ex)
|
281
188
|
end
|
282
189
|
|
283
|
-
|
284
|
-
attr_reader :mutex, :cv, :error, :result
|
285
|
-
|
286
|
-
def set_error_handler(errback)
|
287
|
-
@errback = errback
|
288
|
-
end
|
289
|
-
|
290
|
-
def wait_value()
|
291
|
-
if @rubyio.event_thread?
|
292
|
-
#Waiting in the event thread (eg made a synchronous call inside a callback)
|
293
|
-
#Keep processing events until we are resumed
|
294
|
-
until defined?(@result)
|
295
|
-
break unless @rubyio.pop_event_queue()
|
296
|
-
end
|
297
|
-
else
|
298
|
-
mutex.synchronize do
|
299
|
-
cv.wait()
|
300
|
-
end
|
301
|
-
end
|
190
|
+
end #Class connection
|
302
191
|
|
303
|
-
|
304
|
-
|
192
|
+
module Binding
|
193
|
+
def connect(host,port,timeout)
|
194
|
+
Connection.new(host,port,timeout,self)
|
305
195
|
end
|
306
196
|
end
|
197
|
+
end
|
307
198
|
|
308
|
-
end #ZooKeeper::RubyIO
|
309
|
-
# Add our binding
|
310
|
-
ZooKeeper.add_binding(ZooKeeper::RubyIO::Binding)
|