omq-cli 0.14.1 → 0.14.3
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 +72 -0
- data/lib/omq/cli/base_runner.rb +52 -31
- data/lib/omq/cli/expression_evaluator.rb +21 -29
- data/lib/omq/cli/formatter.rb +57 -23
- data/lib/omq/cli/parallel_worker.rb +1 -12
- data/lib/omq/cli/pipe.rb +1 -9
- data/lib/omq/cli/socket_setup.rb +18 -0
- data/lib/omq/cli/term.rb +30 -11
- data/lib/omq/cli/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a207b74cefe2b70d8f5910610be8ef955a2d466ae78e3b620860a5425361af8b
|
|
4
|
+
data.tar.gz: 7a15e05713459c741f2d780c89057bbfee435e63767464245041bcdfe5b31c59
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d38d7f5548147b2b5a4fdfe391180b29dba07975fe8fbd391586eaf580d739f66c05f66e0fbc488e976d47afaabbced2322940d39c1b783ed4c5636e5ce4d194
|
|
7
|
+
data.tar.gz: 301999bb8145e49e39d28f77dc75a71d22ead3ad3c01ebe85e809a73b2cc8baf3a59e980361dd43319d2c7d5be90ff7f22f74370ad89f7a53ae85e77e09634d9
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,77 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.3 — 2026-04-14
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **`omq req` (and other interactive senders) no longer wedge on
|
|
8
|
+
blank input lines.** `Formatter#decode` used to return `[]` for
|
|
9
|
+
a blank line, which `BaseRunner#send_msg` then silently dropped
|
|
10
|
+
— so REQ never sent a request but still blocked in `recv_msg`
|
|
11
|
+
waiting for a reply. Blank lines now decode to a single empty
|
|
12
|
+
frame (`[""]`) via the new
|
|
13
|
+
`Formatter::EMPTY_MSG = [Protocol::ZMTP::Codec::EMPTY_BINARY]`
|
|
14
|
+
constant, so the request actually goes out. As a side effect,
|
|
15
|
+
ascii/quoted decoding now uses `split("\t", -1)` and preserves
|
|
16
|
+
trailing empty frames (`"a\t\n"` → `["a", ""]`).
|
|
17
|
+
- **Cleaner `:disconnected` log lines on plain peer close.**
|
|
18
|
+
`-vv` used to emit `omq: disconnected tcp://… (Stream finished
|
|
19
|
+
before reading enough data!)` — the raw io-stream message for
|
|
20
|
+
what is really just an `EOFError` at the ZMTP framing boundary.
|
|
21
|
+
`Term.format_event` now routes through a new
|
|
22
|
+
`Term.format_event_detail` helper that rewrites `EOFError` to
|
|
23
|
+
`(closed by peer)`, leaving other errors' messages untouched.
|
|
24
|
+
The underlying `event.detail[:error]` is unchanged.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **Lowercased `Term.format_attach` verbs.** `omq: Bound to …` /
|
|
29
|
+
`omq: Connecting to …` now render as `omq: bound to …` /
|
|
30
|
+
`omq: connecting to …`, matching the lowercase style already
|
|
31
|
+
used by every other `omq:` log line (`disconnected`, `listening`,
|
|
32
|
+
`handshake_succeeded`, …).
|
|
33
|
+
|
|
34
|
+
## 0.14.2 — 2026-04-13
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- `kill_on_protocol_error` is now a single
|
|
39
|
+
`SocketSetup.kill_on_protocol_error(sock, event)` class method.
|
|
40
|
+
Previously `BaseRunner`, `ParallelWorker`, and `PipeRunner` each
|
|
41
|
+
carried an identical 4-line copy of the CLI policy that
|
|
42
|
+
protocol-level disconnects mark the socket dead.
|
|
43
|
+
- `ExpressionEvaluator.extract_block` is now a single class method
|
|
44
|
+
used by both the instance compile path and the
|
|
45
|
+
`compile_inside_ractor` path. The in-Ractor copy previously lived
|
|
46
|
+
as a local lambda that duplicated the instance
|
|
47
|
+
`extract_block` method.
|
|
48
|
+
- `Formatter#encode` drops one String allocation per message on
|
|
49
|
+
the ascii / quoted / jsonl / marshal paths by mutating the
|
|
50
|
+
fresh `.join` / `JSON.generate` / `.inspect` result with `<<`
|
|
51
|
+
instead of `+ "\n"`.
|
|
52
|
+
- `Formatter.marshal_preview` and `Formatter.frames_preview` (extracted
|
|
53
|
+
from `Formatter.preview` in the `-vvv` marshal trace work) are now
|
|
54
|
+
`private_class_method` — they were only ever meant to be called
|
|
55
|
+
through `Formatter.preview` but ended up on the public class surface.
|
|
56
|
+
- Dropped a redundant unary `+` before `Formatter.sanitize(...)` in
|
|
57
|
+
`marshal_preview`: `sanitize` already returns a fresh mutable String
|
|
58
|
+
via `.tr`, so the `+""` dup was dead weight.
|
|
59
|
+
- **`-vvv` marshal trace headers now show plaintext and wire byte
|
|
60
|
+
sizes.** Previously `<< (marshal) ...` carried no size info; it
|
|
61
|
+
now renders as `(135B marshal) ...` and, when ZMTP-Zstd
|
|
62
|
+
compression is negotiated, `(135B wire=50B marshal) ...` —
|
|
63
|
+
matching the frame-based preview format used by every other
|
|
64
|
+
`-vvv` output.
|
|
65
|
+
Other formats (ascii/quoted/jsonl/msgpack/raw) already showed
|
|
66
|
+
plaintext size via the frame preview; they now also pick up
|
|
67
|
+
`wire=NB` when compression is active, since `wire_size` is
|
|
68
|
+
side-channelled from `:message_sent` / `:message_received`
|
|
69
|
+
monitor events. Send-side `wire_size` is best-effort — the
|
|
70
|
+
engine's send pump emits the compressed byte count
|
|
71
|
+
asynchronously, so the value reflects the most recently
|
|
72
|
+
*completed* send; receive-side is exact.
|
|
73
|
+
- Hot-path optimized.
|
|
74
|
+
|
|
3
75
|
## 0.14.1 — 2026-04-13
|
|
4
76
|
|
|
5
77
|
### Changed
|
data/lib/omq/cli/base_runner.rb
CHANGED
|
@@ -304,9 +304,19 @@ module OMQ
|
|
|
304
304
|
# may write to stdout (e.g. `-e 'p it'`), and we want the
|
|
305
305
|
# trace line to precede any such output so the sequence on the
|
|
306
306
|
# terminal reads as: trace → eval side-effects → body.
|
|
307
|
+
#
|
|
308
|
+
# +@last_recv_wire_size+ is populated by the :message_received
|
|
309
|
+
# monitor event, which fires *before* the recv queue enqueue
|
|
310
|
+
# (recv_pump.rb) — so by the time @sock.receive returns here,
|
|
311
|
+
# the cache reflects this message. +@last_recv_uncompressed+
|
|
312
|
+
# is captured in #recv_msg from the raw marshal frame size.
|
|
307
313
|
def trace_recv(parts)
|
|
308
314
|
return unless config.verbose >= 3
|
|
309
|
-
|
|
315
|
+
preview = Formatter.preview(parts,
|
|
316
|
+
format: config.format,
|
|
317
|
+
wire_size: @last_recv_wire_size,
|
|
318
|
+
uncompressed_size: @last_recv_uncompressed)
|
|
319
|
+
$stderr.write("#{Term.log_prefix(config.timestamps)}omq: << #{preview}\n")
|
|
310
320
|
$stderr.flush
|
|
311
321
|
end
|
|
312
322
|
|
|
@@ -331,8 +341,9 @@ module OMQ
|
|
|
331
341
|
def send_msg(parts)
|
|
332
342
|
case config.format
|
|
333
343
|
when :marshal
|
|
334
|
-
|
|
335
|
-
|
|
344
|
+
dumped = Marshal.dump(parts)
|
|
345
|
+
trace_send(parts, uncompressed_size: dumped.bytesize)
|
|
346
|
+
@sock.send([dumped])
|
|
336
347
|
else
|
|
337
348
|
return if parts.empty?
|
|
338
349
|
trace_send(parts)
|
|
@@ -342,12 +353,21 @@ module OMQ
|
|
|
342
353
|
end
|
|
343
354
|
|
|
344
355
|
|
|
345
|
-
# Symmetric to #trace_recv — log the outgoing message
|
|
346
|
-
# Marshal.dump
|
|
347
|
-
# (`[nil, :foo, "bar"]`) instead of the wire-side dump
|
|
348
|
-
|
|
356
|
+
# Symmetric to #trace_recv — log the outgoing message using the
|
|
357
|
+
# pre-Marshal.dump +parts+, so -M traces show the app-level
|
|
358
|
+
# object (`[nil, :foo, "bar"]`) instead of the wire-side dump
|
|
359
|
+
# bytes. +@last_send_wire_size+ is best-effort: it reflects the
|
|
360
|
+
# *previous* message (populated by the :message_sent monitor
|
|
361
|
+
# event, which fires on a separate fiber after the pump writes),
|
|
362
|
+
# so early sends may show no `wire=` at all. Receive-side tracing
|
|
363
|
+
# is the authoritative path for observing wire bytes.
|
|
364
|
+
def trace_send(parts, uncompressed_size: nil)
|
|
349
365
|
return unless config.verbose >= 3
|
|
350
|
-
|
|
366
|
+
preview = Formatter.preview(parts,
|
|
367
|
+
format: config.format,
|
|
368
|
+
wire_size: @last_send_wire_size,
|
|
369
|
+
uncompressed_size: uncompressed_size)
|
|
370
|
+
$stderr.write("#{Term.log_prefix(config.timestamps)}omq: >> #{preview}\n")
|
|
351
371
|
$stderr.flush
|
|
352
372
|
end
|
|
353
373
|
|
|
@@ -355,7 +375,13 @@ module OMQ
|
|
|
355
375
|
def recv_msg
|
|
356
376
|
parts = @sock.receive
|
|
357
377
|
return nil if parts.nil?
|
|
358
|
-
|
|
378
|
+
|
|
379
|
+
case config.format
|
|
380
|
+
when :marshal
|
|
381
|
+
@last_recv_uncompressed = parts.first.bytesize
|
|
382
|
+
parts = Marshal.load(parts.first)
|
|
383
|
+
end
|
|
384
|
+
|
|
359
385
|
transient_ready!
|
|
360
386
|
parts
|
|
361
387
|
end
|
|
@@ -499,37 +525,32 @@ module OMQ
|
|
|
499
525
|
# -vvv also log message sent/received traces
|
|
500
526
|
# --timestamps[=s|ms|us]: prepend UTC timestamps to log lines
|
|
501
527
|
#
|
|
502
|
-
# :message_received and :message_sent are
|
|
503
|
-
#
|
|
504
|
-
#
|
|
528
|
+
# :message_received and :message_sent are not *logged* from the
|
|
529
|
+
# monitor fiber — #trace_recv / #trace_send render them inline
|
|
530
|
+
# on the same fiber as the body write, so trace-then-body
|
|
505
531
|
# ordering is strict on a shared tty. The monitor-fiber path
|
|
506
532
|
# suffered from $stderr/$stdout buffer races and from dumping
|
|
507
533
|
# wire-side bytes (pre-Marshal.load on recv, post-Marshal.dump
|
|
508
|
-
# on send) instead of app-level parts.
|
|
509
|
-
|
|
534
|
+
# on send) instead of app-level parts. We still *observe* these
|
|
535
|
+
# events here to side-channel the compressed wire_size — for
|
|
536
|
+
# :message_received the event fires before the recv queue
|
|
537
|
+
# enqueue (engine/recv_pump.rb), so by the time @sock.receive
|
|
538
|
+
# returns, @last_recv_wire_size reflects the current message.
|
|
510
539
|
def start_event_monitor
|
|
511
540
|
trace = config.verbose >= 3
|
|
512
541
|
log_events = config.verbose >= 2
|
|
513
542
|
@sock.monitor(verbose: trace) do |event|
|
|
514
|
-
|
|
515
|
-
|
|
543
|
+
case event.type
|
|
544
|
+
when :message_received
|
|
545
|
+
@last_recv_wire_size = event.detail[:wire_size]
|
|
546
|
+
when :message_sent
|
|
547
|
+
@last_send_wire_size = event.detail[:wire_size]
|
|
548
|
+
else
|
|
549
|
+
Term.write_event(event, config.timestamps) if log_events
|
|
550
|
+
end
|
|
551
|
+
SocketSetup.kill_on_protocol_error(@sock, event)
|
|
516
552
|
end
|
|
517
553
|
end
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
# omq-cli policy: a peer that commits a protocol-level violation
|
|
521
|
-
# (Protocol::ZMTP::Error — oversized frame, decompression
|
|
522
|
-
# bytebomb, bad framing, …) is almost certainly a
|
|
523
|
-
# misconfiguration the user needs to see. Mark the socket dead
|
|
524
|
-
# so the next receive raises SocketDeadError. The library
|
|
525
|
-
# itself just drops the connection and keeps serving the
|
|
526
|
-
# others; this stricter policy is CLI-only.
|
|
527
|
-
def kill_on_protocol_error(event)
|
|
528
|
-
return unless event.type == :disconnected
|
|
529
|
-
error = event.detail && event.detail[:error]
|
|
530
|
-
return unless error.is_a?(Protocol::ZMTP::Error)
|
|
531
|
-
@sock.engine.signal_fatal_error(error)
|
|
532
|
-
end
|
|
533
554
|
end
|
|
534
555
|
end
|
|
535
556
|
end
|
|
@@ -49,7 +49,7 @@ module OMQ
|
|
|
49
49
|
return result if @format == :marshal
|
|
50
50
|
|
|
51
51
|
result = result.is_a?(Array) ? result : [result]
|
|
52
|
-
result.map
|
|
52
|
+
result.map { |part| part.to_s }
|
|
53
53
|
rescue => e
|
|
54
54
|
$stderr.puts "omq: eval error: #{e.message} (#{e.class})"
|
|
55
55
|
exit 3
|
|
@@ -67,7 +67,7 @@ module OMQ
|
|
|
67
67
|
return nil if result.nil?
|
|
68
68
|
return result if format == :marshal
|
|
69
69
|
result = result.is_a?(Array) ? result : [result]
|
|
70
|
-
result.map
|
|
70
|
+
result.map { |part| part.to_s }
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
|
|
@@ -79,22 +79,8 @@ module OMQ
|
|
|
79
79
|
def self.compile_inside_ractor(src)
|
|
80
80
|
return [nil, nil, nil] unless src
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return [expr, nil] unless s
|
|
85
|
-
ci = expr.index("{", s)
|
|
86
|
-
depth = 1
|
|
87
|
-
j = ci + 1
|
|
88
|
-
while j < expr.length && depth > 0
|
|
89
|
-
depth += 1 if expr[j] == "{"
|
|
90
|
-
depth -= 1 if expr[j] == "}"
|
|
91
|
-
j += 1
|
|
92
|
-
end
|
|
93
|
-
[expr[0...s] + expr[j..], expr[(ci + 1)..(j - 2)]]
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
expr, begin_body = extract.(src, "BEGIN")
|
|
97
|
-
expr, end_body = extract.(expr, "END")
|
|
82
|
+
expr, begin_body = extract_block(src, "BEGIN")
|
|
83
|
+
expr, end_body = extract_block(expr, "END")
|
|
98
84
|
|
|
99
85
|
begin_proc = eval("proc { #{begin_body} }") if begin_body
|
|
100
86
|
end_proc = eval("proc { #{end_body} }") if end_body
|
|
@@ -107,17 +93,13 @@ module OMQ
|
|
|
107
93
|
end
|
|
108
94
|
|
|
109
95
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def extract_block(expr, keyword)
|
|
96
|
+
# Strips a +BEGIN {...}+ or +END {...}+ block from +expr+ and
|
|
97
|
+
# returns +[trimmed_expr, block_body_or_nil]+. Brace-matched scan,
|
|
98
|
+
# so nested `{}` inside the block body are handled. Shared by
|
|
99
|
+
# instance and Ractor compile paths, so must be a class method
|
|
100
|
+
# (Ractors cannot call back into instance state).
|
|
101
|
+
#
|
|
102
|
+
def self.extract_block(expr, keyword)
|
|
121
103
|
start = expr.index(/#{keyword}\s*\{/)
|
|
122
104
|
return [expr, nil] unless start
|
|
123
105
|
|
|
@@ -138,6 +120,16 @@ module OMQ
|
|
|
138
120
|
trimmed = expr[0...start] + expr[j..]
|
|
139
121
|
[trimmed, body]
|
|
140
122
|
end
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def extract_blocks(expr)
|
|
129
|
+
expr, begin_body = self.class.extract_block(expr, "BEGIN")
|
|
130
|
+
expr, end_body = self.class.extract_block(expr, "END")
|
|
131
|
+
[expr, begin_body, end_body]
|
|
132
|
+
end
|
|
141
133
|
end
|
|
142
134
|
end
|
|
143
135
|
end
|
data/lib/omq/cli/formatter.rb
CHANGED
|
@@ -7,6 +7,10 @@ module OMQ
|
|
|
7
7
|
# (omq-rfc-zstd) once enabled via +socket.compression=+; the
|
|
8
8
|
# formatter sees plaintext frames in both directions.
|
|
9
9
|
class Formatter
|
|
10
|
+
# Single empty frame — used as the decoded form of a blank input line.
|
|
11
|
+
EMPTY_MSG = [::Protocol::ZMTP::Codec::EMPTY_BINARY].freeze
|
|
12
|
+
|
|
13
|
+
|
|
10
14
|
# @param format [Symbol] wire format (:ascii, :quoted, :raw, :jsonl, :msgpack, :marshal)
|
|
11
15
|
def initialize(format)
|
|
12
16
|
@format = format
|
|
@@ -20,20 +24,20 @@ module OMQ
|
|
|
20
24
|
def encode(parts)
|
|
21
25
|
case @format
|
|
22
26
|
when :ascii
|
|
23
|
-
parts.map { |p| p.b.gsub(/[^[:print:]\t]/, ".") }.join("\t")
|
|
27
|
+
parts.map { |p| p.b.gsub(/[^[:print:]\t]/, ".") }.join("\t") << "\n"
|
|
24
28
|
when :quoted
|
|
25
|
-
parts.map { |p| p.b.dump[1..-2] }.join("\t")
|
|
29
|
+
parts.map { |p| p.b.dump[1..-2] }.join("\t") << "\n"
|
|
26
30
|
when :raw
|
|
27
31
|
parts.each_with_index.map do |p, i|
|
|
28
32
|
Protocol::ZMTP::Codec::Frame.new(p.to_s, more: i < parts.size - 1).to_wire
|
|
29
33
|
end.join
|
|
30
34
|
when :jsonl
|
|
31
|
-
JSON.generate(parts)
|
|
35
|
+
JSON.generate(parts) << "\n"
|
|
32
36
|
when :msgpack
|
|
33
37
|
MessagePack.pack(parts)
|
|
34
38
|
when :marshal
|
|
35
39
|
# Under -M, `parts` is a single Ruby object (not a frame array).
|
|
36
|
-
parts.inspect
|
|
40
|
+
parts.inspect << "\n"
|
|
37
41
|
end
|
|
38
42
|
end
|
|
39
43
|
|
|
@@ -45,9 +49,11 @@ module OMQ
|
|
|
45
49
|
def decode(line)
|
|
46
50
|
case @format
|
|
47
51
|
when :ascii, :marshal
|
|
48
|
-
line.chomp.split("\t")
|
|
52
|
+
parts = line.chomp.split("\t", -1)
|
|
53
|
+
parts.empty? ? EMPTY_MSG : parts
|
|
49
54
|
when :quoted
|
|
50
|
-
line.chomp.split("\t").map { |p| "\"#{p}\"".undump }
|
|
55
|
+
parts = line.chomp.split("\t", -1).map { |p| "\"#{p}\"".undump }
|
|
56
|
+
parts.empty? ? EMPTY_MSG : parts
|
|
51
57
|
when :raw
|
|
52
58
|
[line]
|
|
53
59
|
when :jsonl
|
|
@@ -103,37 +109,65 @@ module OMQ
|
|
|
103
109
|
# itself (not an Array of frames); the preview inspects it so
|
|
104
110
|
# the reader sees the actual payload structure (e.g.
|
|
105
111
|
# `[nil, :foo, "bar"]`) instead of a meaningless "1obj" header.
|
|
112
|
+
# For marshal, +uncompressed_size+ is the Marshal.dump bytesize
|
|
113
|
+
# (known to the caller, which already serialized for send or
|
|
114
|
+
# received the wire frame for recv) — passed through instead of
|
|
115
|
+
# redumping here.
|
|
106
116
|
#
|
|
107
117
|
# @param parts [Array<String, Object>, Object] message frames, or raw object when +format+ is :marshal
|
|
108
118
|
# @param format [Symbol, nil] active CLI format (:marshal enables object-inspect mode)
|
|
109
119
|
# @param wire_size [Integer, nil] compressed bytes on the wire
|
|
120
|
+
# @param uncompressed_size [Integer, nil] plaintext bytes (marshal only)
|
|
110
121
|
# @return [String] truncated preview of each frame joined by |
|
|
111
|
-
def self.preview(parts, format: nil, wire_size: nil)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
out << "…" if truncated
|
|
118
|
-
return out
|
|
122
|
+
def self.preview(parts, format: nil, wire_size: nil, uncompressed_size: nil)
|
|
123
|
+
case format
|
|
124
|
+
when :marshal
|
|
125
|
+
marshal_preview(parts, uncompressed_size: uncompressed_size, wire_size: wire_size)
|
|
126
|
+
else
|
|
127
|
+
frames_preview(parts, format: format, wire_size: wire_size)
|
|
119
128
|
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def self.marshal_preview(parts, uncompressed_size:, wire_size:)
|
|
133
|
+
inspected = parts.inspect
|
|
134
|
+
truncated = inspected.bytesize > 60
|
|
135
|
+
inspected = inspected.byteslice(0, 60) if truncated
|
|
136
|
+
body = sanitize(inspected)
|
|
137
|
+
|
|
138
|
+
body << "…" if truncated
|
|
139
|
+
|
|
140
|
+
header = case
|
|
141
|
+
when uncompressed_size && wire_size
|
|
142
|
+
"(#{uncompressed_size}B wire=#{wire_size}B marshal)"
|
|
143
|
+
when uncompressed_size
|
|
144
|
+
"(#{uncompressed_size}B marshal)"
|
|
145
|
+
else
|
|
146
|
+
"(marshal)"
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
"#{header} #{body}"
|
|
150
|
+
end
|
|
151
|
+
private_class_method :marshal_preview
|
|
152
|
+
|
|
120
153
|
|
|
154
|
+
def self.frames_preview(parts, format:, wire_size:)
|
|
121
155
|
nparts = parts.size
|
|
122
156
|
shown = parts.first(3).map { |p| preview_frame(p) }
|
|
123
157
|
tail = nparts > 3 ? "|…" : ""
|
|
124
|
-
total = parts.all?(String) ? parts.sum
|
|
125
|
-
size =
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
end
|
|
158
|
+
total = parts.all?(String) ? parts.sum { |p| p.bytesize } : nil
|
|
159
|
+
size = if wire_size && total
|
|
160
|
+
"#{total}B wire=#{wire_size}B"
|
|
161
|
+
elsif total
|
|
162
|
+
"#{total}B"
|
|
163
|
+
else
|
|
164
|
+
"#{nparts}obj"
|
|
165
|
+
end
|
|
133
166
|
header = nparts > 1 ? "(#{size} #{nparts}F)" : "(#{size})"
|
|
134
167
|
|
|
135
168
|
"#{header} #{shown.join("|")}#{tail}"
|
|
136
169
|
end
|
|
170
|
+
private_class_method :frames_preview
|
|
137
171
|
|
|
138
172
|
|
|
139
173
|
# Renders one frame or decoded object for {Formatter.preview}.
|
|
@@ -70,22 +70,11 @@ module OMQ
|
|
|
70
70
|
log_events = @config.verbose >= 2
|
|
71
71
|
@sock.monitor(verbose: trace) do |event|
|
|
72
72
|
@log_port.send(OMQ::CLI::Term.format_event(event, @config.timestamps)) if log_events
|
|
73
|
-
kill_on_protocol_error(event)
|
|
73
|
+
OMQ::CLI::SocketSetup.kill_on_protocol_error(@sock, event)
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
|
|
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)
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
|
|
89
78
|
def wait_for_peer
|
|
90
79
|
if @config.timeout
|
|
91
80
|
Fiber.scheduler.with_timeout(@config.timeout) do
|
data/lib/omq/cli/pipe.rb
CHANGED
|
@@ -203,18 +203,10 @@ module OMQ
|
|
|
203
203
|
[@pull, @push].each do |sock|
|
|
204
204
|
sock.monitor(verbose: trace) do |event|
|
|
205
205
|
Term.write_event(event, config.timestamps) if log_events
|
|
206
|
-
kill_on_protocol_error(sock, event)
|
|
206
|
+
SocketSetup.kill_on_protocol_error(sock, event)
|
|
207
207
|
end
|
|
208
208
|
end
|
|
209
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
|
|
218
210
|
end
|
|
219
211
|
end
|
|
220
212
|
end
|
data/lib/omq/cli/socket_setup.rb
CHANGED
|
@@ -179,6 +179,24 @@ module OMQ
|
|
|
179
179
|
$stderr.puts "OMQ_SERVER_KEY='#{Protocol::ZMTP::Z85.encode(server_pub)}'"
|
|
180
180
|
end
|
|
181
181
|
end
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# CLI-level policy: a peer that commits a protocol-level violation
|
|
185
|
+
# (Protocol::ZMTP::Error — oversized frame, decompression bytebomb,
|
|
186
|
+
# bad framing, …) is almost certainly a misconfiguration the user
|
|
187
|
+
# needs to see. Mark +sock+ dead so the next receive raises
|
|
188
|
+
# SocketDeadError. The library itself just drops the connection and
|
|
189
|
+
# keeps serving the others; this stricter policy is CLI-only.
|
|
190
|
+
#
|
|
191
|
+
# @param sock [OMQ::Socket]
|
|
192
|
+
# @param event [OMQ::MonitorEvent]
|
|
193
|
+
#
|
|
194
|
+
def self.kill_on_protocol_error(sock, event)
|
|
195
|
+
return unless event.type == :disconnected
|
|
196
|
+
error = event.detail && event.detail[:error]
|
|
197
|
+
return unless error.is_a?(Protocol::ZMTP::Error)
|
|
198
|
+
sock.engine.signal_fatal_error(error)
|
|
199
|
+
end
|
|
182
200
|
end
|
|
183
201
|
end
|
|
184
202
|
end
|
data/lib/omq/cli/term.rb
CHANGED
|
@@ -49,27 +49,46 @@ module OMQ
|
|
|
49
49
|
"#{prefix}omq: << ZDICT (#{event.detail[:size]}B)"
|
|
50
50
|
else
|
|
51
51
|
ep = event.endpoint ? " #{event.endpoint}" : ""
|
|
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
|
|
52
|
+
detail = format_event_detail(event.detail)
|
|
60
53
|
"#{prefix}omq: #{event.type}#{ep}#{detail}"
|
|
61
54
|
end
|
|
62
55
|
end
|
|
63
56
|
|
|
64
57
|
|
|
65
|
-
#
|
|
58
|
+
# Renders +MonitorEvent#detail+ as a " (...)" suffix for log lines.
|
|
59
|
+
# Rewrites plain peer-close exceptions (EOFError) to "closed by
|
|
60
|
+
# peer" — the io-stream library reports these as "Stream finished
|
|
61
|
+
# before reading enough data!", which is confusing noise for what
|
|
62
|
+
# is just a normal disconnect.
|
|
63
|
+
#
|
|
64
|
+
# @param detail [Hash, Object, nil]
|
|
65
|
+
# @return [String]
|
|
66
|
+
def format_event_detail(detail)
|
|
67
|
+
return "" if detail.nil?
|
|
68
|
+
return " #{detail}" unless detail.is_a?(Hash)
|
|
69
|
+
|
|
70
|
+
error = detail[:error]
|
|
71
|
+
reason = detail[:reason]
|
|
72
|
+
|
|
73
|
+
case error
|
|
74
|
+
when nil
|
|
75
|
+
reason ? " (#{reason})" : ""
|
|
76
|
+
when EOFError
|
|
77
|
+
" (closed by peer)"
|
|
78
|
+
else
|
|
79
|
+
" (#{reason || error.message})"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# Formats an "attached endpoint" log line (bound to / connecting to).
|
|
66
85
|
#
|
|
67
86
|
# @param kind [:bind, :connect]
|
|
68
87
|
# @param url [String]
|
|
69
88
|
# @param timestamps [Symbol, nil]
|
|
70
89
|
# @return [String]
|
|
71
90
|
def format_attach(kind, url, timestamps)
|
|
72
|
-
verb = kind == :bind ? "
|
|
91
|
+
verb = kind == :bind ? "bound to" : "connecting to"
|
|
73
92
|
"#{log_prefix(timestamps)}omq: #{verb} #{url}"
|
|
74
93
|
end
|
|
75
94
|
|
|
@@ -85,7 +104,7 @@ module OMQ
|
|
|
85
104
|
end
|
|
86
105
|
|
|
87
106
|
|
|
88
|
-
# Writes one "
|
|
107
|
+
# Writes one "bound to / connecting to" line to +io+
|
|
89
108
|
# (default $stderr).
|
|
90
109
|
#
|
|
91
110
|
# @param kind [:bind, :connect]
|
data/lib/omq/cli/version.rb
CHANGED
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.14.
|
|
4
|
+
version: 0.14.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrik Wenger
|
|
@@ -18,7 +18,7 @@ dependencies:
|
|
|
18
18
|
version: '0.19'
|
|
19
19
|
- - ">="
|
|
20
20
|
- !ruby/object:Gem::Version
|
|
21
|
-
version: 0.19.
|
|
21
|
+
version: 0.19.3
|
|
22
22
|
type: :runtime
|
|
23
23
|
prerelease: false
|
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -28,7 +28,7 @@ dependencies:
|
|
|
28
28
|
version: '0.19'
|
|
29
29
|
- - ">="
|
|
30
30
|
- !ruby/object:Gem::Version
|
|
31
|
-
version: 0.19.
|
|
31
|
+
version: 0.19.3
|
|
32
32
|
- !ruby/object:Gem::Dependency
|
|
33
33
|
name: omq-ffi
|
|
34
34
|
requirement: !ruby/object:Gem::Requirement
|