base#
Generic external-app subprocess supervisor (Qt-free, stdlib only).
Launches an arbitrary command, streams its stdout, fires on_ready when a
handshake token appears, and on_finished(exit_code) when the process exits.
terminate() does a graceful terminate then a kill fallback.
Whole-tree teardown. Stimulus apps spawn their own helper processes (PsychoPy launches a separate iohub process that binds a UDP port; Panda3D may fork workers). Killing only the launched parent orphans those grandchildren, which then linger and hold their sockets – so the next launch fails with “only one usage of each socket address”. To prevent that, the child is launched into a kill-on-close container and the whole tree is reaped:
Windows – a Job Object with
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. Every descendant joins the job;TerminateJobObject(or simply closing the last handle, e.g. when mesofield itself exits) kills the entire tree, even after the parent has already died on its own (a crash).POSIX – a new session/process group (
start_new_session); the group is signalled withkillpgso descendants die with the leader.
The tree is also reaped the instant the parent exits (see _read_stdout()),
so a stimulus that crashes on startup cannot leave a hung helper behind.
This is the reusable engine behind mesofield.devices.stimulus_base
.SubprocessStimulusDevice – it is deliberately framework-agnostic (no Qt, no
mesofield config) so it can babysit any external stimulus app (MousePortal,
PsychoPy, …). See mesofield/devices/mouseportal_device.py for a concrete
subclass-driven user.
- class mesofield.devices.subprocesses.base.SubprocessSupervisor[source]#
Bases:
objectLaunch and supervise an external subprocess with a stdout handshake.
Parameters#
- command:
Full argv to launch (e.g.
[python_exe, "-m", "mouseportal", ...]).- ready_token:
Substring printed by the child on stdout once it is ready. When seen,
on_readyfires andwait_ready()unblocks.- cwd:
Working directory for the child (so its relative asset paths resolve).
- env:
Optional environment mapping;
Noneinherits the parent’s.- on_ready / on_finished:
Callbacks fired (from the reader thread) on the readiness handshake and on process exit, respectively.
on_finishedreceives the exit code.- name:
Short label used in log lines and the reader thread name.
- __init__(command, *, ready_token, cwd=None, env=None, on_ready=None, on_finished=None, name='subprocess')[source]#
- start()[source]#
Launch the subprocess (in its own group/job) and stream its stdout.
- Return type:
None
- terminate(timeout=5.0)[source]#
Stop the subprocess and its whole tree.
Graceful
terminateon the parent first,killas a fallback, then an unconditional tree reap (Job Object / process group) so descendants the app spawned – e.g. PsychoPy’s iohub helper – never linger, even when the parent has already exited on its own.- Parameters:
timeout (float)
- Return type:
None