/
Async.pod6
446 lines (319 loc) · 14 KB
/
Async.pod6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
=begin pod
=TITLE class Proc::Async
=SUBTITLE Running process (asynchronous interface)
=begin code
class Proc::Async {}
=end code
C<Proc::Async> allows you to run external commands asynchronously,
capturing standard output and error handles, and optionally write to its
standard input.
=begin code
my $file = ‘foo’.IO;
spurt $file, “and\nCamelia\n♡\nme\n”;
my $proc = Proc::Async.new: :w, ‘tac’, ‘--’, $file, ‘-’;
# my $proc = Proc::Async.new: :w, ‘sleep’, 15; # uncomment to try timeouts
react {
whenever $proc.stdout.lines { # split input on \r\n, \n, and \r
say ‘line: ’, $_
}
whenever $proc.stderr { # chunks
say ‘stderr: ’, $_
}
whenever $proc.ready {
say ‘PID: ’, $_ # Only in Rakudo 2018.04 and newer, otherwise Nil
}
whenever $proc.start {
say ‘Proc finished: exitcode=’, .exitcode, ‘ signal=’, .signal;
done # gracefully jump from the react block
}
whenever $proc.print: “I\n♥\nCamelia\n” {
$proc.close-stdin
}
whenever signal(SIGTERM).merge: signal(SIGINT) {
once {
say ‘Signal received, asking the process to stop’;
$proc.kill; # sends SIGHUP, change appropriately
whenever signal($_).zip: Promise.in(2).Supply {
say ‘Kill it!’;
$proc.kill: SIGKILL
}
}
}
whenever Promise.in(5) {
say ‘Timeout. Asking the process to stop’;
$proc.kill; # sends SIGHUP, change appropriately
whenever Promise.in(2) {
say ‘Timeout. Forcing the process to stop’;
$proc.kill: SIGKILL
}
}
}
say ‘Program finished’;
=end code
Example above produces the following output:
=begin code :lang<text>
line: me
line: ♡
line: Camelia
line: and
line: Camelia
line: ♥
line: I
Proc finished. Exit code: 0
Program finished
=end code
Alternatively, you can use C<Proc::Async> without using a
L<react|/language/concurrency#index-entry-react> block:
# command with arguments
my $proc = Proc::Async.new('echo', 'foo', 'bar');
# subscribe to new output from out and err handles:
$proc.stdout.tap(-> $v { print "Output: $v" }, quit => { say 'caught exception ' ~ .^name });
$proc.stderr.tap(-> $v { print "Error: $v" });
say "Starting...";
my $promise = $proc.start;
# wait for the external program to terminate
await $promise;
say "Done.";
This produces the following output:
=begin code :lang<text>
Starting...
Output: foo bar
Done.
=end code
An example that opens an external program for writing:
my $prog = Proc::Async.new(:w, 'hexdump', '-C');
my $promise = $prog.start;
await $prog.write(Buf.new(12, 42));
$prog.close-stdin;
await $promise;
An example of piping several commands like C<echo "Hello, world" | cat -n>:
my $proc-echo = Proc::Async.new: 'echo', 'Hello, world';
my $proc-cat = Proc::Async.new: 'cat', '-n';
$proc-cat.bind-stdin: $proc-echo.stdout;
await $proc-echo.start, $proc-cat.start;
=head1 Methods
=head2 method new
multi method new(*@ ($path, *@args), :$w, :$enc, :$translate-nl --> Proc::Async:D)
multi method new( :$path, :@args, :$w, :$enc, :$translate-nl --> Proc::Async:D)
Creates a new C<Proc::Async> object with external program name or path
C<$path> and the command line arguments C<@args>.
If C<:w> is passed to C<new>, then a pipe to the external program's standard
input stream (stdin) is opened, to which you can write with C<write> and
C<say>.
The C<:enc> specifies L<the encoding|/type/IO::Handle#method_encoding>
for streams (can still be overridden in individual methods) and defaults
to C<utf8>.
If C<:translate-nl> is set to C<True> (default value), OS-specific
newline terminators (e.g. C<\r\n> on Windows) will be automatically
translated to C<\n>.
=head2 method stdout
method stdout(Proc::Async:D: :$bin --> Supply:D)
Returns the L<Supply|/type/Supply> for the external program's standard output
stream. If C<:bin> is passed, the standard output is passed along in binary as
L<Blob|/type/Blob>, otherwise it is interpreted as UTF-8, decoded, and passed
along as L<Str|/type/Str>.
my $proc = Proc::Async.new(:r, 'echo', 'Perl 6');
$proc.stdout.tap( -> $str {
say "Got output '$str' from the external program";
});
my $promise = $proc.start;
await $promise;
You must call C<stdout> before you call L<#method start>. Otherwise an
exception of class
L<X::Proc::Async::TapBeforeSpawn|/type/X::Proc::Async::TapBeforeSpawn> is
thrown.
If C<stdout> is not called, the external program's standard output is not
captured at all.
Note that you cannot call C<stdout> both with and without C<:bin> on the same
object; it will throw an exception of type L<X::Proc::Async::CharsOrBytes> if
you try.
Use L«C<.Supply>|/type/Proc::Async#method_Supply» for merged STDOUT and STDERR.
=head2 method stderr
method stderr(Proc::Async:D: :$bin --> Supply:D)
Returns the L<Supply|/type/Supply> for the external program's standard error
stream. If C<:bin> is passed, the standard error is passed along in binary as
L<Blob|/type/Blob>, otherwise it is interpreted as UTF-8, decoded, and passed
along as L<Str|/type/Str>.
my $proc = Proc::Async.new(:r, 'echo', 'Perl 6');
$proc.stderr.tap( -> $str {
say "Got error '$str' from the external program";
});
my $promise = $proc.start;
await $promise;
You must call C<stderr> before you call L<#method start>. Otherwise an
exception of class
L<X::Proc::Async::TapBeforeSpawn|/type/X::Proc::Async::TapBeforeSpawn>
is thrown.
If C<stderr> is not called, the external program's standard error stream is not
captured at all.
Note that you cannot call C<stderr> both with and without C<:bin> on the same
object; it will throw an exception of type L<X::Proc::Async::CharsOrBytes> if
you try.
Use L«C<.Supply>|/type/Proc::Async#method_Supply» for merged STDOUT and STDERR.
=head2 method bind-stdin
multi method bind-stdin(IO::Handle:D $handle)
multi method bind-stdin(Proc::Async::Pipe:D $pipe)
Sets a handle (which must be opened) or a C<Pipe> as a source of
C<STDIN>. The C<STDIN> of the target process must be writable or
L<X::Proc::Async::BindOrUse> will be thrown.
my $p = Proc::Async.new("cat", :in);
my $h = "/etc/profile".IO.open;
$p.bind-stdin($h);
$p.start;
This is equivalent to
=for code :lang<shell>
cat < /etc/profile
and will print the content of C</etc/profile> to standard output.
=head2 method bind-stdout
method bind-stdout(IO::Handle:D $handle)
Redirects STDOUT of the target process to a handle (which must be
opened). If STDOUT is closed
L<X::Proc::Async::BindOrUse> will be
thrown.
my $p = Proc::Async.new("ls", :out);
my $h = "ls.out".IO.open(:w);
$p.bind-stdout($h);
$p.start;
This program will pipe the output of the C<ls> shell command to a file
called C<ls.out>, which we are opened for reading.
=head2 method bind-stderr
method bind-stderr(IO::Handle:D $handle)
Redirects C<STDERR> of the target process to a handle (which must be opened).
If C<STDERR> is closed L<X::Proc::Async::BindOrUse> will be thrown.
my $p = Proc::Async.new("ls", "--foo", :err);
my $h = "ls.err".IO.open(:w);
$p.bind-stderr($h);
$p.start;
=head2 method w
method w(Proc::Async:D:)
Returns a true value if C<:w> was passed to the constructor, that is, if the
external program is started with its input stream made available to output to
the program through the C<.print>, C<.say> and C<.write> methods.
=head2 method start
method start(Proc::Async:D: :$scheduler = $*SCHEDULER, :$ENV, :$cwd = $*CWD)
Initiates spawning of the external program. Returns a L<Promise|/type/Promise>
that will be kept with a L<Proc|/type/Proc> object once the external program
exits or broken if the program cannot be started. Optionally, you can use a
scheduler instead of the default C<$*SCHEDULER>, or change the environment the
process is going to run in via the named argument C<:$ENV> or the directory via
the named argument C<:$cwd>.
If C<start> is called on a Proc::Async object on which it has already been
called before, an exception of L<type
X::Proc::Async::AlreadyStarted|/type/X::Proc::Async::AlreadyStarted> is
thrown.
Note: If you wish to C<await> the Promise and discard its result, using
=begin code
try await $p.start;
=end code
B<will throw> if the program exited with non-zero status, as the C<Proc>
returned as the result of the Promise throws when sunk and in this case it
will get sunk outside the C<try>. To avoid that, sink it yourself I<inside> the
C<try>:
=begin code
try sink await $p.start;
=end code
=head2 method started
method started(Proc::Async:D: --> Bool:D)
Returns C<False> before C<.start> has been called, and C<True> afterwards.
=head2 method ready
method ready(Proc::Async:D: --> Promise:D)
Returns a L<Promise|/type/Promise> that will be kept once the process
has successfully started. L<Promise|/type/Promise> will be broken if the program
fails to start.
B<Implementation-specific note:> Starting from Rakudo 2018.04, the
returned promise will hold the process id (PID).
=head2 method pid
method pid(Proc::Async:D: --> Promise:D)
Equivalent to L<ready|/routine/ready>.
Returns a L<Promise|/type/Promise> that will be kept once the process
has successfully started. L<Promise|/type/Promise> will be broken if the program
fails to start. Returned promise will hold the process id (PID).
B<Implementation-specific note:> Available starting from Rakudo 2018.04.
=head2 method path
method path(Proc::Async:D:)
B<Deprecated as of v6.d>. Use L<command|/routine/command> instead.
Returns the name and/or path of the external program that was passed to the
C<new> method as first argument.
=head2 method args
method args(Proc::Async:D: --> Positional:D)
B<Deprecated as of v6.d>. Use L<command|/routine/command> instead.
Returns the command line arguments for the external programs, as passed to the
C<new> method.
=head2 method command
method command(Proc::Async:D: --> List:D)
I<Available as of v6.d.>
Returns the command and arguments used for this C<Proc::Async> object:
my $p := Proc::Async.new: 'cat', 'some', 'files';
$p.command.say; # OUTPUT: «(cat some files)»
=head2 method write
method write(Proc::Async:D: Blob:D $b, :$scheduler = $*SCHEDULER --> Promise:D)
Write the binary data in C<$b> to the standard input stream of the external
program.
Returns a L<Promise|/type/Promise> that will be kept once the data has fully
landed in the input buffer of the external program.
The C<Proc::Async> object must be created for writing (with
C<Proc::Async.new(:w, $path, @args)>). Otherwise an
L<X::Proc::Async::OpenForWriting> exception will the thrown.
C<start> must have been called before calling method write, otherwise an
L<X::Proc::Async::MustBeStarted> exception is thrown.
=head2 method print
method print(Proc::Async:D: Str(Any) $str, :$scheduler = $*SCHEDULER)
Write the text data in C<$str> to the standard input stream of the external
program, encoding it as UTF-8.
Returns a L<Promise|/type/Promise> that will be kept once the data has fully
landed in the input buffer of the external program.
The C<Proc::Async> object must be created for writing (with
C<Proc::Async.new(:w, $path, @args)>). Otherwise an
L<X::Proc::Async::OpenForWriting> exception will the thrown.
C<start> must have been called before calling method print, otherwise an
L<X::Proc::Async::MustBeStarted> exception is thrown.
=head2 method say
method say(Proc::Async:D: $output, :$scheduler = $*SCHEDULER)
Calls method C<gist> on the C<$output>, adds a newline, encodes it as UTF-8,
and sends it to the standard input stream of the external
program, encoding it as UTF-8.
Returns a L<Promise|/type/Promise> that will be kept once the data has fully
landed in the input buffer of the external program.
The C<Proc::Async> object must be created for writing (with
C<Proc::Async.new(:w, $path, @args)>). Otherwise an
L<X::Proc::Async::OpenForWriting> exception will the thrown.
C<start> must have been called before calling method say, otherwise an
L<X::Proc::Async::MustBeStarted> exception is thrown.
=head2 method Supply
multi method Supply(Proc::Async:D: :$bin!)
multi method Supply(Proc::Async:D: :$enc, :$translate-nl)
Returns a L<Supply|/type/Supply> of merged L<stdout|/routine/stdout> and L<stderr|/routine/stderr> streams. If C<:$bin>
named argument is provided, the C<Supply> will be binary, producing L<Buf|/type/Buf>
objects, otherwise, it will be in character mode, producing L<Str|/type/Str> objects and
C<:$enc> named argument can specify L<encoding|/routine/encoding> to use.
The C<:$translate-nl> option specifies whether new line endings should be
translated for to match those used by the current operating system (e.g.
C<\r\n> on Windows).
=for code
react {
with Proc::Async.new: «"$*EXECUTABLE" -e 'say 42; note 100'» {
whenever .Supply { .print } # OUTPUT: «42100»
whenever .start {}
}
}
It is an error to create both binary and non-binary
L«C<.Supply>|/type/Proc::Async#method_Supply». It is also an error
to use both L«C<.Supply>|/type/Proc::Async#method_Supply» and either
L<stderr|/routine/stderr> or L<stdout|/routine/stdout> supplies.
=head2 method close-stdin
method close-stdin(Proc::Async:D: --> True)
Closes the standard input stream of the external program. Programs that read
from STDIN often only terminate when their input stream is closed. So if
waiting for the promise from L<#method start> hangs (for a program opened for
writing), it might be a forgotten C<close-stdin>.
The C<Proc::Async> object must be created for writing (with
C<Proc::Async.new(:w, $path, @args)>). Otherwise an
L<X::Proc::Async::OpenForWriting> exception will the thrown.
C<start> must have been called before calling method close-stdin,
otherwise an L<X::Proc::Async::MustBeStarted> exception is thrown.
=head2 method kill
method kill(Proc::Async:D: $signal = "HUP")
Sends a signal to the running program. The signal can be a signal name
("KILL" or "SIGKILL"), an integer (9) or an element of the C<Signal> enum
(Signal::SIGKILL).
=end pod