Introducing Tcl 8.7 Part 4: process management
This is the fourth in a series of posts describing new features in the upcoming 8.7 release of Tcl. It introduces the new process
command added to allow monitoring and management of subprocesses spawned from Tcl.
To take Tcl 8.7 for a spin, you can download a pre-alpha binary for your platform. Alternatively, you can build it yourself from the
core-8-branch
branch in the Tcl fossil repository.
It is fairly common in Tcl scripts to invoke external programs for specialized tasks and Tcl provides several means for doing this including the built-in exec
and open
commands. This post assumes the reader is familiar with these commands. If not, read an overview here.
However, Tcl's support for monitoring status and errors in spawned subprocesses has been limited, particularly when run asynchronously. The ::tcl::process
command ensemble fills in this missing capability.
Checking status of subprocesses
Consider the following attempt for the Tcl shell to invoke a copy of itself to run a non-existing script.
% exec [info nameofexe] nosuchfile.tcl
couldn't read file "nosuchfile.tcl": no such file or directory
% set errorCode
CHILDSTATUS 12816 1
Because the invocation was synchronous, an error was raised with an error code so if desired, the user or invoking application could take appropriate action.
Many times however, it is desired for the subprocess to execute asynchronously, perhaps because it is a monitoring application that needs to be constantly running, or it is time consuming while the parent needs to do other work etc. Tcl's exec
command runs subprocesses asynchronously in the background when an ampersand is appended.
% exec [info nameofexe] nosuchfile.tcl &
24288
In this case, all the parent got back was the PID of the spawned process with no means to find out its status, whether it is still running or has exited and if the latter, the exit code. The parent had no means of taking remedial action, such as restarting the program, without the use of external platform-specific extensions. Tcl 8.7 remedies this with the introduction of the process status
command.
% tcl::process status 24288
24288 {1 {child process exited abnormally} {CHILDSTATUS 24288 1}}
Given a PID or a list of PIDs (as a single argument), the command returns a dictionary keyed by the subprocess ids. If the subprocess is still running, the associated value is an empty list. Otherwise it is a list of up to three elements. The first is the exit code. If the exit code is not 0, indicating an error exit, the second element is an error message and the third an error code list in the standard errorCode
format. If a PID is not a subprocess, it is not included in the returned dictionary.
Note that for asynchronous processes created with the open
pipeline syntax, it was possible to obtain exit status when closing the pipe. However, this could not be done in non-blocking fashion making it impossible to just check status and do other processing if the subprocess had not completed or to check multiple subprocesses simultaneously.
Synchronizing with subprocess exits
Normally, the tcl::process status
command returns immediately. However, the -wait
option can be specified to have it block until all subprocesses passed in the argument have exited, either normally or with error. This can be used to synchronize with one or more asynchronously started processes.
Cleaning up after yourself
When a process started by Tcl exits, certain resources are not immediately released until the waitpid
system call (on U*x) or the subprocess handle is closed (on Windows). In older versions, as well as Tcl 8.7 by default, this is done on the next call that starts a subprocess. Tcl offers more control over this through two commands, tcl::process purge
and tcl::process autopurge
.
The first of these cleans up the resources associated with the specified subprocesses. Once this is done the exit status of those subprocesses is no longer available. This has multiple benefits in that the resources are not left hanging around until the next exec
and moreover, provides control over which resources are released.
The other related command autopurge
provides control over Tcl's automatic purging on every exec
or open
pipeline invocation. If called with a boolean false value, this automatic purging, which is on by default, is turned off. What this allows is for several subprocess invocations to be done in sequence without losing the exit status of preceding subprocesses.
The following sequence illustrates the use.
% tcl::process autopurge 0
0
% set first [exec [info name] nosuchfile.exe &]
22920
% set second [exec [info name] nosuchfile.exe &]
21252
% tcl::process status
21252 {1 {child process exited abnormally} {CHILDSTATUS 21252 1}} 22920 {1 {child process exited abnormally} {CHILDSTATUS 22920 1}}
% tcl::process purge $first; # Status of $first is purge, $second still available
% tcl::process status
21252 {1 {child process exited abnormally} {CHILDSTATUS 21252 1}}
% tcl::process purge; # ALL subprocess status is discarded
% tcl::process status
%
Introspection
The last command in the process
ensemble is list
which is primarily useful in interactive introspection. This simply returns a list of subprocess PIDs.
% exec notepad.exe &
23096
% exec notepad.exe &
6364
% tcl::process list
6364 23096
Open pipelines
Our examples above all used exec
to run subprocesses. The commands work in identical fashion with subprocess created through open
.
References
TIP 462: Add New ::tcl::process Ensemble for Subprocess Management