omq-ffi 0.1.2 → 0.1.3

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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/omq/ffi/engine.rb +76 -9
  3. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b7ad0a7f4790f7f0cb08c671209d5c22929d9a8cef2c64e31d0433222592c321
4
- data.tar.gz: f3cd090c1b603a14e73ed2417b3b72fd4c389e4b78321b8068ed3379bfe311e0
3
+ metadata.gz: 644d87fb23e1290cbe1a879a705c835e16858479305a2ebf453b996a10e7cf77
4
+ data.tar.gz: bcd37e11f05619293cb8776b229c2317e488647ae6cfb4b7e401b72e6c614c7e
5
5
  SHA512:
6
- metadata.gz: 503838fbd444270a982a105ca270a380411ddd8b5038f156de76b0fe66343bfd0c548dbc8d27e9e71144cbead45524e97f495cf4b39122938e814760159f23b6
7
- data.tar.gz: 1675ee0f7c5d2a46ec2d31142db8f908ec3e041ae77a770ee14bbb5ac1ef7fc75e58ee756dda9a1096b95d1ea0ac819744666a60888fd47a8b44f878dc6d65c7
6
+ metadata.gz: e10cb300fdebdfe538149a55e901f0594c1f13d69a2d58adbde5674bb632e90dcfaa2fa11749364df762a7220f4a92ad8cfa09f186bd2869e4e6167f1f4792c1
7
+ data.tar.gz: b6971619da183b23a3969a6afdaa7ac8a09de80485253b2f1a82cad8d8d81af865d88c227297507f4c945c82bd2596dbc2dd8f85bd8d92338c4a1168b9f4e4f4
@@ -28,8 +28,14 @@ module OMQ
28
28
  attr_reader :peer_connected
29
29
  # @return [Async::Promise] resolved when all peers have disconnected
30
30
  attr_reader :all_peers_gone
31
+ # @return [Async::Task, nil] root of the engine's task tree
32
+ attr_reader :parent_task
31
33
  # @param value [Boolean] enables or disables automatic reconnection
32
34
  attr_writer :reconnect_enabled
35
+ # @note Monitor events are not yet emitted by the FFI backend; these
36
+ # writers exist so Socket#monitor can attach without raising. Wiring
37
+ # libzmq's zmq_socket_monitor is a TODO.
38
+ attr_writer :monitor_queue, :verbose_monitor
33
39
 
34
40
  # Routing stub that delegates subscribe/unsubscribe/join/leave to
35
41
  # libzmq socket options via the I/O thread.
@@ -168,6 +174,12 @@ module OMQ
168
174
 
169
175
  # Closes the socket and shuts down the I/O thread.
170
176
  #
177
+ # Honors `options.linger`:
178
+ # nil → wait forever for Ruby-side queue to drain into libzmq
179
+ # and for libzmq's own LINGER to flush to the network
180
+ # 0 → drop anything not yet in libzmq's kernel buffers, close fast
181
+ # N → up to N seconds for drain + N + 1s grace for join
182
+ #
171
183
  # @return [void]
172
184
  def close
173
185
  return if @closed
@@ -175,7 +187,15 @@ module OMQ
175
187
  if @io_thread
176
188
  @cmd_queue.push([:stop])
177
189
  wake_io_thread
178
- @io_thread.join(2)
190
+ linger = @options.linger
191
+ if linger.nil?
192
+ @io_thread.join
193
+ elsif linger.zero?
194
+ @io_thread.join(0.5) # fast path: zmq_close is non-blocking with LINGER=0
195
+ else
196
+ @io_thread.join(linger + 1.0)
197
+ end
198
+ @io_thread.kill if @io_thread.alive? # hard stop if deadline exceeded
179
199
  else
180
200
  # IO thread never started — close socket directly
181
201
  L.zmq_close(@zmq_socket)
@@ -308,14 +328,48 @@ module OMQ
308
328
  rescue
309
329
  # Thread exit
310
330
  ensure
311
- # Flush any messages still queued on the Ruby side into libzmq
312
- # before zmq_close, so the socket's LINGER period covers them.
313
- drain_sends rescue nil
331
+ # Drain Ruby-side send queue into libzmq, bounded by linger deadline.
332
+ # Then re-apply current linger to libzmq (user may have changed it
333
+ # after apply_options ran in initialize) and zmq_close uses it to
334
+ # flush libzmq's own queue to TCP.
335
+ drain_sends_with_deadline(zmq_fd_io, shutdown_deadline) rescue nil
336
+ linger_ms = @options.linger.nil? ? -1 : (@options.linger * 1000).to_i
337
+ set_int_option(L::ZMQ_LINGER, linger_ms) rescue nil
314
338
  zmq_fd_io&.close rescue nil
315
339
  L.zmq_close(@zmq_socket)
316
340
  end
317
341
 
318
342
 
343
+ # Returns a monotonic deadline for the Ruby-side drain phase, or nil
344
+ # for infinite, or the current clock for "drop immediately".
345
+ #
346
+ def shutdown_deadline
347
+ linger = @options.linger
348
+ return nil if linger.nil?
349
+ now = Async::Clock.now
350
+ return now if linger.zero?
351
+ now + linger
352
+ end
353
+
354
+
355
+ # Retries drain_sends with IO.select until either the Ruby-side queue
356
+ # is empty or the deadline is hit. nil deadline = wait forever.
357
+ #
358
+ def drain_sends_with_deadline(zmq_fd_io, deadline)
359
+ loop do
360
+ drain_sends
361
+ break if @pending_send.nil? && @send_queue.empty?
362
+ if deadline
363
+ remaining = deadline - Async::Clock.now
364
+ break if remaining <= 0
365
+ IO.select([zmq_fd_io], nil, nil, [remaining, 0.1].min)
366
+ else
367
+ IO.select([zmq_fd_io], nil, nil, 0.1)
368
+ end
369
+ end
370
+ end
371
+
372
+
319
373
  def zmq_has_events?
320
374
  @events_buf ||= ::FFI::MemoryPointer.new(:int)
321
375
  @events_len ||= ::FFI::MemoryPointer.new(:size_t).tap { |p| p.write(:size_t, ::FFI.type_size(:int)) }
@@ -344,16 +398,16 @@ module OMQ
344
398
  return false
345
399
  when :bind
346
400
  rc = L.zmq_bind(@zmq_socket, args[0])
347
- result&.push(rc >= 0 ? nil : RuntimeError.new(L.zmq_strerror(L.zmq_errno)))
401
+ result&.push(rc >= 0 ? nil : syscall_error)
348
402
  when :connect
349
403
  rc = L.zmq_connect(@zmq_socket, args[0])
350
- result&.push(rc >= 0 ? nil : RuntimeError.new(L.zmq_strerror(L.zmq_errno)))
404
+ result&.push(rc >= 0 ? nil : syscall_error)
351
405
  when :disconnect
352
406
  rc = L.zmq_disconnect(@zmq_socket, args[0])
353
- result&.push(rc >= 0 ? nil : RuntimeError.new(L.zmq_strerror(L.zmq_errno)))
407
+ result&.push(rc >= 0 ? nil : syscall_error)
354
408
  when :unbind
355
409
  rc = L.zmq_unbind(@zmq_socket, args[0])
356
- result&.push(rc >= 0 ? nil : RuntimeError.new(L.zmq_strerror(L.zmq_errno)))
410
+ result&.push(rc >= 0 ? nil : syscall_error)
357
411
  when :set_identity
358
412
  set_bytes_option(L::ZMQ_IDENTITY, args[0])
359
413
  result&.push(nil)
@@ -477,7 +531,9 @@ module OMQ
477
531
  def apply_options
478
532
  set_int_option(L::ZMQ_SNDHWM, @options.send_hwm)
479
533
  set_int_option(L::ZMQ_RCVHWM, @options.recv_hwm)
480
- set_int_option(L::ZMQ_LINGER, (@options.linger * 1000).to_i)
534
+ # linger: nil = infinite (-1), N seconds → N*1000 ms, 0 = immediate
535
+ linger_ms = @options.linger.nil? ? -1 : (@options.linger * 1000).to_i
536
+ set_int_option(L::ZMQ_LINGER, linger_ms)
481
537
  set_int_option(L::ZMQ_CONFLATE, @options.conflate ? 1 : 0)
482
538
 
483
539
  if @options.identity && !@options.identity.empty?
@@ -531,6 +587,17 @@ module OMQ
531
587
  end
532
588
 
533
589
 
590
+ # Builds an Errno::XXX exception from the current zmq_errno so callers
591
+ # can rescue the same classes they would from the pure-Ruby backend
592
+ # (e.g. `Errno::EADDRINUSE`, `Errno::ECONNREFUSED`). Falls back to a
593
+ # plain SystemCallError when the errno is libzmq-specific.
594
+ #
595
+ def syscall_error
596
+ errno = L.zmq_errno
597
+ SystemCallError.new(L.zmq_strerror(errno), errno)
598
+ end
599
+
600
+
534
601
  def extract_tcp_port(endpoint)
535
602
  return nil unless endpoint
536
603
  port = endpoint.split(":").last.to_i
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omq-ffi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger