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
@@ -1,143 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OMQ
4
- module ZMTP
5
- module Routing
6
- # Mixin for routing strategies that send via round-robin.
7
- #
8
- # Provides reactive connection management: Async::Promise waits
9
- # for the first connection, Array#cycle handles round-robin,
10
- # and a new Promise is created when all connections drop.
11
- #
12
- # Including classes must call `init_round_robin(engine)` from
13
- # their #initialize.
14
- #
15
- module RoundRobin
16
- private
17
-
18
-
19
- # Initializes round-robin state for the including class.
20
- #
21
- # @param engine [Engine]
22
- #
23
- def init_round_robin(engine)
24
- @connections = []
25
- @cycle = @connections.cycle
26
- @connection_available = Async::Promise.new
27
- @send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
28
- @send_pump_started = false
29
- @send_pump_idle = true
30
- end
31
-
32
-
33
- # Resolves the connection-available promise so blocked
34
- # senders can proceed.
35
- #
36
- def signal_connection_available
37
- unless @connection_available.resolved?
38
- @connection_available.resolve(true)
39
- end
40
- end
41
-
42
-
43
- # Blocks until a connection is available, then returns
44
- # the next one in round-robin order.
45
- #
46
- # @return [Connection]
47
- #
48
- def next_connection
49
- @cycle.next
50
- rescue StopIteration
51
- @connection_available = Async::Promise.new
52
- @connection_available.wait
53
- @cycle = @connections.cycle
54
- retry
55
- end
56
-
57
-
58
- # Transforms parts before sending. Override in subclasses
59
- # (e.g. REQ prepends an empty delimiter frame).
60
- #
61
- # @param parts [Array<String>]
62
- # @return [Array<String>]
63
- #
64
- def transform_send(parts) = parts
65
-
66
-
67
- # Starts the background send pump that dequeues messages
68
- # and dispatches them round-robin across connections.
69
- #
70
- # @return [Boolean] true when the send pump is idle (not sending a batch)
71
- def send_pump_idle? = @send_pump_idle
72
-
73
-
74
- def start_send_pump
75
- @send_pump_started = true
76
- @tasks << @engine.spawn_pump_task(annotation: "send pump") do
77
- loop do
78
- @send_pump_idle = true
79
- batch = [@send_queue.dequeue]
80
- @send_pump_idle = false
81
- Routing.drain_send_queue(@send_queue, batch)
82
-
83
- if batch.size == 1
84
- send_with_retry(batch[0])
85
- else
86
- send_batch(batch)
87
- end
88
- end
89
- end
90
- end
91
-
92
-
93
- # Sends a single message, retrying on a new connection if
94
- # the current one is lost.
95
- #
96
- # @param parts [Array<String>]
97
- #
98
- def send_with_retry(parts)
99
- conn = next_connection
100
- conn.send_message(transform_send(parts))
101
- rescue *ZMTP::CONNECTION_LOST
102
- @engine.connection_lost(conn)
103
- retry
104
- end
105
-
106
-
107
- # Sends a batch of messages, writing without flushing for
108
- # throughput. Falls back to #send_with_retry on failure.
109
- #
110
- # @param batch [Array<Array<String>>]
111
- #
112
- def send_batch(batch)
113
- written = Set.new
114
- batch.each_with_index do |parts, i|
115
- conn = next_connection
116
- begin
117
- conn.write_message(transform_send(parts))
118
- written << conn
119
- rescue *ZMTP::CONNECTION_LOST
120
- @engine.connection_lost(conn)
121
- # Flush what we've written so far
122
- written.each do |c|
123
- c.flush
124
- rescue *ZMTP::CONNECTION_LOST
125
- # will be cleaned up
126
- end
127
- written.clear
128
- # Fall back to send_with_retry for this and remaining
129
- send_with_retry(parts)
130
- batch[(i + 1)..].each { |p| send_with_retry(p) }
131
- return
132
- end
133
- end
134
- written.each do |conn|
135
- conn.flush
136
- rescue *ZMTP::CONNECTION_LOST
137
- # will be cleaned up
138
- end
139
- end
140
- end
141
- end
142
- end
143
- end
@@ -1,109 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "securerandom"
4
- require "socket"
5
-
6
- module OMQ
7
- module ZMTP
8
- module Routing
9
- # ROUTER socket routing: identity-based routing.
10
- #
11
- # Prepends peer identity frame on receive. Uses first frame as
12
- # routing identity on send.
13
- #
14
- class Router
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_identity = {}
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
- identity = connection.peer_identity
35
- identity = SecureRandom.bytes(5) if identity.nil? || identity.empty?
36
- @connections_by_identity[identity] = connection
37
-
38
- task = @engine.start_recv_pump(connection, @recv_queue) do |msg|
39
- [identity, *msg]
40
- end
41
- @tasks << task if task
42
-
43
- start_send_pump unless @send_pump_started
44
- end
45
-
46
- # @param connection [Connection]
47
- #
48
- def connection_removed(connection)
49
- @connections_by_identity.reject! { |_, c| c == connection }
50
- end
51
-
52
- # Enqueues a message for sending.
53
- #
54
- # @param parts [Array<String>]
55
- #
56
- def enqueue(parts)
57
- if @engine.options.router_mandatory?
58
- identity = parts.first
59
- unless @connections_by_identity[identity]
60
- raise SocketError, "no route to identity #{identity.inspect}"
61
- end
62
- end
63
- @send_queue.enqueue(parts)
64
- end
65
-
66
- def stop
67
- @tasks.each(&:stop)
68
- @tasks.clear
69
- end
70
-
71
- private
72
-
73
- def send_pump_idle? = @send_pump_idle
74
-
75
-
76
- def start_send_pump
77
- @send_pump_started = true
78
- @tasks << @engine.spawn_pump_task(annotation: "send pump") do
79
- loop do
80
- @send_pump_idle = true
81
- batch = [@send_queue.dequeue]
82
- @send_pump_idle = false
83
- Routing.drain_send_queue(@send_queue, batch)
84
-
85
- written = Set.new
86
- batch.each do |parts|
87
- identity = parts.first
88
- conn = @connections_by_identity[identity]
89
- next unless conn # silently drop (peer may have disconnected)
90
- begin
91
- conn.write_message(parts[1..])
92
- written << conn
93
- rescue *ZMTP::CONNECTION_LOST
94
- # will be cleaned up
95
- end
96
- end
97
-
98
- written.each do |conn|
99
- conn.flush
100
- rescue *ZMTP::CONNECTION_LOST
101
- # will be cleaned up
102
- end
103
- end
104
- end
105
- end
106
- end
107
- end
108
- end
109
- end
@@ -1,81 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OMQ
4
- module ZMTP
5
- module Routing
6
- # SCATTER socket routing: round-robin send to GATHER peers.
7
- #
8
- class Scatter
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
- # SCATTER is write-only.
26
- #
27
- def recv_queue
28
- raise "SCATTER 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 by
67
- # blocking on a receive that only returns on disconnect.
68
- #
69
- # @param conn [Connection]
70
- #
71
- def start_reaper(conn)
72
- @tasks << Reactor.spawn_pump(annotation: "reaper") do
73
- conn.receive_message
74
- rescue *ZMTP::CONNECTION_LOST
75
- @engine.connection_lost(conn)
76
- end
77
- end
78
- end
79
- end
80
- end
81
- end
@@ -1,100 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "securerandom"
4
-
5
- module OMQ
6
- module ZMTP
7
- module Routing
8
- # SERVER socket routing: identity-based routing 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 Server
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,80 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OMQ
4
- module ZMTP
5
- module Routing
6
- # SUB socket routing: subscription-based receive from PUB peers.
7
- #
8
- # Sends SUBSCRIBE/CANCEL commands to connected PUB peers.
9
- #
10
- class Sub
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
- @subscriptions = 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 subscriptions to new peer
31
- @subscriptions.each do |prefix|
32
- connection.send_command(Codec::Command.subscribe(prefix))
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
- # SUB is read-only.
45
- #
46
- def enqueue(_parts)
47
- raise "SUB sockets cannot send"
48
- end
49
-
50
- # Subscribes to a topic prefix.
51
- #
52
- # @param prefix [String]
53
- #
54
- def subscribe(prefix)
55
- @subscriptions << prefix
56
- @connections.each do |conn|
57
- conn.send_command(Codec::Command.subscribe(prefix))
58
- end
59
- end
60
-
61
- # Unsubscribes from a topic prefix.
62
- #
63
- # @param prefix [String]
64
- #
65
- def unsubscribe(prefix)
66
- @subscriptions.delete(prefix)
67
- @connections.each do |conn|
68
- conn.send_command(Codec::Command.cancel(prefix))
69
- end
70
- end
71
-
72
- def stop
73
- @tasks.each(&:stop)
74
- @tasks.clear
75
- end
76
-
77
- end
78
- end
79
- end
80
- end
@@ -1,74 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OMQ
4
- module ZMTP
5
- module Routing
6
- # XPUB socket routing: like PUB but exposes subscription messages.
7
- #
8
- # Subscription/unsubscription messages from peers are delivered to
9
- # the application as data frames: \x01 + prefix for subscribe,
10
- # \x00 + prefix for unsubscribe.
11
- #
12
- class XPub
13
- include FanOut
14
-
15
- # @param engine [Engine]
16
- #
17
- def initialize(engine)
18
- @engine = engine
19
- @recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
20
- @tasks = []
21
- init_fan_out(engine)
22
- end
23
-
24
- # @return [Async::LimitedQueue]
25
- #
26
- attr_reader :recv_queue, :send_queue
27
-
28
- # @param connection [Connection]
29
- #
30
- def connection_added(connection)
31
- @connections << connection
32
- @subscriptions[connection] = Set.new
33
- start_subscription_listener(connection)
34
- start_send_pump unless @send_pump_started
35
- end
36
-
37
- # @param connection [Connection]
38
- #
39
- def connection_removed(connection)
40
- @connections.delete(connection)
41
- @subscriptions.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
- # Expose subscription to application as data message.
59
- #
60
- def on_subscribe(conn, prefix)
61
- super
62
- @recv_queue.enqueue(["\x01#{prefix}".b])
63
- end
64
-
65
- # Expose unsubscription to application as data message.
66
- #
67
- def on_cancel(conn, prefix)
68
- super
69
- @recv_queue.enqueue(["\x00#{prefix}".b])
70
- end
71
- end
72
- end
73
- end
74
- end
@@ -1,86 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OMQ
4
- module ZMTP
5
- module Routing
6
- # XSUB socket routing: like SUB but subscriptions sent as data messages.
7
- #
8
- # Subscriptions are sent as data frames: \x01 + prefix for subscribe,
9
- # \x00 + prefix for unsubscribe.
10
- #
11
- class XSub
12
-
13
- # @param engine [Engine]
14
- #
15
- def initialize(engine)
16
- @engine = engine
17
- @connections = []
18
- @recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
19
- @send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
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
- @connections << connection
33
- task = @engine.start_recv_pump(connection, @recv_queue)
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
- def send_pump_idle? = @send_pump_idle
59
-
60
-
61
- def start_send_pump
62
- @send_pump_started = true
63
- @tasks << @engine.spawn_pump_task(annotation: "send pump") do
64
- loop do
65
- @send_pump_idle = true
66
- parts = @send_queue.dequeue
67
- @send_pump_idle = false
68
- frame = parts.first&.b
69
- next if frame.nil? || frame.empty?
70
-
71
- flag = frame.getbyte(0)
72
- prefix = frame.byteslice(1..) || "".b
73
-
74
- case flag
75
- when 0x01
76
- @connections.each { |c| c.send_command(Codec::Command.subscribe(prefix)) }
77
- when 0x00
78
- @connections.each { |c| c.send_command(Codec::Command.cancel(prefix)) }
79
- end
80
- end
81
- end
82
- end
83
- end
84
- end
85
- end
86
- end