log-courier 1.10.0 → 2.7.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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