omq-cli 0.7.1 → 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 +4 -4
- data/CHANGELOG.md +41 -0
- data/lib/omq/cli/base_runner.rb +61 -41
- data/lib/omq/cli/cli_parser.rb +57 -52
- data/lib/omq/cli/formatter.rb +16 -0
- data/lib/omq/cli/parallel_worker.rb +191 -0
- data/lib/omq/cli/pipe.rb +52 -172
- data/lib/omq/cli/pipe_worker.rb +149 -0
- data/lib/omq/cli/push_pull.rb +8 -0
- data/lib/omq/cli/ractor_helpers.rb +81 -0
- data/lib/omq/cli/req_rep.rb +5 -0
- data/lib/omq/cli/scatter_gather.rb +8 -0
- data/lib/omq/cli/socket_setup.rb +21 -14
- data/lib/omq/cli/transient_monitor.rb +1 -1
- data/lib/omq/cli/version.rb +1 -1
- data/lib/omq/cli.rb +5 -1
- metadata +4 -1
|
@@ -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,14 +5,18 @@ 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
|
|
|
11
16
|
|
|
12
17
|
# @param config [Config] frozen CLI configuration
|
|
13
18
|
def initialize(config)
|
|
14
|
-
@config
|
|
15
|
-
@fmt = Formatter.new(config.format, compress: config.compress)
|
|
19
|
+
@config = config
|
|
16
20
|
@fmt_in = Formatter.new(config.format, compress: config.compress_in || config.compress)
|
|
17
21
|
@fmt_out = Formatter.new(config.format, compress: config.compress_out || config.compress)
|
|
18
22
|
end
|
|
@@ -43,30 +47,28 @@ module OMQ
|
|
|
43
47
|
end
|
|
44
48
|
|
|
45
49
|
|
|
46
|
-
|
|
47
|
-
SocketSetup.attach_endpoints(sock, endpoints, verbose: config.verbose >= 1)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
# ── Sequential ───────────────────────────────────────────────────
|
|
50
|
+
# -- Sequential ---------------------------------------------------
|
|
52
51
|
|
|
53
52
|
|
|
54
53
|
def run_sequential(task)
|
|
54
|
+
set_pipe_process_title
|
|
55
55
|
in_eps, out_eps = resolve_endpoints
|
|
56
|
-
@pull, @push = build_pull_push(
|
|
57
|
-
{ linger: config.linger, recv_timeout: config.timeout },
|
|
58
|
-
{ linger: config.linger, send_timeout: config.timeout },
|
|
59
|
-
in_eps, out_eps
|
|
60
|
-
)
|
|
56
|
+
@pull, @push = build_pull_push(in_eps, out_eps)
|
|
61
57
|
compile_expr
|
|
62
58
|
@sock = @pull # for eval instance_exec
|
|
63
59
|
start_event_monitors if config.verbose >= 2
|
|
64
|
-
|
|
60
|
+
wait_body = proc do
|
|
65
61
|
Barrier do |barrier|
|
|
66
62
|
barrier.async(annotation: "wait push peer") { @push.peer_connected.wait }
|
|
67
63
|
barrier.async(annotation: "wait pull peer") { @pull.peer_connected.wait }
|
|
68
64
|
end
|
|
69
65
|
end
|
|
66
|
+
|
|
67
|
+
if config.timeout
|
|
68
|
+
Fiber.scheduler.with_timeout(config.timeout, &wait_body)
|
|
69
|
+
else
|
|
70
|
+
wait_body.call
|
|
71
|
+
end
|
|
70
72
|
setup_sequential_transient(task)
|
|
71
73
|
@sock.instance_exec(&@recv_begin_proc) if @recv_begin_proc
|
|
72
74
|
sequential_message_loop
|
|
@@ -77,23 +79,15 @@ module OMQ
|
|
|
77
79
|
end
|
|
78
80
|
|
|
79
81
|
|
|
80
|
-
def
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def build_pull_push(pull_opts, push_opts, in_eps, out_eps)
|
|
91
|
-
pull = OMQ::PULL.new(**pull_opts)
|
|
92
|
-
push = OMQ::PUSH.new(**push_opts)
|
|
93
|
-
apply_socket_options(pull)
|
|
94
|
-
apply_socket_options(push)
|
|
95
|
-
attach_endpoints(pull, in_eps)
|
|
96
|
-
attach_endpoints(push, out_eps)
|
|
82
|
+
def build_pull_push(in_eps, out_eps)
|
|
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
|
|
89
|
+
SocketSetup.attach_endpoints(pull, in_eps, verbose: config.verbose >= 1)
|
|
90
|
+
SocketSetup.attach_endpoints(push, out_eps, verbose: config.verbose >= 1)
|
|
97
91
|
[pull, push]
|
|
98
92
|
end
|
|
99
93
|
|
|
@@ -117,8 +111,7 @@ module OMQ
|
|
|
117
111
|
parts = @fmt_in.decompress(parts)
|
|
118
112
|
parts = eval_recv_expr(parts)
|
|
119
113
|
if parts && !parts.empty?
|
|
120
|
-
|
|
121
|
-
@push.send(out)
|
|
114
|
+
@push.send(@fmt_out.compress(parts))
|
|
122
115
|
end
|
|
123
116
|
i += 1
|
|
124
117
|
break if n && n > 0 && i >= n
|
|
@@ -126,149 +119,47 @@ module OMQ
|
|
|
126
119
|
end
|
|
127
120
|
|
|
128
121
|
|
|
129
|
-
#
|
|
122
|
+
# -- Parallel -----------------------------------------------------
|
|
130
123
|
|
|
131
124
|
|
|
132
125
|
def run_parallel(task)
|
|
126
|
+
set_pipe_process_title
|
|
133
127
|
OMQ.freeze_for_ractors!
|
|
134
128
|
in_eps, out_eps = resolve_endpoints
|
|
135
|
-
in_eps = preresolve_tcp(in_eps)
|
|
136
|
-
out_eps = preresolve_tcp(out_eps)
|
|
137
|
-
|
|
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
|
|
132
|
+
workers = config.parallel.times.map do
|
|
133
|
+
::Ractor.new(config, in_eps, out_eps, log_port) do |cfg, ins, outs, lport|
|
|
134
|
+
PipeWorker.new(cfg, ins, outs, lport).call
|
|
135
|
+
end
|
|
136
|
+
end
|
|
138
137
|
workers.each do |w|
|
|
139
138
|
w.join
|
|
140
139
|
rescue ::Ractor::RemoteError => e
|
|
141
140
|
$stderr.write("omq: Ractor error: #{e.cause&.message || e.message}\n")
|
|
142
141
|
end
|
|
142
|
+
ensure
|
|
143
|
+
RactorHelpers.stop_consumer(log_port, log_thread) if log_port
|
|
143
144
|
end
|
|
144
145
|
|
|
145
146
|
|
|
146
|
-
|
|
147
|
-
config.parallel.times.map do
|
|
148
|
-
::Ractor.new(config, in_eps, out_eps) do |cfg, ins, outs|
|
|
149
|
-
Async do
|
|
150
|
-
pull = OMQ::PULL.new(linger: cfg.linger)
|
|
151
|
-
push = OMQ::PUSH.new(linger: cfg.linger)
|
|
152
|
-
pull.recv_timeout = cfg.timeout if cfg.timeout
|
|
153
|
-
push.send_timeout = cfg.timeout if cfg.timeout
|
|
154
|
-
pull.reconnect_interval = cfg.reconnect_ivl if cfg.reconnect_ivl
|
|
155
|
-
push.reconnect_interval = cfg.reconnect_ivl if cfg.reconnect_ivl
|
|
156
|
-
pull.heartbeat_interval = cfg.heartbeat_ivl if cfg.heartbeat_ivl
|
|
157
|
-
push.heartbeat_interval = cfg.heartbeat_ivl if cfg.heartbeat_ivl
|
|
158
|
-
pull.send_hwm = cfg.send_hwm if cfg.send_hwm
|
|
159
|
-
pull.recv_hwm = cfg.recv_hwm if cfg.recv_hwm
|
|
160
|
-
push.send_hwm = cfg.send_hwm if cfg.send_hwm
|
|
161
|
-
push.recv_hwm = cfg.recv_hwm if cfg.recv_hwm
|
|
162
|
-
pull.sndbuf = cfg.sndbuf if cfg.sndbuf
|
|
163
|
-
pull.rcvbuf = cfg.rcvbuf if cfg.rcvbuf
|
|
164
|
-
push.sndbuf = cfg.sndbuf if cfg.sndbuf
|
|
165
|
-
push.rcvbuf = cfg.rcvbuf if cfg.rcvbuf
|
|
166
|
-
|
|
167
|
-
OMQ::CLI::SocketSetup.attach_endpoints(pull, ins, verbose: cfg.verbose >= 1)
|
|
168
|
-
OMQ::CLI::SocketSetup.attach_endpoints(push, outs, verbose: cfg.verbose >= 1)
|
|
169
|
-
|
|
170
|
-
Barrier do |barrier|
|
|
171
|
-
barrier.async { pull.peer_connected.wait }
|
|
172
|
-
barrier.async { push.peer_connected.wait }
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
begin_proc, end_proc, eval_proc =
|
|
176
|
-
OMQ::CLI::ExpressionEvaluator.compile_inside_ractor(cfg.recv_expr)
|
|
177
|
-
|
|
178
|
-
fmt_in = OMQ::CLI::Formatter.new(cfg.format, compress: cfg.compress_in || cfg.compress)
|
|
179
|
-
fmt_out = OMQ::CLI::Formatter.new(cfg.format, compress: cfg.compress_out || cfg.compress)
|
|
180
|
-
|
|
181
|
-
_ctx = Object.new
|
|
182
|
-
_ctx.instance_exec(&begin_proc) if begin_proc
|
|
183
|
-
|
|
184
|
-
n_count = cfg.count
|
|
185
|
-
begin
|
|
186
|
-
if eval_proc
|
|
187
|
-
if n_count && n_count > 0
|
|
188
|
-
n_count.times do
|
|
189
|
-
parts = pull.receive
|
|
190
|
-
break if parts.nil?
|
|
191
|
-
parts = OMQ::CLI::ExpressionEvaluator.normalize_result(
|
|
192
|
-
_ctx.instance_exec(fmt_in.decompress(parts), &eval_proc)
|
|
193
|
-
)
|
|
194
|
-
next if parts.nil?
|
|
195
|
-
push << fmt_out.compress(parts) unless parts.empty?
|
|
196
|
-
end
|
|
197
|
-
else
|
|
198
|
-
loop do
|
|
199
|
-
parts = pull.receive
|
|
200
|
-
break if parts.nil?
|
|
201
|
-
parts = OMQ::CLI::ExpressionEvaluator.normalize_result(
|
|
202
|
-
_ctx.instance_exec(fmt_in.decompress(parts), &eval_proc)
|
|
203
|
-
)
|
|
204
|
-
next if parts.nil?
|
|
205
|
-
push << fmt_out.compress(parts) unless parts.empty?
|
|
206
|
-
end
|
|
207
|
-
end
|
|
208
|
-
else
|
|
209
|
-
if n_count && n_count > 0
|
|
210
|
-
n_count.times do
|
|
211
|
-
parts = pull.receive
|
|
212
|
-
break if parts.nil?
|
|
213
|
-
push << fmt_out.compress(fmt_in.decompress(parts))
|
|
214
|
-
end
|
|
215
|
-
else
|
|
216
|
-
loop do
|
|
217
|
-
parts = pull.receive
|
|
218
|
-
break if parts.nil?
|
|
219
|
-
push << fmt_out.compress(fmt_in.decompress(parts))
|
|
220
|
-
end
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
rescue IO::TimeoutError, Async::TimeoutError
|
|
224
|
-
# recv timed out — fall through to END block
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
if end_proc
|
|
228
|
-
out = OMQ::CLI::ExpressionEvaluator.normalize_result(
|
|
229
|
-
_ctx.instance_exec(&end_proc)
|
|
230
|
-
)
|
|
231
|
-
push << fmt_out.compress(out) if out && !out.empty?
|
|
232
|
-
end
|
|
233
|
-
ensure
|
|
234
|
-
pull&.close
|
|
235
|
-
push&.close
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
end
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
# ── Shared helpers ────────────────────────────────────────────────
|
|
147
|
+
# -- Process title -------------------------------------------------
|
|
243
148
|
|
|
244
149
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
ip = addr.ip_address
|
|
255
|
-
ip = "[#{ip}]" if ip.include?(":")
|
|
256
|
-
Endpoint.new("tcp://#{ip}:#{addr.ip_port}", ep.bind?)
|
|
257
|
-
end
|
|
258
|
-
else
|
|
259
|
-
ep
|
|
260
|
-
end
|
|
261
|
-
end
|
|
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(" "))
|
|
262
159
|
end
|
|
263
160
|
|
|
264
161
|
|
|
265
|
-
|
|
266
|
-
if seconds
|
|
267
|
-
Async::Task.current.with_timeout(seconds) { yield }
|
|
268
|
-
else
|
|
269
|
-
yield
|
|
270
|
-
end
|
|
271
|
-
end
|
|
162
|
+
# -- Expression eval ----------------------------------------------
|
|
272
163
|
|
|
273
164
|
|
|
274
165
|
def compile_expr
|
|
@@ -285,9 +176,7 @@ module OMQ
|
|
|
285
176
|
end
|
|
286
177
|
|
|
287
178
|
|
|
288
|
-
|
|
289
|
-
$stderr.write("#{msg}\n") if config.verbose >= 1
|
|
290
|
-
end
|
|
179
|
+
# -- Event monitoring ---------------------------------------------
|
|
291
180
|
|
|
292
181
|
|
|
293
182
|
def start_event_monitors
|
|
@@ -296,9 +185,9 @@ module OMQ
|
|
|
296
185
|
sock.monitor(verbose: verbose) do |event|
|
|
297
186
|
case event.type
|
|
298
187
|
when :message_sent
|
|
299
|
-
$stderr.write("omq: >> #{
|
|
188
|
+
$stderr.write("omq: >> #{Formatter.preview(event.detail[:parts])}\n")
|
|
300
189
|
when :message_received
|
|
301
|
-
$stderr.write("omq: << #{
|
|
190
|
+
$stderr.write("omq: << #{Formatter.preview(event.detail[:parts])}\n")
|
|
302
191
|
else
|
|
303
192
|
ep = event.endpoint ? " #{event.endpoint}" : ""
|
|
304
193
|
detail = event.detail ? " #{event.detail}" : ""
|
|
@@ -307,15 +196,6 @@ module OMQ
|
|
|
307
196
|
end
|
|
308
197
|
end
|
|
309
198
|
end
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
def msg_preview(parts)
|
|
313
|
-
parts.map { |p|
|
|
314
|
-
bytes = p.b
|
|
315
|
-
preview = bytes[0, 10].gsub(/[^[:print:]]/, ".")
|
|
316
|
-
bytes.bytesize > 10 ? "#{preview}... (#{bytes.bytesize}B)" : preview
|
|
317
|
-
}.join(" | ")
|
|
318
|
-
end
|
|
319
199
|
end
|
|
320
200
|
end
|
|
321
201
|
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module CLI
|
|
5
|
+
# Worker that runs inside a Ractor for pipe -P parallel mode.
|
|
6
|
+
# Each worker owns its own Async reactor, PULL socket, and PUSH socket.
|
|
7
|
+
#
|
|
8
|
+
class PipeWorker
|
|
9
|
+
def initialize(config, in_eps, out_eps, log_port)
|
|
10
|
+
@config = config
|
|
11
|
+
@in_eps = in_eps
|
|
12
|
+
@out_eps = out_eps
|
|
13
|
+
@log_port = log_port
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
Async do
|
|
19
|
+
setup_sockets
|
|
20
|
+
log_endpoints if @config.verbose >= 1
|
|
21
|
+
start_monitors if @config.verbose >= 2
|
|
22
|
+
wait_for_peers
|
|
23
|
+
compile_expr
|
|
24
|
+
run_message_loop
|
|
25
|
+
run_end_block
|
|
26
|
+
ensure
|
|
27
|
+
@pull&.close
|
|
28
|
+
@push&.close
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def setup_sockets
|
|
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
|
|
43
|
+
OMQ::CLI::SocketSetup.attach_endpoints(@pull, @in_eps, verbose: false)
|
|
44
|
+
OMQ::CLI::SocketSetup.attach_endpoints(@push, @out_eps, verbose: false)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def log_endpoints
|
|
49
|
+
@in_eps.each { |ep| @log_port.send(ep.bind? ? "Bound to #{ep.url}" : "Connecting to #{ep.url}") }
|
|
50
|
+
@out_eps.each { |ep| @log_port.send(ep.bind? ? "Bound to #{ep.url}" : "Connecting to #{ep.url}") }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def start_monitors
|
|
55
|
+
trace = @config.verbose >= 3
|
|
56
|
+
[@pull, @push].each do |sock|
|
|
57
|
+
sock.monitor(verbose: trace) do |event|
|
|
58
|
+
@log_port.send(format_event(event))
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def format_event(event)
|
|
65
|
+
case event.type
|
|
66
|
+
when :message_sent
|
|
67
|
+
"omq: >> #{OMQ::CLI::Formatter.preview(event.detail[:parts])}"
|
|
68
|
+
when :message_received
|
|
69
|
+
"omq: << #{OMQ::CLI::Formatter.preview(event.detail[:parts])}"
|
|
70
|
+
else
|
|
71
|
+
ep = event.endpoint ? " #{event.endpoint}" : ""
|
|
72
|
+
detail = event.detail ? " #{event.detail}" : ""
|
|
73
|
+
"omq: #{event.type}#{ep}#{detail}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def wait_for_peers
|
|
79
|
+
Barrier do |barrier|
|
|
80
|
+
barrier.async { @pull.peer_connected.wait }
|
|
81
|
+
barrier.async { @push.peer_connected.wait }
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def compile_expr
|
|
87
|
+
@begin_proc, @end_proc, @eval_proc =
|
|
88
|
+
OMQ::CLI::ExpressionEvaluator.compile_inside_ractor(@config.recv_expr)
|
|
89
|
+
@fmt_in = OMQ::CLI::Formatter.new(@config.format, compress: @config.compress_in || @config.compress)
|
|
90
|
+
@fmt_out = OMQ::CLI::Formatter.new(@config.format, compress: @config.compress_out || @config.compress)
|
|
91
|
+
@ctx = Object.new
|
|
92
|
+
@ctx.instance_exec(&@begin_proc) if @begin_proc
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def run_message_loop
|
|
97
|
+
n = @config.count
|
|
98
|
+
if @eval_proc
|
|
99
|
+
if n && n > 0
|
|
100
|
+
n.times do
|
|
101
|
+
parts = @pull.receive
|
|
102
|
+
break if parts.nil?
|
|
103
|
+
parts = OMQ::CLI::ExpressionEvaluator.normalize_result(
|
|
104
|
+
@ctx.instance_exec(@fmt_in.decompress(parts), &@eval_proc)
|
|
105
|
+
)
|
|
106
|
+
next if parts.nil?
|
|
107
|
+
@push << @fmt_out.compress(parts) unless parts.empty?
|
|
108
|
+
end
|
|
109
|
+
else
|
|
110
|
+
loop do
|
|
111
|
+
parts = @pull.receive
|
|
112
|
+
break if parts.nil?
|
|
113
|
+
parts = OMQ::CLI::ExpressionEvaluator.normalize_result(
|
|
114
|
+
@ctx.instance_exec(@fmt_in.decompress(parts), &@eval_proc)
|
|
115
|
+
)
|
|
116
|
+
next if parts.nil?
|
|
117
|
+
@push << @fmt_out.compress(parts) unless parts.empty?
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
else
|
|
121
|
+
if n && n > 0
|
|
122
|
+
n.times do
|
|
123
|
+
parts = @pull.receive
|
|
124
|
+
break if parts.nil?
|
|
125
|
+
@push << @fmt_out.compress(@fmt_in.decompress(parts))
|
|
126
|
+
end
|
|
127
|
+
else
|
|
128
|
+
loop do
|
|
129
|
+
parts = @pull.receive
|
|
130
|
+
break if parts.nil?
|
|
131
|
+
@push << @fmt_out.compress(@fmt_in.decompress(parts))
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
rescue IO::TimeoutError, Async::TimeoutError
|
|
136
|
+
# recv timed out -- fall through to END block
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def run_end_block
|
|
141
|
+
return unless @end_proc
|
|
142
|
+
out = OMQ::CLI::ExpressionEvaluator.normalize_result(
|
|
143
|
+
@ctx.instance_exec(&@end_proc)
|
|
144
|
+
)
|
|
145
|
+
@push << @fmt_out.compress(out) if out && !out.empty?
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
data/lib/omq/cli/push_pull.rb
CHANGED
|
@@ -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
|