zkruby 3.4.3 → 3.4.4.rc3
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|