gui.jobs.SubprocessJob

gui.jobs.SubprocessJob(
    argv,
    cwd,
    log=None,
    env=None,
    interrupt_after=10.0,
    terminate_after=5.0,
)

A command in a child process, with a live log and a gentle cancel.

The child starts in its own session and process group with PYTHONUNBUFFERED=1, so its lines arrive as they are written. Two pump threads read its output into a queue, and the interface drains the queue from its own thread with poll. Every line is also appended to the log file.

When the child has ended, whatever is left in its process group is swept: SIGTERM, then SIGKILL after terminate_after seconds. A grandchild that holds the pipes open therefore cannot keep the job from ending, and JobExit always arrives.

cancel sends SIGINT to the whole process group, SIGTERM after interrupt_after seconds and SIGKILL after terminate_after more. SIGINT lets TemporaryDirectory and git worktree clean-ups in the child run; a plain terminate() would skip every finally block and leave temporary copies, pytest processes and stale worktrees behind.

Parameters

Name Type Description Default
argv Sequence[str] The command, without a shell. required
cwd Path | str The working directory of the child, usually the repository root. required
log Path | None A file that receives every line of both streams. Its directory is created at start. Defaults to None: no log file. None
env Mapping[str, str] | None Variables added to the environment of the child. None
interrupt_after float Seconds between SIGINT and SIGTERM. 10.0
terminate_after float Seconds between SIGTERM and SIGKILL. 5.0

Examples

import sys

from spotregen.gui.jobs import JobExit, SubprocessJob

job = SubprocessJob([sys.executable, "-c", "print('one'); print('two')"], cwd=".")
job.start()
print(job.wait())
print([e.text if not isinstance(e, JobExit) else e for e in job.poll()])
0
['one', 'two', JobExit(returncode=0, cancelled=False)]

Attributes

Name Description
cancelled Whether cancel was called.
returncode The exit code of the child, or None while it runs.
running Whether the child, or the delivery of its last lines, is not finished.
started Whether start has been called.
stdout Everything the child wrote to stdout so far, lines joined by line breaks.

Methods

Name Description
cancel Stop the child: SIGINT, then SIGTERM, then SIGKILL.
poll The events since the last call, in order. Never blocks.
start Start the child and the threads that read it.
wait Block until the child has ended; meant for scripts and tests.

cancel

gui.jobs.SubprocessJob.cancel()

Stop the child: SIGINT, then SIGTERM, then SIGKILL.

The call returns at once; the escalation runs in a thread.

Returns

Name Type Description
bool bool True if the cancel began, False if the job is not running or was cancelled already.

poll

gui.jobs.SubprocessJob.poll()

The events since the last call, in order. Never blocks.

Returns

Name Type Description
list[JobLine | JobExit] list[JobLine | JobExit]: The new lines, then a JobExit once the child has ended and all its lines were delivered.

start

gui.jobs.SubprocessJob.start()

Start the child and the threads that read it.

Raises

Name Type Description
RuntimeError If the job was started before.
OSError If the program cannot be started.

wait

gui.jobs.SubprocessJob.wait(timeout=30.0)

Block until the child has ended; meant for scripts and tests.

Parameters

Name Type Description Default
timeout float Seconds to wait. 30.0

Returns

Name Type Description
int int The exit code of the child.

Raises

Name Type Description
TimeoutError If the child does not end in time.