omq 0.9.0 → 0.11.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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +129 -0
  3. data/README.md +28 -3
  4. data/lib/omq/channel.rb +5 -5
  5. data/lib/omq/client_server.rb +10 -10
  6. data/lib/omq/engine.rb +702 -0
  7. data/lib/omq/options.rb +48 -0
  8. data/lib/omq/pair.rb +4 -4
  9. data/lib/omq/peer.rb +5 -5
  10. data/lib/omq/pub_sub.rb +18 -18
  11. data/lib/omq/push_pull.rb +6 -6
  12. data/lib/omq/queue_interface.rb +73 -0
  13. data/lib/omq/radio_dish.rb +6 -6
  14. data/lib/omq/reactor.rb +128 -0
  15. data/lib/omq/readable.rb +44 -0
  16. data/lib/omq/req_rep.rb +8 -8
  17. data/lib/omq/router_dealer.rb +8 -8
  18. data/lib/omq/routing/channel.rb +83 -0
  19. data/lib/omq/routing/client.rb +56 -0
  20. data/lib/omq/routing/dealer.rb +57 -0
  21. data/lib/omq/routing/dish.rb +78 -0
  22. data/lib/omq/routing/fan_out.rb +140 -0
  23. data/lib/omq/routing/gather.rb +46 -0
  24. data/lib/omq/routing/pair.rb +86 -0
  25. data/lib/omq/routing/peer.rb +101 -0
  26. data/lib/omq/routing/pub.rb +60 -0
  27. data/lib/omq/routing/pull.rb +46 -0
  28. data/lib/omq/routing/push.rb +81 -0
  29. data/lib/omq/routing/radio.rb +150 -0
  30. data/lib/omq/routing/rep.rb +101 -0
  31. data/lib/omq/routing/req.rb +65 -0
  32. data/lib/omq/routing/round_robin.rb +168 -0
  33. data/lib/omq/routing/router.rb +110 -0
  34. data/lib/omq/routing/scatter.rb +82 -0
  35. data/lib/omq/routing/server.rb +101 -0
  36. data/lib/omq/routing/sub.rb +78 -0
  37. data/lib/omq/routing/xpub.rb +72 -0
  38. data/lib/omq/routing/xsub.rb +83 -0
  39. data/lib/omq/routing.rb +66 -0
  40. data/lib/omq/scatter_gather.rb +8 -8
  41. data/lib/omq/single_frame.rb +18 -0
  42. data/lib/omq/socket.rb +32 -11
  43. data/lib/omq/transport/inproc.rb +355 -0
  44. data/lib/omq/transport/ipc.rb +117 -0
  45. data/lib/omq/transport/tcp.rb +111 -0
  46. data/lib/omq/transport/tls.rb +146 -0
  47. data/lib/omq/version.rb +1 -1
  48. data/lib/omq/writable.rb +66 -0
  49. data/lib/omq.rb +64 -4
  50. metadata +34 -33
  51. data/lib/omq/zmtp/engine.rb +0 -551
  52. data/lib/omq/zmtp/options.rb +0 -48
  53. data/lib/omq/zmtp/reactor.rb +0 -131
  54. data/lib/omq/zmtp/readable.rb +0 -29
  55. data/lib/omq/zmtp/routing/channel.rb +0 -81
  56. data/lib/omq/zmtp/routing/client.rb +0 -56
  57. data/lib/omq/zmtp/routing/dealer.rb +0 -57
  58. data/lib/omq/zmtp/routing/dish.rb +0 -80
  59. data/lib/omq/zmtp/routing/fan_out.rb +0 -131
  60. data/lib/omq/zmtp/routing/gather.rb +0 -48
  61. data/lib/omq/zmtp/routing/pair.rb +0 -84
  62. data/lib/omq/zmtp/routing/peer.rb +0 -100
  63. data/lib/omq/zmtp/routing/pub.rb +0 -62
  64. data/lib/omq/zmtp/routing/pull.rb +0 -48
  65. data/lib/omq/zmtp/routing/push.rb +0 -80
  66. data/lib/omq/zmtp/routing/radio.rb +0 -139
  67. data/lib/omq/zmtp/routing/rep.rb +0 -101
  68. data/lib/omq/zmtp/routing/req.rb +0 -65
  69. data/lib/omq/zmtp/routing/round_robin.rb +0 -143
  70. data/lib/omq/zmtp/routing/router.rb +0 -109
  71. data/lib/omq/zmtp/routing/scatter.rb +0 -81
  72. data/lib/omq/zmtp/routing/server.rb +0 -100
  73. data/lib/omq/zmtp/routing/sub.rb +0 -80
  74. data/lib/omq/zmtp/routing/xpub.rb +0 -74
  75. data/lib/omq/zmtp/routing/xsub.rb +0 -86
  76. data/lib/omq/zmtp/routing.rb +0 -65
  77. data/lib/omq/zmtp/single_frame.rb +0 -20
  78. data/lib/omq/zmtp/transport/inproc.rb +0 -359
  79. data/lib/omq/zmtp/transport/ipc.rb +0 -118
  80. data/lib/omq/zmtp/transport/tcp.rb +0 -117
  81. data/lib/omq/zmtp/writable.rb +0 -61
  82. data/lib/omq/zmtp.rb +0 -81
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OMQ
4
+ # Pure Ruby socket options.
5
+ #
6
+ # All timeouts are in seconds (Numeric) or nil (no timeout).
7
+ # HWM values are integers.
8
+ #
9
+ class Options
10
+ DEFAULT_HWM = 1000
11
+
12
+ # @param linger [Integer] linger period in seconds (default 0)
13
+ #
14
+ def initialize(linger: 0)
15
+ @send_hwm = DEFAULT_HWM
16
+ @recv_hwm = DEFAULT_HWM
17
+ @linger = linger
18
+ @identity = "".b
19
+ @router_mandatory = false
20
+ @read_timeout = nil # seconds, nil = no timeout
21
+ @write_timeout = nil
22
+ @reconnect_interval = 0.1 # seconds, or Range for backoff (e.g. 0.1..5.0)
23
+ @heartbeat_interval = nil # seconds, nil = disabled
24
+ @heartbeat_ttl = nil # seconds, nil = use heartbeat_interval
25
+ @heartbeat_timeout = nil # seconds, nil = use heartbeat_interval
26
+ @max_message_size = nil # bytes, nil = unlimited
27
+ @conflate = false
28
+ @mechanism = Protocol::ZMTP::Mechanism::Null.new
29
+ @tls_context = nil # OpenSSL::SSL::SSLContext for tls+tcp://
30
+ end
31
+
32
+ attr_accessor :send_hwm, :recv_hwm,
33
+ :linger, :identity,
34
+ :router_mandatory, :conflate,
35
+ :read_timeout, :write_timeout,
36
+ :reconnect_interval,
37
+ :heartbeat_interval, :heartbeat_ttl, :heartbeat_timeout,
38
+ :max_message_size,
39
+ :mechanism,
40
+ :tls_context
41
+
42
+ alias_method :router_mandatory?, :router_mandatory
43
+ alias_method :recv_timeout, :read_timeout
44
+ alias_method :recv_timeout=, :read_timeout=
45
+ alias_method :send_timeout, :write_timeout
46
+ alias_method :send_timeout=, :write_timeout=
47
+ end
48
+ end
data/lib/omq/pair.rb CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  module OMQ
4
4
  class PAIR < Socket
5
- include ZMTP::Readable
6
- include ZMTP::Writable
5
+ include Readable
6
+ include Writable
7
7
 
8
- def initialize(endpoints = nil, linger: 0)
9
- _init_engine(:PAIR, linger: linger)
8
+ def initialize(endpoints = nil, linger: 0, backend: nil)
9
+ _init_engine(:PAIR, linger: linger, backend: backend)
10
10
  _attach(endpoints, default: :connect)
11
11
  end
12
12
  end
data/lib/omq/peer.rb CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  module OMQ
4
4
  class PEER < Socket
5
- include ZMTP::Readable
6
- include ZMTP::Writable
7
- include ZMTP::SingleFrame
5
+ include Readable
6
+ include Writable
7
+ include SingleFrame
8
8
 
9
- def initialize(endpoints = nil, linger: 0)
10
- _init_engine(:PEER, linger: linger)
9
+ def initialize(endpoints = nil, linger: 0, backend: nil)
10
+ _init_engine(:PEER, linger: linger, backend: backend)
11
11
  _attach(endpoints, default: :connect)
12
12
  end
13
13
 
data/lib/omq/pub_sub.rb CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  module OMQ
4
4
  class PUB < Socket
5
- include ZMTP::Writable
5
+ include Writable
6
6
 
7
- def initialize(endpoints = nil, linger: 0, conflate: false)
8
- _init_engine(:PUB, linger: linger, conflate: conflate)
7
+ def initialize(endpoints = nil, linger: 0, conflate: false, backend: nil)
8
+ _init_engine(:PUB, linger: linger, conflate: conflate, backend: backend)
9
9
  _attach(endpoints, default: :bind)
10
10
  end
11
11
  end
@@ -13,7 +13,7 @@ module OMQ
13
13
  # SUB socket.
14
14
  #
15
15
  class SUB < Socket
16
- include ZMTP::Readable
16
+ include Readable
17
17
 
18
18
  # @return [String] subscription prefix to subscribe to everything
19
19
  #
@@ -21,13 +21,13 @@ module OMQ
21
21
 
22
22
  # @param endpoints [String, nil]
23
23
  # @param linger [Integer]
24
- # @param prefix [String, nil] subscription prefix; +nil+ (default)
24
+ # @param subscribe [String, nil] subscription prefix; +nil+ (default)
25
25
  # means no subscription — call {#subscribe} explicitly.
26
26
  #
27
- def initialize(endpoints = nil, linger: 0, prefix: nil)
28
- _init_engine(:SUB, linger: linger)
27
+ def initialize(endpoints = nil, linger: 0, subscribe: nil, backend: nil)
28
+ _init_engine(:SUB, linger: linger, backend: backend)
29
29
  _attach(endpoints, default: :connect)
30
- subscribe(prefix) unless prefix.nil?
30
+ self.subscribe(subscribe) unless subscribe.nil?
31
31
  end
32
32
 
33
33
  # Subscribes to a topic prefix.
@@ -50,28 +50,28 @@ module OMQ
50
50
  end
51
51
 
52
52
  class XPUB < Socket
53
- include ZMTP::Readable
54
- include ZMTP::Writable
53
+ include Readable
54
+ include Writable
55
55
 
56
- def initialize(endpoints = nil, linger: 0)
57
- _init_engine(:XPUB, linger: linger)
56
+ def initialize(endpoints = nil, linger: 0, backend: nil)
57
+ _init_engine(:XPUB, linger: linger, backend: backend)
58
58
  _attach(endpoints, default: :bind)
59
59
  end
60
60
  end
61
61
 
62
62
  class XSUB < Socket
63
- include ZMTP::Readable
64
- include ZMTP::Writable
63
+ include Readable
64
+ include Writable
65
65
 
66
66
  # @param endpoints [String, nil]
67
67
  # @param linger [Integer]
68
- # @param prefix [String, nil] subscription prefix; +nil+ (default)
68
+ # @param subscribe [String, nil] subscription prefix; +nil+ (default)
69
69
  # means no subscription — send a subscribe frame explicitly.
70
70
  #
71
- def initialize(endpoints = nil, linger: 0, prefix: nil)
72
- _init_engine(:XSUB, linger: linger)
71
+ def initialize(endpoints = nil, linger: 0, subscribe: nil, backend: nil)
72
+ _init_engine(:XSUB, linger: linger, backend: backend)
73
73
  _attach(endpoints, default: :connect)
74
- send("\x01#{prefix}".b) unless prefix.nil?
74
+ send("\x01#{subscribe}".b) unless subscribe.nil?
75
75
  end
76
76
  end
77
77
  end
data/lib/omq/push_pull.rb CHANGED
@@ -2,19 +2,19 @@
2
2
 
3
3
  module OMQ
4
4
  class PUSH < Socket
5
- include ZMTP::Writable
5
+ include Writable
6
6
 
7
- def initialize(endpoints = nil, linger: 0, send_hwm: nil, send_timeout: nil)
8
- _init_engine(:PUSH, linger: linger, send_hwm: send_hwm, send_timeout: send_timeout)
7
+ def initialize(endpoints = nil, linger: 0, send_hwm: nil, send_timeout: nil, backend: nil)
8
+ _init_engine(:PUSH, linger: linger, send_hwm: send_hwm, send_timeout: send_timeout, backend: backend)
9
9
  _attach(endpoints, default: :connect)
10
10
  end
11
11
  end
12
12
 
13
13
  class PULL < Socket
14
- include ZMTP::Readable
14
+ include Readable
15
15
 
16
- def initialize(endpoints = nil, linger: 0, recv_hwm: nil, recv_timeout: nil)
17
- _init_engine(:PULL, linger: linger, recv_hwm: recv_hwm, recv_timeout: recv_timeout)
16
+ def initialize(endpoints = nil, linger: 0, recv_hwm: nil, recv_timeout: nil, backend: nil)
17
+ _init_engine(:PULL, linger: linger, recv_hwm: recv_hwm, recv_timeout: recv_timeout, backend: backend)
18
18
  _attach(endpoints, default: :bind)
19
19
  end
20
20
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OMQ
4
+ # Async::Queue-compatible read interface.
5
+ #
6
+ # Automatically included by {Readable}. Provides #dequeue, #pop,
7
+ # #wait, and #each so sockets can be used where an Async::Queue
8
+ # is expected.
9
+ #
10
+ module QueueReadable
11
+ # Dequeues the next message.
12
+ #
13
+ # @param timeout [Numeric, nil] timeout in seconds (overrides
14
+ # the socket's +read_timeout+ for this call)
15
+ # @return [Array<String>] message parts
16
+ # @raise [IO::TimeoutError] if timeout exceeded
17
+ #
18
+ def dequeue(timeout: @options.read_timeout)
19
+ msg = @recv_mutex.synchronize { @recv_buffer.shift }
20
+ return msg if msg
21
+
22
+ batch = Reactor.run { with_timeout(timeout) { @engine.dequeue_recv_batch(Readable::RECV_BATCH_SIZE) } }
23
+ msg = batch.shift
24
+ @recv_mutex.synchronize { @recv_buffer.concat(batch) } unless batch.empty?
25
+ msg
26
+ end
27
+
28
+ alias_method :pop, :dequeue
29
+
30
+ # Waits for the next message indefinitely (ignores read_timeout).
31
+ #
32
+ # @return [Array<String>] message parts
33
+ #
34
+ def wait
35
+ dequeue(timeout: nil)
36
+ end
37
+
38
+ # Yields each received message until the socket is closed or
39
+ # a receive timeout expires.
40
+ #
41
+ # @yield [Array<String>] message parts
42
+ # @return [void]
43
+ #
44
+ def each
45
+ while (msg = receive)
46
+ yield msg
47
+ end
48
+ rescue IO::TimeoutError
49
+ nil
50
+ end
51
+ end
52
+
53
+
54
+ # Async::Queue-compatible write interface.
55
+ #
56
+ # Automatically included by {Writable}. Provides #enqueue, #push,
57
+ # and #signal so sockets can be used where an Async::Queue is
58
+ # expected.
59
+ #
60
+ module QueueWritable
61
+ # Enqueues one or more messages for sending.
62
+ #
63
+ # @param messages [String, Array<String>]
64
+ # @return [self]
65
+ #
66
+ def enqueue(*messages)
67
+ messages.each { |msg| send(msg) }
68
+ self
69
+ end
70
+
71
+ alias_method :push, :enqueue
72
+ end
73
+ end
@@ -2,10 +2,10 @@
2
2
 
3
3
  module OMQ
4
4
  class RADIO < Socket
5
- include ZMTP::Writable
5
+ include Writable
6
6
 
7
- def initialize(endpoints = nil, linger: 0, conflate: false)
8
- _init_engine(:RADIO, linger: linger, conflate: conflate)
7
+ def initialize(endpoints = nil, linger: 0, conflate: false, backend: nil)
8
+ _init_engine(:RADIO, linger: linger, conflate: conflate, backend: backend)
9
9
  _attach(endpoints, default: :bind)
10
10
  end
11
11
 
@@ -45,10 +45,10 @@ module OMQ
45
45
  end
46
46
 
47
47
  class DISH < Socket
48
- include ZMTP::Readable
48
+ include Readable
49
49
 
50
- def initialize(endpoints = nil, linger: 0, group: nil)
51
- _init_engine(:DISH, linger: linger)
50
+ def initialize(endpoints = nil, linger: 0, group: nil, backend: nil)
51
+ _init_engine(:DISH, linger: linger, backend: backend)
52
52
  _attach(endpoints, default: :connect)
53
53
  join(group) if group
54
54
  end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "async"
4
+
5
+ module OMQ
6
+ # Shared IO reactor for the Ruby backend.
7
+ #
8
+ # When user code runs inside an Async reactor, engine tasks are
9
+ # spawned directly under the caller's Async task. When no reactor
10
+ # is available (e.g. bare Thread.new), a single shared IO thread
11
+ # hosts all engine tasks — mirroring libzmq's IO thread.
12
+ #
13
+ # Engines obtain the IO thread's root task via {.root_task} and
14
+ # use it as their @parent_task. Blocking operations from the main
15
+ # thread are dispatched to the IO thread via {.run}.
16
+ #
17
+ module Reactor
18
+ @mutex = Mutex.new
19
+ @thread = nil
20
+ @root_task = nil
21
+ @work_queue = nil
22
+ @lingers = Hash.new(0) # linger value → count of active sockets
23
+
24
+ class << self
25
+ # Returns the root Async task inside the shared IO thread.
26
+ # Starts the thread exactly once (double-checked lock).
27
+ #
28
+ # @return [Async::Task]
29
+ #
30
+ def root_task
31
+ return @root_task if @root_task
32
+ @mutex.synchronize do
33
+ return @root_task if @root_task
34
+ ready = Thread::Queue.new
35
+ @work_queue = Async::Queue.new
36
+ @thread = Thread.new { run_reactor(ready) }
37
+ @thread.name = "omq-io"
38
+ @root_task = ready.pop
39
+ at_exit { stop! }
40
+ end
41
+ @root_task
42
+ end
43
+
44
+
45
+ # Runs a block inside the Async reactor.
46
+ #
47
+ # Inside an Async reactor: runs directly.
48
+ # Outside: dispatches to the shared IO thread and blocks
49
+ # the calling thread until the result is available.
50
+ #
51
+ # @return [Object] the block's return value
52
+ #
53
+ def run(&block)
54
+ if Async::Task.current?
55
+ yield
56
+ else
57
+ result = Thread::Queue.new
58
+ root_task # ensure started
59
+ @work_queue.push([block, result])
60
+ status, value = result.pop
61
+ raise value if status == :error
62
+ value
63
+ end
64
+ end
65
+
66
+
67
+ # Registers a socket's linger value.
68
+ #
69
+ # @param seconds [Numeric, nil] linger value
70
+ #
71
+ def track_linger(seconds)
72
+ @lingers[seconds || 0] += 1
73
+ end
74
+
75
+
76
+ # Unregisters a socket's linger value.
77
+ #
78
+ # @param seconds [Numeric, nil] linger value
79
+ #
80
+ def untrack_linger(seconds)
81
+ key = seconds || 0
82
+ @lingers[key] -= 1
83
+ @lingers.delete(key) if @lingers[key] <= 0
84
+ end
85
+
86
+
87
+ # Stops the shared IO thread.
88
+ #
89
+ # @return [void]
90
+ #
91
+ def stop!
92
+ return unless @thread&.alive?
93
+ max_linger = @lingers.empty? ? 0 : @lingers.keys.max
94
+ @work_queue&.push(nil)
95
+ @thread&.join(max_linger + 1)
96
+ @thread = nil
97
+ @root_task = nil
98
+ @work_queue = nil
99
+ @lingers = Hash.new(0)
100
+ end
101
+
102
+ private
103
+
104
+ # Runs the shared Async reactor.
105
+ #
106
+ # Processes work items dispatched via {.run} while engine
107
+ # tasks (accept loops, pumps, etc.) run as transient children.
108
+ #
109
+ # @param ready [Thread::Queue] receives the root task once started
110
+ #
111
+ def run_reactor(ready)
112
+ Async do |task|
113
+ ready.push(task)
114
+ loop do
115
+ item = @work_queue.dequeue
116
+ break if item.nil?
117
+ block, result = item
118
+ task.async do
119
+ result.push([:ok, block.call])
120
+ rescue => e
121
+ result.push([:error, e])
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "timeout"
4
+
5
+ module OMQ
6
+ # Pure Ruby Readable mixin. Dequeues messages from the engine's recv queue.
7
+ #
8
+ module Readable
9
+ include QueueReadable
10
+
11
+ # Maximum messages to prefetch from the recv queue per drain.
12
+ RECV_BATCH_SIZE = 64
13
+
14
+ # Receives the next message. Returns from a local prefetch
15
+ # buffer when available, otherwise drains up to
16
+ # {RECV_BATCH_SIZE} messages from the recv queue in one
17
+ # synchronized dequeue.
18
+ #
19
+ # @return [Array<String>] message parts
20
+ # @raise [IO::TimeoutError] if read_timeout exceeded
21
+ #
22
+ def receive
23
+ @recv_mutex.synchronize { @recv_buffer.shift } || fill_recv_buffer
24
+ end
25
+
26
+ # Waits until the socket is readable.
27
+ #
28
+ # @param timeout [Numeric, nil] timeout in seconds
29
+ # @return [true]
30
+ #
31
+ def wait_readable(timeout = @options.read_timeout)
32
+ true
33
+ end
34
+
35
+ private
36
+
37
+ def fill_recv_buffer
38
+ batch = Reactor.run { with_timeout(@options.read_timeout) { @engine.dequeue_recv_batch(RECV_BATCH_SIZE) } }
39
+ msg = batch.shift
40
+ @recv_mutex.synchronize { @recv_buffer.concat(batch) } unless batch.empty?
41
+ msg
42
+ end
43
+ end
44
+ end
data/lib/omq/req_rep.rb CHANGED
@@ -2,21 +2,21 @@
2
2
 
3
3
  module OMQ
4
4
  class REQ < Socket
5
- include ZMTP::Readable
6
- include ZMTP::Writable
5
+ include Readable
6
+ include Writable
7
7
 
8
- def initialize(endpoints = nil, linger: 0)
9
- _init_engine(:REQ, linger: linger)
8
+ def initialize(endpoints = nil, linger: 0, backend: nil)
9
+ _init_engine(:REQ, linger: linger, backend: backend)
10
10
  _attach(endpoints, default: :connect)
11
11
  end
12
12
  end
13
13
 
14
14
  class REP < Socket
15
- include ZMTP::Readable
16
- include ZMTP::Writable
15
+ include Readable
16
+ include Writable
17
17
 
18
- def initialize(endpoints = nil, linger: 0)
19
- _init_engine(:REP, linger: linger)
18
+ def initialize(endpoints = nil, linger: 0, backend: nil)
19
+ _init_engine(:REP, linger: linger, backend: backend)
20
20
  _attach(endpoints, default: :bind)
21
21
  end
22
22
  end
@@ -2,11 +2,11 @@
2
2
 
3
3
  module OMQ
4
4
  class DEALER < Socket
5
- include ZMTP::Readable
6
- include ZMTP::Writable
5
+ include Readable
6
+ include Writable
7
7
 
8
- def initialize(endpoints = nil, linger: 0)
9
- _init_engine(:DEALER, linger: linger)
8
+ def initialize(endpoints = nil, linger: 0, backend: nil)
9
+ _init_engine(:DEALER, linger: linger, backend: backend)
10
10
  _attach(endpoints, default: :connect)
11
11
  end
12
12
  end
@@ -14,11 +14,11 @@ module OMQ
14
14
  # ROUTER socket.
15
15
  #
16
16
  class ROUTER < Socket
17
- include ZMTP::Readable
18
- include ZMTP::Writable
17
+ include Readable
18
+ include Writable
19
19
 
20
- def initialize(endpoints = nil, linger: 0)
21
- _init_engine(:ROUTER, linger: linger)
20
+ def initialize(endpoints = nil, linger: 0, backend: nil)
21
+ _init_engine(:ROUTER, linger: linger, backend: backend)
22
22
  _attach(endpoints, default: :bind)
23
23
  end
24
24
 
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OMQ
4
+ module Routing
5
+ # CHANNEL socket routing: exclusive 1-to-1 bidirectional.
6
+ #
7
+ class Channel
8
+
9
+ # @param engine [Engine]
10
+ #
11
+ def initialize(engine)
12
+ @engine = engine
13
+ @connection = nil
14
+ @recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
15
+ @send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
16
+ @tasks = []
17
+ @send_pump_idle = true
18
+ end
19
+
20
+ # @return [Async::LimitedQueue]
21
+ #
22
+ attr_reader :recv_queue, :send_queue
23
+
24
+ # @param connection [Connection]
25
+ # @raise [RuntimeError] if a connection already exists
26
+ #
27
+ def connection_added(connection)
28
+ raise "CHANNEL allows only one peer" if @connection
29
+ @connection = connection
30
+ task = @engine.start_recv_pump(connection, @recv_queue)
31
+ @tasks << task if task
32
+ start_send_pump(connection) unless connection.is_a?(Transport::Inproc::DirectPipe)
33
+ end
34
+
35
+ # @param connection [Connection]
36
+ #
37
+ def connection_removed(connection)
38
+ if @connection == connection
39
+ @connection = nil
40
+ @send_pump&.stop
41
+ @send_pump = nil
42
+ end
43
+ end
44
+
45
+ # @param parts [Array<String>]
46
+ #
47
+ def enqueue(parts)
48
+ conn = @connection
49
+ if conn.is_a?(Transport::Inproc::DirectPipe) && conn.direct_recv_queue
50
+ conn.send_message(parts)
51
+ else
52
+ @send_queue.enqueue(parts)
53
+ end
54
+ end
55
+
56
+ #
57
+ def stop
58
+ @tasks.each(&:stop)
59
+ @tasks.clear
60
+ end
61
+
62
+ def send_pump_idle? = @send_pump_idle
63
+
64
+ private
65
+
66
+ def start_send_pump(conn)
67
+ @send_pump = @engine.spawn_pump_task(annotation: "send pump") do
68
+ loop do
69
+ @send_pump_idle = true
70
+ batch = [@send_queue.dequeue]
71
+ @send_pump_idle = false
72
+ Routing.drain_send_queue(@send_queue, batch)
73
+ batch.each { |parts| conn.write_message(parts) }
74
+ conn.flush
75
+ end
76
+ rescue *CONNECTION_LOST
77
+ @engine.connection_lost(conn)
78
+ end
79
+ @tasks << @send_pump
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OMQ
4
+ module Routing
5
+ # CLIENT socket routing: round-robin send, fair-queue receive.
6
+ #
7
+ # Same as DEALER — no envelope manipulation.
8
+ #
9
+ class Client
10
+ include RoundRobin
11
+
12
+ # @param engine [Engine]
13
+ #
14
+ def initialize(engine)
15
+ @engine = engine
16
+ @recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
17
+ @tasks = []
18
+ init_round_robin(engine)
19
+ end
20
+
21
+ # @return [Async::LimitedQueue]
22
+ #
23
+ attr_reader :recv_queue, :send_queue
24
+
25
+ # @param connection [Connection]
26
+ #
27
+ def connection_added(connection)
28
+ @connections << connection
29
+ signal_connection_available
30
+ update_direct_pipe
31
+ task = @engine.start_recv_pump(connection, @recv_queue)
32
+ @tasks << task if task
33
+ start_send_pump unless @send_pump_started
34
+ end
35
+
36
+ # @param connection [Connection]
37
+ #
38
+ def connection_removed(connection)
39
+ @connections.delete(connection)
40
+ update_direct_pipe
41
+ end
42
+
43
+ # @param parts [Array<String>]
44
+ #
45
+ def enqueue(parts)
46
+ enqueue_round_robin(parts)
47
+ end
48
+
49
+ #
50
+ def stop
51
+ @tasks.each(&:stop)
52
+ @tasks.clear
53
+ end
54
+ end
55
+ end
56
+ end