Janet 1.38.0-73334f3 Documentation
(Other Versions:
1.37.1
1.36.0
1.35.0
1.34.0
1.31.0
1.29.1
1.28.0
1.27.0
1.26.0
1.25.1
1.24.0
1.23.0
1.22.0
1.21.0
1.20.0
1.19.0
1.18.1
1.17.1
1.16.1
1.15.0
1.13.1
1.12.2
1.11.1
1.10.1
1.9.1
1.8.1
1.7.0
1.6.0
1.5.1
1.5.0
1.4.0
1.3.1
)
Spawning a Process
Unlike os/execute
, which returns an exit code (or errors) after
the started subprocess completes, os/spawn
returns a
core/process
value. This value represents a subprocess which
may or may not have completed its execution. In contrast to
os/execute
, which "waits" for the subprocess, os/spawn
does not and it is recommended to explicitly arrange for this
"waiting" for resource management and other purposes ("waiting" will
be covered in more detail below).
The arguments for os/spawn
are the same as those of
os/execute
. Again, the required argument, args
, is a
tuple or array representing an invocation of a program with its
arguments.
# prints: :salutations
# the returned value, proc, is a core/process value
(let [proc (os/spawn ["janet" "-e" "(pp :salutations)"] :p)]
(type proc)) # => :core/process
core/process
and os/proc-wait
Passing the core/process
value to the os/proc-wait
function causes Janet to "wait" for the subprocess to complete
execution. This is sometimes referred to as "rejoining" the
subprocess.
The main reason for "waiting" is so that the operating system can release resources. Not "waiting" appropriately can lead to resources remaining unreclaimed and this can become a problem for a running system because it may run out of usable resources.
Once "waiting" has completed, the exit code for the subprocess can be
obtained via the :return-code
key. Accessing this key before
"waiting" will result in a nil
value.
# prints: :relax
(def proc (os/spawn ["janet" "-e" "(pp :relax)"] :p))
(get proc :return-code) # => nil
(os/proc-wait proc) # => 0
(get proc :return-code) # => 0
The os/proc-wait
function takes a single argument, proc
,
which should be a core/process
value. The return value is the
exit code of the subprocess. If os/proc-wait
is called more
than once for the same core/process
value it will error.
# prints: :sit-up
(def proc (os/spawn ["janet" "-e" "(pp :sit-up)"] :p))
(os/proc-wait proc) # => 0
# prints: cannot wait twice on a process
(try
(os/proc-wait proc)
([e]
(eprint e))) # => nil
Note that the first call to os/proc-wait
will cause the current
fiber to pause execution until the subprocess completes.
core/process
keys and the env
argument
As seen above, once "waiting" for a subprocess has completed, the exit
code of a subprocess becomes available via the :return-code
key. Other information about a subprocess can also be accessed via
the keys of an associated core/process
value such as
:in
, :out
, and :err
. On UNIX-like platforms,
there is an additional :pid
key.
The :in
, :out
, and :err
keys for a
core/process
value provide access to the standard input, output,
and error of the associated subprocess provided that corresponding
choices were made via the env
dictionary (i.e. table or struct)
argument to os/spawn
. This means that, for example, if there
is no key-value pair specified via :in
in the env
dictionary, then the standard input of the subprocess will not be
programmatically accessible via the associated core/process
's
:in
key.
The values associated with the :in
, :out
, and
:err
keys of the env
argument can be core/file
or
core/stream
values.
(def out-file (file/temp))
(type out-file) # => :core/file
(def proc
(os/spawn ["janet" "-e" `(print "again") (flush)`]
:p {:out out-file}))
# only standard output of the subprocess is accessible
(proc :in) # => nil
(type (proc :out)) # => :core/stream
(proc :err) # => nil
(os/proc-wait proc) # => 0
(file/seek out-file :set 0)
# prints: again
(print (file/read out-file :all))
(file/close out-file)
Note that in the example above, the standard input and error of the
subprocess were not accessible -- attempts to access them resulted in
nil
values -- because corresponding keys for the env
argument were not specified. In contrast, because a key-value pair
for :out
and out-file
were included in the env
struct, the standard output of the subprocess could be accessed
programmatically.
:pipe
and ev/gather
For each of the :in
, :out
, and :err
keys of the
env
argument to os/spawn
, the associated value can be
the keyword :pipe
. This causes core/stream
values to be
used that can be read from and written to for appropriate standard IO
of the subprocess.
In order to avoid deadlocks (aka hanging), it's important for data to
be transferred between processes in a timely manner. One example of
how this can fail is if output redirected to pipes is not read from
(sufficiently). In such a situation, pipe buffers might become full
and this can prevent a process from completing its writing. That in
turn can result in the writing process being unable to finish
executing. To keep data flowing appropriately, apply the
ev/gather
macro.
In the following example, ev/write
and os/proc-wait
are
both run in parallel via ev/gather
. The ev/gather
macro
runs a number of fibers in parallel (in this case, two) on the event
loop and returns the gathered results in an array.
(def proc
(os/spawn ["janet" "-e" "(print (file/read stdin :all))"]
:p {:in :pipe}))
(ev/gather
(do
# leads to printing via subprocess: know thyself
(ev/write (proc :in) "know thyself")
(ev/close (proc :in))
:done)
(os/proc-wait proc)) # => @[:done 0]
os/proc-close
The next example is a slightly modified version of the previous one.
It involves adding a third fiber (to ev/gather
) for reading
standard output from the subprocess via ev/read
.
(def proc
(os/spawn ["janet" "-e" "(print (file/read stdin :all))"]
:p {:in :pipe :out :pipe}))
(def buf @"")
(ev/gather
(do
(ev/write (proc :in) "know thyself")
(ev/close (proc :in))
:write-done)
# hi! i'm new here :)
(do
(ev/read (proc :out) :all buf)
:read-done)
(os/proc-wait proc)) # => @[:write-done :read-done 0]
(os/proc-close proc) # => nil
buf # => @"know thyself\n"
Beware that if pipe streams are not closed soon enough, the process
that created them (e.g. the janet
executable) may run out of
file descriptors or handles due to process limits enforced by an
operating system.
Note the use of the function os/proc-close
in the code above.
This function takes a core/process
value and closes all of the
unclosed pipes that were created for it via os/spawn
. In the
example above, (proc :in)
had already been closed explicitly,
but (proc :out)
had not and is thus closed by
os/proc-close
.
The os/proc-close
function will also attempt to wait on the
subprocess associated with the passed core/process
value if it
has not been waited on already. The function's return value is
nil
if waiting was not attempted and otherwise it is the exit
code of the subprocess corresponding to the core/process
value.
Effects of :x
If the flag
keyword argument contains x
for an
invocation of os/spawn
and the exit code of the subprocess is
non-zero, calling a function such as os/proc-wait
,
os/proc-close
, and os/proc-kill
(if invoked to wait)
with the subprocess' core/process
value results in an error.
(def proc
(os/spawn ["janet" "-e" "(os/exit 1)"] :px))
(defn invoke-an-error-raiser
[proc]
(def zero-one-or-two
(-> (os/cryptorand 8)
math/rng
(math/rng-int 3)))
# any of the following should raise an error
(case zero-one-or-two
0 (os/proc-wait proc)
1 (os/proc-close proc)
2 (os/proc-kill proc true)))
# prints: error
(try
(invoke-an-error-raiser proc)
([_]
(eprint "error")))