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/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
- # JRuby cannot do non-blocking connects, which means there is
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
- sock.setsockopt(SOL_TCP, TCP_NODELAY,[0].pack("i_"))
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
- resp = IO.select(nil, [sock], nil, timeout)
66
+
58
67
  begin
59
- sock.connect_nonblock(sockaddr)
60
- rescue Errno::ECONNREFUSED
61
- logger.warn("Connection refused to #{ host }:#{ port }")
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
- @socket = sock
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, but only within
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
- while socket
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
- disconnect()
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
- socket = @socket
143
+ def read_loop(socket)
144
+ Thread.current[:name] = "ZooKeeper::RubyIO::ReadLoop"
145
+ session.prime_connection(self)
99
146
  ping = 0
100
- while socket # effectively forever
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],[],[],@session.ping_interval)
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 ; @session.synchronize { @session.ping() }
159
+ when 1 ; session.ping()
113
160
  when 2
114
- logger.debug{"No response to ping in #{@session.ping_interval}*2"}
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
- def disconnect()
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
- @session.synchronize { @session.receive_records(packet_io) }
179
+ session.receive_records(packet_io)
141
180
  end
142
181
 
143
- end #Class connection
144
-
145
- class Binding
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
- logger.warn( "Exception in event thread", ex )
170
- return true
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
- private
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
- raise error if error
304
- result
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)