Simpervisor: Simple Async Process Supervisor
Simpervisor is a lightweight Python library providing an asynchronous process supervisor. It offers the `SupervisedProcess` class to manage external processes with async methods like `start`, `ready`, `terminate`, and `kill`. The library is actively maintained, with its latest major release (1.0.0) in May 2023, indicating a stable but less frequent release cadence focused on robustness.
Warnings
- breaking The `loop` argument was removed from the `SupervisedProcess` constructor and its methods (`start`, `ready`, `terminate`, `kill`) in version 1.0.0. If you were explicitly passing an `asyncio` event loop, this code will break.
- gotcha Running `SupervisedProcess` on Windows has historically encountered issues, particularly related to process termination and signal handling. While some fixes have been introduced, full cross-platform compatibility for all subprocess scenarios (especially complex ones) may not be guaranteed.
- gotcha As `simpervisor` relies on `asyncio`, blocking the event loop within your async application (e.g., by calling synchronous I/O operations without `run_in_executor`) can prevent the supervisor from properly monitoring and managing child processes, leading to unresponsive behavior.
Install
-
pip install simpervisor
Imports
- SupervisedProcess
from simpervisor import SupervisedProcess
Quickstart
import asyncio
import sys
import os
from simpervisor import SupervisedProcess
# Create a dummy script to supervise
dummy_script_content = """
import asyncio
import sys
import time
async def child_process_task():
print(f"Child process {os.getpid()} started.")
try:
for i in range(5):
print(f"Child process {os.getpid()}: Working... {i+1}/5")
await asyncio.sleep(1)
print(f"Child process {os.getpid()}: Done working, exiting.")
except asyncio.CancelledError:
print(f"Child process {os.getpid()}: Cancelled, cleaning up.")
except Exception as e:
print(f"Child process {os.getpid()}: Error - {e}")
finally:
print(f"Child process {os.getpid()} exiting gracefully.")
if __name__ == '__main__':
asyncio.run(child_process_task())
"""
async def main():
script_path = "./dummy_child_script.py"
with open(script_path, "w") as f:
f.write(dummy_script_content)
print("Starting supervisor example...")
# Supervise the dummy script using python as the executable
# This assumes 'python' is in your PATH
process = SupervisedProcess(
[sys.executable, script_path], # Command to run the child process
always_restart=False # For this example, don't restart automatically
)
await process.start()
print(f"Supervisor: Process started with PID {process.pid}")
await asyncio.sleep(2) # Give child some time to work
if await process.ready():
print("Supervisor: Child process reports ready (or has started).")
else:
print("Supervisor: Child process not yet ready.")
await asyncio.sleep(3)
print("Supervisor: Attempting to terminate child process...")
await process.terminate()
# await process.wait() # Can wait for termination if needed
if not process.running:
print("Supervisor: Child process terminated.")
else:
print("Supervisor: Child process is still running after terminate attempt.")
os.remove(script_path)
print("Supervisor: Example finished.")
if __name__ == '__main__':
asyncio.run(main())