omq-cli 0.7.2 → 0.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5af8706d0afd5ad199301ec7b37a47a232c5732a930eada3696a9f60a31aecc4
4
- data.tar.gz: 6e5b15d9bb2af20181cccbc71d5b4d432938660a8e052d62598c18386ddf2a5d
3
+ metadata.gz: e9d13a0ffe0dbde54709fe0489385447cf61913d8a0b7d9abc2caa67e64fd7a5
4
+ data.tar.gz: 6987e6aa0569b9e2bc41d6265adf7ea6bc05458086764df0e3c6ca501f08849d
5
5
  SHA512:
6
- metadata.gz: 8d35e218e4327b8a0c22c6ce15d9d205f62feffeec2fa2afd21cda6fcef96724645ea76922f0d0dd67099883b2795a0443211bbabf729c4c857e77e1a3b4299b
7
- data.tar.gz: 34af31e1dc6605c6b90bc7dcb3f519755d13a12e71f4417de7a5d07f71617610fc6d728f57f1be5f51bda6923d49fc1a6fc8cb1cbc6234d31c37ad392b8c3452
6
+ metadata.gz: 71c929c48a622fe318a531b03a6a7ab3f15c418dcf5b7662f000be0865423c660beb5967536d9ff30d26cd601648638cfdaa63f535a2da0e3dcc076c5bb36ed3
7
+ data.tar.gz: a3e937b036c55ae4072ddccb38b9acf278e22f8e60ee957b9441611d2a256f34b02cde3c3c847b53811e7365e498fcdeeab5aa512bf2f22c05fa309ce6c997e0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.0 — 2026-04-08
4
+
5
+ ### Added
6
+
7
+ - **`-P` for pull, gather, and rep** — parallel Ractor workers for recv-only
8
+ and request-reply socket types. Output serialized through `Ractor::Port`
9
+ to avoid scrambled stdout.
10
+ - **`RactorHelpers` module** — shared Ractor infrastructure: `preresolve_tcp`,
11
+ `start_log_consumer`, `start_output_consumer` with `SHUTDOWN` sentinel for
12
+ clean consumer shutdown.
13
+ - **`ParallelWorker` class** — general Ractor worker for parallel socket modes.
14
+ - **Process titles** — all runners set descriptive `proctitle`
15
+ (`omq TYPE [-z] [-PN] ENDPOINTS`). Pipe shows `omq pipe [-z] [-PN] IN -> OUT`.
16
+ Bare script mode shows `omq script`.
17
+
18
+ ### Changed
19
+
20
+ - **ASCII-only source** — replaced all Unicode special characters (em-dashes,
21
+ box-drawing, arrows) with ASCII equivalents in lib/ and test/.
22
+ - **Pipe default HWM** — pipe sockets now default to HWM of 64 (instead of
23
+ the socket default 1000) to bound memory with large messages in pipeline
24
+ stages. Override with `--send-hwm` / `--recv-hwm`.
25
+ - **Message preview** — total byte count first, 12 chars per part, max 3 parts
26
+ shown (`(1234B) frame1|frame2|frame3|...(5 parts)`).
27
+ - **`SocketSetup.apply_options`** — extracted shared socket option setup,
28
+ used by `BaseRunner`, `PipeRunner`, `PipeWorker`, and `ParallelWorker`.
29
+ - **`Formatter.preview`** — extracted from duplicated `msg_preview` methods.
30
+ - **Pipe/PipeWorker** — use bare `.new` for sockets, `SocketSetup.apply_options`
31
+ for configuration, `RactorHelpers` for Ractor infrastructure.
32
+
3
33
  ## 0.7.2 — 2026-04-07
4
34
 
5
35
  ### Changed
@@ -24,6 +24,7 @@ module OMQ
24
24
  # @param task [Async::Task] the parent async task
25
25
  # @return [void]
26
26
  def call(task)
27
+ set_process_title
27
28
  setup_socket
28
29
  start_event_monitor if config.verbose >= 2
29
30
  maybe_start_transient_monitor(task)
@@ -46,7 +47,33 @@ module OMQ
46
47
  end
47
48
 
48
49
 
49
- # ── Socket creation ─────────────────────────────────────────────
50
+ # -- Parallel Ractor workers -----------------------------------------
51
+
52
+
53
+ def run_parallel_workers(socket_sym)
54
+ OMQ.freeze_for_ractors!
55
+ eps = RactorHelpers.preresolve_tcp(config.endpoints)
56
+ output_port, output_thread = RactorHelpers.start_output_consumer
57
+ log_port, log_thread = RactorHelpers.start_log_consumer
58
+
59
+ workers = config.parallel.times.map do
60
+ ::Ractor.new(config, socket_sym, eps, output_port, log_port) do |cfg, sym, e, oport, lport|
61
+ ParallelWorker.new(cfg, sym, e, oport, lport).call
62
+ end
63
+ end
64
+
65
+ workers.each do |w|
66
+ w.join
67
+ rescue ::Ractor::RemoteError => e
68
+ $stderr.write("omq: Ractor error: #{e.cause&.message || e.message}\n")
69
+ end
70
+ ensure
71
+ RactorHelpers.stop_consumer(output_port, output_thread) if output_port
72
+ RactorHelpers.stop_consumer(log_port, log_thread) if log_port
73
+ end
74
+
75
+
76
+ # -- Socket creation ---------------------------------------------
50
77
 
51
78
 
52
79
  def setup_socket
@@ -68,7 +95,7 @@ module OMQ
68
95
  end
69
96
 
70
97
 
71
- # ── Transient disconnect monitor ────────────────────────────────
98
+ # -- Transient disconnect monitor --------------------------------
72
99
 
73
100
 
74
101
  def maybe_start_transient_monitor(task)
@@ -83,7 +110,7 @@ module OMQ
83
110
  end
84
111
 
85
112
 
86
- # ── BEGIN / END blocks ──────────────────────────────────────────
113
+ # -- BEGIN / END blocks ------------------------------------------
87
114
 
88
115
 
89
116
  def run_begin_blocks
@@ -98,7 +125,7 @@ module OMQ
98
125
  end
99
126
 
100
127
 
101
- # ── Peer wait with grace period ─────────────────────────────────
128
+ # -- Peer wait with grace period ---------------------------------
102
129
 
103
130
 
104
131
  def needs_peer_wait?
@@ -139,7 +166,7 @@ module OMQ
139
166
  end
140
167
 
141
168
 
142
- # ── Socket setup ────────────────────────────────────────────────
169
+ # -- Socket setup ------------------------------------------------
143
170
 
144
171
 
145
172
  def setup_subscriptions
@@ -157,7 +184,7 @@ module OMQ
157
184
  end
158
185
 
159
186
 
160
- # ── Shared loop bodies ──────────────────────────────────────────
187
+ # -- Shared loop bodies ------------------------------------------
161
188
 
162
189
 
163
190
  def run_send_logic
@@ -273,7 +300,7 @@ module OMQ
273
300
  end
274
301
 
275
302
 
276
- # ── Message I/O ─────────────────────────────────────────────────
303
+ # -- Message I/O -------------------------------------------------
277
304
 
278
305
 
279
306
  def send_msg(parts)
@@ -360,7 +387,7 @@ module OMQ
360
387
  end
361
388
 
362
389
 
363
- # ── Eval ────────────────────────────────────────────────────────
390
+ # -- Eval --------------------------------------------------------
364
391
 
365
392
 
366
393
  def compile_expr
@@ -377,7 +404,7 @@ module OMQ
377
404
 
378
405
 
379
406
  def assign_send_aliases
380
- # Keep ivar aliases subclasses check these directly
407
+ # Keep ivar aliases -- subclasses check these directly
381
408
  @send_begin_proc = @send_evaluator.begin_proc
382
409
  @send_eval_proc = @send_evaluator.eval_proc
383
410
  @send_end_proc = @send_evaluator.end_proc
@@ -404,7 +431,22 @@ module OMQ
404
431
  SENT = ExpressionEvaluator::SENT
405
432
 
406
433
 
407
- # ── Logging ─────────────────────────────────────────────────────
434
+ # -- Process title -------------------------------------------------
435
+
436
+
437
+ def set_process_title(endpoints: nil)
438
+ eps = endpoints || config.endpoints
439
+ title = ["omq", config.type_name]
440
+ title << "-z" if config.compress
441
+ title << "-P#{config.parallel}" if config.parallel
442
+ eps.each do |ep|
443
+ title << (ep.respond_to?(:url) ? ep.url : ep.to_s)
444
+ end
445
+ Process.setproctitle(title.join(" "))
446
+ end
447
+
448
+
449
+ # -- Logging -----------------------------------------------------
408
450
 
409
451
 
410
452
  def log(msg)
@@ -419,9 +461,9 @@ module OMQ
419
461
  @sock.monitor(verbose: verbose) do |event|
420
462
  case event.type
421
463
  when :message_sent
422
- $stderr.write("omq: >> #{msg_preview(event.detail[:parts])}\n")
464
+ $stderr.write("omq: >> #{Formatter.preview(event.detail[:parts])}\n")
423
465
  when :message_received
424
- $stderr.write("omq: << #{msg_preview(event.detail[:parts])}\n")
466
+ $stderr.write("omq: << #{Formatter.preview(event.detail[:parts])}\n")
425
467
  else
426
468
  ep = event.endpoint ? " #{event.endpoint}" : ""
427
469
  detail = event.detail ? " #{event.detail}" : ""
@@ -429,22 +471,6 @@ module OMQ
429
471
  end
430
472
  end
431
473
  end
432
-
433
-
434
- def msg_preview(parts)
435
- parts.map { |p| preview_bytes(p) }.join(" | ")
436
- end
437
-
438
-
439
- def preview_bytes(str)
440
- bytes = str.b
441
- preview = bytes[0, 10].gsub(/[^[:print:]]/, ".")
442
- if bytes.bytesize > 10
443
- "#{preview}... (#{bytes.bytesize}B)"
444
- else
445
- preview
446
- end
447
- end
448
474
  end
449
475
  end
450
476
  end
@@ -6,12 +6,12 @@ module OMQ
6
6
  #
7
7
  class CliParser
8
8
  EXAMPLES = <<~'TEXT'
9
- ── Request / Reply ──────────────────────────────────────────
9
+ -- Request / Reply ------------------------------------------
10
10
 
11
- ┌─────┐ "hello" ┌─────┐
12
- REQ │────────────→│ REP
13
- │←────────────│
14
- └─────┘ "HELLO" └─────┘
11
+ +-----+ "hello" +-----+
12
+ | REQ |------------->| REP |
13
+ | |<-------------| |
14
+ +-----+ "HELLO" +-----+
15
15
 
16
16
  # terminal 1: echo server
17
17
  omq rep --bind tcp://:5555 --recv-eval '$F.map(&:upcase)'
@@ -23,11 +23,11 @@ module OMQ
23
23
  omq rep --bind ipc:///tmp/echo.sock --echo &
24
24
  echo "hello" | omq req --connect ipc:///tmp/echo.sock
25
25
 
26
- ── Publish / Subscribe ──────────────────────────────────────
26
+ -- Publish / Subscribe --------------------------------------
27
27
 
28
- ┌─────┐ "weather.nyc 72F" ┌─────┐
29
- PUB │────────────────────→│ SUB --subscribe "weather."
30
- └─────┘ └─────┘
28
+ +-----+ "weather.nyc 72F" +-----+
29
+ | PUB |--------------------->| SUB | --subscribe "weather."
30
+ +-----+ +-----+
31
31
 
32
32
  # terminal 1: subscriber (all topics by default)
33
33
  omq sub --bind tcp://:5556
@@ -35,11 +35,11 @@ module OMQ
35
35
  # terminal 2: publisher (needs --delay for subscription to propagate)
36
36
  echo "weather.nyc 72F" | omq pub --connect tcp://localhost:5556 --delay 1
37
37
 
38
- ── Periodic Publish ───────────────────────────────────────────
38
+ -- Periodic Publish -------------------------------------------
39
39
 
40
- ┌─────┐ "tick 1" ┌─────┐
41
- PUB │──(every 1s)─→│ SUB
42
- └─────┘ └─────┘
40
+ +-----+ "tick 1" +-----+
41
+ | PUB |--(every 1s)-->| SUB |
42
+ +-----+ +-----+
43
43
 
44
44
  # terminal 1: subscriber
45
45
  omq sub --bind tcp://:5556
@@ -52,11 +52,11 @@ module OMQ
52
52
  omq pub --connect tcp://localhost:5556 --delay 1 \
53
53
  --data "tick" --interval 1 --count 5
54
54
 
55
- ── Pipeline ─────────────────────────────────────────────────
55
+ -- Pipeline -------------------------------------------------
56
56
 
57
- ┌──────┐ ┌──────┐
58
- PUSH │──────────→│ PULL
59
- └──────┘ └──────┘
57
+ +------+ +------+
58
+ | PUSH |----------->| PULL |
59
+ +------+ +------+
60
60
 
61
61
  # terminal 1: worker
62
62
  omq pull --bind tcp://:5557
@@ -68,16 +68,16 @@ module OMQ
68
68
  omq pull --bind ipc:///tmp/pipeline.sock &
69
69
  echo "task 1" | omq push --connect ipc:///tmp/pipeline.sock
70
70
 
71
- ── Pipe (PULL eval PUSH) ────────────────────────────────
71
+ -- Pipe (PULL -> eval -> PUSH) --------------------------------
72
72
 
73
- ┌──────┐ ┌──────┐ ┌──────┐
74
- PUSH │────────→│ pipe │────────→│ PULL
75
- └──────┘ └──────┘ └──────┘
73
+ +------+ +------+ +------+
74
+ | PUSH |--------->| pipe |--------->| PULL |
75
+ +------+ +------+ +------+
76
76
 
77
77
  # terminal 1: producer
78
78
  echo -e "hello\nworld" | omq push --bind ipc://@work
79
79
 
80
- # terminal 2: worker uppercase each message
80
+ # terminal 2: worker -- uppercase each message
81
81
  omq pipe -c ipc://@work -c ipc://@sink -e '$F.map(&:upcase)'
82
82
  # terminal 3: collector
83
83
  omq pull --bind ipc://@sink
@@ -88,19 +88,19 @@ module OMQ
88
88
  # exit when producer disconnects (--transient)
89
89
  omq pipe -c ipc://@work -c ipc://@sink --transient -e '$F.map(&:upcase)'
90
90
 
91
- # fan-in: multiple sources one sink
91
+ # fan-in: multiple sources -> one sink
92
92
  omq pipe --in -c ipc://@work1 -c ipc://@work2 \
93
93
  --out -c ipc://@sink -e '$F.map(&:upcase)'
94
94
 
95
- # fan-out: one source multiple sinks (round-robin)
95
+ # fan-out: one source -> multiple sinks (round-robin)
96
96
  omq pipe --in -b tcp://:5555 --out -c ipc://@sink1 -c ipc://@sink2 -e '$F'
97
97
 
98
- ── CLIENT / SERVER (draft) ──────────────────────────────────
98
+ -- CLIENT / SERVER (draft) ----------------------------------
99
99
 
100
- ┌────────┐ "hello" ┌────────┐
101
- CLIENT │───────────→│ SERVER --recv-eval '$F.map(&:upcase)'
102
- │←───────────│
103
- └────────┘ "HELLO" └────────┘
100
+ +--------+ "hello" +--------+
101
+ | CLIENT |------------>| SERVER | --recv-eval '$F.map(&:upcase)'
102
+ | |<------------| |
103
+ +--------+ "HELLO" +--------+
104
104
 
105
105
  # terminal 1: upcasing server
106
106
  omq server --bind tcp://:5555 --recv-eval '$F.map(&:upcase)'
@@ -108,28 +108,28 @@ module OMQ
108
108
  # terminal 2: client
109
109
  echo "hello" | omq client --connect tcp://localhost:5555
110
110
 
111
- ── Formats ──────────────────────────────────────────────────
111
+ -- Formats --------------------------------------------------
112
112
 
113
- # ascii (default) non-printable replaced with dots
113
+ # ascii (default) -- non-printable replaced with dots
114
114
  omq pull --bind tcp://:5557 --ascii
115
115
 
116
- # quoted lossless, round-trippable (uses String#dump escaping)
116
+ # quoted -- lossless, round-trippable (uses String#dump escaping)
117
117
  omq pull --bind tcp://:5557 --quoted
118
118
 
119
- # JSON Lines structured, multipart as arrays
119
+ # JSON Lines -- structured, multipart as arrays
120
120
  echo '["key","value"]' | omq push --connect tcp://localhost:5557 --jsonl
121
121
  omq pull --bind tcp://:5557 --jsonl
122
122
 
123
123
  # multipart via tabs
124
124
  printf "routing-key\tpayload" | omq push --connect tcp://localhost:5557
125
125
 
126
- ── Compression ──────────────────────────────────────────────
126
+ -- Compression ----------------------------------------------
127
127
 
128
128
  # both sides must use --compress
129
129
  omq pull --bind tcp://:5557 --compress &
130
130
  echo "compressible data" | omq push --connect tcp://localhost:5557 --compress
131
131
 
132
- ── CURVE Encryption ─────────────────────────────────────────
132
+ -- CURVE Encryption -----------------------------------------
133
133
 
134
134
  # server (prints OMQ_SERVER_KEY=...)
135
135
  omq rep --bind tcp://:5555 --echo --curve-server
@@ -138,12 +138,12 @@ module OMQ
138
138
  echo "secret" | omq req --connect tcp://localhost:5555 \
139
139
  --curve-server-key '<key from server>'
140
140
 
141
- ── ROUTER / DEALER ──────────────────────────────────────────
141
+ -- ROUTER / DEALER ------------------------------------------
142
142
 
143
- ┌────────┐ ┌────────┐
144
- DEALER │─────────→│ ROUTER
145
- id=w1
146
- └────────┘ └────────┘
143
+ +--------+ +--------+
144
+ | DEALER |---------->| ROUTER |
145
+ | id=w1 | | |
146
+ +--------+ +--------+
147
147
 
148
148
  # terminal 1: router shows identity + message
149
149
  omq router --bind tcp://:5555
@@ -151,7 +151,7 @@ module OMQ
151
151
  # terminal 2: dealer with identity
152
152
  echo "hello" | omq dealer --connect tcp://localhost:5555 --identity worker-1
153
153
 
154
- ── Ruby Eval ────────────────────────────────────────────────
154
+ -- Ruby Eval ------------------------------------------------
155
155
 
156
156
  # filter incoming: only pass messages containing "error"
157
157
  omq pull -b tcp://:5557 --recv-eval '$F.first.include?("error") ? $F : nil'
@@ -162,10 +162,10 @@ module OMQ
162
162
  # require a local file, use its methods
163
163
  omq rep --bind tcp://:5555 --require ./transform.rb -e 'upcase_all($F)'
164
164
 
165
- # next skips, break stops regexps match against $_
165
+ # next skips, break stops -- regexps match against $_
166
166
  omq pull -b tcp://:5557 -e 'next if /^#/; break if /quit/; $F'
167
167
 
168
- # BEGIN/END blocks (like awk) accumulate and summarize
168
+ # BEGIN/END blocks (like awk) -- accumulate and summarize
169
169
  omq pull -b tcp://:5557 -e 'BEGIN{@sum = 0} @sum += Integer($_); nil END{puts @sum}'
170
170
 
171
171
  # transform outgoing messages
@@ -174,9 +174,9 @@ module OMQ
174
174
  # REQ: transform request and reply independently
175
175
  echo hello | omq req -c tcp://localhost:5555 -E '$F.map(&:upcase)' -e '$_'
176
176
 
177
- ── Script Handlers (-r) ────────────────────────────────────
177
+ -- Script Handlers (-r) ------------------------------------
178
178
 
179
- # handler.rb register transforms from a file
179
+ # handler.rb -- register transforms from a file
180
180
  # db = PG.connect("dbname=app")
181
181
  # OMQ.incoming { |first_part, _| db.exec(first_part).values.flatten }
182
182
  # at_exit { db.close }
@@ -185,8 +185,8 @@ module OMQ
185
185
  # combine script handlers with inline eval
186
186
  omq req -c tcp://localhost:5555 -r./handler.rb -E '$F.map(&:upcase)'
187
187
 
188
- # OMQ.outgoing { |msg| ... } registered outgoing transform
189
- # OMQ.incoming { |msg| ... } registered incoming transform
188
+ # OMQ.outgoing { |msg| ... } -- registered outgoing transform
189
+ # OMQ.incoming { |msg| ... } -- registered incoming transform
190
190
  # CLI flags (-e/-E) override registered handlers
191
191
  TEXT
192
192
 
@@ -271,7 +271,7 @@ module OMQ
271
271
  o.banner = "Usage: omq TYPE [options]\n\n" \
272
272
  "Types: req, rep, pub, sub, push, pull, pair, dealer, router\n" \
273
273
  "Draft: client, server, radio, dish, scatter, gather, channel, peer\n" \
274
- "Virtual: pipe (PULL eval PUSH)\n\n"
274
+ "Virtual: pipe (PULL -> eval -> PUSH)\n\n"
275
275
 
276
276
  o.separator "Connection:"
277
277
  o.on("-c", "--connect URL", "Connect to endpoint (repeatable)") { |v|
@@ -412,7 +412,7 @@ module OMQ
412
412
  type_name = argv.shift
413
413
  if type_name.nil?
414
414
  abort parser.to_s if opts[:scripts].empty?
415
- # bare script mode type_name stays nil
415
+ # bare script mode -- type_name stays nil
416
416
  elsif !SOCKET_TYPE_NAMES.include?(type_name.downcase)
417
417
  abort "Unknown socket type: #{type_name}. Known: #{SOCKET_TYPE_NAMES.join(', ')}"
418
418
  else
@@ -486,10 +486,15 @@ module OMQ
486
486
  abort "--send-eval and --target are mutually exclusive" if opts[:send_expr] && opts[:target]
487
487
 
488
488
  if opts[:parallel]
489
- abort "-P/--parallel is only valid for pipe" unless type_name == "pipe"
489
+ parallel_types = %w[pipe pull gather rep]
490
+ abort "-P/--parallel is only valid for #{parallel_types.join(", ")}" unless parallel_types.include?(type_name)
490
491
  abort "-P/--parallel must be 1..16" unless (1..16).include?(opts[:parallel])
491
- all_pipe_eps = opts[:in_endpoints] + opts[:out_endpoints] + opts[:endpoints]
492
- abort "-P/--parallel requires all endpoints to use --connect (not --bind)" if all_pipe_eps.any?(&:bind?)
492
+ if type_name == "pipe"
493
+ all_eps = opts[:in_endpoints] + opts[:out_endpoints] + opts[:endpoints]
494
+ else
495
+ all_eps = opts[:endpoints]
496
+ end
497
+ abort "-P/--parallel requires all endpoints to use --connect (not --bind)" if all_eps.any?(&:bind?)
493
498
  end
494
499
 
495
500
  (opts[:connects] + opts[:binds]).each do |url|
@@ -98,6 +98,22 @@ module OMQ
98
98
  rescue
99
99
  abort "omq: decompression failed (did the sender use --compress?)"
100
100
  end
101
+
102
+
103
+ # Formats message parts for human-readable preview (logging).
104
+ #
105
+ # @param parts [Array<String>] message frames
106
+ # @return [String] truncated preview of each frame joined by |
107
+ def self.preview(parts)
108
+ total = parts.sum(&:bytesize)
109
+ shown = parts.first(3).map do |p|
110
+ bytes = p.b
111
+ preview = bytes[0, 12].gsub(/[^[:print:]]/, ".")
112
+ bytes.bytesize > 12 ? "#{preview}..." : preview
113
+ end
114
+ tail = parts.size > 3 ? "|...(#{parts.size} parts)" : ""
115
+ "(#{total}B) #{shown.join("|")}#{tail}"
116
+ end
101
117
  end
102
118
  end
103
119
  end
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OMQ
4
+ module CLI
5
+ # Worker that runs inside a Ractor for parallel socket modes (-P).
6
+ # Each worker owns its own Async reactor and socket instance.
7
+ #
8
+ # Supported socket types:
9
+ # - pull, gather (recv-only)
10
+ # - rep (recv-reply with echo/data/eval)
11
+ #
12
+ class ParallelWorker
13
+ def initialize(config, socket_sym, endpoints, output_port, log_port)
14
+ @config = config
15
+ @socket_sym = socket_sym
16
+ @endpoints = endpoints
17
+ @output_port = output_port
18
+ @log_port = log_port
19
+ end
20
+
21
+
22
+ def call
23
+ Async do
24
+ setup_socket
25
+ log_endpoints
26
+ start_monitors
27
+ wait_for_peer
28
+ compile_expr
29
+ run_loop
30
+ run_end_block
31
+ ensure
32
+ @sock&.close
33
+ end
34
+ end
35
+
36
+
37
+ private
38
+
39
+
40
+ def setup_socket
41
+ @sock = OMQ.const_get(@socket_sym).new
42
+ OMQ::CLI::SocketSetup.apply_options(@sock, @config)
43
+ @sock.identity = @config.identity if @config.identity
44
+ OMQ::CLI::SocketSetup.attach_endpoints(@sock, @endpoints, verbose: false)
45
+ end
46
+
47
+
48
+ def log_endpoints
49
+ return unless @config.verbose >= 1
50
+ @endpoints.each do |ep|
51
+ @log_port.send(ep.bind? ? "Bound to #{ep.url}" : "Connecting to #{ep.url}")
52
+ end
53
+ end
54
+
55
+
56
+ def start_monitors
57
+ return unless @config.verbose >= 2
58
+ trace = @config.verbose >= 3
59
+ @sock.monitor(verbose: trace) do |event|
60
+ @log_port.send(format_event(event))
61
+ end
62
+ end
63
+
64
+
65
+ def format_event(event)
66
+ case event.type
67
+ when :message_sent
68
+ "omq: >> #{OMQ::CLI::Formatter.preview(event.detail[:parts])}"
69
+ when :message_received
70
+ "omq: << #{OMQ::CLI::Formatter.preview(event.detail[:parts])}"
71
+ else
72
+ ep = event.endpoint ? " #{event.endpoint}" : ""
73
+ detail = event.detail ? " #{event.detail}" : ""
74
+ "omq: #{event.type}#{ep}#{detail}"
75
+ end
76
+ end
77
+
78
+
79
+ def wait_for_peer
80
+ if @config.timeout
81
+ Fiber.scheduler.with_timeout(@config.timeout) do
82
+ @sock.peer_connected.wait
83
+ end
84
+ else
85
+ @sock.peer_connected.wait
86
+ end
87
+ rescue IO::TimeoutError, Async::TimeoutError
88
+ # Proceed anyway -- recv will timeout if no messages arrive
89
+ end
90
+
91
+
92
+ def compile_expr
93
+ @begin_proc, @end_proc, @eval_proc =
94
+ OMQ::CLI::ExpressionEvaluator.compile_inside_ractor(@config.recv_expr)
95
+ @fmt = OMQ::CLI::Formatter.new(@config.format, compress: @config.compress)
96
+ @ctx = Object.new
97
+ @ctx.instance_exec(&@begin_proc) if @begin_proc
98
+ end
99
+
100
+
101
+ def run_loop
102
+ case @config.type_name
103
+ when "pull", "gather"
104
+ run_recv_loop
105
+ when "rep"
106
+ run_rep_loop
107
+ end
108
+ end
109
+
110
+
111
+ # -- Recv-only loop (PULL, GATHER) -----------------------------------
112
+
113
+
114
+ def run_recv_loop
115
+ n = @config.count
116
+ i = 0
117
+ loop do
118
+ parts = @sock.receive
119
+ break if parts.nil?
120
+ parts = @fmt.decompress(parts)
121
+ if @eval_proc
122
+ parts = normalize(
123
+ @ctx.instance_exec(parts, &@eval_proc)
124
+ )
125
+ next if parts.nil?
126
+ end
127
+ output(parts) unless parts.empty?
128
+ i += 1
129
+ break if n && n > 0 && i >= n
130
+ end
131
+ rescue IO::TimeoutError, Async::TimeoutError
132
+ # recv timed out -- fall through to END block
133
+ end
134
+
135
+
136
+ # -- REP loop (recv request, process, send reply) --------------------
137
+
138
+
139
+ def run_rep_loop
140
+ n = @config.count
141
+ i = 0
142
+ loop do
143
+ parts = @sock.receive
144
+ break if parts.nil?
145
+ parts = @fmt.decompress(parts)
146
+ reply = compute_reply(parts)
147
+ output(reply)
148
+ @sock.send(@fmt.compress(reply))
149
+ i += 1
150
+ break if n && n > 0 && i >= n
151
+ end
152
+ rescue IO::TimeoutError, Async::TimeoutError
153
+ # recv timed out -- fall through to END block
154
+ end
155
+
156
+
157
+ def compute_reply(parts)
158
+ if @eval_proc
159
+ normalize(@ctx.instance_exec(parts, &@eval_proc)) || [""]
160
+ elsif @config.echo
161
+ parts
162
+ elsif @config.data
163
+ @fmt.decode(@config.data + "\n")
164
+ else
165
+ parts
166
+ end
167
+ end
168
+
169
+
170
+ # -- Output and helpers ----------------------------------------------
171
+
172
+
173
+ def output(parts)
174
+ return if @config.quiet || parts.nil?
175
+ @output_port.send(@fmt.encode(parts))
176
+ end
177
+
178
+
179
+ def normalize(result)
180
+ OMQ::CLI::ExpressionEvaluator.normalize_result(result)
181
+ end
182
+
183
+
184
+ def run_end_block
185
+ return unless @end_proc
186
+ out = normalize(@ctx.instance_exec(&@end_proc))
187
+ output(out) if out && !out.empty?
188
+ end
189
+ end
190
+ end
191
+ end
data/lib/omq/cli/pipe.rb CHANGED
@@ -5,6 +5,11 @@ module OMQ
5
5
  # Runner for the virtual "pipe" socket type (PULL -> eval -> PUSH).
6
6
  # Supports sequential and parallel (Ractor-based) processing modes.
7
7
  class PipeRunner
8
+ # Default HWM for pipe sockets when the user hasn't set one.
9
+ # Much lower than the socket default (1000) to bound memory
10
+ # with large messages in pipeline stages.
11
+ PIPE_HWM = 64
12
+
8
13
  # @return [Config] frozen CLI configuration
9
14
  attr_reader :config
10
15
 
@@ -42,10 +47,11 @@ module OMQ
42
47
  end
43
48
 
44
49
 
45
- # ── Sequential ───────────────────────────────────────────────────
50
+ # -- Sequential ---------------------------------------------------
46
51
 
47
52
 
48
53
  def run_sequential(task)
54
+ set_pipe_process_title
49
55
  in_eps, out_eps = resolve_endpoints
50
56
  @pull, @push = build_pull_push(in_eps, out_eps)
51
57
  compile_expr
@@ -73,21 +79,13 @@ module OMQ
73
79
  end
74
80
 
75
81
 
76
- def apply_socket_options(sock)
77
- sock.reconnect_interval = config.reconnect_ivl if config.reconnect_ivl
78
- sock.heartbeat_interval = config.heartbeat_ivl if config.heartbeat_ivl
79
- sock.send_hwm = config.send_hwm if config.send_hwm
80
- sock.recv_hwm = config.recv_hwm if config.recv_hwm
81
- sock.sndbuf = config.sndbuf if config.sndbuf
82
- sock.rcvbuf = config.rcvbuf if config.rcvbuf
83
- end
84
-
85
-
86
82
  def build_pull_push(in_eps, out_eps)
87
- pull = OMQ::PULL.new(linger: config.linger, recv_timeout: config.timeout)
88
- push = OMQ::PUSH.new(linger: config.linger, send_timeout: config.timeout)
89
- apply_socket_options(pull)
90
- apply_socket_options(push)
83
+ pull = OMQ::PULL.new
84
+ push = OMQ::PUSH.new
85
+ SocketSetup.apply_options(pull, config)
86
+ SocketSetup.apply_options(push, config)
87
+ pull.recv_hwm = PIPE_HWM unless config.recv_hwm
88
+ push.send_hwm = PIPE_HWM unless config.send_hwm
91
89
  SocketSetup.attach_endpoints(pull, in_eps, verbose: config.verbose >= 1)
92
90
  SocketSetup.attach_endpoints(push, out_eps, verbose: config.verbose >= 1)
93
91
  [pull, push]
@@ -121,15 +119,16 @@ module OMQ
121
119
  end
122
120
 
123
121
 
124
- # ── Parallel ─────────────────────────────────────────────────────
122
+ # -- Parallel -----------------------------------------------------
125
123
 
126
124
 
127
125
  def run_parallel(task)
126
+ set_pipe_process_title
128
127
  OMQ.freeze_for_ractors!
129
128
  in_eps, out_eps = resolve_endpoints
130
- in_eps = PipeWorker.preresolve_tcp(in_eps)
131
- out_eps = PipeWorker.preresolve_tcp(out_eps)
132
- log_port, log_thread = PipeWorker.start_log_consumer
129
+ in_eps = RactorHelpers.preresolve_tcp(in_eps)
130
+ out_eps = RactorHelpers.preresolve_tcp(out_eps)
131
+ log_port, log_thread = RactorHelpers.start_log_consumer
133
132
  workers = config.parallel.times.map do
134
133
  ::Ractor.new(config, in_eps, out_eps, log_port) do |cfg, ins, outs, lport|
135
134
  PipeWorker.new(cfg, ins, outs, lport).call
@@ -141,12 +140,26 @@ module OMQ
141
140
  $stderr.write("omq: Ractor error: #{e.cause&.message || e.message}\n")
142
141
  end
143
142
  ensure
144
- log_port.close
145
- log_thread.join
143
+ RactorHelpers.stop_consumer(log_port, log_thread) if log_port
146
144
  end
147
145
 
148
146
 
149
- # ── Expression eval ──────────────────────────────────────────────
147
+ # -- Process title -------------------------------------------------
148
+
149
+
150
+ def set_pipe_process_title
151
+ in_eps, out_eps = resolve_endpoints
152
+ title = ["omq pipe"]
153
+ title << "-z" if config.compress || config.compress_in || config.compress_out
154
+ title << "-P#{config.parallel}" if config.parallel
155
+ title.concat(in_eps.map(&:url))
156
+ title << "->"
157
+ title.concat(out_eps.map(&:url))
158
+ Process.setproctitle(title.join(" "))
159
+ end
160
+
161
+
162
+ # -- Expression eval ----------------------------------------------
150
163
 
151
164
 
152
165
  def compile_expr
@@ -163,7 +176,7 @@ module OMQ
163
176
  end
164
177
 
165
178
 
166
- # ── Event monitoring ─────────────────────────────────────────────
179
+ # -- Event monitoring ---------------------------------------------
167
180
 
168
181
 
169
182
  def start_event_monitors
@@ -172,9 +185,9 @@ module OMQ
172
185
  sock.monitor(verbose: verbose) do |event|
173
186
  case event.type
174
187
  when :message_sent
175
- $stderr.write("omq: >> #{msg_preview(event.detail[:parts])}\n")
188
+ $stderr.write("omq: >> #{Formatter.preview(event.detail[:parts])}\n")
176
189
  when :message_received
177
- $stderr.write("omq: << #{msg_preview(event.detail[:parts])}\n")
190
+ $stderr.write("omq: << #{Formatter.preview(event.detail[:parts])}\n")
178
191
  else
179
192
  ep = event.endpoint ? " #{event.endpoint}" : ""
180
193
  detail = event.detail ? " #{event.detail}" : ""
@@ -183,15 +196,6 @@ module OMQ
183
196
  end
184
197
  end
185
198
  end
186
-
187
-
188
- def msg_preview(parts)
189
- parts.map { |p|
190
- bytes = p.b
191
- preview = bytes[0, 10].gsub(/[^[:print:]]/, ".")
192
- bytes.bytesize > 10 ? "#{preview}... (#{bytes.bytesize}B)" : preview
193
- }.join(" | ")
194
- end
195
199
  end
196
200
  end
197
201
  end
@@ -6,42 +6,6 @@ module OMQ
6
6
  # Each worker owns its own Async reactor, PULL socket, and PUSH socket.
7
7
  #
8
8
  class PipeWorker
9
- # Starts a Ractor::Port and a consumer thread that drains log
10
- # messages to stderr sequentially. Returns [port, thread].
11
- #
12
- def self.start_log_consumer
13
- port = Ractor::Port.new
14
- thread = Thread.new(port) do |p|
15
- loop do
16
- $stderr.write("#{p.receive}\n")
17
- rescue Ractor::ClosedError
18
- break
19
- end
20
- end
21
- [port, thread]
22
- end
23
-
24
-
25
- # Resolves TCP hostnames to IP addresses so Ractors don't touch
26
- # Resolv::DefaultResolver (which is not shareable).
27
- #
28
- def self.preresolve_tcp(endpoints)
29
- endpoints.flat_map do |ep|
30
- url = ep.url
31
- if url.start_with?("tcp://")
32
- host, port = OMQ::Transport::TCP.parse_endpoint(url)
33
- Addrinfo.getaddrinfo(host, port, nil, :STREAM).map do |addr|
34
- ip = addr.ip_address
35
- ip = "[#{ip}]" if ip.include?(":")
36
- Endpoint.new("tcp://#{ip}:#{addr.ip_port}", ep.bind?)
37
- end
38
- else
39
- ep
40
- end
41
- end
42
- end
43
-
44
-
45
9
  def initialize(config, in_eps, out_eps, log_port)
46
10
  @config = config
47
11
  @in_eps = in_eps
@@ -70,27 +34,17 @@ module OMQ
70
34
 
71
35
 
72
36
  def setup_sockets
73
- @pull = OMQ::PULL.new(linger: @config.linger)
74
- @push = OMQ::PUSH.new(linger: @config.linger)
75
- @pull.recv_timeout = @config.timeout if @config.timeout
76
- @push.send_timeout = @config.timeout if @config.timeout
77
- apply_socket_options(@pull)
78
- apply_socket_options(@push)
37
+ @pull = OMQ::PULL.new
38
+ @push = OMQ::PUSH.new
39
+ OMQ::CLI::SocketSetup.apply_options(@pull, @config)
40
+ OMQ::CLI::SocketSetup.apply_options(@push, @config)
41
+ @pull.recv_hwm = PipeRunner::PIPE_HWM unless @config.recv_hwm
42
+ @push.send_hwm = PipeRunner::PIPE_HWM unless @config.send_hwm
79
43
  OMQ::CLI::SocketSetup.attach_endpoints(@pull, @in_eps, verbose: false)
80
44
  OMQ::CLI::SocketSetup.attach_endpoints(@push, @out_eps, verbose: false)
81
45
  end
82
46
 
83
47
 
84
- def apply_socket_options(sock)
85
- sock.reconnect_interval = @config.reconnect_ivl if @config.reconnect_ivl
86
- sock.heartbeat_interval = @config.heartbeat_ivl if @config.heartbeat_ivl
87
- sock.send_hwm = @config.send_hwm if @config.send_hwm
88
- sock.recv_hwm = @config.recv_hwm if @config.recv_hwm
89
- sock.sndbuf = @config.sndbuf if @config.sndbuf
90
- sock.rcvbuf = @config.rcvbuf if @config.rcvbuf
91
- end
92
-
93
-
94
48
  def log_endpoints
95
49
  @in_eps.each { |ep| @log_port.send(ep.bind? ? "Bound to #{ep.url}" : "Connecting to #{ep.url}") }
96
50
  @out_eps.each { |ep| @log_port.send(ep.bind? ? "Bound to #{ep.url}" : "Connecting to #{ep.url}") }
@@ -110,9 +64,9 @@ module OMQ
110
64
  def format_event(event)
111
65
  case event.type
112
66
  when :message_sent
113
- "omq: >> #{msg_preview(event.detail[:parts])}"
67
+ "omq: >> #{OMQ::CLI::Formatter.preview(event.detail[:parts])}"
114
68
  when :message_received
115
- "omq: << #{msg_preview(event.detail[:parts])}"
69
+ "omq: << #{OMQ::CLI::Formatter.preview(event.detail[:parts])}"
116
70
  else
117
71
  ep = event.endpoint ? " #{event.endpoint}" : ""
118
72
  detail = event.detail ? " #{event.detail}" : ""
@@ -121,15 +75,6 @@ module OMQ
121
75
  end
122
76
 
123
77
 
124
- def msg_preview(parts)
125
- parts.map { |p|
126
- bytes = p.b
127
- preview = bytes[0, 10].gsub(/[^[:print:]]/, ".")
128
- bytes.bytesize > 10 ? "#{preview}... (#{bytes.bytesize}B)" : preview
129
- }.join(" | ")
130
- end
131
-
132
-
133
78
  def wait_for_peers
134
79
  Barrier do |barrier|
135
80
  barrier.async { @pull.peer_connected.wait }
@@ -188,7 +133,7 @@ module OMQ
188
133
  end
189
134
  end
190
135
  rescue IO::TimeoutError, Async::TimeoutError
191
- # recv timed out fall through to END block
136
+ # recv timed out -- fall through to END block
192
137
  end
193
138
 
194
139
 
@@ -10,6 +10,14 @@ module OMQ
10
10
 
11
11
  # Runner for PULL sockets (receive-only pipeline consumer).
12
12
  class PullRunner < BaseRunner
13
+ def call(task)
14
+ config.parallel ? run_parallel_workers(:PULL) : super
15
+ end
16
+
17
+
18
+ private
19
+
20
+
13
21
  def run_loop(task) = run_recv_logic
14
22
  end
15
23
  end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OMQ
4
+ module CLI
5
+ # Shared Ractor infrastructure for parallel worker modes.
6
+ module RactorHelpers
7
+ # Sentinel value sent through ports to signal consumer threads to exit.
8
+ # Port#close does not unblock a waiting #receive, so we must send an
9
+ # explicit shutdown marker.
10
+ SHUTDOWN = :__omq_shutdown__
11
+
12
+
13
+ # Resolves TCP hostnames to IP addresses so Ractors don't touch
14
+ # Resolv::DefaultResolver (which is not shareable).
15
+ #
16
+ def self.preresolve_tcp(endpoints)
17
+ endpoints.flat_map do |ep|
18
+ url = ep.url
19
+ if url.start_with?("tcp://")
20
+ host, port = OMQ::Transport::TCP.parse_endpoint(url)
21
+ Addrinfo.getaddrinfo(host, port, nil, :STREAM).map do |addr|
22
+ ip = addr.ip_address
23
+ ip = "[#{ip}]" if ip.include?(":")
24
+ Endpoint.new("tcp://#{ip}:#{addr.ip_port}", ep.bind?)
25
+ end
26
+ else
27
+ ep
28
+ end
29
+ end
30
+ end
31
+
32
+
33
+ # Starts a Ractor::Port and a consumer thread that drains log
34
+ # messages to stderr sequentially. Returns [port, thread].
35
+ # Send SHUTDOWN through the port to stop the consumer.
36
+ #
37
+ def self.start_log_consumer
38
+ port = Ractor::Port.new
39
+ thread = Thread.new(port) do |p|
40
+ loop do
41
+ msg = p.receive
42
+ break if msg.equal?(SHUTDOWN)
43
+ $stderr.write("#{msg}\n")
44
+ rescue Ractor::ClosedError
45
+ break
46
+ end
47
+ end
48
+ [port, thread]
49
+ end
50
+
51
+
52
+ # Starts a Ractor::Port and a consumer thread that drains
53
+ # formatted output to stdout sequentially. Returns [port, thread].
54
+ # Send SHUTDOWN through the port to stop the consumer.
55
+ #
56
+ def self.start_output_consumer
57
+ port = Ractor::Port.new
58
+ thread = Thread.new(port) do |p|
59
+ loop do
60
+ msg = p.receive
61
+ break if msg.equal?(SHUTDOWN)
62
+ $stdout.write(msg)
63
+ rescue Ractor::ClosedError
64
+ break
65
+ end
66
+ end
67
+ [port, thread]
68
+ end
69
+
70
+
71
+ # Sends the shutdown sentinel and joins the consumer thread.
72
+ #
73
+ def self.stop_consumer(port, thread)
74
+ port.send(SHUTDOWN)
75
+ thread.join
76
+ rescue Ractor::ClosedError
77
+ thread.join(1)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -37,6 +37,11 @@ module OMQ
37
37
 
38
38
  # Runner for REP sockets (synchronous request-reply server).
39
39
  class RepRunner < BaseRunner
40
+ def call(task)
41
+ config.parallel ? run_parallel_workers(:REP) : super
42
+ end
43
+
44
+
40
45
  private
41
46
 
42
47
 
@@ -10,6 +10,14 @@ module OMQ
10
10
 
11
11
  # Runner for GATHER sockets (draft; fan-in receive).
12
12
  class GatherRunner < BaseRunner
13
+ def call(task)
14
+ config.parallel ? run_parallel_workers(:GATHER) : super
15
+ end
16
+
17
+
18
+ private
19
+
20
+
13
21
  def run_loop(task) = run_recv_logic
14
22
  end
15
23
  end
@@ -6,23 +6,30 @@ module OMQ
6
6
  # All methods are module-level so callers compose rather than inherit.
7
7
  #
8
8
  module SocketSetup
9
+ # Apply common socket options from +config+ to +sock+.
10
+ #
11
+ def self.apply_options(sock, config)
12
+ sock.linger = config.linger
13
+ sock.recv_timeout = config.timeout if config.timeout
14
+ sock.send_timeout = config.timeout if config.timeout
15
+ sock.reconnect_interval = config.reconnect_ivl if config.reconnect_ivl
16
+ sock.heartbeat_interval = config.heartbeat_ivl if config.heartbeat_ivl
17
+ sock.send_hwm = config.send_hwm if config.send_hwm
18
+ sock.recv_hwm = config.recv_hwm if config.recv_hwm
19
+ sock.sndbuf = config.sndbuf if config.sndbuf
20
+ sock.rcvbuf = config.rcvbuf if config.rcvbuf
21
+ end
22
+
23
+
9
24
  # Create and fully configure a socket from +klass+ and +config+.
10
25
  #
11
26
  def self.build(klass, config)
12
- sock_opts = { linger: config.linger }
13
- sock_opts[:conflate] = true if config.conflate && %w[pub radio].include?(config.type_name)
14
- sock = klass.new(**sock_opts)
15
- sock.recv_timeout = config.timeout if config.timeout
16
- sock.send_timeout = config.timeout if config.timeout
17
- sock.max_message_size = config.recv_maxsz if config.recv_maxsz
18
- sock.send_hwm = config.send_hwm if config.send_hwm
19
- sock.recv_hwm = config.recv_hwm if config.recv_hwm
20
- sock.sndbuf = config.sndbuf if config.sndbuf
21
- sock.rcvbuf = config.rcvbuf if config.rcvbuf
22
- sock.reconnect_interval = config.reconnect_ivl if config.reconnect_ivl
23
- sock.heartbeat_interval = config.heartbeat_ivl if config.heartbeat_ivl
24
- sock.identity = config.identity if config.identity
25
- sock.router_mandatory = true if config.type_name == "router"
27
+ sock = klass.new
28
+ sock.conflate = true if config.conflate && %w[pub radio].include?(config.type_name)
29
+ apply_options(sock, config)
30
+ sock.max_message_size = config.recv_maxsz if config.recv_maxsz
31
+ sock.identity = config.identity if config.identity
32
+ sock.router_mandatory = true if config.type_name == "router"
26
33
  sock
27
34
  end
28
35
 
@@ -31,7 +31,7 @@ module OMQ
31
31
 
32
32
 
33
33
  # Signal that the first message has been sent or received.
34
- # Idempotent safe to call multiple times.
34
+ # Idempotent -- safe to call multiple times.
35
35
  #
36
36
  def ready!
37
37
  @barrier.resolve(true) unless @barrier.resolved?
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OMQ
4
4
  module CLI
5
- VERSION = "0.7.2"
5
+ VERSION = "0.8.0"
6
6
  end
7
7
  end
data/lib/omq/cli.rb CHANGED
@@ -18,6 +18,8 @@ require_relative "cli/req_rep"
18
18
  require_relative "cli/pair"
19
19
  require_relative "cli/router_dealer"
20
20
  require_relative "cli/client_server"
21
+ require_relative "cli/ractor_helpers"
22
+ require_relative "cli/parallel_worker"
21
23
  require_relative "cli/pipe_worker"
22
24
  require_relative "cli/pipe"
23
25
 
@@ -169,7 +171,7 @@ module OMQ
169
171
  abort "CURVE requires libsodium. Install it:\n" \
170
172
  " apt install libsodium-dev # Debian/Ubuntu\n" \
171
173
  " brew install libsodium # macOS\n" \
172
- "Or use nuckle (pure Ruby, DANGEROUS not audited):\n" \
174
+ "Or use nuckle (pure Ruby, DANGEROUS -- not audited):\n" \
173
175
  " --crypto nuckle"
174
176
  end
175
177
  else
@@ -221,6 +223,7 @@ module OMQ
221
223
  end
222
224
 
223
225
  if config.type_name.nil?
226
+ Process.setproctitle("omq script")
224
227
  Object.include(OMQ) unless Object.include?(OMQ)
225
228
  Async annotation: 'omq' do
226
229
  Async::Debug.serve(endpoint: debug_ep) if debug_ep
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.7.2
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger
@@ -165,10 +165,12 @@ files:
165
165
  - lib/omq/cli/expression_evaluator.rb
166
166
  - lib/omq/cli/formatter.rb
167
167
  - lib/omq/cli/pair.rb
168
+ - lib/omq/cli/parallel_worker.rb
168
169
  - lib/omq/cli/pipe.rb
169
170
  - lib/omq/cli/pipe_worker.rb
170
171
  - lib/omq/cli/pub_sub.rb
171
172
  - lib/omq/cli/push_pull.rb
173
+ - lib/omq/cli/ractor_helpers.rb
172
174
  - lib/omq/cli/radio_dish.rb
173
175
  - lib/omq/cli/req_rep.rb
174
176
  - lib/omq/cli/router_dealer.rb