omq-cli 0.13.0 → 0.14.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 +81 -0
- data/README.md +10 -2
- data/lib/omq/cli/base_runner.rb +40 -31
- data/lib/omq/cli/cli_parser.rb +21 -47
- data/lib/omq/cli/client_server.rb +5 -7
- data/lib/omq/cli/config.rb +1 -2
- data/lib/omq/cli/formatter.rb +9 -33
- data/lib/omq/cli/parallel_worker.rb +29 -29
- data/lib/omq/cli/pipe.rb +24 -9
- data/lib/omq/cli/pipe_worker.rb +24 -35
- data/lib/omq/cli/radio_dish.rb +0 -1
- data/lib/omq/cli/router_dealer.rb +1 -2
- data/lib/omq/cli/socket_setup.rb +44 -4
- data/lib/omq/cli/term.rb +14 -3
- data/lib/omq/cli/version.rb +1 -1
- data/lib/omq/cli.rb +1 -3
- metadata +23 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b5a558fd06799fcf4bee5d5036a4cac86c8482554df3998e5f42171d04e520db
|
|
4
|
+
data.tar.gz: e53bb5236bb5bc660e04ad00c10aa0f3b1e4a848ed9d74363c3943f877bf210f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5841f42d980388be4ce4d0feea88b76a358e86f6ba6e64f6b1775dfdcad16dc349ba3866c824fa74ffb88005876e64c027de9fd93fa21417c8a5a53be3180861
|
|
7
|
+
data.tar.gz: 4b68d2c56882c87a06e75fa348eff4f9df75d5cd0c3a85c26e20e6676780c61c0413e329dff7bdbb082b861795cdf09bb30aa4b61c7279c88bbd2e29373d1552
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,86 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.0 — 2026-04-13
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Receive-capable sockets decompress by default.** All socket types
|
|
8
|
+
except pure senders (`push`, `pub`, `scatter`, `radio`) now advertise
|
|
9
|
+
the ZMTP-Zstd profile in **passive mode** at startup, so they accept
|
|
10
|
+
compressed frames from any active-sender peer without requiring
|
|
11
|
+
`-z` on the receive side. They never compress their own outgoing
|
|
12
|
+
frames in this mode — use `-z` / `-Z` / `--compress=LEVEL` on the
|
|
13
|
+
sender to opt it in. A `push` piped into a `pull` with no flags on
|
|
14
|
+
either side stays uncompressed; `omq push -z | omq pull` compresses
|
|
15
|
+
on the wire and the pull side decodes transparently. This is the
|
|
16
|
+
RFC Sec. 6.4 "Passive senders" mode; requires omq-rfc-zstd >= 0.1.0.
|
|
17
|
+
- **`-Z` flag for better-ratio compression (zstd level 3).** `-z`
|
|
18
|
+
remains the fast default (level -3) and `--compress=LEVEL` takes
|
|
19
|
+
a custom zstd level (e.g. `--compress=19`, `--compress=-1`). Short
|
|
20
|
+
bundling (`-zvvv`, `-Zvvv`) still works.
|
|
21
|
+
- **`-vvv` logs `ZDICT` exchange.** When the auto-trained dictionary
|
|
22
|
+
is shipped/received, the trace prints `>> ZDICT (NB)` on the sender
|
|
23
|
+
and `<< ZDICT (NB)` on the receiver.
|
|
24
|
+
- **`-vvv` wire-size annotation for compressed traces.** Message
|
|
25
|
+
previews on compressed sockets include the post-compression byte
|
|
26
|
+
count: `(280B wire=29B) ZZ…`. Plumbed from the ZMTP-Zstd wrapper
|
|
27
|
+
through the engine's verbose monitor.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- **Compression backend switched from `rlz4` to `omq-rfc-zstd`.**
|
|
32
|
+
Compression is now a ZMTP wire-protocol extension negotiated via
|
|
33
|
+
the `X-Compression` READY property and applied below the
|
|
34
|
+
application API. Auto-trained dictionaries are shipped over a
|
|
35
|
+
`ZDICT` command frame once the sender has enough samples. The
|
|
36
|
+
`Formatter` no longer compresses or decompresses anything — it
|
|
37
|
+
only encodes/decodes wire formats. Pipe `-z` is no longer modal
|
|
38
|
+
(`compress_in`/`compress_out` removed) since compression is a
|
|
39
|
+
per-socket, send-side property negotiated with each peer.
|
|
40
|
+
- **`-vvv` output ordering under compression.** At `-vvv`, the
|
|
41
|
+
monitor fiber now writes both the trace line and the plaintext
|
|
42
|
+
body, so trace-and-body pairs land on the tty in order instead of
|
|
43
|
+
interleaving between the recv pump and the app fiber.
|
|
44
|
+
- **TCP host normalization moved into `OMQ::Transport::TCP`.** `omq`
|
|
45
|
+
v0.19.0 now handles `tcp://*:PORT`, `tcp://:PORT`, and
|
|
46
|
+
`tcp://localhost:PORT` natively (including dual-stack `*` binding
|
|
47
|
+
both IPv4 and IPv6 wildcards), so `CliParser` no longer rewrites
|
|
48
|
+
these URLs before handing them off. Removed
|
|
49
|
+
`CliParser.loopback_bind_host` and the `normalize_bind`/
|
|
50
|
+
`normalize_connect`/`normalize_ep` block. Requires `omq ~> 0.19`.
|
|
51
|
+
- **Terminate on protocol errors instead of silent reconnect.** When
|
|
52
|
+
a peer sends a frame that violates the ZMTP wire protocol
|
|
53
|
+
(oversized, bad framing, zstd bytebomb, nonce exhaustion, …), the
|
|
54
|
+
library drops that one connection and reconnects — the libzmq
|
|
55
|
+
parity behavior. The CLI is a different audience: a persistent
|
|
56
|
+
protocol violation is almost always a misconfiguration the user
|
|
57
|
+
needs to see, not silently paper over. Every runner
|
|
58
|
+
(`BaseRunner`, `PipeRunner`, `ParallelWorker`, `PipeWorker`) now
|
|
59
|
+
attaches a monitor that watches for `:disconnected` events whose
|
|
60
|
+
`detail[:error]` is a `Protocol::ZMTP::Error`, prints
|
|
61
|
+
`omq: <reason>` to stderr, kills the socket, and exits with
|
|
62
|
+
status 1. Requires `omq ~> 0.19.2` for the new `:disconnected`
|
|
63
|
+
detail shape and `Socket#engine` accessor.
|
|
64
|
+
- **`-vvv` disconnect events render the reason in parentheses.**
|
|
65
|
+
`Term.format_event` now pretty-prints `:disconnected` details
|
|
66
|
+
that contain a `:reason` key, e.g.
|
|
67
|
+
`disconnected tcp://:5555 (frame size 1024 exceeds max_message_size 32)`,
|
|
68
|
+
instead of dumping the raw hash.
|
|
69
|
+
|
|
70
|
+
### Fixed
|
|
71
|
+
|
|
72
|
+
- **`--recv-maxsz` is now actually applied in pipe and parallel
|
|
73
|
+
modes.** `PipeRunner`, `PipeWorker`, and `ParallelWorker` were
|
|
74
|
+
only calling `SocketSetup.apply_options` + `apply_compression`
|
|
75
|
+
and skipping `max_message_size` entirely — so the default 1 MiB
|
|
76
|
+
cap (and any `--recv-maxsz` override) silently had no effect on
|
|
77
|
+
`omq pipe` or `omq pull -P`. Extracted the logic into
|
|
78
|
+
`SocketSetup.apply_recv_maxsz` and wired it into all four setup
|
|
79
|
+
paths (sequential pull/rep, sequential pipe, parallel worker,
|
|
80
|
+
pipe worker). Oversized frames now drop the connection as
|
|
81
|
+
intended and — combined with the CLI termination policy above —
|
|
82
|
+
exit with a clear error instead of hanging in a reconnect loop.
|
|
83
|
+
|
|
3
84
|
## 0.13.0 — 2026-04-12
|
|
4
85
|
|
|
5
86
|
### Added
|
data/README.md
CHANGED
|
@@ -348,8 +348,16 @@ omq pull -b tcp://:5557 -t 5
|
|
|
348
348
|
|
|
349
349
|
## Compression
|
|
350
350
|
|
|
351
|
-
|
|
352
|
-
by
|
|
351
|
+
Set `--compress` (`-z`) on either or both sides. The flag enables
|
|
352
|
+
ZMTP-Zstd (provided by `omq-rfc-zstd`), a wire-protocol extension
|
|
353
|
+
that negotiates Zstandard compression during the ZMTP handshake via
|
|
354
|
+
an `X-Compression` READY metadata field. If both peers advertise it,
|
|
355
|
+
each side compresses its outgoing frames; if only one side does, the
|
|
356
|
+
connection stays plaintext (no error). The extension uses the
|
|
357
|
+
auto-trained dictionary mode: the sender feeds the first messages
|
|
358
|
+
into a dictionary trainer, ships the trained dictionary over a
|
|
359
|
+
ZDICT command frame, then switches to dict-bound compression for
|
|
360
|
+
the rest of the connection.
|
|
353
361
|
|
|
354
362
|
```sh
|
|
355
363
|
omq push -c tcp://remote:5557 -z < data.txt
|
data/lib/omq/cli/base_runner.rb
CHANGED
|
@@ -15,7 +15,7 @@ module OMQ
|
|
|
15
15
|
def initialize(config, socket_class)
|
|
16
16
|
@config = config
|
|
17
17
|
@klass = socket_class
|
|
18
|
-
@fmt = Formatter.new(config.format
|
|
18
|
+
@fmt = Formatter.new(config.format)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
|
|
@@ -26,13 +26,17 @@ module OMQ
|
|
|
26
26
|
def call(task)
|
|
27
27
|
set_process_title
|
|
28
28
|
setup_socket
|
|
29
|
-
start_event_monitor
|
|
29
|
+
start_event_monitor
|
|
30
30
|
maybe_start_transient_monitor(task)
|
|
31
31
|
sleep(config.delay) if config.delay && config.recv_only?
|
|
32
32
|
wait_for_peer if needs_peer_wait?
|
|
33
33
|
run_begin_blocks
|
|
34
34
|
run_loop(task)
|
|
35
35
|
run_end_blocks
|
|
36
|
+
rescue OMQ::SocketDeadError => error
|
|
37
|
+
reason = error.cause&.message || error.message
|
|
38
|
+
$stderr.write("omq: #{reason}\n")
|
|
39
|
+
exit 1
|
|
36
40
|
ensure
|
|
37
41
|
@sock&.close
|
|
38
42
|
end
|
|
@@ -313,41 +317,21 @@ module OMQ
|
|
|
313
317
|
|
|
314
318
|
def send_msg(parts)
|
|
315
319
|
return if parts.empty?
|
|
316
|
-
log_parts = parts
|
|
317
320
|
parts = [Marshal.dump(parts)] if config.format == :marshal
|
|
318
|
-
|
|
319
|
-
wire = @fmt.compress(parts)
|
|
320
|
-
trace_msg(">>", log_parts, wire)
|
|
321
|
-
@sock.send(wire)
|
|
322
|
-
else
|
|
323
|
-
@sock.send(parts)
|
|
324
|
-
end
|
|
321
|
+
@sock.send(parts)
|
|
325
322
|
transient_ready!
|
|
326
323
|
end
|
|
327
324
|
|
|
328
325
|
|
|
329
326
|
def recv_msg
|
|
330
|
-
|
|
331
|
-
return nil if
|
|
332
|
-
parts = @fmt.decompress(raw)
|
|
333
|
-
trace_msg("<<", parts, raw) if config.compress
|
|
327
|
+
parts = @sock.receive
|
|
328
|
+
return nil if parts.nil?
|
|
334
329
|
parts = Marshal.load(parts.first) if config.format == :marshal
|
|
335
330
|
transient_ready!
|
|
336
331
|
parts
|
|
337
332
|
end
|
|
338
333
|
|
|
339
334
|
|
|
340
|
-
# Writes an explicit -vvv message trace line. Used when compression
|
|
341
|
-
# is active: the engine-level monitor would see compressed bytes,
|
|
342
|
-
# so we log post-decompression (recv) / pre-compression (send)
|
|
343
|
-
# and annotate the compressed wire size.
|
|
344
|
-
def trace_msg(marker, parts, wire)
|
|
345
|
-
return unless config.verbose >= 3
|
|
346
|
-
wire_size = wire.sum(&:bytesize)
|
|
347
|
-
$stderr.write("#{Term.log_prefix(config.timestamps)}omq: #{marker} #{Formatter.preview(parts, wire_size: wire_size)}\n")
|
|
348
|
-
end
|
|
349
|
-
|
|
350
|
-
|
|
351
335
|
def recv_msg_raw
|
|
352
336
|
msg = @sock.receive
|
|
353
337
|
msg&.dup
|
|
@@ -408,6 +392,10 @@ module OMQ
|
|
|
408
392
|
|
|
409
393
|
def output(parts)
|
|
410
394
|
return if config.quiet || parts.nil?
|
|
395
|
+
# At -vvv, the monitor fiber owns both the trace and the body
|
|
396
|
+
# output (see start_event_monitor). Skipping the app-side write
|
|
397
|
+
# avoids interleaving between the two fibers on a shared tty.
|
|
398
|
+
return if config.verbose >= 3
|
|
411
399
|
$stdout.write(@fmt.encode(parts))
|
|
412
400
|
$stdout.flush
|
|
413
401
|
end
|
|
@@ -480,17 +468,38 @@ module OMQ
|
|
|
480
468
|
end
|
|
481
469
|
|
|
482
470
|
|
|
483
|
-
# -
|
|
484
|
-
#
|
|
471
|
+
# Always attached so protocol-level disconnect events can kill
|
|
472
|
+
# the socket. Verbose gating lives inside the callback:
|
|
473
|
+
# -vv log connect/disconnect/retry/timeout events
|
|
474
|
+
# -vvv also log message sent/received traces
|
|
485
475
|
# --timestamps[=s|ms|us]: prepend UTC timestamps to log lines
|
|
486
476
|
def start_event_monitor
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
trace = config.verbose >= 3 && !config.compress
|
|
477
|
+
trace = config.verbose >= 3
|
|
478
|
+
log_events = config.verbose >= 2
|
|
490
479
|
@sock.monitor(verbose: trace) do |event|
|
|
491
|
-
Term.write_event(event, config.timestamps)
|
|
480
|
+
Term.write_event(event, config.timestamps) if log_events
|
|
481
|
+
if trace && event.type == :message_received && !config.quiet
|
|
482
|
+
$stdout.write(@fmt.encode(event.detail[:parts]))
|
|
483
|
+
$stdout.flush
|
|
484
|
+
end
|
|
485
|
+
kill_on_protocol_error(event)
|
|
492
486
|
end
|
|
493
487
|
end
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
# omq-cli policy: a peer that commits a protocol-level violation
|
|
491
|
+
# (Protocol::ZMTP::Error — oversized frame, decompression
|
|
492
|
+
# bytebomb, bad framing, …) is almost certainly a
|
|
493
|
+
# misconfiguration the user needs to see. Mark the socket dead
|
|
494
|
+
# so the next receive raises SocketDeadError. The library
|
|
495
|
+
# itself just drops the connection and keeps serving the
|
|
496
|
+
# others; this stricter policy is CLI-only.
|
|
497
|
+
def kill_on_protocol_error(event)
|
|
498
|
+
return unless event.type == :disconnected
|
|
499
|
+
error = event.detail && event.detail[:error]
|
|
500
|
+
return unless error.is_a?(Protocol::ZMTP::Error)
|
|
501
|
+
@sock.engine.signal_fatal_error(error)
|
|
502
|
+
end
|
|
494
503
|
end
|
|
495
504
|
end
|
|
496
505
|
end
|
data/lib/omq/cli/cli_parser.rb
CHANGED
|
@@ -125,8 +125,12 @@ module OMQ
|
|
|
125
125
|
|
|
126
126
|
-- Compression ----------------------------------------------
|
|
127
127
|
|
|
128
|
-
#
|
|
129
|
-
|
|
128
|
+
# ZMTP-Zstd is negotiated transparently during the handshake.
|
|
129
|
+
# Receive-capable sockets (pull, sub, rep, ...) advertise the
|
|
130
|
+
# profile by default in passive mode: they decode compressed
|
|
131
|
+
# frames from an active sender but never compress their own
|
|
132
|
+
# outgoing frames. Use -z / -Z on the sender to opt it in.
|
|
133
|
+
omq pull --bind tcp://:5557 &
|
|
130
134
|
echo "compressible data" | omq push --connect tcp://localhost:5557 -z
|
|
131
135
|
|
|
132
136
|
-- CURVE Encryption -----------------------------------------
|
|
@@ -225,8 +229,7 @@ module OMQ
|
|
|
225
229
|
rcvbuf: nil,
|
|
226
230
|
conflate: false,
|
|
227
231
|
compress: false,
|
|
228
|
-
|
|
229
|
-
compress_out: false,
|
|
232
|
+
compress_level: nil,
|
|
230
233
|
send_expr: nil,
|
|
231
234
|
recv_expr: nil,
|
|
232
235
|
parallel: nil,
|
|
@@ -386,16 +389,17 @@ module OMQ
|
|
|
386
389
|
o.on("--conflate", "Keep only last message per subscriber (PUB/RADIO)") { opts[:conflate] = true }
|
|
387
390
|
|
|
388
391
|
o.separator "\nCompression:"
|
|
389
|
-
o.on("-z", "
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
392
|
+
o.on("-z", "Zstd compression (level -3, fast)") do
|
|
393
|
+
opts[:compress] = true
|
|
394
|
+
opts[:compress_level] = -3
|
|
395
|
+
end
|
|
396
|
+
o.on("-Z", "Zstd compression (level 3, better ratio)") do
|
|
397
|
+
opts[:compress] = true
|
|
398
|
+
opts[:compress_level] = 3
|
|
399
|
+
end
|
|
400
|
+
o.on("--compress=LEVEL", Integer, "Zstd compression with custom level (e.g. 19, -1)") do |v|
|
|
401
|
+
opts[:compress] = true
|
|
402
|
+
opts[:compress_level] = v
|
|
399
403
|
end
|
|
400
404
|
|
|
401
405
|
o.separator "\nProcessing (-e = incoming, -E = outgoing):"
|
|
@@ -470,26 +474,9 @@ module OMQ
|
|
|
470
474
|
opts[:type_name] = type_name.downcase
|
|
471
475
|
end
|
|
472
476
|
|
|
473
|
-
#
|
|
474
|
-
#
|
|
475
|
-
#
|
|
476
|
-
# tcp://*:PORT → 0.0.0.0 (all interfaces, IPv4)
|
|
477
|
-
# tcp://0.0.0.0:PORT, tcp://[::]:PORT → pass through
|
|
478
|
-
#
|
|
479
|
-
# Connects: tcp://:PORT → localhost (Happy Eyeballs)
|
|
480
|
-
# tcp://*:PORT → localhost
|
|
481
|
-
#
|
|
482
|
-
# The hang on macOS (IPv6 connect(2) not getting ECONNREFUSED via
|
|
483
|
-
# kqueue) is fixed by the connect timeout in Engine::Reconnect.
|
|
484
|
-
loopback = self.class.loopback_bind_host
|
|
485
|
-
normalize_bind = ->(url) { url.sub(%r{\Atcp://\*:}, "tcp://0.0.0.0:").sub(%r{\Atcp://:}, "tcp://#{loopback}:") }
|
|
486
|
-
normalize_connect = ->(url) { url.sub(%r{\Atcp://(\*|):}, "tcp://localhost:") }
|
|
487
|
-
normalize_ep = ->(ep) { Endpoint.new(ep.bind? ? normalize_bind.call(ep.url) : normalize_connect.call(ep.url), ep.bind?) }
|
|
488
|
-
opts[:binds].map!(&normalize_bind)
|
|
489
|
-
opts[:connects].map!(&normalize_connect)
|
|
490
|
-
opts[:endpoints].map!(&normalize_ep)
|
|
491
|
-
opts[:in_endpoints].map!(&normalize_ep)
|
|
492
|
-
opts[:out_endpoints].map!(&normalize_ep)
|
|
477
|
+
# Host shorthand (tcp://*:PORT, tcp://:PORT, tcp://localhost:PORT)
|
|
478
|
+
# is normalized inside OMQ::Transport::TCP — see its
|
|
479
|
+
# #normalize_bind_host / #normalize_connect_host / #loopback_host.
|
|
493
480
|
|
|
494
481
|
opts
|
|
495
482
|
end
|
|
@@ -592,19 +579,6 @@ module OMQ
|
|
|
592
579
|
end
|
|
593
580
|
|
|
594
581
|
|
|
595
|
-
# Returns the loopback address for bind normalization.
|
|
596
|
-
# Prefers IPv6 loopback ([::1]) when the host has at least one
|
|
597
|
-
# non-loopback, non-link-local IPv6 address, otherwise 127.0.0.1.
|
|
598
|
-
#
|
|
599
|
-
def self.loopback_bind_host
|
|
600
|
-
@loopback_bind_host ||= begin
|
|
601
|
-
has_ipv6 = ::Socket.getifaddrs.any? { |ifa|
|
|
602
|
-
addr = ifa.addr
|
|
603
|
-
addr&.ipv6? && !addr.ipv6_loopback? && !addr.ipv6_linklocal?
|
|
604
|
-
}
|
|
605
|
-
has_ipv6 ? "[::1]" : "127.0.0.1"
|
|
606
|
-
end
|
|
607
|
-
end
|
|
608
582
|
end
|
|
609
583
|
end
|
|
610
584
|
end
|
|
@@ -25,8 +25,7 @@ module OMQ
|
|
|
25
25
|
parts = recv_msg_raw
|
|
26
26
|
break if parts.nil?
|
|
27
27
|
routing_id = parts.shift
|
|
28
|
-
|
|
29
|
-
break unless handle_server_request(routing_id, body)
|
|
28
|
+
break unless handle_server_request(routing_id, parts)
|
|
30
29
|
i += 1
|
|
31
30
|
break if n && n > 0 && i >= n
|
|
32
31
|
end
|
|
@@ -37,15 +36,15 @@ module OMQ
|
|
|
37
36
|
if config.recv_expr || @recv_eval_proc
|
|
38
37
|
reply = eval_recv_expr(body)
|
|
39
38
|
output([display_routing_id(routing_id), *(reply || [""])])
|
|
40
|
-
@sock.send_to(routing_id,
|
|
39
|
+
@sock.send_to(routing_id, (reply || [""]).first)
|
|
41
40
|
elsif config.echo
|
|
42
41
|
output([display_routing_id(routing_id), *body])
|
|
43
|
-
@sock.send_to(routing_id,
|
|
42
|
+
@sock.send_to(routing_id, body.first || "")
|
|
44
43
|
elsif config.data || config.file || !config.stdin_is_tty
|
|
45
44
|
reply = read_next
|
|
46
45
|
return false unless reply
|
|
47
46
|
output([display_routing_id(routing_id), *body])
|
|
48
|
-
@sock.send_to(routing_id,
|
|
47
|
+
@sock.send_to(routing_id, reply.first || "")
|
|
49
48
|
end
|
|
50
49
|
true
|
|
51
50
|
end
|
|
@@ -66,7 +65,6 @@ module OMQ
|
|
|
66
65
|
parts = recv_msg_raw
|
|
67
66
|
break if parts.nil?
|
|
68
67
|
routing_id = parts.shift
|
|
69
|
-
parts = @fmt.decompress(parts)
|
|
70
68
|
result = eval_recv_expr([display_routing_id(routing_id), *parts])
|
|
71
69
|
output(result)
|
|
72
70
|
i += 1
|
|
@@ -77,7 +75,7 @@ module OMQ
|
|
|
77
75
|
|
|
78
76
|
|
|
79
77
|
def send_to_peer(id, parts)
|
|
80
|
-
@sock.send_to(id,
|
|
78
|
+
@sock.send_to(id, parts.first || "")
|
|
81
79
|
end
|
|
82
80
|
end
|
|
83
81
|
end
|
data/lib/omq/cli/config.rb
CHANGED
data/lib/omq/cli/formatter.rb
CHANGED
|
@@ -2,17 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module OMQ
|
|
4
4
|
module CLI
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
#
|
|
9
|
-
# plus optional LZ4 compression.
|
|
5
|
+
# Handles encoding/decoding messages in the configured format.
|
|
6
|
+
# Compression is handled below the application API by ZMTP-Zstd
|
|
7
|
+
# (omq-rfc-zstd) once enabled via +socket.compression=+; the
|
|
8
|
+
# formatter sees plaintext frames in both directions.
|
|
10
9
|
class Formatter
|
|
11
10
|
# @param format [Symbol] wire format (:ascii, :quoted, :raw, :jsonl, :msgpack, :marshal)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@format = format
|
|
15
|
-
@compress = compress
|
|
11
|
+
def initialize(format)
|
|
12
|
+
@format = format
|
|
16
13
|
end
|
|
17
14
|
|
|
18
15
|
|
|
@@ -83,32 +80,11 @@ module OMQ
|
|
|
83
80
|
end
|
|
84
81
|
|
|
85
82
|
|
|
86
|
-
# Compresses each frame with LZ4 if compression is enabled.
|
|
87
|
-
#
|
|
88
|
-
# @param parts [Array<String>] message frames
|
|
89
|
-
# @return [Array<String>] optionally compressed frames
|
|
90
|
-
def compress(parts)
|
|
91
|
-
@compress ? parts.map { |p| RLZ4.compress(p) if p } : parts
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
# Decompresses each frame with LZ4 if compression is enabled.
|
|
96
|
-
# nil/empty frames pass through — they were nil before send coercion.
|
|
97
|
-
#
|
|
98
|
-
# @param parts [Array<String>] possibly compressed message frames
|
|
99
|
-
# @return [Array<String>] decompressed frames
|
|
100
|
-
def decompress(parts)
|
|
101
|
-
@compress ? parts.map { |p| p && !p.empty? ? RLZ4.decompress(p) : p } : parts
|
|
102
|
-
rescue RLZ4::DecompressError
|
|
103
|
-
raise DecompressError, "decompression failed (did the sender use --compress?)"
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
|
|
107
83
|
# Formats message parts for human-readable preview (logging).
|
|
108
|
-
# When +wire_size+ is given, the header
|
|
109
|
-
# compressed on-the-wire size: "(29B wire=12B)".
|
|
84
|
+
# When +wire_size+ is given (ZMTP-Zstd negotiated), the header
|
|
85
|
+
# also shows the compressed on-the-wire size: "(29B wire=12B)".
|
|
110
86
|
#
|
|
111
|
-
# @param parts [Array<String>] message frames
|
|
87
|
+
# @param parts [Array<String>] plaintext message frames
|
|
112
88
|
# @param wire_size [Integer, nil] compressed bytes on the wire
|
|
113
89
|
# @return [String] truncated preview of each frame joined by |
|
|
114
90
|
def self.preview(parts, wire_size: nil)
|
|
@@ -29,8 +29,15 @@ module OMQ
|
|
|
29
29
|
compile_expr
|
|
30
30
|
run_loop
|
|
31
31
|
run_end_block
|
|
32
|
-
rescue
|
|
33
|
-
|
|
32
|
+
rescue OMQ::SocketDeadError => error
|
|
33
|
+
# Socket was killed by a protocol violation on the peer side
|
|
34
|
+
# (see Engine#signal_fatal_error). Surface the underlying
|
|
35
|
+
# cause via the log stream and exit cleanly -- the Ractor
|
|
36
|
+
# completes, consumer threads unblock.
|
|
37
|
+
reason = error.cause&.message || error.message
|
|
38
|
+
@log_port.send("omq: #{reason}")
|
|
39
|
+
rescue => error
|
|
40
|
+
@error_port.send("#{error.class}: #{error.message}")
|
|
34
41
|
ensure
|
|
35
42
|
@sock&.close
|
|
36
43
|
end
|
|
@@ -43,6 +50,8 @@ module OMQ
|
|
|
43
50
|
def setup_socket
|
|
44
51
|
@sock = @config.ffi ? OMQ.const_get(@socket_sym).new(backend: :ffi) : OMQ.const_get(@socket_sym).new
|
|
45
52
|
OMQ::CLI::SocketSetup.apply_options(@sock, @config)
|
|
53
|
+
OMQ::CLI::SocketSetup.apply_recv_maxsz(@sock, @config)
|
|
54
|
+
OMQ::CLI::SocketSetup.apply_compression(@sock, @config, @config.type_name)
|
|
46
55
|
@sock.identity = @config.identity if @config.identity
|
|
47
56
|
OMQ::CLI::SocketSetup.attach_endpoints(@sock, @endpoints, verbose: 0)
|
|
48
57
|
end
|
|
@@ -57,26 +66,23 @@ module OMQ
|
|
|
57
66
|
|
|
58
67
|
|
|
59
68
|
def start_monitors
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
# decompression; suppress engine-level message trace to avoid
|
|
63
|
-
# showing compressed bytes.
|
|
64
|
-
trace = @config.verbose >= 3 && !@config.compress
|
|
69
|
+
trace = @config.verbose >= 3
|
|
70
|
+
log_events = @config.verbose >= 2
|
|
65
71
|
@sock.monitor(verbose: trace) do |event|
|
|
66
|
-
@log_port.send(OMQ::CLI::Term.format_event(event, @config.timestamps))
|
|
72
|
+
@log_port.send(OMQ::CLI::Term.format_event(event, @config.timestamps)) if log_events
|
|
73
|
+
kill_on_protocol_error(event)
|
|
67
74
|
end
|
|
68
75
|
end
|
|
69
76
|
|
|
70
77
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
@log_port.send("#{OMQ::CLI::Term.log_prefix(@config.timestamps)}omq: >> #{OMQ::CLI::Formatter.preview(parts, wire_size: wire.sum(&:bytesize))}")
|
|
78
|
+
# Mirrors BaseRunner#kill_on_protocol_error: CLI-level policy
|
|
79
|
+
# that protocol-level disconnects kill the socket so the
|
|
80
|
+
# recv loop unblocks with SocketDeadError.
|
|
81
|
+
def kill_on_protocol_error(event)
|
|
82
|
+
return unless event.type == :disconnected
|
|
83
|
+
error = event.detail && event.detail[:error]
|
|
84
|
+
return unless error.is_a?(Protocol::ZMTP::Error)
|
|
85
|
+
@sock.engine.signal_fatal_error(error)
|
|
80
86
|
end
|
|
81
87
|
|
|
82
88
|
|
|
@@ -96,7 +102,7 @@ module OMQ
|
|
|
96
102
|
def compile_expr
|
|
97
103
|
@begin_proc, @end_proc, @eval_proc =
|
|
98
104
|
OMQ::CLI::ExpressionEvaluator.compile_inside_ractor(@config.recv_expr)
|
|
99
|
-
@fmt = OMQ::CLI::Formatter.new(@config.format
|
|
105
|
+
@fmt = OMQ::CLI::Formatter.new(@config.format)
|
|
100
106
|
@ctx = Object.new
|
|
101
107
|
@ctx.instance_exec(&@begin_proc) if @begin_proc
|
|
102
108
|
end
|
|
@@ -119,10 +125,8 @@ module OMQ
|
|
|
119
125
|
n = @config.count
|
|
120
126
|
i = 0
|
|
121
127
|
loop do
|
|
122
|
-
|
|
123
|
-
break if
|
|
124
|
-
parts = @fmt.decompress(wire)
|
|
125
|
-
trace_in(parts, wire)
|
|
128
|
+
parts = @sock.receive
|
|
129
|
+
break if parts.nil?
|
|
126
130
|
if @eval_proc
|
|
127
131
|
parts = normalize(
|
|
128
132
|
@ctx.instance_exec(parts, &@eval_proc)
|
|
@@ -145,15 +149,11 @@ module OMQ
|
|
|
145
149
|
n = @config.count
|
|
146
150
|
i = 0
|
|
147
151
|
loop do
|
|
148
|
-
|
|
149
|
-
break if
|
|
150
|
-
parts = @fmt.decompress(wire)
|
|
151
|
-
trace_in(parts, wire)
|
|
152
|
+
parts = @sock.receive
|
|
153
|
+
break if parts.nil?
|
|
152
154
|
reply = compute_reply(parts)
|
|
153
155
|
output(reply)
|
|
154
|
-
|
|
155
|
-
trace_out(reply, reply_wire)
|
|
156
|
-
@sock.send(reply_wire)
|
|
156
|
+
@sock.send(reply)
|
|
157
157
|
i += 1
|
|
158
158
|
break if n && n > 0 && i >= n
|
|
159
159
|
end
|
data/lib/omq/cli/pipe.rb
CHANGED
|
@@ -11,9 +11,8 @@ module OMQ
|
|
|
11
11
|
|
|
12
12
|
# @param config [Config] frozen CLI configuration
|
|
13
13
|
def initialize(config)
|
|
14
|
-
@config
|
|
15
|
-
@
|
|
16
|
-
@fmt_out = Formatter.new(config.format, compress: config.compress_out || config.compress)
|
|
14
|
+
@config = config
|
|
15
|
+
@fmt = Formatter.new(config.format)
|
|
17
16
|
end
|
|
18
17
|
|
|
19
18
|
|
|
@@ -51,12 +50,16 @@ module OMQ
|
|
|
51
50
|
@pull, @push = build_pull_push(in_eps, out_eps)
|
|
52
51
|
compile_expr
|
|
53
52
|
@sock = @pull # for eval instance_exec
|
|
54
|
-
start_event_monitors
|
|
53
|
+
start_event_monitors
|
|
55
54
|
wait_for_peers_with_timeout if config.timeout
|
|
56
55
|
setup_sequential_transient(task)
|
|
57
56
|
@sock.instance_exec(&@recv_begin_proc) if @recv_begin_proc
|
|
58
57
|
sequential_message_loop(fan_out: out_eps.size > 1)
|
|
59
58
|
@sock.instance_exec(&@recv_end_proc) if @recv_end_proc
|
|
59
|
+
rescue OMQ::SocketDeadError => error
|
|
60
|
+
reason = error.cause&.message || error.message
|
|
61
|
+
$stderr.write("omq: #{reason}\n")
|
|
62
|
+
exit 1
|
|
60
63
|
ensure
|
|
61
64
|
@pull&.close
|
|
62
65
|
@push&.close
|
|
@@ -85,6 +88,9 @@ module OMQ
|
|
|
85
88
|
push = OMQ::PUSH.new(**kwargs)
|
|
86
89
|
SocketSetup.apply_options(pull, config)
|
|
87
90
|
SocketSetup.apply_options(push, config)
|
|
91
|
+
SocketSetup.apply_recv_maxsz(pull, config)
|
|
92
|
+
SocketSetup.apply_compression(pull, config, "pull")
|
|
93
|
+
SocketSetup.apply_compression(push, config, "push")
|
|
88
94
|
SocketSetup.attach_endpoints(pull, in_eps, verbose: config.verbose, timestamps: config.timestamps)
|
|
89
95
|
SocketSetup.attach_endpoints(push, out_eps, verbose: config.verbose, timestamps: config.timestamps)
|
|
90
96
|
[pull, push]
|
|
@@ -107,10 +113,9 @@ module OMQ
|
|
|
107
113
|
loop do
|
|
108
114
|
parts = @pull.receive
|
|
109
115
|
break if parts.nil?
|
|
110
|
-
parts = @fmt_in.decompress(parts)
|
|
111
116
|
parts = eval_recv_expr(parts)
|
|
112
117
|
if parts && !parts.empty?
|
|
113
|
-
@push.send(
|
|
118
|
+
@push.send(parts)
|
|
114
119
|
end
|
|
115
120
|
# Yield after send so send-pump fibers can drain the queue
|
|
116
121
|
# before the next message is enqueued. Without this, one pump
|
|
@@ -163,7 +168,7 @@ module OMQ
|
|
|
163
168
|
def set_pipe_process_title
|
|
164
169
|
in_eps, out_eps = resolve_endpoints
|
|
165
170
|
title = ["omq pipe"]
|
|
166
|
-
title << "-z" if config.compress
|
|
171
|
+
title << "-z" if config.compress
|
|
167
172
|
title << "-P#{config.parallel}" if config.parallel
|
|
168
173
|
title.concat(in_eps.map(&:url))
|
|
169
174
|
title << "->"
|
|
@@ -193,13 +198,23 @@ module OMQ
|
|
|
193
198
|
|
|
194
199
|
|
|
195
200
|
def start_event_monitors
|
|
196
|
-
trace
|
|
201
|
+
trace = config.verbose >= 3
|
|
202
|
+
log_events = config.verbose >= 2
|
|
197
203
|
[@pull, @push].each do |sock|
|
|
198
204
|
sock.monitor(verbose: trace) do |event|
|
|
199
|
-
Term.write_event(event, config.timestamps)
|
|
205
|
+
Term.write_event(event, config.timestamps) if log_events
|
|
206
|
+
kill_on_protocol_error(sock, event)
|
|
200
207
|
end
|
|
201
208
|
end
|
|
202
209
|
end
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def kill_on_protocol_error(sock, event)
|
|
213
|
+
return unless event.type == :disconnected
|
|
214
|
+
error = event.detail && event.detail[:error]
|
|
215
|
+
return unless error.is_a?(Protocol::ZMTP::Error)
|
|
216
|
+
sock.engine.signal_fatal_error(error)
|
|
217
|
+
end
|
|
203
218
|
end
|
|
204
219
|
end
|
|
205
220
|
end
|
data/lib/omq/cli/pipe_worker.rb
CHANGED
|
@@ -19,13 +19,14 @@ module OMQ
|
|
|
19
19
|
Async do
|
|
20
20
|
setup_sockets
|
|
21
21
|
log_endpoints if @config.verbose >= 1
|
|
22
|
-
start_monitors
|
|
22
|
+
start_monitors
|
|
23
23
|
wait_for_peers_with_timeout if @config.timeout
|
|
24
24
|
compile_expr
|
|
25
25
|
run_message_loop
|
|
26
26
|
run_end_block
|
|
27
|
-
rescue OMQ::
|
|
28
|
-
|
|
27
|
+
rescue OMQ::SocketDeadError => error
|
|
28
|
+
reason = error.cause&.message || error.message
|
|
29
|
+
@log_port.send("omq: #{reason}")
|
|
29
30
|
ensure
|
|
30
31
|
@pull&.close
|
|
31
32
|
@push&.close
|
|
@@ -42,6 +43,9 @@ module OMQ
|
|
|
42
43
|
@push = OMQ::PUSH.new(**kwargs)
|
|
43
44
|
OMQ::CLI::SocketSetup.apply_options(@pull, @config)
|
|
44
45
|
OMQ::CLI::SocketSetup.apply_options(@push, @config)
|
|
46
|
+
OMQ::CLI::SocketSetup.apply_recv_maxsz(@pull, @config)
|
|
47
|
+
OMQ::CLI::SocketSetup.apply_compression(@pull, @config, "pull")
|
|
48
|
+
OMQ::CLI::SocketSetup.apply_compression(@push, @config, "push")
|
|
45
49
|
OMQ::CLI::SocketSetup.attach_endpoints(@pull, @in_eps, verbose: 0)
|
|
46
50
|
OMQ::CLI::SocketSetup.attach_endpoints(@push, @out_eps, verbose: 0)
|
|
47
51
|
end
|
|
@@ -55,28 +59,22 @@ module OMQ
|
|
|
55
59
|
|
|
56
60
|
|
|
57
61
|
def start_monitors
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
# traces off to avoid showing compressed bytes.
|
|
61
|
-
compress = @config.compress || @config.compress_in || @config.compress_out
|
|
62
|
-
trace = @config.verbose >= 3 && !compress
|
|
62
|
+
trace = @config.verbose >= 3
|
|
63
|
+
log_events = @config.verbose >= 2
|
|
63
64
|
[@pull, @push].each do |sock|
|
|
64
65
|
sock.monitor(verbose: trace) do |event|
|
|
65
|
-
@log_port.send(OMQ::CLI::Term.format_event(event, @config.timestamps))
|
|
66
|
+
@log_port.send(OMQ::CLI::Term.format_event(event, @config.timestamps)) if log_events
|
|
67
|
+
kill_on_protocol_error(sock, event)
|
|
66
68
|
end
|
|
67
69
|
end
|
|
68
70
|
end
|
|
69
71
|
|
|
70
72
|
|
|
71
|
-
def
|
|
72
|
-
return unless
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def trace_out(parts, wire)
|
|
78
|
-
return unless @config.verbose >= 3 && (@config.compress || @config.compress_out)
|
|
79
|
-
@log_port.send("#{OMQ::CLI::Term.log_prefix(@config.timestamps)}omq: >> #{OMQ::CLI::Formatter.preview(parts, wire_size: wire.sum(&:bytesize))}")
|
|
73
|
+
def kill_on_protocol_error(sock, event)
|
|
74
|
+
return unless event.type == :disconnected
|
|
75
|
+
error = event.detail && event.detail[:error]
|
|
76
|
+
return unless error.is_a?(Protocol::ZMTP::Error)
|
|
77
|
+
sock.engine.signal_fatal_error(error)
|
|
80
78
|
end
|
|
81
79
|
|
|
82
80
|
|
|
@@ -96,8 +94,7 @@ module OMQ
|
|
|
96
94
|
def compile_expr
|
|
97
95
|
@begin_proc, @end_proc, @eval_proc =
|
|
98
96
|
OMQ::CLI::ExpressionEvaluator.compile_inside_ractor(@config.recv_expr)
|
|
99
|
-
@
|
|
100
|
-
@fmt_out = OMQ::CLI::Formatter.new(@config.format, compress: @config.compress_out || @config.compress)
|
|
97
|
+
@fmt = OMQ::CLI::Formatter.new(@config.format)
|
|
101
98
|
@ctx = Object.new
|
|
102
99
|
@ctx.instance_exec(&@begin_proc) if @begin_proc
|
|
103
100
|
end
|
|
@@ -124,29 +121,21 @@ module OMQ
|
|
|
124
121
|
|
|
125
122
|
|
|
126
123
|
def process_one_eval
|
|
127
|
-
|
|
128
|
-
return false if
|
|
129
|
-
parts_in = @fmt_in.decompress(wire_in)
|
|
130
|
-
trace_in(parts_in, wire_in)
|
|
124
|
+
parts_in = @pull.receive
|
|
125
|
+
return false if parts_in.nil?
|
|
131
126
|
parts_out = OMQ::CLI::ExpressionEvaluator.normalize_result(
|
|
132
127
|
@ctx.instance_exec(parts_in, &@eval_proc), format: @config.format
|
|
133
128
|
)
|
|
134
129
|
return true if parts_out.nil? || parts_out.empty?
|
|
135
|
-
|
|
136
|
-
trace_out(parts_out, wire_out)
|
|
137
|
-
@push << wire_out
|
|
130
|
+
@push << parts_out
|
|
138
131
|
true
|
|
139
132
|
end
|
|
140
133
|
|
|
141
134
|
|
|
142
135
|
def process_one_passthrough
|
|
143
|
-
|
|
144
|
-
return false if
|
|
145
|
-
|
|
146
|
-
trace_in(parts_in, wire_in)
|
|
147
|
-
wire_out = @fmt_out.compress(parts_in)
|
|
148
|
-
trace_out(parts_in, wire_out)
|
|
149
|
-
@push << wire_out
|
|
136
|
+
parts_in = @pull.receive
|
|
137
|
+
return false if parts_in.nil?
|
|
138
|
+
@push << parts_in
|
|
150
139
|
true
|
|
151
140
|
end
|
|
152
141
|
|
|
@@ -156,7 +145,7 @@ module OMQ
|
|
|
156
145
|
out = OMQ::CLI::ExpressionEvaluator.normalize_result(
|
|
157
146
|
@ctx.instance_exec(&@end_proc), format: @config.format
|
|
158
147
|
)
|
|
159
|
-
@push <<
|
|
148
|
+
@push << out if out && !out.empty?
|
|
160
149
|
end
|
|
161
150
|
end
|
|
162
151
|
end
|
data/lib/omq/cli/radio_dish.rb
CHANGED
|
@@ -25,7 +25,6 @@ module OMQ
|
|
|
25
25
|
break if parts.nil?
|
|
26
26
|
identity = parts.shift
|
|
27
27
|
parts.shift if parts.first == ""
|
|
28
|
-
parts = @fmt.decompress(parts)
|
|
29
28
|
result = eval_recv_expr([display_routing_id(identity), *parts])
|
|
30
29
|
output(result)
|
|
31
30
|
i += 1
|
|
@@ -36,7 +35,7 @@ module OMQ
|
|
|
36
35
|
|
|
37
36
|
|
|
38
37
|
def send_to_peer(id, parts)
|
|
39
|
-
@sock.send([id, "",
|
|
38
|
+
@sock.send([id, "", *parts])
|
|
40
39
|
end
|
|
41
40
|
end
|
|
42
41
|
end
|
data/lib/omq/cli/socket_setup.rb
CHANGED
|
@@ -22,6 +22,40 @@ module OMQ
|
|
|
22
22
|
# it entirely with --recv-maxsz 0.
|
|
23
23
|
DEFAULT_RECV_MAXSZ = 1 << 20
|
|
24
24
|
|
|
25
|
+
# Socket types that never receive application data. These opt
|
|
26
|
+
# out of the default passive-compression behavior -- they would
|
|
27
|
+
# otherwise advertise a profile and wrap every outgoing frame in
|
|
28
|
+
# a 4-byte uncompressed sentinel for no benefit, since there's
|
|
29
|
+
# nothing to decompress in return.
|
|
30
|
+
PURE_SEND_TYPES = %w[push pub scatter radio].freeze
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Installs ZMTP-Zstd compression on +sock+ based on +type_name+
|
|
34
|
+
# and the explicit flags in +config+. Three outcomes:
|
|
35
|
+
#
|
|
36
|
+
# * +config.compress+ is true --> active compression (auto-dict).
|
|
37
|
+
# Outgoing frames are compressed; incoming are decoded.
|
|
38
|
+
# * +config.compress+ is false and +type_name+ can receive -->
|
|
39
|
+
# passive compression (RFC Sec. 6.4). The socket advertises the
|
|
40
|
+
# profile so an active peer can compress on the wire and we can
|
|
41
|
+
# decode it, but we never compress our own outgoing frames.
|
|
42
|
+
# * +config.compress+ is false and +type_name+ is in
|
|
43
|
+
# +PURE_SEND_TYPES+ --> no compression. Pure senders have no
|
|
44
|
+
# incoming traffic to decompress, so passive mode is pure
|
|
45
|
+
# overhead on outgoing.
|
|
46
|
+
#
|
|
47
|
+
# Callers pass +type_name+ explicitly (rather than reading it off
|
|
48
|
+
# +config+) so the pipe runners can install different modes on
|
|
49
|
+
# their push/pull ends of a single pipe.
|
|
50
|
+
def self.apply_compression(sock, config, type_name)
|
|
51
|
+
if config.compress
|
|
52
|
+
sock.compression = OMQ::RFC::Zstd::Compression.auto(level: config.compress_level || -3)
|
|
53
|
+
elsif !PURE_SEND_TYPES.include?(type_name)
|
|
54
|
+
sock.compression = OMQ::RFC::Zstd::Compression.auto(passive: true)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
|
|
25
59
|
# Apply common socket options from +config+ to +sock+.
|
|
26
60
|
#
|
|
27
61
|
def self.apply_options(sock, config)
|
|
@@ -44,16 +78,22 @@ module OMQ
|
|
|
44
78
|
sock = config.ffi ? klass.new(backend: :ffi) : klass.new
|
|
45
79
|
sock.conflate = true if config.conflate && %w[pub radio].include?(config.type_name)
|
|
46
80
|
apply_options(sock, config)
|
|
47
|
-
|
|
81
|
+
apply_recv_maxsz(sock, config)
|
|
82
|
+
sock.identity = config.identity if config.identity
|
|
83
|
+
sock.router_mandatory = true if config.type_name == "router"
|
|
84
|
+
apply_compression(sock, config, config.type_name)
|
|
85
|
+
sock
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# --recv-maxsz: nil → 1 MiB default; 0 → explicitly unlimited; else → as-is.
|
|
90
|
+
def self.apply_recv_maxsz(sock, config)
|
|
48
91
|
sock.max_message_size =
|
|
49
92
|
case config.recv_maxsz
|
|
50
93
|
when nil then DEFAULT_RECV_MAXSZ
|
|
51
94
|
when 0 then nil
|
|
52
95
|
else config.recv_maxsz
|
|
53
96
|
end
|
|
54
|
-
sock.identity = config.identity if config.identity
|
|
55
|
-
sock.router_mandatory = true if config.type_name == "router"
|
|
56
|
-
sock
|
|
57
97
|
end
|
|
58
98
|
|
|
59
99
|
|
data/lib/omq/cli/term.rb
CHANGED
|
@@ -40,12 +40,23 @@ module OMQ
|
|
|
40
40
|
prefix = log_prefix(timestamps)
|
|
41
41
|
case event.type
|
|
42
42
|
when :message_sent
|
|
43
|
-
"#{prefix}omq: >> #{Formatter.preview(event.detail[:parts])}"
|
|
43
|
+
"#{prefix}omq: >> #{Formatter.preview(event.detail[:parts], wire_size: event.detail[:wire_size])}"
|
|
44
44
|
when :message_received
|
|
45
|
-
"#{prefix}omq: << #{Formatter.preview(event.detail[:parts])}"
|
|
45
|
+
"#{prefix}omq: << #{Formatter.preview(event.detail[:parts], wire_size: event.detail[:wire_size])}"
|
|
46
|
+
when :zdict_sent
|
|
47
|
+
"#{prefix}omq: >> ZDICT (#{event.detail[:size]}B)"
|
|
48
|
+
when :zdict_received
|
|
49
|
+
"#{prefix}omq: << ZDICT (#{event.detail[:size]}B)"
|
|
46
50
|
else
|
|
47
51
|
ep = event.endpoint ? " #{event.endpoint}" : ""
|
|
48
|
-
detail =
|
|
52
|
+
detail =
|
|
53
|
+
if event.detail.is_a?(Hash) && event.detail[:reason]
|
|
54
|
+
" (#{event.detail[:reason]})"
|
|
55
|
+
elsif event.detail
|
|
56
|
+
" #{event.detail}"
|
|
57
|
+
else
|
|
58
|
+
""
|
|
59
|
+
end
|
|
49
60
|
"#{prefix}omq: #{event.type}#{ep}#{detail}"
|
|
50
61
|
end
|
|
51
62
|
end
|
data/lib/omq/cli/version.rb
CHANGED
data/lib/omq/cli.rb
CHANGED
|
@@ -197,6 +197,7 @@ module OMQ
|
|
|
197
197
|
require "omq/rfc/scattergather"
|
|
198
198
|
require "omq/rfc/channel"
|
|
199
199
|
require "omq/rfc/p2p"
|
|
200
|
+
require "omq/rfc/zstd"
|
|
200
201
|
require "async"
|
|
201
202
|
require "json"
|
|
202
203
|
require "console"
|
|
@@ -247,9 +248,6 @@ module OMQ
|
|
|
247
248
|
runner_class.new(config)
|
|
248
249
|
end
|
|
249
250
|
runner.call(task)
|
|
250
|
-
rescue DecompressError => e
|
|
251
|
-
$stderr.puts "omq: #{e.message}"
|
|
252
|
-
exit 1
|
|
253
251
|
rescue IO::TimeoutError, Async::TimeoutError
|
|
254
252
|
$stderr.puts "omq: timeout" unless config.quiet
|
|
255
253
|
exit 2
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: omq-cli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.14.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrik Wenger
|
|
@@ -15,14 +15,20 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0.
|
|
18
|
+
version: '0.19'
|
|
19
|
+
- - ">="
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: 0.19.2
|
|
19
22
|
type: :runtime
|
|
20
23
|
prerelease: false
|
|
21
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
25
|
requirements:
|
|
23
26
|
- - "~>"
|
|
24
27
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0.
|
|
28
|
+
version: '0.19'
|
|
29
|
+
- - ">="
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: 0.19.2
|
|
26
32
|
- !ruby/object:Gem::Dependency
|
|
27
33
|
name: omq-ffi
|
|
28
34
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -108,51 +114,51 @@ dependencies:
|
|
|
108
114
|
- !ruby/object:Gem::Version
|
|
109
115
|
version: '0.1'
|
|
110
116
|
- !ruby/object:Gem::Dependency
|
|
111
|
-
name:
|
|
117
|
+
name: omq-rfc-zstd
|
|
112
118
|
requirement: !ruby/object:Gem::Requirement
|
|
113
119
|
requirements:
|
|
114
|
-
- - "
|
|
120
|
+
- - "~>"
|
|
115
121
|
- !ruby/object:Gem::Version
|
|
116
|
-
version: '0'
|
|
122
|
+
version: '0.1'
|
|
117
123
|
type: :runtime
|
|
118
124
|
prerelease: false
|
|
119
125
|
version_requirements: !ruby/object:Gem::Requirement
|
|
120
126
|
requirements:
|
|
121
|
-
- - "
|
|
127
|
+
- - "~>"
|
|
122
128
|
- !ruby/object:Gem::Version
|
|
123
|
-
version: '0'
|
|
129
|
+
version: '0.1'
|
|
124
130
|
- !ruby/object:Gem::Dependency
|
|
125
|
-
name:
|
|
131
|
+
name: msgpack
|
|
126
132
|
requirement: !ruby/object:Gem::Requirement
|
|
127
133
|
requirements:
|
|
128
|
-
- - "
|
|
134
|
+
- - ">="
|
|
129
135
|
- !ruby/object:Gem::Version
|
|
130
|
-
version: '
|
|
136
|
+
version: '0'
|
|
131
137
|
type: :runtime
|
|
132
138
|
prerelease: false
|
|
133
139
|
version_requirements: !ruby/object:Gem::Requirement
|
|
134
140
|
requirements:
|
|
135
|
-
- - "
|
|
141
|
+
- - ">="
|
|
136
142
|
- !ruby/object:Gem::Version
|
|
137
|
-
version: '
|
|
143
|
+
version: '0'
|
|
138
144
|
- !ruby/object:Gem::Dependency
|
|
139
|
-
name:
|
|
145
|
+
name: rbnacl
|
|
140
146
|
requirement: !ruby/object:Gem::Requirement
|
|
141
147
|
requirements:
|
|
142
148
|
- - "~>"
|
|
143
149
|
- !ruby/object:Gem::Version
|
|
144
|
-
version: '0
|
|
150
|
+
version: '7.0'
|
|
145
151
|
type: :runtime
|
|
146
152
|
prerelease: false
|
|
147
153
|
version_requirements: !ruby/object:Gem::Requirement
|
|
148
154
|
requirements:
|
|
149
155
|
- - "~>"
|
|
150
156
|
- !ruby/object:Gem::Version
|
|
151
|
-
version: '0
|
|
157
|
+
version: '7.0'
|
|
152
158
|
description: Command-line tool for sending and receiving ZeroMQ messages on any socket
|
|
153
159
|
type (REQ/REP, PUB/SUB, PUSH/PULL, DEALER/ROUTER, and all draft types). Supports
|
|
154
160
|
Ruby eval (-e/-E), script handlers (-r), pipe virtual socket with Ractor parallelism,
|
|
155
|
-
multiple formats (ASCII, JSON Lines, msgpack, Marshal),
|
|
161
|
+
multiple formats (ASCII, JSON Lines, msgpack, Marshal), Zstd compression, and CURVE
|
|
156
162
|
encryption. Like nngcat from libnng, but with Ruby superpowers.
|
|
157
163
|
email:
|
|
158
164
|
- paddor@gmail.com
|