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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +143 -0
- data/README.md +3 -1
- data/lib/omq/drop_queue.rb +54 -0
- data/lib/omq/engine/connection_setup.rb +47 -0
- data/lib/omq/engine/heartbeat.rb +40 -0
- data/lib/omq/engine/reconnect.rb +56 -0
- data/lib/omq/engine/recv_pump.rb +76 -0
- data/lib/omq/engine.rb +145 -371
- 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/conn_send_pump.rb +36 -0
- data/lib/omq/routing/dealer.rb +8 -10
- data/lib/omq/routing/fair_queue.rb +144 -0
- data/lib/omq/routing/fair_recv.rb +27 -0
- data/lib/omq/routing/fan_out.rb +116 -63
- data/lib/omq/routing/pair.rb +39 -20
- data/lib/omq/routing/pub.rb +5 -7
- data/lib/omq/routing/pull.rb +5 -4
- data/lib/omq/routing/push.rb +3 -10
- data/lib/omq/routing/rep.rb +31 -51
- data/lib/omq/routing/req.rb +15 -12
- data/lib/omq/routing/round_robin.rb +82 -72
- data/lib/omq/routing/router.rb +23 -48
- data/lib/omq/routing/sub.rb +8 -6
- data/lib/omq/routing/xpub.rb +8 -4
- data/lib/omq/routing/xsub.rb +43 -27
- data/lib/omq/routing.rb +44 -11
- data/lib/omq/socket.rb +46 -5
- data/lib/omq/transport/inproc/direct_pipe.rb +162 -0
- data/lib/omq/transport/inproc.rb +37 -200
- 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 +11 -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: 91e6db2b4fd881530030f63c0c47a534c3f6361497d0f55ad1d28c7dbb85a669
|
|
4
|
+
data.tar.gz: 1af1d5692333586b650f7d0f14118ae04f64a65ab3d127d8ea3a3781e0eaae34
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4a724ddc29600b8d101046ca82869e4598f733564be4022fc8eb287084a80319a1586d5e0ced449f3eea3a26127add8f962fc3914177cc95f4f495e98f87e7d4
|
|
7
|
+
data.tar.gz: '08935cd0ad4548af6a526984d84514b242ab1d29bfd14fb8933f7b84b9f906f1197588f6bc0fec95332d2dbac6e016308cbdb5bf5efe9e2cba7b2bbf7d695d58'
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,148 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.13.0
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **`Engine` internals: `ConnectionRecord` + lifecycle state** — three parallel
|
|
8
|
+
per-connection ivars (`@connections` Array, `@connection_endpoints`,
|
|
9
|
+
`@connection_promises`) replaced by a single `@connections` Hash keyed by
|
|
10
|
+
connection, with values `ConnectionRecord = Data.define(:endpoint, :done)`.
|
|
11
|
+
`@connected_endpoints` renamed to `@dialed` (`Set`). `@closed`/`@closing`
|
|
12
|
+
booleans replaced by a `@state` symbol (`:open`/`:closing`/`:closed`).
|
|
13
|
+
Net: −4 instance variables.
|
|
14
|
+
- **`@connections` in `FanOut`, `Sub`, `XSub` routing strategies changed from
|
|
15
|
+
`Array` to `Set`** — O(1) `#delete` on peer disconnect; semantics already
|
|
16
|
+
required uniqueness.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **FanOut send queues no longer drop messages** — per-connection send queues in
|
|
21
|
+
`FanOut` (PUB/XPUB/RADIO) used `DropQueue` (`Thread::SizedQueue`) which never
|
|
22
|
+
blocked the publisher fiber. When burst-sending beyond `send_hwm`, the sender
|
|
23
|
+
ran without yielding and messages were silently dropped. Switched to
|
|
24
|
+
`Async::LimitedQueue` (`:block`) so the publisher yields when a per-connection
|
|
25
|
+
queue is full, giving the send pump fiber a chance to drain it.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- **Benchmark suite redesign** — replaced ASCII plots (unicode_plot) with JSONL
|
|
30
|
+
result storage and a colored terminal regression report. Results are appended
|
|
31
|
+
to `bench/results.jsonl` (gitignored, machine-local). New commands:
|
|
32
|
+
`ruby bench/run_all.rb` (run all patterns), `ruby bench/report.rb` (compare
|
|
33
|
+
last runs, highlight regressions/improvements).
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- **Per-peer HWM** — send and receive high-water marks now apply per connected
|
|
38
|
+
peer (RFC 28/29/30). Each peer gets its own bounded send queue and its own
|
|
39
|
+
bounded recv queue. A slow or muted peer no longer steals capacity from
|
|
40
|
+
other peers. `FairQueue` + `SignalingQueue` aggregate per-connection recv
|
|
41
|
+
queues with fair round-robin delivery; `RoundRobin` and `FanOut` mixins
|
|
42
|
+
maintain per-connection send queues with dedicated send pump fibers.
|
|
43
|
+
`PUSH`/`DEALER`/`PAIR` buffer messages in a staging queue when no peers are
|
|
44
|
+
connected yet, draining into the first peer's queue on connect.
|
|
45
|
+
- **`FairQueue`** — new aggregator class (`lib/omq/routing/fair_queue.rb`)
|
|
46
|
+
that fair-queues across per-connection bounded queues. Pending messages from
|
|
47
|
+
a disconnected peer are drained before the queue is discarded.
|
|
48
|
+
- **`Socket.bind` / `Socket.connect` class-method fix** — now pass the
|
|
49
|
+
endpoint via `@`/`>` prefix into the constructor so any post-attach
|
|
50
|
+
initialization in subclasses (e.g. XSUB's `subscribe:` kwarg) runs after
|
|
51
|
+
the connection is established.
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
- **QoS infrastructure** — `Options#qos` attribute (default 0) and inproc
|
|
56
|
+
command queue support for QoS-enabled connections. The
|
|
57
|
+
[omq-qos](https://github.com/paddor/omq-qos) gem activates delivery
|
|
58
|
+
guarantees via prepends.
|
|
59
|
+
- **REQ send/recv ordering** — REQ sockets now enforce strict
|
|
60
|
+
send/recv/send/recv alternation. Calling `#send` twice without a
|
|
61
|
+
`#receive` in between raises `SocketError`.
|
|
62
|
+
- **DirectPipe command frame support** — `DirectPipe#receive_message`
|
|
63
|
+
accepts a block for command frames, matching the `Protocol::ZMTP::Connection`
|
|
64
|
+
interface. Enables inproc transports to handle ACK/NACK and other
|
|
65
|
+
command-level protocols.
|
|
66
|
+
|
|
67
|
+
### Fixed
|
|
68
|
+
|
|
69
|
+
- **`send_pump_idle?` visibility** — moved above `private` in `RoundRobin`
|
|
70
|
+
and `FanOut` so `Engine#drain_send_queues` can call it during socket close.
|
|
71
|
+
|
|
72
|
+
- **`Socket#monitor`** — observe connection lifecycle events via a
|
|
73
|
+
block-based API. Returns an `Async::Task` that yields `MonitorEvent`
|
|
74
|
+
(Data.define) instances for `:listening`, `:accepted`, `:connected`,
|
|
75
|
+
`:connect_delayed`, `:connect_retried`, `:handshake_succeeded`,
|
|
76
|
+
`:handshake_failed`, `:accept_failed`, `:bind_failed`, `:disconnected`,
|
|
77
|
+
`:closed`, and `:monitor_stopped`. Event types align with libzmq's
|
|
78
|
+
`zmq_socket_monitor` where applicable. Pattern-matchable, zero overhead
|
|
79
|
+
when no monitor is attached.
|
|
80
|
+
- **Pluggable transport registry** — `Engine.transports` is a scheme →
|
|
81
|
+
module hash. Built-in transports (`tcp`, `ipc`, `inproc`) are registered
|
|
82
|
+
at load time. External gems register via
|
|
83
|
+
`OMQ::Engine.transports["scheme"] = MyTransport`. Each transport
|
|
84
|
+
implements `.bind(endpoint, engine)` → Listener, `.connect(endpoint,
|
|
85
|
+
engine)`, and optionally `.validate_endpoint!(endpoint)`. Listeners
|
|
86
|
+
implement `#start_accept_loops(parent_task, &on_accepted)`, `#stop`,
|
|
87
|
+
`#endpoint`, and optionally `#port`.
|
|
88
|
+
- **Mutable error lists** — `CONNECTION_LOST` and `CONNECTION_FAILED` are
|
|
89
|
+
no longer frozen at load time. Transport plugins can append error classes
|
|
90
|
+
(e.g. `OpenSSL::SSL::SSLError`) before the first `#bind`/`#connect`,
|
|
91
|
+
which freezes both arrays.
|
|
92
|
+
|
|
93
|
+
- **`on_mute` option** — controls behavior when a socket enters the mute state
|
|
94
|
+
(HWM full). PUB, XPUB, and RADIO default to `on_mute: :drop_newest` — slow
|
|
95
|
+
subscribers are skipped in the fan-out rather than blocking the publisher.
|
|
96
|
+
SUB, XSUB, and DISH accept `on_mute: :drop_newest` or `:drop_oldest` to
|
|
97
|
+
drop messages on the receive side instead of applying backpressure. All other
|
|
98
|
+
socket types default to `:block` (existing behavior).
|
|
99
|
+
- **`DropQueue`** — bounded queue with `:drop_newest` (tail drop) and
|
|
100
|
+
`:drop_oldest` (head drop) strategies. Used by recv queues when `on_mute`
|
|
101
|
+
is a drop strategy.
|
|
102
|
+
- **`Routing.build_queue`** — factory method for building send/recv queues
|
|
103
|
+
based on HWM and mute strategy. Supports HWM of `0` or `nil` for unbounded
|
|
104
|
+
queues.
|
|
105
|
+
|
|
106
|
+
### Changed
|
|
107
|
+
|
|
108
|
+
- **`max_message_size` defaults to 1 MiB** — frames exceeding this limit cause
|
|
109
|
+
the connection to be dropped before the body is read from the wire, preventing
|
|
110
|
+
a malicious peer from causing arbitrary memory allocation. Set `socket.max_message_size = nil`
|
|
111
|
+
to restore the previous unlimited behavior.
|
|
112
|
+
- **Accept loops moved into Listeners** — `TCP::Listener` and
|
|
113
|
+
`IPC::Listener` now own their accept loop logic via
|
|
114
|
+
`#start_accept_loops(parent_task, &on_accepted)`. Engine delegates
|
|
115
|
+
via duck-type check. This enables external transports to define
|
|
116
|
+
custom accept behavior without modifying Engine.
|
|
117
|
+
- `Engine#transport_for` uses registry lookup instead of `case/when`.
|
|
118
|
+
- `Engine#validate_endpoint!` delegates to transport module.
|
|
119
|
+
- `Engine#bind` reads `listener.port` instead of parsing the endpoint
|
|
120
|
+
string.
|
|
121
|
+
|
|
122
|
+
### Removed
|
|
123
|
+
|
|
124
|
+
- **Draft socket types extracted** — `RADIO`, `DISH`, `CLIENT`, `SERVER`,
|
|
125
|
+
`SCATTER`, `GATHER`, `CHANNEL`, and `PEER` are no longer bundled with `omq`.
|
|
126
|
+
Use the [omq-draft](https://github.com/paddor/omq-draft) gem and require
|
|
127
|
+
the relevant entry point (`omq/draft/radiodish`, `omq/draft/clientserver`,
|
|
128
|
+
etc.).
|
|
129
|
+
- **UDP transport extracted** — `udp://` endpoints are provided by
|
|
130
|
+
`omq-draft` (via `require "omq/draft/radiodish"`). No longer registered by
|
|
131
|
+
default.
|
|
132
|
+
- **`Routing.for` plugin registry** — draft socket type removal added
|
|
133
|
+
`Routing.register(socket_type, strategy_class)` for external gems to
|
|
134
|
+
register routing strategies. Unknown types fall through the built-in
|
|
135
|
+
`case` to this registry before raising `ArgumentError`.
|
|
136
|
+
|
|
137
|
+
- **TLS transport** — extracted to the
|
|
138
|
+
[omq-transport-tls](https://github.com/paddor/omq-transport-tls) gem.
|
|
139
|
+
(Experimental) `require "omq/transport/tls"` to restore `tls+tcp://` support.
|
|
140
|
+
- `tls_context` / `tls_context=` removed from `Options` and `Socket`
|
|
141
|
+
(provided by omq-transport-tls).
|
|
142
|
+
- `OpenSSL::SSL::SSLError` removed from `CONNECTION_LOST` (added back
|
|
143
|
+
by omq-transport-tls).
|
|
144
|
+
- TLS benchmark transport removed from `bench_helper.rb` and `plot.rb`.
|
|
145
|
+
|
|
3
146
|
## 0.11.0
|
|
4
147
|
|
|
5
148
|
### 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
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
class Engine
|
|
5
|
+
# Performs ZMTP handshake and registers a new connection.
|
|
6
|
+
#
|
|
7
|
+
class ConnectionSetup
|
|
8
|
+
# @param io [#read, #write, #close] underlying transport stream
|
|
9
|
+
# @param engine [Engine]
|
|
10
|
+
# @param as_server [Boolean]
|
|
11
|
+
# @param endpoint [String, nil]
|
|
12
|
+
# @param done [Async::Promise, nil] resolved when connection is lost
|
|
13
|
+
# @return [Connection]
|
|
14
|
+
#
|
|
15
|
+
def self.run(io, engine, as_server:, endpoint: nil, done: nil)
|
|
16
|
+
conn = build_connection(io, engine, as_server)
|
|
17
|
+
conn.handshake!
|
|
18
|
+
Heartbeat.start(engine.parent_task, conn, engine.options, engine.tasks)
|
|
19
|
+
conn = engine.connection_wrapper.call(conn) if engine.connection_wrapper
|
|
20
|
+
register(conn, engine, endpoint, done)
|
|
21
|
+
engine.emit_monitor_event(:handshake_succeeded, endpoint: endpoint)
|
|
22
|
+
conn
|
|
23
|
+
rescue Protocol::ZMTP::Error, *CONNECTION_LOST => error
|
|
24
|
+
engine.emit_monitor_event(:handshake_failed, endpoint: endpoint, detail: { error: error })
|
|
25
|
+
conn&.close
|
|
26
|
+
raise
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.build_connection(io, engine, as_server)
|
|
30
|
+
Protocol::ZMTP::Connection.new(
|
|
31
|
+
io,
|
|
32
|
+
socket_type: engine.socket_type.to_s,
|
|
33
|
+
identity: engine.options.identity,
|
|
34
|
+
as_server: as_server,
|
|
35
|
+
mechanism: engine.options.mechanism&.dup,
|
|
36
|
+
max_message_size: engine.options.max_message_size,
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.register(conn, engine, endpoint, done)
|
|
41
|
+
engine.connections[conn] = Engine::ConnectionRecord.new(endpoint: endpoint, done: done)
|
|
42
|
+
engine.routing.connection_added(conn)
|
|
43
|
+
engine.peer_connected.resolve(conn)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
class Engine
|
|
5
|
+
# Spawns a heartbeat task for a connection.
|
|
6
|
+
#
|
|
7
|
+
# Sends PING frames at +interval+ seconds and closes the connection
|
|
8
|
+
# if no traffic is seen within +timeout+ seconds.
|
|
9
|
+
#
|
|
10
|
+
class Heartbeat
|
|
11
|
+
# @param parent_task [Async::Task]
|
|
12
|
+
# @param conn [Connection]
|
|
13
|
+
# @param options [Options]
|
|
14
|
+
# @param tasks [Array]
|
|
15
|
+
#
|
|
16
|
+
def self.start(parent_task, conn, options, tasks)
|
|
17
|
+
interval = options.heartbeat_interval
|
|
18
|
+
return unless interval
|
|
19
|
+
|
|
20
|
+
ttl = options.heartbeat_ttl || interval
|
|
21
|
+
timeout = options.heartbeat_timeout || interval
|
|
22
|
+
conn.touch_heartbeat
|
|
23
|
+
|
|
24
|
+
tasks << parent_task.async(transient: true, annotation: "heartbeat") do
|
|
25
|
+
loop do
|
|
26
|
+
sleep interval
|
|
27
|
+
conn.send_command(Protocol::ZMTP::Codec::Command.ping(ttl: ttl, context: "".b))
|
|
28
|
+
if conn.heartbeat_expired?(timeout)
|
|
29
|
+
conn.close
|
|
30
|
+
break
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
rescue Async::Stop
|
|
34
|
+
rescue *CONNECTION_LOST
|
|
35
|
+
# connection closed
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
class Engine
|
|
5
|
+
# Schedules reconnect attempts with exponential back-off.
|
|
6
|
+
#
|
|
7
|
+
# Runs a background task that loops until a connection is established
|
|
8
|
+
# or the engine is closed.
|
|
9
|
+
#
|
|
10
|
+
class Reconnect
|
|
11
|
+
# @param endpoint [String]
|
|
12
|
+
# @param options [Options]
|
|
13
|
+
# @param parent_task [Async::Task]
|
|
14
|
+
# @param engine [Engine] for transport_for / emit_monitor_event / signal_fatal_error / closed?
|
|
15
|
+
# @param delay [Numeric, nil] initial delay (defaults to reconnect_interval)
|
|
16
|
+
#
|
|
17
|
+
def self.schedule(endpoint, options, parent_task, engine, delay: nil)
|
|
18
|
+
ri = options.reconnect_interval
|
|
19
|
+
delay, max_delay = init_delay(ri, delay)
|
|
20
|
+
|
|
21
|
+
engine.tasks << parent_task.async(transient: true, annotation: "reconnect #{endpoint}") do
|
|
22
|
+
loop do
|
|
23
|
+
break if engine.closed?
|
|
24
|
+
sleep delay if delay > 0
|
|
25
|
+
break if engine.closed?
|
|
26
|
+
begin
|
|
27
|
+
engine.transport_for(endpoint).connect(endpoint, engine)
|
|
28
|
+
break
|
|
29
|
+
rescue *CONNECTION_LOST, *CONNECTION_FAILED, Protocol::ZMTP::Error
|
|
30
|
+
delay = next_delay(delay, max_delay, ri)
|
|
31
|
+
engine.emit_monitor_event(:connect_retried, endpoint: endpoint, detail: { interval: delay })
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
rescue Async::Stop
|
|
35
|
+
rescue => error
|
|
36
|
+
engine.signal_fatal_error(error)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.init_delay(ri, delay)
|
|
41
|
+
if ri.is_a?(Range)
|
|
42
|
+
[delay || ri.begin, ri.end]
|
|
43
|
+
else
|
|
44
|
+
[delay || ri, nil]
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.next_delay(delay, max_delay, ri)
|
|
49
|
+
delay = delay * 2
|
|
50
|
+
delay = [delay, max_delay].min if max_delay
|
|
51
|
+
delay = (ri.is_a?(Range) ? ri.begin : ri) if delay == 0
|
|
52
|
+
delay
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
class Engine
|
|
5
|
+
# Starts a recv pump for a connection.
|
|
6
|
+
#
|
|
7
|
+
# For inproc DirectPipe: wires the direct recv path (no fiber spawned).
|
|
8
|
+
# For TCP/IPC: spawns a transient task that reads messages from the
|
|
9
|
+
# connection and enqueues them into +recv_queue+.
|
|
10
|
+
#
|
|
11
|
+
# The two-branch structure (with/without transform) is intentional for
|
|
12
|
+
# YJIT: it gives the JIT a monomorphic call per routing strategy instead
|
|
13
|
+
# of a megamorphic `transform.call` dispatch inside a shared loop.
|
|
14
|
+
#
|
|
15
|
+
# @param parent_task [Async::Task]
|
|
16
|
+
# @param conn [Connection, Transport::Inproc::DirectPipe]
|
|
17
|
+
# @param recv_queue [SignalingQueue]
|
|
18
|
+
# @param engine [Engine] for connection_lost / signal_fatal_error callbacks
|
|
19
|
+
# @param transform [Proc, nil]
|
|
20
|
+
# @return [Async::Task, nil]
|
|
21
|
+
#
|
|
22
|
+
class RecvPump
|
|
23
|
+
FAIRNESS_MESSAGES = 64
|
|
24
|
+
FAIRNESS_BYTES = 1 << 20 # 1 MB
|
|
25
|
+
|
|
26
|
+
def self.start(parent_task, conn, recv_queue, engine, transform)
|
|
27
|
+
if conn.is_a?(Transport::Inproc::DirectPipe) && conn.peer
|
|
28
|
+
conn.peer.direct_recv_queue = recv_queue
|
|
29
|
+
conn.peer.direct_recv_transform = transform
|
|
30
|
+
return nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
if transform
|
|
34
|
+
parent_task.async(transient: true, annotation: "recv pump") do |task|
|
|
35
|
+
loop do
|
|
36
|
+
count = 0
|
|
37
|
+
bytes = 0
|
|
38
|
+
while count < FAIRNESS_MESSAGES && bytes < FAIRNESS_BYTES
|
|
39
|
+
msg = conn.receive_message
|
|
40
|
+
msg = transform.call(msg).freeze
|
|
41
|
+
recv_queue.enqueue(msg)
|
|
42
|
+
count += 1
|
|
43
|
+
bytes += msg.is_a?(Array) && msg.first.is_a?(String) ? msg.sum(&:bytesize) : 0
|
|
44
|
+
end
|
|
45
|
+
task.yield
|
|
46
|
+
end
|
|
47
|
+
rescue Async::Stop
|
|
48
|
+
rescue Protocol::ZMTP::Error, *CONNECTION_LOST
|
|
49
|
+
engine.connection_lost(conn)
|
|
50
|
+
rescue => error
|
|
51
|
+
engine.signal_fatal_error(error)
|
|
52
|
+
end
|
|
53
|
+
else
|
|
54
|
+
parent_task.async(transient: true, annotation: "recv pump") do |task|
|
|
55
|
+
loop do
|
|
56
|
+
count = 0
|
|
57
|
+
bytes = 0
|
|
58
|
+
while count < FAIRNESS_MESSAGES && bytes < FAIRNESS_BYTES
|
|
59
|
+
msg = conn.receive_message
|
|
60
|
+
recv_queue.enqueue(msg)
|
|
61
|
+
count += 1
|
|
62
|
+
bytes += msg.is_a?(Array) && msg.first.is_a?(String) ? msg.sum(&:bytesize) : 0
|
|
63
|
+
end
|
|
64
|
+
task.yield
|
|
65
|
+
end
|
|
66
|
+
rescue Async::Stop
|
|
67
|
+
rescue Protocol::ZMTP::Error, *CONNECTION_LOST
|
|
68
|
+
engine.connection_lost(conn)
|
|
69
|
+
rescue => error
|
|
70
|
+
engine.signal_fatal_error(error)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|