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.
- checksums.yaml +4 -4
- data/lib/log-courier/client.rb +141 -181
- data/lib/log-courier/client_tcp.rb +117 -87
- data/lib/log-courier/event_queue.rb +18 -23
- data/lib/log-courier/server.rb +32 -58
- data/lib/log-courier/server_tcp.rb +174 -120
- metadata +9 -26
- data/lib/log-courier/server_zmq.rb +0 -400
- data/lib/log-courier/zmq_qpoll.rb +0 -324
@@ -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
|