omq 0.8.0 → 0.10.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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +87 -0
  3. data/README.md +9 -49
  4. data/lib/omq/channel.rb +3 -3
  5. data/lib/omq/client_server.rb +6 -6
  6. data/lib/omq/engine.rb +641 -0
  7. data/lib/omq/options.rb +46 -0
  8. data/lib/omq/pair.rb +2 -2
  9. data/lib/omq/peer.rb +3 -3
  10. data/lib/omq/pub_sub.rb +6 -6
  11. data/lib/omq/push_pull.rb +2 -2
  12. data/lib/omq/radio_dish.rb +2 -2
  13. data/lib/omq/reactor.rb +128 -0
  14. data/lib/omq/readable.rb +42 -0
  15. data/lib/omq/req_rep.rb +4 -4
  16. data/lib/omq/router_dealer.rb +4 -4
  17. data/lib/omq/routing/channel.rb +83 -0
  18. data/lib/omq/routing/client.rb +56 -0
  19. data/lib/omq/routing/dealer.rb +57 -0
  20. data/lib/omq/routing/dish.rb +78 -0
  21. data/lib/omq/routing/fan_out.rb +131 -0
  22. data/lib/omq/routing/gather.rb +46 -0
  23. data/lib/omq/routing/pair.rb +86 -0
  24. data/lib/omq/routing/peer.rb +101 -0
  25. data/lib/omq/routing/pub.rb +60 -0
  26. data/lib/omq/routing/pull.rb +46 -0
  27. data/lib/omq/routing/push.rb +81 -0
  28. data/lib/omq/routing/radio.rb +140 -0
  29. data/lib/omq/routing/rep.rb +101 -0
  30. data/lib/omq/routing/req.rb +65 -0
  31. data/lib/omq/routing/round_robin.rb +168 -0
  32. data/lib/omq/routing/router.rb +110 -0
  33. data/lib/omq/routing/scatter.rb +82 -0
  34. data/lib/omq/routing/server.rb +101 -0
  35. data/lib/omq/routing/sub.rb +78 -0
  36. data/lib/omq/routing/xpub.rb +72 -0
  37. data/lib/omq/routing/xsub.rb +83 -0
  38. data/lib/omq/routing.rb +66 -0
  39. data/lib/omq/scatter_gather.rb +4 -4
  40. data/lib/omq/single_frame.rb +18 -0
  41. data/lib/omq/socket.rb +24 -9
  42. data/lib/omq/transport/inproc.rb +355 -0
  43. data/lib/omq/transport/ipc.rb +117 -0
  44. data/lib/omq/transport/tcp.rb +111 -0
  45. data/lib/omq/version.rb +1 -1
  46. data/lib/omq/writable.rb +65 -0
  47. data/lib/omq.rb +60 -4
  48. metadata +38 -58
  49. data/exe/omq +0 -6
  50. data/lib/omq/cli/base_runner.rb +0 -459
  51. data/lib/omq/cli/channel.rb +0 -8
  52. data/lib/omq/cli/client_server.rb +0 -111
  53. data/lib/omq/cli/config.rb +0 -54
  54. data/lib/omq/cli/formatter.rb +0 -75
  55. data/lib/omq/cli/pair.rb +0 -31
  56. data/lib/omq/cli/peer.rb +0 -8
  57. data/lib/omq/cli/pipe.rb +0 -265
  58. data/lib/omq/cli/pub_sub.rb +0 -14
  59. data/lib/omq/cli/push_pull.rb +0 -14
  60. data/lib/omq/cli/radio_dish.rb +0 -27
  61. data/lib/omq/cli/req_rep.rb +0 -83
  62. data/lib/omq/cli/router_dealer.rb +0 -76
  63. data/lib/omq/cli/scatter_gather.rb +0 -14
  64. data/lib/omq/cli.rb +0 -540
  65. data/lib/omq/zmtp/engine.rb +0 -551
  66. data/lib/omq/zmtp/options.rb +0 -48
  67. data/lib/omq/zmtp/reactor.rb +0 -131
  68. data/lib/omq/zmtp/readable.rb +0 -29
  69. data/lib/omq/zmtp/routing/channel.rb +0 -81
  70. data/lib/omq/zmtp/routing/client.rb +0 -56
  71. data/lib/omq/zmtp/routing/dealer.rb +0 -57
  72. data/lib/omq/zmtp/routing/dish.rb +0 -80
  73. data/lib/omq/zmtp/routing/fan_out.rb +0 -131
  74. data/lib/omq/zmtp/routing/gather.rb +0 -48
  75. data/lib/omq/zmtp/routing/pair.rb +0 -84
  76. data/lib/omq/zmtp/routing/peer.rb +0 -100
  77. data/lib/omq/zmtp/routing/pub.rb +0 -62
  78. data/lib/omq/zmtp/routing/pull.rb +0 -48
  79. data/lib/omq/zmtp/routing/push.rb +0 -80
  80. data/lib/omq/zmtp/routing/radio.rb +0 -139
  81. data/lib/omq/zmtp/routing/rep.rb +0 -101
  82. data/lib/omq/zmtp/routing/req.rb +0 -65
  83. data/lib/omq/zmtp/routing/round_robin.rb +0 -143
  84. data/lib/omq/zmtp/routing/router.rb +0 -109
  85. data/lib/omq/zmtp/routing/scatter.rb +0 -81
  86. data/lib/omq/zmtp/routing/server.rb +0 -100
  87. data/lib/omq/zmtp/routing/sub.rb +0 -80
  88. data/lib/omq/zmtp/routing/xpub.rb +0 -74
  89. data/lib/omq/zmtp/routing/xsub.rb +0 -86
  90. data/lib/omq/zmtp/routing.rb +0 -65
  91. data/lib/omq/zmtp/single_frame.rb +0 -20
  92. data/lib/omq/zmtp/transport/inproc.rb +0 -359
  93. data/lib/omq/zmtp/transport/ipc.rb +0 -118
  94. data/lib/omq/zmtp/transport/tcp.rb +0 -117
  95. data/lib/omq/zmtp/writable.rb +0 -61
  96. data/lib/omq/zmtp.rb +0 -81
@@ -1,100 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "securerandom"
4
-
5
- module OMQ
6
- module ZMTP
7
- module Routing
8
- # PEER socket routing: bidirectional multi-peer with auto-generated
9
- # 4-byte routing IDs.
10
- #
11
- # Prepends routing ID on receive. Strips routing ID on send and
12
- # routes to the identified connection.
13
- #
14
- class Peer
15
- # @param engine [Engine]
16
- #
17
- def initialize(engine)
18
- @engine = engine
19
- @recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
20
- @send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
21
- @connections_by_routing_id = {}
22
- @tasks = []
23
- @send_pump_started = false
24
- @send_pump_idle = true
25
- end
26
-
27
- # @return [Async::LimitedQueue]
28
- #
29
- attr_reader :recv_queue, :send_queue
30
-
31
- # @param connection [Connection]
32
- #
33
- def connection_added(connection)
34
- routing_id = SecureRandom.bytes(4)
35
- @connections_by_routing_id[routing_id] = connection
36
-
37
- task = @engine.start_recv_pump(connection, @recv_queue) do |msg|
38
- [routing_id, *msg]
39
- end
40
- @tasks << task if task
41
-
42
- start_send_pump unless @send_pump_started
43
- end
44
-
45
- # @param connection [Connection]
46
- #
47
- def connection_removed(connection)
48
- @connections_by_routing_id.reject! { |_, c| c == connection }
49
- end
50
-
51
- # @param parts [Array<String>]
52
- #
53
- def enqueue(parts)
54
- @send_queue.enqueue(parts)
55
- end
56
-
57
- def stop
58
- @tasks.each(&:stop)
59
- @tasks.clear
60
- end
61
-
62
- private
63
-
64
- def send_pump_idle? = @send_pump_idle
65
-
66
-
67
- def start_send_pump
68
- @send_pump_started = true
69
- @tasks << @engine.spawn_pump_task(annotation: "send pump") do
70
- loop do
71
- @send_pump_idle = true
72
- batch = [@send_queue.dequeue]
73
- @send_pump_idle = false
74
- Routing.drain_send_queue(@send_queue, batch)
75
-
76
- written = Set.new
77
- batch.each do |parts|
78
- routing_id = parts.first
79
- conn = @connections_by_routing_id[routing_id]
80
- next unless conn # silently drop if peer gone
81
- begin
82
- conn.write_message(parts[1..])
83
- written << conn
84
- rescue *ZMTP::CONNECTION_LOST
85
- # will be cleaned up
86
- end
87
- end
88
-
89
- written.each do |conn|
90
- conn.flush
91
- rescue *ZMTP::CONNECTION_LOST
92
- # will be cleaned up
93
- end
94
- end
95
- end
96
- end
97
- end
98
- end
99
- end
100
- end
@@ -1,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OMQ
4
- module ZMTP
5
- module Routing
6
- # PUB socket routing: fan-out to all subscribers.
7
- #
8
- # Listens for SUBSCRIBE/CANCEL commands from peers.
9
- # Drops messages if a subscriber's connection write fails.
10
- #
11
- class Pub
12
- include FanOut
13
-
14
- # @param engine [Engine]
15
- #
16
- def initialize(engine)
17
- @engine = engine
18
- @tasks = []
19
- init_fan_out(engine)
20
- end
21
-
22
- # @return [Async::LimitedQueue]
23
- #
24
- attr_reader :send_queue
25
-
26
- # PUB is write-only.
27
- #
28
- def recv_queue
29
- raise "PUB sockets cannot receive"
30
- end
31
-
32
- # @param connection [Connection]
33
- #
34
- def connection_added(connection)
35
- @connections << connection
36
- @subscriptions[connection] = Set.new
37
- start_subscription_listener(connection)
38
- start_send_pump unless @send_pump_started
39
- end
40
-
41
- # @param connection [Connection]
42
- #
43
- def connection_removed(connection)
44
- @connections.delete(connection)
45
- @subscriptions.delete(connection)
46
- end
47
-
48
- # @param parts [Array<String>]
49
- #
50
- def enqueue(parts)
51
- @send_queue.enqueue(parts)
52
- end
53
-
54
- #
55
- def stop
56
- @tasks.each(&:stop)
57
- @tasks.clear
58
- end
59
- end
60
- end
61
- end
62
- end
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OMQ
4
- module ZMTP
5
- module Routing
6
- # PULL socket routing: fair-queue receive from PUSH peers.
7
- #
8
- class Pull
9
- # @param engine [Engine]
10
- #
11
- def initialize(engine)
12
- @engine = engine
13
- @recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
14
- @tasks = []
15
- end
16
-
17
- # @return [Async::LimitedQueue]
18
- #
19
- attr_reader :recv_queue
20
-
21
- # @param connection [Connection]
22
- #
23
- def connection_added(connection)
24
- task = @engine.start_recv_pump(connection, @recv_queue)
25
- @tasks << task if task
26
- end
27
-
28
- # @param connection [Connection]
29
- #
30
- def connection_removed(connection)
31
- # recv pump stops on EOFError
32
- end
33
-
34
- # PULL is read-only.
35
- #
36
- def enqueue(_parts)
37
- raise "PULL sockets cannot send"
38
- end
39
-
40
- #
41
- def stop
42
- @tasks.each(&:stop)
43
- @tasks.clear
44
- end
45
- end
46
- end
47
- end
48
- end
@@ -1,80 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OMQ
4
- module ZMTP
5
- module Routing
6
- # PUSH socket routing: round-robin send to PULL peers.
7
- #
8
- class Push
9
- include RoundRobin
10
-
11
- # @param engine [Engine]
12
- #
13
- def initialize(engine)
14
- @engine = engine
15
- @tasks = []
16
- init_round_robin(engine)
17
- end
18
-
19
-
20
- # @return [Async::LimitedQueue]
21
- #
22
- attr_reader :send_queue
23
-
24
-
25
- # PUSH is write-only.
26
- #
27
- def recv_queue
28
- raise "PUSH sockets cannot receive"
29
- end
30
-
31
-
32
- # @param connection [Connection]
33
- #
34
- def connection_added(connection)
35
- @connections << connection
36
- signal_connection_available
37
- start_send_pump unless @send_pump_started
38
- start_reaper(connection)
39
- end
40
-
41
-
42
- # @param connection [Connection]
43
- #
44
- def connection_removed(connection)
45
- @connections.delete(connection)
46
- end
47
-
48
-
49
- # @param parts [Array<String>]
50
- #
51
- def enqueue(parts)
52
- @send_queue.enqueue(parts)
53
- end
54
-
55
-
56
- # Stops all background tasks (send pump, reapers).
57
- #
58
- def stop
59
- @tasks.each(&:stop)
60
- @tasks.clear
61
- end
62
-
63
- private
64
-
65
-
66
- # Detects peer disconnection on write-only sockets. Without
67
- # this, a dead peer is only noticed on the next send — which
68
- # may succeed if the kernel send buffer absorbs the data.
69
- #
70
- def start_reaper(conn)
71
- @tasks << Reactor.spawn_pump(annotation: "reaper") do
72
- conn.receive_message # blocks until peer disconnects
73
- rescue *ZMTP::CONNECTION_LOST
74
- @engine.connection_lost(conn)
75
- end
76
- end
77
- end
78
- end
79
- end
80
- end
@@ -1,139 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OMQ
4
- module ZMTP
5
- module Routing
6
- # RADIO socket routing: group-based fan-out to DISH peers.
7
- #
8
- # Like PUB/FanOut but with exact group matching and JOIN/LEAVE
9
- # commands instead of SUBSCRIBE/CANCEL.
10
- #
11
- # Messages are sent as two frames on the wire:
12
- # group (MORE=1) + body (MORE=0)
13
- #
14
- class Radio
15
-
16
- # @param engine [Engine]
17
- #
18
- def initialize(engine)
19
- @engine = engine
20
- @connections = []
21
- @groups = {} # connection => Set of joined groups
22
- @send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
23
- @send_pump_started = false
24
- @conflate = engine.options.conflate
25
- @tasks = []
26
- end
27
-
28
- # @return [Async::LimitedQueue]
29
- #
30
- attr_reader :send_queue
31
-
32
- # RADIO is write-only.
33
- #
34
- def recv_queue
35
- raise "RADIO sockets cannot receive"
36
- end
37
-
38
- # @param connection [Connection]
39
- #
40
- def connection_added(connection)
41
- @connections << connection
42
- @groups[connection] = Set.new
43
- start_group_listener(connection)
44
- start_send_pump unless @send_pump_started
45
- end
46
-
47
- # @param connection [Connection]
48
- #
49
- def connection_removed(connection)
50
- @connections.delete(connection)
51
- @groups.delete(connection)
52
- end
53
-
54
- # Enqueues a message for sending.
55
- #
56
- # @param parts [Array<String>] [group, body]
57
- #
58
- def enqueue(parts)
59
- @send_queue.enqueue(parts)
60
- end
61
-
62
- def stop
63
- @tasks.each(&:stop)
64
- @tasks.clear
65
- end
66
-
67
- private
68
-
69
- def start_send_pump
70
- @send_pump_started = true
71
- @tasks << @engine.spawn_pump_task(annotation: "send pump") do
72
- loop do
73
- @send_pump_idle = true
74
- batch = [@send_queue.dequeue]
75
- @send_pump_idle = false
76
- Routing.drain_send_queue(@send_queue, batch)
77
-
78
- written = Set.new
79
-
80
- if @conflate
81
- # Keep only the last matching message per connection.
82
- latest = {} # conn => [group, body]
83
- batch.each do |parts|
84
- group = parts[0]
85
- body = parts[1] || "".b
86
- @connections.each do |conn|
87
- next unless @groups[conn]&.include?(group)
88
- latest[conn] = [group, body]
89
- end
90
- end
91
- latest.each do |conn, msg|
92
- begin
93
- conn.write_message(msg)
94
- written << conn
95
- rescue *ZMTP::CONNECTION_LOST
96
- end
97
- end
98
- else
99
- batch.each do |parts|
100
- group = parts[0]
101
- body = parts[1] || "".b
102
- @connections.each do |conn|
103
- next unless @groups[conn]&.include?(group)
104
- begin
105
- conn.write_message([group, body])
106
- written << conn
107
- rescue *ZMTP::CONNECTION_LOST
108
- end
109
- end
110
- end
111
- end
112
-
113
- written.each do |conn|
114
- conn.flush
115
- rescue *ZMTP::CONNECTION_LOST
116
- end
117
- end
118
- end
119
- end
120
-
121
- def start_group_listener(conn)
122
- @tasks << Reactor.spawn_pump(annotation: "recv pump") do
123
- loop do
124
- frame = conn.read_frame
125
- next unless frame.command?
126
- cmd = Codec::Command.from_body(frame.body)
127
- case cmd.name
128
- when "JOIN" then @groups[conn]&.add(cmd.data)
129
- when "LEAVE" then @groups[conn]&.delete(cmd.data)
130
- end
131
- end
132
- rescue *ZMTP::CONNECTION_LOST
133
- @engine.connection_lost(conn)
134
- end
135
- end
136
- end
137
- end
138
- end
139
- end
@@ -1,101 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OMQ
4
- module ZMTP
5
- module Routing
6
- # REP socket routing: fair-queue receive, reply routed back to sender.
7
- #
8
- # REP strips the routing envelope (everything up to and including the
9
- # empty delimiter) on receive, saves it internally, and restores it
10
- # on send.
11
- #
12
- class Rep
13
- # @param engine [Engine]
14
- #
15
- def initialize(engine)
16
- @engine = engine
17
- @recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
18
- @send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
19
- @pending_replies = []
20
- @tasks = []
21
- @send_pump_started = false
22
- @send_pump_idle = true
23
- end
24
-
25
- # @return [Async::LimitedQueue]
26
- #
27
- attr_reader :recv_queue, :send_queue
28
-
29
- # @param connection [Connection]
30
- #
31
- def connection_added(connection)
32
- task = @engine.start_recv_pump(connection, @recv_queue) do |msg|
33
- delimiter = msg.index(&:empty?) || msg.size
34
- envelope = msg[0, delimiter]
35
- body = msg[(delimiter + 1)..] || []
36
- @pending_replies << { conn: connection, envelope: envelope }
37
- body
38
- end
39
- @tasks << task if task
40
- start_send_pump unless @send_pump_started
41
- end
42
-
43
- # @param connection [Connection]
44
- #
45
- def connection_removed(connection)
46
- # Remove any pending replies for this connection
47
- @pending_replies.reject! { |r| r[:conn] == connection }
48
- end
49
-
50
- # Enqueues a reply for sending.
51
- #
52
- # @param parts [Array<String>]
53
- #
54
- def enqueue(parts)
55
- @send_queue.enqueue(parts)
56
- end
57
-
58
- def stop
59
- @tasks.each(&:stop)
60
- @tasks.clear
61
- end
62
-
63
- private
64
-
65
- def send_pump_idle? = @send_pump_idle
66
-
67
-
68
- def start_send_pump
69
- @send_pump_started = true
70
- @tasks << @engine.spawn_pump_task(annotation: "send pump") do
71
- loop do
72
- @send_pump_idle = true
73
- batch = [@send_queue.dequeue]
74
- @send_pump_idle = false
75
- Routing.drain_send_queue(@send_queue, batch)
76
-
77
- written = Set.new
78
- batch.each do |parts|
79
- reply_info = @pending_replies.shift
80
- next unless reply_info
81
- conn = reply_info[:conn]
82
- begin
83
- conn.write_message([*reply_info[:envelope], "".b, *parts])
84
- written << conn
85
- rescue *ZMTP::CONNECTION_LOST
86
- # connection lost mid-write
87
- end
88
- end
89
-
90
- written.each do |conn|
91
- conn.flush
92
- rescue *ZMTP::CONNECTION_LOST
93
- # connection lost mid-flush
94
- end
95
- end
96
- end
97
- end
98
- end
99
- end
100
- end
101
- end
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OMQ
4
- module ZMTP
5
- module Routing
6
- # REQ socket routing: round-robin send with strict send/recv alternation.
7
- #
8
- # REQ prepends an empty delimiter frame on send and strips it on receive.
9
- #
10
- class Req
11
- include RoundRobin
12
-
13
- # @param engine [Engine]
14
- #
15
- def initialize(engine)
16
- @engine = engine
17
- @recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
18
- @tasks = []
19
- init_round_robin(engine)
20
- end
21
-
22
- # @return [Async::LimitedQueue]
23
- #
24
- attr_reader :recv_queue, :send_queue
25
-
26
- # @param connection [Connection]
27
- #
28
- def connection_added(connection)
29
- @connections << connection
30
- signal_connection_available
31
- task = @engine.start_recv_pump(connection, @recv_queue) do |msg|
32
- msg.first&.empty? ? msg[1..] : msg
33
- end
34
- @tasks << task if task
35
- start_send_pump unless @send_pump_started
36
- end
37
-
38
- # @param connection [Connection]
39
- #
40
- def connection_removed(connection)
41
- @connections.delete(connection)
42
- end
43
-
44
- # @param parts [Array<String>]
45
- #
46
- def enqueue(parts)
47
- @send_queue.enqueue(parts)
48
- end
49
-
50
- #
51
- def stop
52
- @tasks.each(&:stop)
53
- @tasks.clear
54
- end
55
-
56
- private
57
-
58
- # REQ prepends empty delimiter frame on the wire.
59
- #
60
- def transform_send(parts) = ["".b, *parts]
61
-
62
- end
63
- end
64
- end
65
- end