log-courier 1.10.0 → 2.7.0

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.
@@ -1,400 +0,0 @@
1
- # encoding: utf-8
2
-
3
- # Copyright 2014-2019 Jason Woods and Contributors.
4
- #
5
- # Licensed under the Apache License, Version 2.0 (the "License");
6
- # you may not use this file except in compliance with the License.
7
- # You may obtain a copy of the License at
8
- #
9
- # http://www.apache.org/licenses/LICENSE-2.0
10
- #
11
- # Unless required by applicable law or agreed to in writing, software
12
- # distributed under the License is distributed on an "AS IS" BASIS,
13
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- # See the License for the specific language governing permissions and
15
- # limitations under the License.
16
-
17
- require 'thread'
18
- require 'log-courier/zmq_qpoll'
19
-
20
- module LogCourier
21
- # ZMQ transport implementation for the server
22
- class ServerZmq
23
- class ZMQError < StandardError; end
24
-
25
- class << self
26
- @print_zmq_versions = false
27
-
28
- def print_zmq_versions(logger)
29
- return if @print_zmq_versions || logger.nil?
30
-
31
- libversion = LibZMQ.version
32
- libversion = "#{libversion[:major]}.#{libversion[:minor]}.#{libversion[:patch]}"
33
-
34
- logger.info 'libzmq', :version => libversion
35
- logger.info 'ffi-rzmq-core', :version => LibZMQ::VERSION
36
- logger.info 'ffi-rzmq', :version => ZMQ.version
37
-
38
- @print_zmq_versions = true
39
- end
40
- end
41
-
42
- attr_reader :port
43
-
44
- def initialize(options = {})
45
- @options = {
46
- logger: nil,
47
- transport: 'zmq',
48
- port: 0,
49
- address: '0.0.0.0',
50
- curve_secret_key: nil,
51
- max_packet_size: 10_485_760,
52
- peer_recv_queue: 10,
53
- }.merge!(options)
54
-
55
- @logger = @options[:logger]
56
-
57
- self.class.print_zmq_versions @logger
58
-
59
- if @options[:transport] == 'zmq'
60
- fail "input/courier: Transport 'zmq' requires libzmq version >= 4" unless LibZMQ.version4?
61
-
62
- fail 'input/courier: \'curve_secret_key\' is required' if @options[:curve_secret_key].nil?
63
-
64
- fail 'input/courier: \'curve_secret_key\' must be a valid 40 character Z85 encoded string' if @options[:curve_secret_key].length != 40 || !z85validate(@options[:curve_secret_key])
65
- end
66
-
67
- begin
68
- @context = ZMQ::Context.new
69
- fail ZMQError, 'context creation error: ' + ZMQ::Util.error_string if @context.nil?
70
-
71
- # Router so we can send multiple responses
72
- @socket = @context.socket(ZMQ::ROUTER)
73
- fail ZMQError, 'socket creation error: ' + ZMQ::Util.error_string if @socket.nil?
74
-
75
- rc = @socket.setsockopt(ZMQ::LINGER, 0)
76
- fail ZMQError, 'setsockopt LINGER failure: ' + ZMQ::Util.error_string unless ZMQ::Util.resultcode_ok?(rc)
77
-
78
- if @options[:transport] == 'zmq'
79
- rc = @socket.setsockopt(ZMQ::CURVE_SERVER, 1)
80
- fail 'setsockopt CURVE_SERVER failure: ' + ZMQ::Util.error_string unless ZMQ::Util.resultcode_ok?(rc)
81
-
82
- rc = @socket.setsockopt(ZMQ::CURVE_SECRETKEY, @options[:curve_secret_key])
83
- fail 'setsockopt CURVE_SECRETKEY failure: ' + ZMQ::Util.error_string unless ZMQ::Util.resultcode_ok?(rc)
84
- end
85
-
86
- bind = 'tcp://' + @options[:address] + (@options[:port] == 0 ? ':*' : ':' + @options[:port].to_s)
87
- rc = @socket.bind(bind)
88
- fail 'failed to bind at ' + bind + ': ' + ZMQ::Util.error_string unless ZMQ::Util.resultcode_ok?(rc)
89
-
90
- # Lookup port number that was allocated in case it was set to 0
91
- endpoint = ''
92
- rc = @socket.getsockopt(ZMQ::LAST_ENDPOINT, endpoint)
93
- fail 'getsockopt LAST_ENDPOINT failure: ' + ZMQ::Util.error_string unless ZMQ::Util.resultcode_ok?(rc) && %r{\Atcp://(?:.*):(?<endpoint_port>\d+)\0\z} =~ endpoint
94
- @port = endpoint_port.to_i
95
-
96
- if @options[:port] == 0
97
- @logger.warn 'Ephemeral port allocated', :transport => @options[:transport], :port => @port unless @logger.nil?
98
- end
99
- rescue => e
100
- raise "input/courier: Failed to initialise: #{e}"
101
- end
102
-
103
- # TODO: Implement workers option by receiving on a ROUTER and proxying to a DEALER, with workers connecting to the DEALER
104
-
105
- # TODO: Make this send queue configurable?
106
- @send_queue = EventQueue.new 2
107
- @factory = ClientFactoryZmq.new(@options, @send_queue)
108
-
109
- # Setup poller
110
- @poller = ZMQPoll::ZMQPoll.new(@context)
111
- @poller.register_socket @socket, ZMQ::POLLIN
112
- @poller.register_queue_to_socket @send_queue, @socket
113
- end
114
-
115
- def run(&block)
116
- errors = 0
117
- loop do
118
- begin
119
- @poller.poll(5_000) do |socket, r, w|
120
- next if socket != @socket
121
- next if !r
122
-
123
- receive &block
124
- end
125
- errors = 0
126
- rescue ZMQPoll::ZMQError => e
127
- @logger.warn e, :hint => 'ZMQ poll failure' unless @logger.nil?
128
- next
129
- rescue ZMQPoll::ZMQTerm
130
- # Fall into shutdown signal, context was terminated
131
- # This can happen in JRuby - it seems to run finalisers too early
132
- fail ShutdownSignal
133
- rescue ZMQPoll::TimeoutError
134
- # We'll let ZeroMQ manage reconnections and new connections
135
- # There is no point in us doing any form of reconnect ourselves
136
- next
137
- end
138
- end
139
- return
140
- rescue ShutdownSignal
141
- # Shutting down
142
- @logger.warn 'Server shutting down' unless @logger.nil?
143
- return
144
- rescue StandardError, NativeException => e
145
- # Some other unknown problem
146
- @logger.warn e, :hint => 'Unknown error, shutting down' unless @logger.nil?
147
- return
148
- ensure
149
- @poller.shutdown
150
- @factory.shutdown
151
- @socket.close
152
- @context.terminate
153
- end
154
-
155
- private
156
-
157
- def z85validate(z85)
158
- # ffi-rzmq does not implement decode - but we want to validate during startup
159
- decoded = FFI::MemoryPointer.from_string(' ' * (8 * z85.length / 10))
160
- ret = LibZMQ.zmq_z85_decode decoded, z85
161
- return false if ret.nil?
162
- true
163
- end
164
-
165
- def receive(&block)
166
- # Try to receive a message
167
- data = []
168
- rc = @socket.recv_strings(data, ZMQ::DONTWAIT)
169
- unless ZMQ::Util.resultcode_ok?(rc)
170
- fail ZMQError, 'recv_string error: ' + ZMQ::Util.error_string if ZMQ::Util.errno != ZMQ::EAGAIN
171
- end
172
-
173
- # Save the source information that appears before the null messages
174
- source = []
175
- source.push data.shift until data.length == 0 || data[0] == ''
176
-
177
- if data.length == 0
178
- @logger.warn 'Invalid message: no data', :source_length => source.length unless @logger.nil?
179
- return
180
- elsif data.length == 1
181
- @logger.warn 'Invalid message: empty data', :source_length => source.length unless @logger.nil?
182
- return
183
- end
184
-
185
- # Drop the null message separator
186
- data.shift
187
-
188
- if data.length != 1
189
- @logger.warn 'Invalid message: multipart unexpected', :source_length => source.length, :data_length => data.length unless @logger.nil?
190
- if !@logger.nil? && @logger.debug?
191
- i = 0
192
- parts = {}
193
- data.each do |msg|
194
- i += 1
195
- parts[i] = "#{part.length}:[#{msg[0..31].gsub(/[^[:print:]]/, '.')}]"
196
- end
197
- @logger.debug 'Data', parts
198
- end
199
- return
200
- end
201
-
202
- @factory.deliver source, data.first, &block
203
- return
204
- end
205
- end
206
-
207
- class ClientFactoryZmq
208
- attr_reader :options
209
- attr_reader :send_queue
210
-
211
- def initialize(options, send_queue)
212
- @options = options
213
- @logger = @options[:logger]
214
-
215
- @send_queue = send_queue
216
- @index = {}
217
- @client_threads = {}
218
- @mutex = Mutex.new
219
- end
220
-
221
- def shutdown
222
- # Stop other threads from try_drop collisions
223
- client_threads = @mutex.synchronize do
224
- client_threads = @client_threads
225
- @client_threads = {}
226
- client_threads
227
- end
228
-
229
- client_threads.each_value do |thr|
230
- thr.raise ShutdownSignal
231
- end
232
-
233
- client_threads.each_value(&:join)
234
- return
235
- end
236
-
237
- def deliver(source, data, &block)
238
- # Find the handling thread
239
- # We separate each source into threads so that each thread can respond
240
- # with partial ACKs if we hit a slow down
241
- # If we processed in a single thread, we'd only be able to respond to
242
- # a single client with partial ACKs
243
- @mutex.synchronize do
244
- index = @index
245
- source.each do |identity|
246
- index[identity] = {} if !index.key?(identity)
247
- index = index[identity]
248
- end
249
-
250
- if !index.key?('')
251
- source_str = source.map do |s|
252
- s.each_byte.map do |b|
253
- b.to_s(16).rjust(2, '0')
254
- end
255
- end.join
256
-
257
- @logger.info 'New source', :source => source_str unless @logger.nil?
258
-
259
- # Create the client and associated thread
260
- client = ClientZmq.new(self, source, source_str) do
261
- try_drop source, source_str
262
- end
263
-
264
- thread = Thread.new do
265
- client.run &block
266
- end
267
-
268
- @client_threads[thread] = thread
269
-
270
- index[''] = {
271
- 'client' => client,
272
- 'thread' => thread,
273
- }
274
- end
275
-
276
- # Existing thread, throw on the queue, if not enough room (timeout) drop the message
277
- begin
278
- index['']['client'].push data, 0
279
- rescue LogCourier::TimeoutError
280
- # TODO: Log a warning about this?
281
- end
282
- end
283
- return
284
- end
285
-
286
- private
287
-
288
- def try_drop(source, source_str)
289
- # This is called when a client goes idle, to cleanup resources
290
- # We may tie this into zmq monitor
291
- @mutex.synchronize do
292
- index = @index
293
- parents = []
294
- source.each do |identity|
295
- if !index.key?(identity)
296
- @logger.warn 'Unknown idle source failed to shutdown', :source => source_str unless @logger.nil?
297
- break
298
- end
299
- parents.push [index, identity]
300
- index = index[identity]
301
- end
302
-
303
- if !index.key?('')
304
- @logger.warn 'Unknown idle source failed to shutdown', :source => source_str unless @logger.nil?
305
- break
306
- end
307
-
308
- # Don't allow drop if we have messages in the queue
309
- if index['']['client'].length != 0
310
- @logger.warn 'Failed idle source shutdown as message queue is not empty', :source => source_str unless @logger.nil?
311
- return false
312
- end
313
-
314
- @logger.info 'Idle source shutting down', :source => source_str unless @logger.nil?
315
-
316
- # Delete the entry
317
- @client_threads.delete(index['']['thread'])
318
- index.delete('')
319
-
320
- # Cleanup orphaned leafs
321
- parents.reverse_each do |path|
322
- path[0].delete(path[1]) if path[0][path[1]].length == 0
323
- end
324
- end
325
-
326
- return true
327
- end
328
- end
329
-
330
- class ClientZmq < EventQueue
331
- def initialize(factory, source, source_str, &try_drop)
332
- @factory = factory
333
- @logger = @factory.options[:logger]
334
- @send_queue = @factory.send_queue
335
- @source = source
336
- @source_str = source_str
337
- @try_drop = try_drop
338
-
339
- # Setup the queue for receiving events to process
340
- super @factory.options[:peer_recv_queue]
341
- end
342
-
343
- def run(&block)
344
- loop do
345
- begin
346
- # TODO: Make timeout configurable?
347
- data = self.pop(30)
348
- recv(data, &block)
349
- rescue TimeoutError
350
- # Try to clean up resources - if we fail, new messages have arrived
351
- retry if !@try_drop.call(@source)
352
- break
353
- end
354
- end
355
- return
356
- rescue ShutdownSignal
357
- # Shutting down
358
- @logger.info 'Source shutting down', :source => @source_str unless @logger.nil?
359
- return
360
- rescue StandardError, NativeException => e
361
- # Some other unknown problem
362
- @logger.warn e, :hint => 'Unknown error, connection aborted', :source => @source_str unless @logger.nil?
363
- raise e
364
- end
365
-
366
- def send(signature, message)
367
- data = signature + [message.length].pack('N') + message
368
- @send_queue.push @source + ['', data]
369
- return
370
- end
371
-
372
- def add_fields(event)
373
- end
374
-
375
- private
376
-
377
- def recv(data)
378
- if data.length < 8
379
- @logger.warn 'Invalid message: not enough data', :data_length => data.length, :source => @source_str unless @logger.nil?
380
- return
381
- end
382
-
383
- # Unpack the header
384
- signature, length = data.unpack('A4N')
385
-
386
- # Verify length
387
- if data.length - 8 != length
388
- @logger.warn 'Invalid message: data has invalid length', :data_length => data.length - 8, :encoded_length => length, :source => @source_str unless @logger.nil?
389
- return
390
- elsif length > @factory.options[:max_packet_size]
391
- @logger.warn 'Invalid message: packet too large', :size => length, :max_packet_size => @options[:max_packet_size], :source => @source_str unless @logger.nil?
392
- return
393
- end
394
-
395
- # Yield the parts
396
- yield signature, data[8, length], self
397
- return
398
- end
399
- end
400
- end
@@ -1,324 +0,0 @@
1
- # encoding: utf-8
2
-
3
- # Copyright 2014-2019 Jason Woods and Contributors.
4
- #
5
- # Licensed under the Apache License, Version 2.0 (the "License");
6
- # you may not use this file except in compliance with the License.
7
- # You may obtain a copy of the License at
8
- #
9
- # http://www.apache.org/licenses/LICENSE-2.0
10
- #
11
- # Unless required by applicable law or agreed to in writing, software
12
- # distributed under the License is distributed on an "AS IS" BASIS,
13
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- # See the License for the specific language governing permissions and
15
- # limitations under the License.
16
-
17
- begin
18
- require 'ffi-rzmq'
19
- require 'ffi-rzmq/version'
20
- require 'ffi-rzmq-core/version'
21
- rescue LoadError => e
22
- raise "ZMQPoll could not initialise: #{e}"
23
- end
24
-
25
- module ZMQPoll
26
- class ZMQError < StandardError; end
27
- class ZMQTerm < StandardError; end
28
- class TimeoutError < StandardError; end
29
-
30
- class ZMQPoll
31
- def initialize(context, logger=nil)
32
- @logger = logger
33
- @context = context
34
- @poller = ZMQ::Poller.new
35
- @sockets = []
36
- @socket_to_socket = []
37
- @handlers = {}
38
- @queues = {}
39
- end
40
-
41
- def readables
42
- @poller.readables
43
- end
44
-
45
- def writables
46
- @poller.writables
47
- end
48
-
49
- def shutdown
50
- @queues.each_key do |queue|
51
- deregister_queue queue
52
- end
53
-
54
- @socket_to_socket.each do |socket|
55
- _close_socket_to_socket socket
56
- end
57
-
58
- @sockets.each do |socket|
59
- socket.close
60
- end
61
- return
62
- end
63
-
64
- def register_socket(socket, flags)
65
- @poller.register socket, flags
66
- return
67
- end
68
-
69
- def deregister_socket(socket)
70
- return if @handlers.key?(socket)
71
-
72
- @poller.delete socket
73
- return
74
- end
75
-
76
- def register_queue_to_socket(queue, socket)
77
- s2s_state = _create_socket_to_socket(socket)
78
-
79
- state = {
80
- state: s2s_state,
81
- mutex: Mutex.new,
82
- shutdown: false,
83
- }
84
-
85
- state[:thread] = Thread.new do
86
- loop do
87
- data = queue.pop
88
- break if data.nil?
89
- begin
90
- send s2s_state[:sender], data
91
- rescue TimeoutError
92
- state[:mutex].synchronize do
93
- break if state[:shutdown]
94
- end
95
- retry
96
- end
97
- end
98
- end
99
-
100
- @queues[queue] = state
101
- return
102
- end
103
-
104
- def deregister_queue(queue)
105
- return if !@queues.key?(queue)
106
-
107
- # Push nil so if we're idle we jump into action and exit
108
- # But also set shutdown to try so if we're mid-send and timeout, we exit
109
- @queues[queue][:mutex].synchronize do
110
- queue.push nil
111
- @queues[queue][:shutdown] = true
112
- end
113
- @queues[queue][:thread].join
114
-
115
- _close_socket_to_socket @queues[queue][:state]
116
- @queues.delete queue
117
- return
118
- end
119
-
120
- def create_socket_to_socket(socket)
121
- state = _create_socket_to_socket(socket)
122
- @socket_to_socket[state[:sender]] = state
123
- state[:sender]
124
- end
125
-
126
- def close_socket_to_socket(socket)
127
- return if !@socket_to_socket.include?(socket)
128
- state = @socket_to_socket[socket]
129
- @socket_to_socket.delete socket
130
- _close_socket_to_socket(state)
131
- return
132
- end
133
-
134
- def poll(timeout)
135
- if @poller.size == 0
136
- fail ZMQError, 'poll run called with zero socket/queues'
137
- end
138
-
139
- rc = @poller.poll(timeout)
140
- unless ZMQ::Util.resultcode_ok?(rc)
141
- # If we get ETERM or ENOTSOCK - we may have hit JRuby bug where finalisers are called too early
142
- # We throw it so the caller knows NOT to retry and hit a bad loop
143
- # The downside is - this Jruby bug will cause an inevitable deadlock
144
- # The finaliser for the context will have run before finalisers for sockets (random order?)
145
- # The resulting ZMQ context termination will block, waiting for sockets to be closed
146
- # However, when closing a socket, ffi-rzmq will attempt to remove it's finaliser
147
- # This removal will deadlock as it attempts to lock the finaliser list, which is locked by the
148
- # thread that is running the ZMQ context termination, which is blocked...
149
- # TODO: Raise issue in JRuby github and try to track down why finalisers run while code is running
150
- # if the exit! call is made
151
- fail ZMQTerm, 'poll error: ' + ZMQ::Util.error_string if ZMQ::Util.errno == ZMQ::ETERM || ZMQ::Util.errno == ZMQ::ENOTSOCK
152
- fail ZMQError, 'poll error: ' + ZMQ::Util.error_string
153
- end
154
-
155
- return if rc == 0
156
-
157
- ready = (@poller.readables|@poller.writables)
158
-
159
- ready.each do |socket|
160
- if @handlers.key?(socket)
161
- __send__ @handlers[socket][:callback], @handlers[socket]
162
- end
163
-
164
- yield socket, @poller.readables.include?(socket), @poller.writables.include?(socket)
165
- end
166
-
167
- return
168
- end
169
-
170
- private
171
-
172
- def _create_socket_to_socket(socket)
173
- receiver = @context.socket(ZMQ::PULL)
174
- fail ZMQError, 'socket creation error: ' + ZMQ::Util.error_string if receiver.nil?
175
-
176
- rc = receiver.setsockopt(ZMQ::LINGER, 0)
177
- fail ZMQError, 'setsockopt LINGER failure: ' + ZMQ::Util.error_string unless ZMQ::Util.resultcode_ok?(rc)
178
-
179
- rc = receiver.bind("inproc://zmqpollreceiver-#{receiver.hash}")
180
- fail ZMQError, 'bind error: ' + ZMQ::Util.error_string unless ZMQ::Util.resultcode_ok?(rc)
181
-
182
- sender = @context.socket(ZMQ::PUSH)
183
- fail ZMQError, 'socket creation error: ' + ZMQ::Util.error_string if sender.nil?
184
-
185
- rc = sender.setsockopt(ZMQ::LINGER, 0)
186
- fail ZMQError, 'setsockopt LINGER failure: ' + ZMQ::Util.error_string unless ZMQ::Util.resultcode_ok?(rc)
187
-
188
- rc = sender.connect("inproc://zmqpollreceiver-#{receiver.hash}")
189
- fail ZMQError, 'bind error: ' + ZMQ::Util.error_string unless ZMQ::Util.resultcode_ok?(rc)
190
-
191
- state = {
192
- :callback => :handle_socket_to_socket,
193
- :sender => sender,
194
- :receiver => receiver,
195
- :socket => socket,
196
- :buffer => nil,
197
- :send_ok => false,
198
- :recv_ok => false,
199
- }
200
-
201
- @poller.register receiver, ZMQ::POLLIN
202
- @poller.register socket, ZMQ::POLLOUT
203
- @handlers[receiver] = state
204
- @handlers[socket] = state
205
-
206
- @sockets.push sender
207
-
208
- state
209
- end
210
-
211
- def _close_socket_to_socket(state)
212
- @sockets.delete state[:sender]
213
-
214
- @poller.delete state[:receiver]
215
- @poller.delete state[:socket]
216
-
217
- state[:sender].close
218
- state[:receiver].close
219
-
220
- @handlers.delete state[:receiver]
221
- @handlers.delete state[:socket]
222
-
223
- return
224
- end
225
-
226
- def handle_socket_to_socket(state)
227
- state[:recv_ok] = @poller.readables.include?(state[:receiver]) || state[:recv_ok]
228
- state[:send_ok] = @poller.writables.include?(state[:socket]) || state[:send_ok]
229
-
230
- loop do
231
- if state[:send_ok] && !state[:buffer].nil?
232
- begin
233
- send state[:socket], state[:buffer]
234
- rescue TimeoutError
235
- end
236
- state[:buffer] = nil if state[:buffer].length == 0
237
- state[:send_ok] = false
238
- end
239
-
240
- break if !state[:recv_ok]
241
-
242
- if state[:recv_ok] && state[:buffer].nil?
243
- begin
244
- state[:buffer] = recv(state[:receiver])
245
- rescue TimeoutError
246
- end
247
- state[:recv_ok] = false
248
- end
249
-
250
- break if !state[:send_ok]
251
- end
252
-
253
- if state[:recv_ok]
254
- @poller.deregister state[:receiver], ZMQ::POLLIN
255
- else
256
- @poller.register state[:receiver], ZMQ::POLLIN
257
- end
258
-
259
- if state[:send_ok]
260
- @poller.deregister state[:socket], ZMQ::POLLOUT
261
- else
262
- @poller.register state[:socket], ZMQ::POLLOUT
263
- end
264
-
265
- return
266
- end
267
-
268
- def recv(socket)
269
- data = []
270
-
271
- poll_eagain(socket, ZMQ::POLLIN, 5) do
272
- # recv_strings appears to be safe, ZMQ documents that a client will either
273
- # receive 0 parts or all parts
274
- socket.recv_strings(data, ZMQ::DONTWAIT)
275
- end
276
-
277
- data
278
- end
279
-
280
- def send(socket, data)
281
- while data.length != 1
282
- send_part socket, data.shift, true
283
- end
284
- send_part socket, data.shift
285
- return
286
- end
287
-
288
- def send_part(socket, data, more=false)
289
- poll_eagain(socket, ZMQ::POLLOUT, 5) do
290
- # Try to send a message but never block
291
- # We could use send_strings but it is vague on if ZMQ can return an
292
- # error midway through sending parts...
293
- socket.send_string(data, (more ? ZMQ::SNDMORE : 0) | ZMQ::DONTWAIT)
294
- end
295
-
296
- return
297
- end
298
-
299
- def poll_eagain(socket, flag, timeout, &block)
300
- poller = nil
301
- timeout = Time.now.to_i + timeout
302
- loop do
303
- rc = block.call()
304
- break if ZMQ::Util.resultcode_ok?(rc)
305
- if ZMQ::Util.errno != ZMQ::EAGAIN
306
- fail ZMQError, 'message receive failed: ' + ZMQ::Util.error_string if flag == ZMQ::POLLIN
307
- fail ZMQError, 'message send failed: ' + ZMQ::Util.error_string
308
- end
309
-
310
- # Wait for send to become available, handling timeouts
311
- if poller.nil?
312
- poller = ZMQ::Poller.new
313
- poller.register socket, flag
314
- end
315
-
316
- while poller.poll(1_000) == 0
317
- # Using this inner while triggers pollThreadEvents in JRuby which checks for Thread.raise immediately
318
- fail TimeoutError while Time.now.to_i >= timeout
319
- end
320
- end
321
- return
322
- end
323
- end
324
- end