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/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)