omq 0.10.0 → 0.12.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +157 -0
  3. data/README.md +31 -4
  4. data/lib/omq/drop_queue.rb +54 -0
  5. data/lib/omq/engine.rb +103 -61
  6. data/lib/omq/monitor_event.rb +16 -0
  7. data/lib/omq/options.rb +6 -2
  8. data/lib/omq/pair.rb +2 -2
  9. data/lib/omq/pub_sub.rb +13 -12
  10. data/lib/omq/push_pull.rb +4 -4
  11. data/lib/omq/queue_interface.rb +73 -0
  12. data/lib/omq/readable.rb +2 -0
  13. data/lib/omq/req_rep.rb +4 -4
  14. data/lib/omq/router_dealer.rb +4 -4
  15. data/lib/omq/routing/dealer.rb +1 -1
  16. data/lib/omq/routing/fan_out.rb +26 -5
  17. data/lib/omq/routing/pair.rb +2 -2
  18. data/lib/omq/routing/pull.rb +1 -1
  19. data/lib/omq/routing/push.rb +2 -0
  20. data/lib/omq/routing/rep.rb +2 -2
  21. data/lib/omq/routing/req.rb +8 -3
  22. data/lib/omq/routing/round_robin.rb +4 -12
  23. data/lib/omq/routing/router.rb +2 -2
  24. data/lib/omq/routing/sub.rb +1 -2
  25. data/lib/omq/routing/xpub.rb +1 -1
  26. data/lib/omq/routing/xsub.rb +2 -2
  27. data/lib/omq/routing.rb +41 -11
  28. data/lib/omq/socket.rb +49 -2
  29. data/lib/omq/transport/inproc.rb +25 -7
  30. data/lib/omq/transport/ipc.rb +16 -4
  31. data/lib/omq/transport/tcp.rb +31 -8
  32. data/lib/omq/version.rb +1 -1
  33. data/lib/omq/writable.rb +1 -0
  34. data/lib/omq.rb +6 -16
  35. metadata +4 -15
  36. data/lib/omq/channel.rb +0 -14
  37. data/lib/omq/client_server.rb +0 -37
  38. data/lib/omq/peer.rb +0 -26
  39. data/lib/omq/radio_dish.rb +0 -74
  40. data/lib/omq/routing/channel.rb +0 -83
  41. data/lib/omq/routing/client.rb +0 -56
  42. data/lib/omq/routing/dish.rb +0 -78
  43. data/lib/omq/routing/gather.rb +0 -46
  44. data/lib/omq/routing/peer.rb +0 -101
  45. data/lib/omq/routing/radio.rb +0 -140
  46. data/lib/omq/routing/scatter.rb +0 -82
  47. data/lib/omq/routing/server.rb +0 -101
  48. data/lib/omq/scatter_gather.rb +0 -23
  49. data/lib/omq/single_frame.rb +0 -18
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OMQ
4
- module Routing
5
- # GATHER socket routing: fair-queue receive from SCATTER peers.
6
- #
7
- class Gather
8
- # @param engine [Engine]
9
- #
10
- def initialize(engine)
11
- @engine = engine
12
- @recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
13
- @tasks = []
14
- end
15
-
16
- # @return [Async::LimitedQueue]
17
- #
18
- attr_reader :recv_queue
19
-
20
- # @param connection [Connection]
21
- #
22
- def connection_added(connection)
23
- task = @engine.start_recv_pump(connection, @recv_queue)
24
- @tasks << task if task
25
- end
26
-
27
- # @param connection [Connection]
28
- #
29
- def connection_removed(connection)
30
- # recv pump stops on CONNECTION_LOST
31
- end
32
-
33
- # GATHER is read-only.
34
- #
35
- def enqueue(_parts)
36
- raise "GATHER sockets cannot send"
37
- end
38
-
39
- #
40
- def stop
41
- @tasks.each(&:stop)
42
- @tasks.clear
43
- end
44
- end
45
- end
46
- end
@@ -1,101 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "securerandom"
4
-
5
- module OMQ
6
- module Routing
7
- # PEER socket routing: bidirectional multi-peer with auto-generated
8
- # 4-byte routing IDs.
9
- #
10
- # Prepends routing ID on receive. Strips routing ID on send and
11
- # routes to the identified connection.
12
- #
13
- class Peer
14
- # @param engine [Engine]
15
- #
16
- def initialize(engine)
17
- @engine = engine
18
- @recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
19
- @send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
20
- @connections_by_routing_id = {}
21
- @routing_id_by_connection = {}
22
- @tasks = []
23
- @send_pump_started = false
24
- @send_pump_idle = true
25
- @written = Set.new
26
- end
27
-
28
- # @return [Async::LimitedQueue]
29
- #
30
- attr_reader :recv_queue, :send_queue
31
-
32
- # @param connection [Connection]
33
- #
34
- def connection_added(connection)
35
- routing_id = SecureRandom.bytes(4)
36
- @connections_by_routing_id[routing_id] = connection
37
- @routing_id_by_connection[connection] = routing_id
38
-
39
- task = @engine.start_recv_pump(connection, @recv_queue) do |msg|
40
- [routing_id, *msg]
41
- end
42
- @tasks << task if task
43
-
44
- start_send_pump unless @send_pump_started
45
- end
46
-
47
- # @param connection [Connection]
48
- #
49
- def connection_removed(connection)
50
- routing_id = @routing_id_by_connection.delete(connection)
51
- @connections_by_routing_id.delete(routing_id) if routing_id
52
- end
53
-
54
- # @param parts [Array<String>]
55
- #
56
- def enqueue(parts)
57
- @send_queue.enqueue(parts)
58
- end
59
-
60
- def stop
61
- @tasks.each(&:stop)
62
- @tasks.clear
63
- end
64
-
65
- def send_pump_idle? = @send_pump_idle
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.clear
79
- batch.each do |parts|
80
- routing_id = parts.first
81
- conn = @connections_by_routing_id[routing_id]
82
- next unless conn # silently drop if peer gone
83
- begin
84
- conn.write_message(parts[1..])
85
- @written << conn
86
- rescue *CONNECTION_LOST
87
- # will be cleaned up
88
- end
89
- end
90
-
91
- @written.each do |conn|
92
- conn.flush
93
- rescue *CONNECTION_LOST
94
- # will be cleaned up
95
- end
96
- end
97
- end
98
- end
99
- end
100
- end
101
- end
@@ -1,140 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OMQ
4
- module Routing
5
- # RADIO socket routing: group-based fan-out to DISH peers.
6
- #
7
- # Like PUB/FanOut but with exact group matching and JOIN/LEAVE
8
- # commands instead of SUBSCRIBE/CANCEL.
9
- #
10
- # Messages are sent as two frames on the wire:
11
- # group (MORE=1) + body (MORE=0)
12
- #
13
- class Radio
14
-
15
- # @param engine [Engine]
16
- #
17
- def initialize(engine)
18
- @engine = engine
19
- @connections = []
20
- @groups = {} # connection => Set of joined groups
21
- @send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
22
- @send_pump_started = false
23
- @conflate = engine.options.conflate
24
- @tasks = []
25
- @written = Set.new
26
- @latest = {} if @conflate
27
- end
28
-
29
- # @return [Async::LimitedQueue]
30
- #
31
- attr_reader :send_queue
32
-
33
- # RADIO is write-only.
34
- #
35
- def recv_queue
36
- raise "RADIO sockets cannot receive"
37
- end
38
-
39
- # @param connection [Connection]
40
- #
41
- def connection_added(connection)
42
- @connections << connection
43
- @groups[connection] = Set.new
44
- start_group_listener(connection)
45
- start_send_pump unless @send_pump_started
46
- end
47
-
48
- # @param connection [Connection]
49
- #
50
- def connection_removed(connection)
51
- @connections.delete(connection)
52
- @groups.delete(connection)
53
- end
54
-
55
- # Enqueues a message for sending.
56
- #
57
- # @param parts [Array<String>] [group, body]
58
- #
59
- def enqueue(parts)
60
- @send_queue.enqueue(parts)
61
- end
62
-
63
- def stop
64
- @tasks.each(&:stop)
65
- @tasks.clear
66
- end
67
-
68
- private
69
-
70
- def start_send_pump
71
- @send_pump_started = true
72
- @tasks << @engine.spawn_pump_task(annotation: "send pump") do
73
- loop do
74
- @send_pump_idle = true
75
- batch = [@send_queue.dequeue]
76
- @send_pump_idle = false
77
- Routing.drain_send_queue(@send_queue, batch)
78
-
79
- @written.clear
80
-
81
- if @conflate
82
- # Keep only the last matching message per connection.
83
- @latest.clear
84
- batch.each do |parts|
85
- group = parts[0]
86
- body = parts[1] || EMPTY_BINARY
87
- @connections.each do |conn|
88
- next unless @groups[conn]&.include?(group)
89
- @latest[conn] = [group, body]
90
- end
91
- end
92
- @latest.each do |conn, msg|
93
- begin
94
- conn.write_message(msg)
95
- @written << conn
96
- rescue *CONNECTION_LOST
97
- end
98
- end
99
- else
100
- batch.each do |parts|
101
- group = parts[0]
102
- body = parts[1] || EMPTY_BINARY
103
- @connections.each do |conn|
104
- next unless @groups[conn]&.include?(group)
105
- begin
106
- conn.write_message([group, body])
107
- @written << conn
108
- rescue *CONNECTION_LOST
109
- end
110
- end
111
- end
112
- end
113
-
114
- @written.each do |conn|
115
- conn.flush
116
- rescue *CONNECTION_LOST
117
- end
118
- end
119
- end
120
- end
121
-
122
-
123
- def start_group_listener(conn)
124
- @tasks << @engine.spawn_pump_task(annotation: "group listener") do
125
- loop do
126
- frame = conn.read_frame
127
- next unless frame.command?
128
- cmd = Protocol::ZMTP::Codec::Command.from_body(frame.body)
129
- case cmd.name
130
- when "JOIN" then @groups[conn]&.add(cmd.data)
131
- when "LEAVE" then @groups[conn]&.delete(cmd.data)
132
- end
133
- end
134
- rescue *CONNECTION_LOST
135
- @engine.connection_lost(conn)
136
- end
137
- end
138
- end
139
- end
140
- end
@@ -1,82 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OMQ
4
- module Routing
5
- # SCATTER socket routing: round-robin send to GATHER peers.
6
- #
7
- class Scatter
8
- include RoundRobin
9
-
10
- # @param engine [Engine]
11
- #
12
- def initialize(engine)
13
- @engine = engine
14
- @tasks = []
15
- init_round_robin(engine)
16
- end
17
-
18
-
19
- # @return [Async::LimitedQueue]
20
- #
21
- attr_reader :send_queue
22
-
23
-
24
- # SCATTER is write-only.
25
- #
26
- def recv_queue
27
- raise "SCATTER sockets cannot receive"
28
- end
29
-
30
-
31
- # @param connection [Connection]
32
- #
33
- def connection_added(connection)
34
- @connections << connection
35
- signal_connection_available
36
- update_direct_pipe
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
- update_direct_pipe
47
- end
48
-
49
-
50
- # @param parts [Array<String>]
51
- #
52
- def enqueue(parts)
53
- enqueue_round_robin(parts)
54
- end
55
-
56
-
57
- # Stops all background tasks (send pump, reapers).
58
- #
59
- def stop
60
- @tasks.each(&:stop)
61
- @tasks.clear
62
- end
63
-
64
- private
65
-
66
-
67
- # Detects peer disconnection on write-only sockets by
68
- # blocking on a receive that only returns on disconnect.
69
- #
70
- # @param conn [Connection]
71
- #
72
- def start_reaper(conn)
73
- return if conn.is_a?(Transport::Inproc::DirectPipe)
74
- @tasks << @engine.spawn_pump_task(annotation: "reaper") do
75
- conn.receive_message
76
- rescue *CONNECTION_LOST
77
- @engine.connection_lost(conn)
78
- end
79
- end
80
- end
81
- end
82
- end
@@ -1,101 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "securerandom"
4
-
5
- module OMQ
6
- module Routing
7
- # SERVER socket routing: identity-based routing with auto-generated
8
- # 4-byte routing IDs.
9
- #
10
- # Prepends routing ID on receive. Strips routing ID on send and
11
- # routes to the identified connection.
12
- #
13
- class Server
14
- # @param engine [Engine]
15
- #
16
- def initialize(engine)
17
- @engine = engine
18
- @recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
19
- @send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
20
- @connections_by_routing_id = {}
21
- @routing_id_by_connection = {}
22
- @tasks = []
23
- @send_pump_started = false
24
- @send_pump_idle = true
25
- @written = Set.new
26
- end
27
-
28
- # @return [Async::LimitedQueue]
29
- #
30
- attr_reader :recv_queue, :send_queue
31
-
32
- # @param connection [Connection]
33
- #
34
- def connection_added(connection)
35
- routing_id = SecureRandom.bytes(4)
36
- @connections_by_routing_id[routing_id] = connection
37
- @routing_id_by_connection[connection] = routing_id
38
-
39
- task = @engine.start_recv_pump(connection, @recv_queue) do |msg|
40
- [routing_id, *msg]
41
- end
42
- @tasks << task if task
43
-
44
- start_send_pump unless @send_pump_started
45
- end
46
-
47
- # @param connection [Connection]
48
- #
49
- def connection_removed(connection)
50
- routing_id = @routing_id_by_connection.delete(connection)
51
- @connections_by_routing_id.delete(routing_id) if routing_id
52
- end
53
-
54
- # @param parts [Array<String>]
55
- #
56
- def enqueue(parts)
57
- @send_queue.enqueue(parts)
58
- end
59
-
60
- def stop
61
- @tasks.each(&:stop)
62
- @tasks.clear
63
- end
64
-
65
- def send_pump_idle? = @send_pump_idle
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.clear
79
- batch.each do |parts|
80
- routing_id = parts.first
81
- conn = @connections_by_routing_id[routing_id]
82
- next unless conn # silently drop if peer gone
83
- begin
84
- conn.write_message(parts[1..])
85
- @written << conn
86
- rescue *CONNECTION_LOST
87
- # will be cleaned up
88
- end
89
- end
90
-
91
- @written.each do |conn|
92
- conn.flush
93
- rescue *CONNECTION_LOST
94
- # will be cleaned up
95
- end
96
- end
97
- end
98
- end
99
- end
100
- end
101
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OMQ
4
- class SCATTER < Socket
5
- include Writable
6
- include SingleFrame
7
-
8
- def initialize(endpoints = nil, linger: 0, send_hwm: nil, send_timeout: nil)
9
- _init_engine(:SCATTER, linger: linger, send_hwm: send_hwm, send_timeout: send_timeout)
10
- _attach(endpoints, default: :connect)
11
- end
12
- end
13
-
14
- class GATHER < Socket
15
- include Readable
16
- include SingleFrame
17
-
18
- def initialize(endpoints = nil, linger: 0, recv_hwm: nil, recv_timeout: nil)
19
- _init_engine(:GATHER, linger: linger, recv_hwm: recv_hwm, recv_timeout: recv_timeout)
20
- _attach(endpoints, default: :bind)
21
- end
22
- end
23
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OMQ
4
- # Mixin that rejects multipart messages.
5
- #
6
- # All draft socket types (CLIENT, SERVER, RADIO, DISH, SCATTER,
7
- # GATHER, PEER, CHANNEL) require single-frame messages for
8
- # thread-safe atomic operations.
9
- #
10
- module SingleFrame
11
- def send(message)
12
- if message.is_a?(Array) && message.size > 1
13
- raise ArgumentError, "#{self.class} does not support multipart messages"
14
- end
15
- super
16
- end
17
- end
18
- end