Author: Frédéric Bonnet <fredericbonnet@free.fr>
State: Voting
Type: Project
Vote: In progress
Created: 23-Jan-2017
Post-History:
Tcl-Version: 8.7
Abstract
This TIP proposes to improve Tcl's handling of subprocesses created by the
exec
and open
commands by adding a new ::tcl::process
ensemble.
Rationale
This TIP is inspired by a request from FlightAware to fix the way Tcl currently handles child process exit status.
Subprocess creation can be either synchronous or asynchronous. In either case, a children with a non-zero return value indicates an error condition that is bubbled up to the Tcl error handling mechanism.
Synchronous subprocesses
Synchronous subprocesses are created using the exec
command with no &
terminal argument. Errors are raised synchronously as well.
Asynchronous subprocesses
Asynchronous subprocesses can be created using two distinct methods:
exec
command with a&
terminal argument. In this case the command returns immediately with a list of the PIDs for all the subprocesses in the pipeline.open "| command"
. In this case the command returns immediately with the channel id of the pipe (hereafter$ch
). The subprocess IDs are given by the[pid $ch]
command. The subprocess status code and error conditions are processed upon channel closure with the[close $ch]
.
Error handling and status code
Errors are caught with the catch
and try
commands, with status
codes given in the -errorcode
options dictionary entry and the
errorCode
global variable in the form {CHILDKILLED pid sigName msg}
/ {CHILDSTATUS pid code}
/ {CHILDSUSP pid sigName msg}
.
C-level access
The Tcl library provides the following procedures for managing subprocesses (excerpts from the Tcl documentation):
Tcl_DetachPids
may be called to ask Tcl to take responsibility for one or more processes whose process ids are contained in the pidPtr array passed as argument. The caller presumably has started these processes running in background and does not want to have to deal with them again.Tcl_ReapDetachedProcs
invokes thewaitpid
kernel call on each of the background processes so that its state can be cleaned up if it has exited. If the process has not exited yet,Tcl_ReapDetachedProcs
does not wait for it to exit; it will check again the next time it is invoked. Tcl automatically callsTcl_ReapDetachedProcs
each time the exec command is executed, so in most cases it is not necessary for any code outside of Tcl to invokeTcl_ReapDetachedProcs
. However, if you callTcl_DetachPids
in situations where the exec command may never get executed, you may wish to callTcl_ReapDetachedProcs
from time to time so that background processes can be cleaned up.Tcl_WaitPid
is a thin wrapper around the facilities provided by the operating system to wait on the end of a spawned process and to check a whether spawned process is still running. It is used byTcl_ReapDetachedProcs
and the channel system to portably access the operating system.
Moreover, Tcl_WaitPid
is blocking unless called with the WNOHANG
option.
Limitations
The current implementation is lacking several key features:
There is no way to get subprocess status other than through the error handling mechanism.
Consequently, there is no way to collect the status code of a asychronous subprocess created with the
exec &
method because such commands don't raise errors once the subprocesses are launched.There is no non-blocking way to query asynchronous subprocess status codes;
catch
/try
uponopen "| command"
pipe closure is blocking.Moreover,
exec
andopen
callTcl_ReapDetachedProcs
, thereby cleaning up all pending information on terminated subprocesses. This prevents any advanced subprocess monitoring at the script level.While reasonable in the general case, a non-zero return value does not always indicates an error condition for all kinds of programs, so it is desirable to provide a subprocess-specific mechanism that does not rely on Tcl's standard error handling facility.
Specifications
A new ::tcl::process
will be created:
::tcl::process
subcommand ?arg ...
: Subprocess management.
The following subcommand values are supported by ::tcl::process
:
::tcl::process list
: Returns the list of subprocess PIDs.::tcl::process status ?switches? ?pids?
: Returns a dictionary mapping subprocess PIDs to their respective statuses. Ifpids
is specified as a list of PIDs then the command only returns the status of the matching subprocesses if they exist, and raises an error otherwise. For active processes, the status is an empty value. For terminated processes, the status is a list with the following format:{code ?msg errorCode?}
, where:code
is a standard Tcl return code,msg
is the human-readable error message,errorCode
uses the same format as the::errorCode
global variable.
Note that
msg
anderrorCode
are only present for abnormally terminated processes (i.e. those wherecode
is nonzero). Under the hood this command callsTcl_WaitPid
with theWNOHANG
flag set for non-blocking behavior, unless the-wait
switch is set (see below).
::tcl::process purge ?pids?
: Cleans up all data associated with terminated subprocesses. Ifpids
is specified as a list of PIDs then the command only cleanup data for the matching subprocesses if they exist, and raises an error otherwise. If the process is still active then it does nothing.::tcl::process autopurge ?flag?
: Automatic purge facility. Ifflag
is specified as a boolean value then it activates or deactivate autopurge. In all cases it returns the current status as a boolean value. When autopurge is active,Tcl_ReapDetachedProcs
is called each time theexec
command is executed or a pipe channel created byopen
is closed. When autopurge is inactive,::tcl::process purge
must be called explicitly. By default autopurge is active and replicates the current Tcl behavior.
Additionally, ::tcl::process status
accepts the following switches:
-wait
: By default the command returns immediately (the underlyingTcl_WaitPid
is called with theWNOHANG
flag set) unless this switch is set. Ifpids
is specified as a list of PIDs then the command waits until the matching subprocess statuses are available. Ifpids
is not specified then it waits for all known subprocesses.--
: Marks the end of switches. The argument following this one will be treated as the first arg even if it starts with a-
.
Examples
% ::tcl::process autopurge
true
% ::tcl::process autopurge false
false
% set pid1 [exec command1 a b c | command2 d e f &]
123 456
% set chan [open "|command1 a b c | command2 d e f"]
file123
% set pid2 [pid $chan]
789 1011
% ::tcl::process list
123 456 789 1011
% ::tcl::process status
123 0 456 {1 "child killed: write on pipe with no readers" {CHILDKILLED 456 SIGPIPE "write on pipe with no readers"}} 789 {1 "child suspended: background tty read" {CHILDSUSP 789 SIGTTIN "background tty read"}} 1011 {}
% ::tcl::process status 123
123 0
% ::tcl::process status 1011
1011 {}
% ::tcl::process status -wait
123 0 456 {1 "child killed: write on pipe with no readers" {CHILDKILLED 456 SIGPIPE "write on pipe with no readers"}} 789 {1 "child suspended: background tty read" {CHILDSUSP 789 SIGTTIN "background tty read"}} 1011 {1 "child process exited abnormally" {CHILDSTATUS 1011 -1}}
% ::tcl::process status 1011
1011 {1 "child process exited abnormally" {CHILDSTATUS 1011 -1}}
% ::tcl::process purge
% exec command1 1 2 3 &
1213
% ::tcl::process list
1213
Rejected Alternatives
The first version proposed to implement the feature as a new ps
option to
the existing info
command. However, almost all operations in [info]
are things that just examine state, not change it, and that's a
principle-of-least-astonishment that should be upheld for the sake of less
experienced users.
Reference implementation
The reference implementation is available on branch tip-462
in the Tcl Fossil repository.
Copyright
This document has been placed in the public domain.