omq 0.11.0 → 0.13.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +143 -0
  3. data/README.md +3 -1
  4. data/lib/omq/drop_queue.rb +54 -0
  5. data/lib/omq/engine/connection_setup.rb +47 -0
  6. data/lib/omq/engine/heartbeat.rb +40 -0
  7. data/lib/omq/engine/reconnect.rb +56 -0
  8. data/lib/omq/engine/recv_pump.rb +76 -0
  9. data/lib/omq/engine.rb +145 -371
  10. data/lib/omq/monitor_event.rb +16 -0
  11. data/lib/omq/options.rb +5 -3
  12. data/lib/omq/pub_sub.rb +9 -8
  13. data/lib/omq/routing/conn_send_pump.rb +36 -0
  14. data/lib/omq/routing/dealer.rb +8 -10
  15. data/lib/omq/routing/fair_queue.rb +144 -0
  16. data/lib/omq/routing/fair_recv.rb +27 -0
  17. data/lib/omq/routing/fan_out.rb +116 -63
  18. data/lib/omq/routing/pair.rb +39 -20
  19. data/lib/omq/routing/pub.rb +5 -7
  20. data/lib/omq/routing/pull.rb +5 -4
  21. data/lib/omq/routing/push.rb +3 -10
  22. data/lib/omq/routing/rep.rb +31 -51
  23. data/lib/omq/routing/req.rb +15 -12
  24. data/lib/omq/routing/round_robin.rb +82 -72
  25. data/lib/omq/routing/router.rb +23 -48
  26. data/lib/omq/routing/sub.rb +8 -6
  27. data/lib/omq/routing/xpub.rb +8 -4
  28. data/lib/omq/routing/xsub.rb +43 -27
  29. data/lib/omq/routing.rb +44 -11
  30. data/lib/omq/socket.rb +46 -5
  31. data/lib/omq/transport/inproc/direct_pipe.rb +162 -0
  32. data/lib/omq/transport/inproc.rb +37 -200
  33. data/lib/omq/transport/ipc.rb +16 -4
  34. data/lib/omq/transport/tcp.rb +31 -8
  35. data/lib/omq/version.rb +1 -1
  36. data/lib/omq.rb +5 -19
  37. metadata +11 -16
  38. data/lib/omq/channel.rb +0 -14
  39. data/lib/omq/client_server.rb +0 -37
  40. data/lib/omq/peer.rb +0 -26
  41. data/lib/omq/radio_dish.rb +0 -74
  42. data/lib/omq/routing/channel.rb +0 -83
  43. data/lib/omq/routing/client.rb +0 -56
  44. data/lib/omq/routing/dish.rb +0 -78
  45. data/lib/omq/routing/gather.rb +0 -46
  46. data/lib/omq/routing/peer.rb +0 -101
  47. data/lib/omq/routing/radio.rb +0 -150
  48. data/lib/omq/routing/scatter.rb +0 -82
  49. data/lib/omq/routing/server.rb +0 -101
  50. data/lib/omq/scatter_gather.rb +0 -23
  51. data/lib/omq/single_frame.rb +0 -18
  52. data/lib/omq/transport/tls.rb +0 -146
@@ -1,83 +0,0 @@
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
@@ -1,56 +0,0 @@
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
@@ -1,78 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OMQ
4
- module Routing
5
- # DISH socket routing: group-based receive from RADIO peers.
6
- #
7
- # Sends JOIN/LEAVE commands to connected RADIO peers.
8
- # Receives two-frame messages (group + body) from RADIO.
9
- #
10
- class Dish
11
-
12
- # @param engine [Engine]
13
- #
14
- def initialize(engine)
15
- @engine = engine
16
- @connections = []
17
- @recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
18
- @groups = Set.new
19
- @tasks = []
20
- end
21
-
22
- # @return [Async::LimitedQueue]
23
- #
24
- attr_reader :recv_queue
25
-
26
- # @param connection [Connection]
27
- #
28
- def connection_added(connection)
29
- @connections << connection
30
- # Send existing group memberships to new peer
31
- @groups.each do |group|
32
- connection.send_command(Protocol::ZMTP::Codec::Command.join(group))
33
- end
34
- task = @engine.start_recv_pump(connection, @recv_queue)
35
- @tasks << task if task
36
- end
37
-
38
- # @param connection [Connection]
39
- #
40
- def connection_removed(connection)
41
- @connections.delete(connection)
42
- end
43
-
44
- # DISH is read-only.
45
- #
46
- def enqueue(_parts)
47
- raise "DISH sockets cannot send"
48
- end
49
-
50
- # Joins a group.
51
- #
52
- # @param group [String]
53
- #
54
- def join(group)
55
- @groups << group
56
- @connections.each do |conn|
57
- conn.send_command(Protocol::ZMTP::Codec::Command.join(group))
58
- end
59
- end
60
-
61
- # Leaves a group.
62
- #
63
- # @param group [String]
64
- #
65
- def leave(group)
66
- @groups.delete(group)
67
- @connections.each do |conn|
68
- conn.send_command(Protocol::ZMTP::Codec::Command.leave(group))
69
- end
70
- end
71
-
72
- def stop
73
- @tasks.each(&:stop)
74
- @tasks.clear
75
- end
76
- end
77
- end
78
- end
@@ -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,150 +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
- msg = [group, body]
104
- wire_bytes = nil
105
-
106
- @connections.each do |conn|
107
- next unless @groups[conn]&.include?(group)
108
- begin
109
- if conn.respond_to?(:curve?) && conn.curve?
110
- conn.write_message(msg)
111
- elsif conn.respond_to?(:write_wire)
112
- wire_bytes ||= Protocol::ZMTP::Codec::Frame.encode_message(msg)
113
- conn.write_wire(wire_bytes)
114
- else
115
- conn.write_message(msg)
116
- end
117
- @written << conn
118
- rescue *CONNECTION_LOST
119
- end
120
- end
121
- end
122
- end
123
-
124
- @written.each do |conn|
125
- conn.flush
126
- rescue *CONNECTION_LOST
127
- end
128
- end
129
- end
130
- end
131
-
132
-
133
- def start_group_listener(conn)
134
- @tasks << @engine.spawn_pump_task(annotation: "group listener") do
135
- loop do
136
- frame = conn.read_frame
137
- next unless frame.command?
138
- cmd = Protocol::ZMTP::Codec::Command.from_body(frame.body)
139
- case cmd.name
140
- when "JOIN" then @groups[conn]&.add(cmd.data)
141
- when "LEAVE" then @groups[conn]&.delete(cmd.data)
142
- end
143
- end
144
- rescue *CONNECTION_LOST
145
- @engine.connection_lost(conn)
146
- end
147
- end
148
- end
149
- end
150
- 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