Source code for aimmd.execute.processes
"""
aimmd.execute.processes
======================
Process-based task executor implementation.
This module defines `ProcessExecutor`, a concrete implementation of
:class:`aimmd.execute.base.TaskExecutor` backed by
`multiprocessing.Process` objects (spawn context).
The executor provides:
- Process construction via the global `ctx` spawn context
- Graceful termination (terminate)
- Forced termination (also terminate; Python processes do not have a
distinct "kill" API in the standard library)
- Resource cleanup via `join`
Notes
-----
- This executor relies on `multiprocessing.get_context('spawn')` from
`aimmd.execute.base` (imported as `ctx`).
- The `_closed` state is backend-specific and uses a private attribute
of `multiprocessing.Process`. This is practical but not part of the
public API; behavior may vary across Python versions.
"""
# external
import os
import signal
# aimmd imports
from .base import TaskExecutor, ctx
[docs]
class ProcessExecutor(TaskExecutor):
"""
Task executor using `multiprocessing.Process`.
Instances of this executor manage a list of processes created with a
spawn start method (safer across platforms and with CUDA/GPU contexts).
Attributes
----------
__task_name__ : str
Human-readable label for the backend task type.
"""
__task_name__ = 'Process'
def _initialize(self, target):
"""
Create a new process for the given target.
Parameters
----------
target : callable
Callable to run in the child process. In practice, this will be a
wrapper produced by `TaskExecutor._build`, which calls
`aimmd.execute.utils.target_wrapper`.
Returns
-------
multiprocessing.Process
A not-yet-started process object.
"""
return ctx.Process(target=target)
def _terminate(self, localid):
"""
Request graceful termination of a process.
Parameters
----------
localid : int
Index of the task to terminate.
Notes
-----
`multiprocessing.Process.terminate()` sends a termination signal
(on POSIX typically SIGTERM) or uses platform-specific mechanisms.
"""
task = self._tasks[localid]
if task and task.is_alive():
task.terminate()
def _kill(self, localid):
"""
Forcefully kill a process.
Parameters
----------
localid : int
Index of the task to kill.
Notes
-----
The standard library provides `.terminate()` but not a separate
"kill" method (unlike subprocess). Here we alias "kill" to
terminate to keep a unified interface across backends.
"""
task = self._tasks[localid]
if task and task.is_alive():
task.terminate()
def _close(self, localid):
"""
Join a process briefly to allow cleanup.
Parameters
----------
localid : int
Index of the task to close.
Notes
-----
A small timeout is used to avoid blocking forever in pathological cases.
"""
task = self._tasks[localid]
if task:
task.join(timeout=0.1) # small timeout to avoid blocking forever
def _closed(self, localid):
"""
Determine whether the underlying process is marked as closed.
Parameters
----------
localid : int
Index of the task to query.
Returns
-------
bool
True if the task exists and the backend considers it closed.
Notes
-----
This uses the private attribute `_closed` of multiprocessing.Process.
It is effective in practice but not guaranteed by the public API.
"""
if self._tasks[localid] is None:
return False
return self._tasks[localid]._closed