omq 0.11.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +95 -0
- data/README.md +3 -1
- data/lib/omq/drop_queue.rb +54 -0
- data/lib/omq/engine.rb +68 -87
- data/lib/omq/monitor_event.rb +16 -0
- data/lib/omq/options.rb +5 -3
- data/lib/omq/pub_sub.rb +9 -8
- data/lib/omq/routing/dealer.rb +1 -1
- data/lib/omq/routing/fan_out.rb +15 -3
- data/lib/omq/routing/pair.rb +2 -2
- data/lib/omq/routing/pull.rb +1 -1
- data/lib/omq/routing/push.rb +2 -0
- data/lib/omq/routing/rep.rb +2 -2
- data/lib/omq/routing/req.rb +8 -3
- data/lib/omq/routing/round_robin.rb +4 -12
- data/lib/omq/routing/router.rb +2 -2
- data/lib/omq/routing/sub.rb +1 -2
- data/lib/omq/routing/xpub.rb +1 -1
- data/lib/omq/routing/xsub.rb +2 -2
- data/lib/omq/routing.rb +41 -11
- data/lib/omq/socket.rb +44 -3
- data/lib/omq/transport/inproc.rb +25 -7
- data/lib/omq/transport/ipc.rb +16 -4
- data/lib/omq/transport/tcp.rb +31 -8
- data/lib/omq/version.rb +1 -1
- data/lib/omq.rb +5 -19
- metadata +3 -16
- data/lib/omq/channel.rb +0 -14
- data/lib/omq/client_server.rb +0 -37
- data/lib/omq/peer.rb +0 -26
- data/lib/omq/radio_dish.rb +0 -74
- data/lib/omq/routing/channel.rb +0 -83
- data/lib/omq/routing/client.rb +0 -56
- data/lib/omq/routing/dish.rb +0 -78
- data/lib/omq/routing/gather.rb +0 -46
- data/lib/omq/routing/peer.rb +0 -101
- data/lib/omq/routing/radio.rb +0 -150
- data/lib/omq/routing/scatter.rb +0 -82
- data/lib/omq/routing/server.rb +0 -101
- data/lib/omq/scatter_gather.rb +0 -23
- data/lib/omq/single_frame.rb +0 -18
- data/lib/omq/transport/tls.rb +0 -146
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d5ea3ce51d24a9181cfe7a4c1f2680ed57c5dbba078010a54de5b1c13d27d484
|
|
4
|
+
data.tar.gz: 97f7e7fcb27a250e71af4732ff06298e7311af052a9d9a6b3868d73059373174
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9f60b7d45d1b3783b2bde482a6f826ec3b89bfff6a9532836dbd2a8a22622709e7c298cc7c2abae6e596641973636b8972efa92de7258bd1e4164ff62d782237
|
|
7
|
+
data.tar.gz: 7024b97bb46c9005b73bc7959cc4138bd5498a514af26253441613aa05206d357d1fbd002afff5820c9a3baff0c4351d66bcf1aa738955783272a2c2b459a697
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,100 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **QoS infrastructure** — `Options#qos` attribute (default 0) and inproc
|
|
8
|
+
command queue support for QoS-enabled connections. The
|
|
9
|
+
[omq-qos](https://github.com/paddor/omq-qos) gem activates delivery
|
|
10
|
+
guarantees via prepends.
|
|
11
|
+
- **REQ send/recv ordering** — REQ sockets now enforce strict
|
|
12
|
+
send/recv/send/recv alternation. Calling `#send` twice without a
|
|
13
|
+
`#receive` in between raises `SocketError`.
|
|
14
|
+
- **DirectPipe command frame support** — `DirectPipe#receive_message`
|
|
15
|
+
accepts a block for command frames, matching the `Protocol::ZMTP::Connection`
|
|
16
|
+
interface. Enables inproc transports to handle ACK/NACK and other
|
|
17
|
+
command-level protocols.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **`send_pump_idle?` visibility** — moved above `private` in `RoundRobin`
|
|
22
|
+
and `FanOut` so `Engine#drain_send_queues` can call it during socket close.
|
|
23
|
+
|
|
24
|
+
- **`Socket#monitor`** — observe connection lifecycle events via a
|
|
25
|
+
block-based API. Returns an `Async::Task` that yields `MonitorEvent`
|
|
26
|
+
(Data.define) instances for `:listening`, `:accepted`, `:connected`,
|
|
27
|
+
`:connect_delayed`, `:connect_retried`, `:handshake_succeeded`,
|
|
28
|
+
`:handshake_failed`, `:accept_failed`, `:bind_failed`, `:disconnected`,
|
|
29
|
+
`:closed`, and `:monitor_stopped`. Event types align with libzmq's
|
|
30
|
+
`zmq_socket_monitor` where applicable. Pattern-matchable, zero overhead
|
|
31
|
+
when no monitor is attached.
|
|
32
|
+
- **Pluggable transport registry** — `Engine.transports` is a scheme →
|
|
33
|
+
module hash. Built-in transports (`tcp`, `ipc`, `inproc`) are registered
|
|
34
|
+
at load time. External gems register via
|
|
35
|
+
`OMQ::Engine.transports["scheme"] = MyTransport`. Each transport
|
|
36
|
+
implements `.bind(endpoint, engine)` → Listener, `.connect(endpoint,
|
|
37
|
+
engine)`, and optionally `.validate_endpoint!(endpoint)`. Listeners
|
|
38
|
+
implement `#start_accept_loops(parent_task, &on_accepted)`, `#stop`,
|
|
39
|
+
`#endpoint`, and optionally `#port`.
|
|
40
|
+
- **Mutable error lists** — `CONNECTION_LOST` and `CONNECTION_FAILED` are
|
|
41
|
+
no longer frozen at load time. Transport plugins can append error classes
|
|
42
|
+
(e.g. `OpenSSL::SSL::SSLError`) before the first `#bind`/`#connect`,
|
|
43
|
+
which freezes both arrays.
|
|
44
|
+
|
|
45
|
+
- **`on_mute` option** — controls behavior when a socket enters the mute state
|
|
46
|
+
(HWM full). PUB, XPUB, and RADIO default to `on_mute: :drop_newest` — slow
|
|
47
|
+
subscribers are skipped in the fan-out rather than blocking the publisher.
|
|
48
|
+
SUB, XSUB, and DISH accept `on_mute: :drop_newest` or `:drop_oldest` to
|
|
49
|
+
drop messages on the receive side instead of applying backpressure. All other
|
|
50
|
+
socket types default to `:block` (existing behavior).
|
|
51
|
+
- **`DropQueue`** — bounded queue with `:drop_newest` (tail drop) and
|
|
52
|
+
`:drop_oldest` (head drop) strategies. Used by recv queues when `on_mute`
|
|
53
|
+
is a drop strategy.
|
|
54
|
+
- **`Routing.build_queue`** — factory method for building send/recv queues
|
|
55
|
+
based on HWM and mute strategy. Supports HWM of `0` or `nil` for unbounded
|
|
56
|
+
queues.
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
|
|
60
|
+
- **`max_message_size` defaults to 1 MiB** — frames exceeding this limit cause
|
|
61
|
+
the connection to be dropped before the body is read from the wire, preventing
|
|
62
|
+
a malicious peer from causing arbitrary memory allocation. Set `socket.max_message_size = nil`
|
|
63
|
+
to restore the previous unlimited behavior.
|
|
64
|
+
- **Accept loops moved into Listeners** — `TCP::Listener` and
|
|
65
|
+
`IPC::Listener` now own their accept loop logic via
|
|
66
|
+
`#start_accept_loops(parent_task, &on_accepted)`. Engine delegates
|
|
67
|
+
via duck-type check. This enables external transports to define
|
|
68
|
+
custom accept behavior without modifying Engine.
|
|
69
|
+
- `Engine#transport_for` uses registry lookup instead of `case/when`.
|
|
70
|
+
- `Engine#validate_endpoint!` delegates to transport module.
|
|
71
|
+
- `Engine#bind` reads `listener.port` instead of parsing the endpoint
|
|
72
|
+
string.
|
|
73
|
+
|
|
74
|
+
### Removed
|
|
75
|
+
|
|
76
|
+
- **Draft socket types extracted** — `RADIO`, `DISH`, `CLIENT`, `SERVER`,
|
|
77
|
+
`SCATTER`, `GATHER`, `CHANNEL`, and `PEER` are no longer bundled with `omq`.
|
|
78
|
+
Use the [omq-draft](https://github.com/paddor/omq-draft) gem and require
|
|
79
|
+
the relevant entry point (`omq/draft/radiodish`, `omq/draft/clientserver`,
|
|
80
|
+
etc.).
|
|
81
|
+
- **UDP transport extracted** — `udp://` endpoints are provided by
|
|
82
|
+
`omq-draft` (via `require "omq/draft/radiodish"`). No longer registered by
|
|
83
|
+
default.
|
|
84
|
+
- **`Routing.for` plugin registry** — draft socket type removal added
|
|
85
|
+
`Routing.register(socket_type, strategy_class)` for external gems to
|
|
86
|
+
register routing strategies. Unknown types fall through the built-in
|
|
87
|
+
`case` to this registry before raising `ArgumentError`.
|
|
88
|
+
|
|
89
|
+
- **TLS transport** — extracted to the
|
|
90
|
+
[omq-transport-tls](https://github.com/paddor/omq-transport-tls) gem.
|
|
91
|
+
(Experimental) `require "omq/transport/tls"` to restore `tls+tcp://` support.
|
|
92
|
+
- `tls_context` / `tls_context=` removed from `Options` and `Socket`
|
|
93
|
+
(provided by omq-transport-tls).
|
|
94
|
+
- `OpenSSL::SSL::SSLError` removed from `CONNECTION_LOST` (added back
|
|
95
|
+
by omq-transport-tls).
|
|
96
|
+
- TLS benchmark transport removed from `bench_helper.rb` and `plot.rb`.
|
|
97
|
+
|
|
3
98
|
## 0.11.0
|
|
4
99
|
|
|
5
100
|
### Added
|
data/README.md
CHANGED
|
@@ -147,7 +147,7 @@ end
|
|
|
147
147
|
|
|
148
148
|
## Socket Types
|
|
149
149
|
|
|
150
|
-
All sockets are thread-safe. Default HWM is 1000 messages per socket. Classes live under `OMQ::` (alias: `ØMQ`).
|
|
150
|
+
All sockets are thread-safe. Default HWM is 1000 messages per socket. Default `max_message_size` is **1 MiB** — frames larger than this cause the connection to be dropped before the body is read from the wire. Set `socket.max_message_size = nil` to disable the limit or raise it as needed. Classes live under `OMQ::` (alias: `ØMQ`).
|
|
151
151
|
|
|
152
152
|
#### Standard (multipart messages)
|
|
153
153
|
|
|
@@ -162,6 +162,8 @@ All sockets are thread-safe. Default HWM is 1000 messages per socket. Classes li
|
|
|
162
162
|
|
|
163
163
|
#### Draft (single-frame only)
|
|
164
164
|
|
|
165
|
+
These require the `omq-draft` gem.
|
|
166
|
+
|
|
165
167
|
| Pattern | Send | Receive | When HWM full |
|
|
166
168
|
|---------|------|---------|---------------|
|
|
167
169
|
| **CLIENT** / **SERVER** | Round-robin / routing-ID | Fair-queue | Block |
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
# A bounded queue that drops messages when full instead of blocking.
|
|
5
|
+
#
|
|
6
|
+
# Two drop strategies:
|
|
7
|
+
# :drop_newest — discard the incoming message (tail drop)
|
|
8
|
+
# :drop_oldest — discard the head, then enqueue the new message
|
|
9
|
+
#
|
|
10
|
+
# Used by SUB/XSUB/DISH recv queues when on_mute is a drop strategy.
|
|
11
|
+
#
|
|
12
|
+
class DropQueue
|
|
13
|
+
# @param limit [Integer] maximum number of items
|
|
14
|
+
# @param strategy [Symbol] :drop_newest or :drop_oldest
|
|
15
|
+
#
|
|
16
|
+
def initialize(limit, strategy: :drop_newest)
|
|
17
|
+
@queue = Thread::SizedQueue.new(limit)
|
|
18
|
+
@strategy = strategy
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Enqueues an item. Drops according to the configured strategy if full.
|
|
22
|
+
#
|
|
23
|
+
# @param item [Object]
|
|
24
|
+
# @return [void]
|
|
25
|
+
#
|
|
26
|
+
def enqueue(item)
|
|
27
|
+
@queue.push(item, true)
|
|
28
|
+
rescue ThreadError
|
|
29
|
+
return if @strategy == :drop_newest
|
|
30
|
+
|
|
31
|
+
# :drop_oldest — discard head, enqueue new
|
|
32
|
+
@queue.pop(true) rescue nil
|
|
33
|
+
retry
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Removes and returns the next item, blocking if empty.
|
|
37
|
+
#
|
|
38
|
+
# @return [Object]
|
|
39
|
+
#
|
|
40
|
+
def dequeue(timeout: nil)
|
|
41
|
+
if timeout
|
|
42
|
+
@queue.pop(timeout: timeout)
|
|
43
|
+
else
|
|
44
|
+
@queue.pop
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [Boolean]
|
|
49
|
+
#
|
|
50
|
+
def empty?
|
|
51
|
+
@queue.empty?
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/omq/engine.rb
CHANGED
|
@@ -9,6 +9,17 @@ module OMQ
|
|
|
9
9
|
# OMQ::Socket instance. Each socket type creates one Engine.
|
|
10
10
|
#
|
|
11
11
|
class Engine
|
|
12
|
+
# Scheme → transport module registry.
|
|
13
|
+
# Plugins add entries via +Engine.transports["scheme"] = MyTransport+.
|
|
14
|
+
#
|
|
15
|
+
@transports = {}
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
# @return [Hash{String => Module}] registered transports
|
|
19
|
+
attr_reader :transports
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
|
|
12
23
|
# @return [Symbol] socket type (e.g. :REQ, :PAIR)
|
|
13
24
|
#
|
|
14
25
|
attr_reader :socket_type
|
|
@@ -57,12 +68,13 @@ module OMQ
|
|
|
57
68
|
@on_io_thread = false
|
|
58
69
|
@connection_promises = {} # connection => Async::Promise
|
|
59
70
|
@fatal_error = nil
|
|
71
|
+
@monitor_queue = nil
|
|
60
72
|
end
|
|
61
73
|
|
|
62
74
|
|
|
63
75
|
attr_reader :peer_connected, :all_peers_gone, :connections, :parent_task
|
|
64
76
|
|
|
65
|
-
attr_writer :reconnect_enabled
|
|
77
|
+
attr_writer :reconnect_enabled, :monitor_queue
|
|
66
78
|
|
|
67
79
|
# Optional proc that wraps new connections (e.g. for serialization).
|
|
68
80
|
# Called with the raw connection; must return the (possibly wrapped) connection.
|
|
@@ -92,12 +104,17 @@ module OMQ
|
|
|
92
104
|
# @raise [ArgumentError] on unsupported transport
|
|
93
105
|
#
|
|
94
106
|
def bind(endpoint)
|
|
107
|
+
freeze_error_lists!
|
|
95
108
|
transport = transport_for(endpoint)
|
|
96
109
|
listener = transport.bind(endpoint, self)
|
|
97
110
|
start_accept_loops(listener)
|
|
98
111
|
@listeners << listener
|
|
99
112
|
@last_endpoint = listener.endpoint
|
|
100
|
-
@last_tcp_port =
|
|
113
|
+
@last_tcp_port = listener.respond_to?(:port) ? listener.port : nil
|
|
114
|
+
emit_monitor_event(:listening, endpoint: listener.endpoint)
|
|
115
|
+
rescue => error
|
|
116
|
+
emit_monitor_event(:bind_failed, endpoint: endpoint, detail: { error: error })
|
|
117
|
+
raise
|
|
101
118
|
end
|
|
102
119
|
|
|
103
120
|
|
|
@@ -107,6 +124,7 @@ module OMQ
|
|
|
107
124
|
# @return [void]
|
|
108
125
|
#
|
|
109
126
|
def connect(endpoint)
|
|
127
|
+
freeze_error_lists!
|
|
110
128
|
validate_endpoint!(endpoint)
|
|
111
129
|
@connected_endpoints << endpoint
|
|
112
130
|
if endpoint.start_with?("inproc://")
|
|
@@ -115,6 +133,7 @@ module OMQ
|
|
|
115
133
|
transport.connect(endpoint, self)
|
|
116
134
|
else
|
|
117
135
|
# TCP/IPC connect in background — never blocks the caller
|
|
136
|
+
emit_monitor_event(:connect_delayed, endpoint: endpoint)
|
|
118
137
|
schedule_reconnect(endpoint, delay: 0)
|
|
119
138
|
end
|
|
120
139
|
end
|
|
@@ -168,6 +187,7 @@ module OMQ
|
|
|
168
187
|
# @return [void]
|
|
169
188
|
#
|
|
170
189
|
def handle_accepted(io, endpoint: nil)
|
|
190
|
+
emit_monitor_event(:accepted, endpoint: endpoint)
|
|
171
191
|
spawn_connection(io, as_server: true, endpoint: endpoint)
|
|
172
192
|
end
|
|
173
193
|
|
|
@@ -178,6 +198,7 @@ module OMQ
|
|
|
178
198
|
# @return [void]
|
|
179
199
|
#
|
|
180
200
|
def handle_connected(io, endpoint: nil)
|
|
201
|
+
emit_monitor_event(:connected, endpoint: endpoint)
|
|
181
202
|
spawn_connection(io, as_server: false, endpoint: endpoint)
|
|
182
203
|
end
|
|
183
204
|
|
|
@@ -194,6 +215,7 @@ module OMQ
|
|
|
194
215
|
@connection_endpoints[pipe] = endpoint if endpoint
|
|
195
216
|
@routing.connection_added(pipe)
|
|
196
217
|
@peer_connected.resolve(pipe)
|
|
218
|
+
emit_monitor_event(:handshake_succeeded, endpoint: endpoint)
|
|
197
219
|
end
|
|
198
220
|
|
|
199
221
|
|
|
@@ -335,6 +357,7 @@ module OMQ
|
|
|
335
357
|
@connections.delete(connection)
|
|
336
358
|
@routing.connection_removed(connection)
|
|
337
359
|
connection.close
|
|
360
|
+
emit_monitor_event(:disconnected, endpoint: endpoint)
|
|
338
361
|
|
|
339
362
|
# Signal the connection task to exit.
|
|
340
363
|
done = @connection_promises.delete(connection)
|
|
@@ -393,6 +416,8 @@ module OMQ
|
|
|
393
416
|
@routing.stop rescue nil
|
|
394
417
|
@tasks.each { |t| t.stop rescue nil }
|
|
395
418
|
@tasks.clear
|
|
419
|
+
emit_monitor_event(:closed)
|
|
420
|
+
close_monitor_queue
|
|
396
421
|
end
|
|
397
422
|
|
|
398
423
|
|
|
@@ -519,8 +544,10 @@ module OMQ
|
|
|
519
544
|
@connection_promises[conn] = done if done
|
|
520
545
|
@routing.connection_added(conn)
|
|
521
546
|
@peer_connected.resolve(conn)
|
|
547
|
+
emit_monitor_event(:handshake_succeeded, endpoint: endpoint)
|
|
522
548
|
conn
|
|
523
|
-
rescue Protocol::ZMTP::Error, *CONNECTION_LOST
|
|
549
|
+
rescue Protocol::ZMTP::Error, *CONNECTION_LOST => error
|
|
550
|
+
emit_monitor_event(:handshake_failed, endpoint: endpoint, detail: { error: error })
|
|
524
551
|
conn&.close
|
|
525
552
|
raise
|
|
526
553
|
end
|
|
@@ -585,6 +612,7 @@ module OMQ
|
|
|
585
612
|
delay = [delay * 2, max_delay].min if max_delay
|
|
586
613
|
# After first attempt with delay: 0, use the configured interval
|
|
587
614
|
delay = ri.is_a?(Range) ? ri.begin : ri if delay == 0
|
|
615
|
+
emit_monitor_event(:connect_retried, endpoint: endpoint, detail: { interval: delay })
|
|
588
616
|
end
|
|
589
617
|
end
|
|
590
618
|
rescue Async::Stop
|
|
@@ -601,102 +629,55 @@ module OMQ
|
|
|
601
629
|
# resolution failures during reconnect are retried with backoff.
|
|
602
630
|
#
|
|
603
631
|
def validate_endpoint!(endpoint)
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
host = URI.parse(endpoint.sub("tcp://", "http://")).hostname
|
|
607
|
-
when /\Atls\+tcp:\/\//
|
|
608
|
-
host = URI.parse("http://#{endpoint.delete_prefix("tls+tcp://")}").hostname
|
|
609
|
-
else
|
|
610
|
-
return
|
|
611
|
-
end
|
|
612
|
-
Addrinfo.getaddrinfo(host, nil, nil, :STREAM) if host
|
|
632
|
+
transport = transport_for(endpoint)
|
|
633
|
+
transport.validate_endpoint!(endpoint) if transport.respond_to?(:validate_endpoint!)
|
|
613
634
|
end
|
|
614
635
|
|
|
615
636
|
|
|
637
|
+
|
|
616
638
|
def transport_for(endpoint)
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
639
|
+
scheme = endpoint[/\A([^:]+):\/\//, 1]
|
|
640
|
+
self.class.transports[scheme] or
|
|
641
|
+
raise ArgumentError, "unsupported transport: #{endpoint}"
|
|
642
|
+
end
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
# Delegates accept loop startup to the listener.
|
|
646
|
+
#
|
|
647
|
+
# Stream-based listeners (TCP, IPC, TLS, …) implement
|
|
648
|
+
# +#start_accept_loops+. Inproc listeners do not — connections
|
|
649
|
+
# are established synchronously during +connect+.
|
|
650
|
+
#
|
|
651
|
+
def start_accept_loops(listener)
|
|
652
|
+
return unless listener.respond_to?(:start_accept_loops)
|
|
653
|
+
listener.start_accept_loops(@parent_task) do |io|
|
|
654
|
+
handle_accepted(io, endpoint: listener.endpoint)
|
|
623
655
|
end
|
|
624
656
|
end
|
|
625
657
|
|
|
626
658
|
|
|
627
|
-
def
|
|
628
|
-
return
|
|
629
|
-
|
|
630
|
-
|
|
659
|
+
def freeze_error_lists!
|
|
660
|
+
return if OMQ::CONNECTION_LOST.frozen?
|
|
661
|
+
OMQ::CONNECTION_LOST.freeze
|
|
662
|
+
OMQ::CONNECTION_FAILED.freeze
|
|
631
663
|
end
|
|
632
664
|
|
|
633
665
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
def start_accept_loops(listener)
|
|
640
|
-
case listener
|
|
641
|
-
when Transport::TLS::Listener
|
|
642
|
-
tasks = listener.servers.map do |server|
|
|
643
|
-
@parent_task.async(transient: true, annotation: "tls accept #{listener.endpoint}") do
|
|
644
|
-
loop do
|
|
645
|
-
client = server.accept
|
|
646
|
-
Async::Task.current.defer_stop do
|
|
647
|
-
ssl = OpenSSL::SSL::SSLSocket.new(client, listener.ssl_context)
|
|
648
|
-
ssl.sync_close = true
|
|
649
|
-
ssl.accept
|
|
650
|
-
handle_accepted(IO::Stream::Buffered.wrap(ssl), endpoint: listener.endpoint)
|
|
651
|
-
rescue OpenSSL::SSL::SSLError
|
|
652
|
-
# Bad certificate, protocol mismatch, etc. — drop this
|
|
653
|
-
# connection but keep the accept loop running.
|
|
654
|
-
ssl&.close rescue nil
|
|
655
|
-
end
|
|
656
|
-
end
|
|
657
|
-
rescue Async::Stop
|
|
658
|
-
rescue IOError
|
|
659
|
-
# server closed
|
|
660
|
-
ensure
|
|
661
|
-
server.close rescue nil
|
|
662
|
-
end
|
|
663
|
-
end
|
|
664
|
-
listener.accept_tasks = tasks
|
|
665
|
-
|
|
666
|
-
when Transport::TCP::Listener
|
|
667
|
-
tasks = listener.servers.map do |server|
|
|
668
|
-
@parent_task.async(transient: true, annotation: "tcp accept #{listener.endpoint}") do
|
|
669
|
-
loop do
|
|
670
|
-
client = server.accept
|
|
671
|
-
Async::Task.current.defer_stop do
|
|
672
|
-
handle_accepted(IO::Stream::Buffered.wrap(client), endpoint: listener.endpoint)
|
|
673
|
-
end
|
|
674
|
-
end
|
|
675
|
-
rescue Async::Stop
|
|
676
|
-
rescue IOError
|
|
677
|
-
# server closed
|
|
678
|
-
ensure
|
|
679
|
-
server.close rescue nil
|
|
680
|
-
end
|
|
681
|
-
end
|
|
682
|
-
listener.accept_tasks = tasks
|
|
666
|
+
def emit_monitor_event(type, endpoint: nil, detail: nil)
|
|
667
|
+
return unless @monitor_queue
|
|
668
|
+
@monitor_queue.push(MonitorEvent.new(type: type, endpoint: endpoint, detail: detail))
|
|
669
|
+
rescue Async::Stop, ClosedQueueError
|
|
670
|
+
end
|
|
683
671
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
Async::Task.current.defer_stop do
|
|
689
|
-
handle_accepted(IO::Stream::Buffered.wrap(client), endpoint: listener.endpoint)
|
|
690
|
-
end
|
|
691
|
-
end
|
|
692
|
-
rescue Async::Stop
|
|
693
|
-
rescue IOError
|
|
694
|
-
# server closed
|
|
695
|
-
ensure
|
|
696
|
-
listener.server.close rescue nil
|
|
697
|
-
end
|
|
698
|
-
listener.accept_task = task
|
|
699
|
-
end
|
|
672
|
+
|
|
673
|
+
def close_monitor_queue
|
|
674
|
+
return unless @monitor_queue
|
|
675
|
+
@monitor_queue.push(nil)
|
|
700
676
|
end
|
|
701
677
|
end
|
|
678
|
+
|
|
679
|
+
# Register built-in transports.
|
|
680
|
+
Engine.transports["tcp"] = Transport::TCP
|
|
681
|
+
Engine.transports["ipc"] = Transport::IPC
|
|
682
|
+
Engine.transports["inproc"] = Transport::Inproc
|
|
702
683
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
# Lifecycle event emitted by {Socket#monitor}.
|
|
5
|
+
#
|
|
6
|
+
# @!attribute [r] type
|
|
7
|
+
# @return [Symbol] event type (:listening, :connected, :disconnected, etc.)
|
|
8
|
+
# @!attribute [r] endpoint
|
|
9
|
+
# @return [String, nil] the endpoint involved
|
|
10
|
+
# @!attribute [r] detail
|
|
11
|
+
# @return [Hash, nil] extra context (e.g. { error: }, { interval: }, etc.)
|
|
12
|
+
#
|
|
13
|
+
MonitorEvent = Data.define(:type, :endpoint, :detail) do
|
|
14
|
+
def initialize(type:, endpoint: nil, detail: nil) = super
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/omq/options.rb
CHANGED
|
@@ -23,10 +23,11 @@ module OMQ
|
|
|
23
23
|
@heartbeat_interval = nil # seconds, nil = disabled
|
|
24
24
|
@heartbeat_ttl = nil # seconds, nil = use heartbeat_interval
|
|
25
25
|
@heartbeat_timeout = nil # seconds, nil = use heartbeat_interval
|
|
26
|
-
@max_message_size =
|
|
26
|
+
@max_message_size = 1 << 20 # bytes (1 MiB default)
|
|
27
27
|
@conflate = false
|
|
28
|
+
@on_mute = :block # :block, :drop_newest, :drop_oldest
|
|
28
29
|
@mechanism = Protocol::ZMTP::Mechanism::Null.new
|
|
29
|
-
@
|
|
30
|
+
@qos = 0 # 0 = fire-and-forget, 1 = at-least-once (see omq-qos gem)
|
|
30
31
|
end
|
|
31
32
|
|
|
32
33
|
attr_accessor :send_hwm, :recv_hwm,
|
|
@@ -36,8 +37,9 @@ module OMQ
|
|
|
36
37
|
:reconnect_interval,
|
|
37
38
|
:heartbeat_interval, :heartbeat_ttl, :heartbeat_timeout,
|
|
38
39
|
:max_message_size,
|
|
40
|
+
:on_mute,
|
|
39
41
|
:mechanism,
|
|
40
|
-
:
|
|
42
|
+
:qos
|
|
41
43
|
|
|
42
44
|
alias_method :router_mandatory?, :router_mandatory
|
|
43
45
|
alias_method :recv_timeout, :read_timeout
|
data/lib/omq/pub_sub.rb
CHANGED
|
@@ -4,8 +4,8 @@ module OMQ
|
|
|
4
4
|
class PUB < Socket
|
|
5
5
|
include Writable
|
|
6
6
|
|
|
7
|
-
def initialize(endpoints = nil, linger: 0, conflate: false, backend: nil)
|
|
8
|
-
_init_engine(:PUB, linger: linger, conflate: conflate, backend: backend)
|
|
7
|
+
def initialize(endpoints = nil, linger: 0, on_mute: :drop_newest, conflate: false, backend: nil)
|
|
8
|
+
_init_engine(:PUB, linger: linger, on_mute: on_mute, conflate: conflate, backend: backend)
|
|
9
9
|
_attach(endpoints, default: :bind)
|
|
10
10
|
end
|
|
11
11
|
end
|
|
@@ -23,9 +23,10 @@ module OMQ
|
|
|
23
23
|
# @param linger [Integer]
|
|
24
24
|
# @param subscribe [String, nil] subscription prefix; +nil+ (default)
|
|
25
25
|
# means no subscription — call {#subscribe} explicitly.
|
|
26
|
+
# @param on_mute [Symbol] :block (default), :drop_newest, or :drop_oldest
|
|
26
27
|
#
|
|
27
|
-
def initialize(endpoints = nil, linger: 0, subscribe: nil, backend: nil)
|
|
28
|
-
_init_engine(:SUB, linger: linger, backend: backend)
|
|
28
|
+
def initialize(endpoints = nil, linger: 0, subscribe: nil, on_mute: :block, backend: nil)
|
|
29
|
+
_init_engine(:SUB, linger: linger, on_mute: on_mute, backend: backend)
|
|
29
30
|
_attach(endpoints, default: :connect)
|
|
30
31
|
self.subscribe(subscribe) unless subscribe.nil?
|
|
31
32
|
end
|
|
@@ -53,8 +54,8 @@ module OMQ
|
|
|
53
54
|
include Readable
|
|
54
55
|
include Writable
|
|
55
56
|
|
|
56
|
-
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
57
|
-
_init_engine(:XPUB, linger: linger, backend: backend)
|
|
57
|
+
def initialize(endpoints = nil, linger: 0, on_mute: :drop_newest, backend: nil)
|
|
58
|
+
_init_engine(:XPUB, linger: linger, on_mute: on_mute, backend: backend)
|
|
58
59
|
_attach(endpoints, default: :bind)
|
|
59
60
|
end
|
|
60
61
|
end
|
|
@@ -68,8 +69,8 @@ module OMQ
|
|
|
68
69
|
# @param subscribe [String, nil] subscription prefix; +nil+ (default)
|
|
69
70
|
# means no subscription — send a subscribe frame explicitly.
|
|
70
71
|
#
|
|
71
|
-
def initialize(endpoints = nil, linger: 0, subscribe: nil, backend: nil)
|
|
72
|
-
_init_engine(:XSUB, linger: linger, backend: backend)
|
|
72
|
+
def initialize(endpoints = nil, linger: 0, subscribe: nil, on_mute: :block, backend: nil)
|
|
73
|
+
_init_engine(:XSUB, linger: linger, on_mute: on_mute, backend: backend)
|
|
73
74
|
_attach(endpoints, default: :connect)
|
|
74
75
|
send("\x01#{subscribe}".b) unless subscribe.nil?
|
|
75
76
|
end
|
data/lib/omq/routing/dealer.rb
CHANGED
data/lib/omq/routing/fan_out.rb
CHANGED
|
@@ -13,12 +13,16 @@ module OMQ
|
|
|
13
13
|
module FanOut
|
|
14
14
|
attr_reader :subscriber_joined
|
|
15
15
|
|
|
16
|
+
# @return [Boolean] true when the send pump is idle (not sending a batch)
|
|
17
|
+
def send_pump_idle? = @send_pump_idle
|
|
18
|
+
|
|
16
19
|
private
|
|
17
20
|
|
|
18
21
|
def init_fan_out(engine)
|
|
19
22
|
@connections = []
|
|
20
23
|
@subscriptions = {} # connection => Set of prefixes
|
|
21
|
-
@send_queue =
|
|
24
|
+
@send_queue = Routing.build_queue(engine.options.send_hwm, :block)
|
|
25
|
+
@on_mute = engine.options.on_mute
|
|
22
26
|
@send_pump_started = false
|
|
23
27
|
@send_pump_idle = true
|
|
24
28
|
@conflate = engine.options.conflate
|
|
@@ -57,8 +61,14 @@ module OMQ
|
|
|
57
61
|
@subscriptions[conn]&.delete(prefix)
|
|
58
62
|
end
|
|
59
63
|
|
|
60
|
-
#
|
|
61
|
-
|
|
64
|
+
# Returns true if the connection is muted (recv queue full) and
|
|
65
|
+
# the on_mute strategy says to drop rather than block.
|
|
66
|
+
#
|
|
67
|
+
def muted?(conn)
|
|
68
|
+
return false if @on_mute == :block
|
|
69
|
+
q = conn.direct_recv_queue if conn.respond_to?(:direct_recv_queue)
|
|
70
|
+
q&.respond_to?(:limited?) && q.limited?
|
|
71
|
+
end
|
|
62
72
|
|
|
63
73
|
|
|
64
74
|
def start_send_pump
|
|
@@ -83,6 +93,7 @@ module OMQ
|
|
|
83
93
|
end
|
|
84
94
|
end
|
|
85
95
|
@latest.each do |conn, parts|
|
|
96
|
+
next if muted?(conn)
|
|
86
97
|
begin
|
|
87
98
|
conn.write_message(parts)
|
|
88
99
|
@written << conn
|
|
@@ -96,6 +107,7 @@ module OMQ
|
|
|
96
107
|
|
|
97
108
|
@connections.each do |conn|
|
|
98
109
|
next unless subscribed?(conn, topic)
|
|
110
|
+
next if muted?(conn)
|
|
99
111
|
begin
|
|
100
112
|
if conn.respond_to?(:curve?) && conn.curve?
|
|
101
113
|
conn.write_message(parts)
|
data/lib/omq/routing/pair.rb
CHANGED
|
@@ -14,8 +14,8 @@ module OMQ
|
|
|
14
14
|
def initialize(engine)
|
|
15
15
|
@engine = engine
|
|
16
16
|
@connection = nil
|
|
17
|
-
@recv_queue =
|
|
18
|
-
@send_queue =
|
|
17
|
+
@recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
|
|
18
|
+
@send_queue = Routing.build_queue(engine.options.send_hwm, :block)
|
|
19
19
|
@tasks = []
|
|
20
20
|
@send_pump_idle = true
|
|
21
21
|
end
|
data/lib/omq/routing/pull.rb
CHANGED
data/lib/omq/routing/push.rb
CHANGED
data/lib/omq/routing/rep.rb
CHANGED
|
@@ -15,8 +15,8 @@ module OMQ
|
|
|
15
15
|
|
|
16
16
|
def initialize(engine)
|
|
17
17
|
@engine = engine
|
|
18
|
-
@recv_queue =
|
|
19
|
-
@send_queue =
|
|
18
|
+
@recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
|
|
19
|
+
@send_queue = Routing.build_queue(engine.options.send_hwm, :block)
|
|
20
20
|
@pending_replies = []
|
|
21
21
|
@tasks = []
|
|
22
22
|
@send_pump_started = false
|