Description:
I am experiencing an issue where the parent process (running a Quart/Hypercorn web server) unexpectedly enters a shutdown sequence whenever a task in the ProcessPool reaches its timeout. This behavior started after upgrading to v5.2.0 and specifically occurs when using the fork multiprocessing context.
Environment:
- Pebble Version: 5.2.0
- Python Version: 3.8+
- OS: Linux
Multiprocessing Context: fork
Steps to Reproduce:
-
Create a ProcessPool with context=multiprocessing.get_context("fork").
-
Schedule a long-running task with a short timeout (e.g., 15s).
-
Use pebble.asynchronous.process or a manual bridge to asyncio.
-
Observe that when the TimeoutError is raised, the parent process receives a signal (likely SIGTERM) and triggers its own shutdown hooks (@app.after_serving in Quart).
Analysis:
Based on the logs and call stacks, when a timeout occurs, PoolManager.update_tasks calls self.worker_manager.maybe_stop_worker(task.worker_id), which eventually invokes stop_process(worker).
In a fork environment, the worker process belongs to the same Process Group as the parent. It appears that the signal sent to terminate the worker is either leaking to the parent process or being treated as a group-wide signal, causing the parent (web server) to initiate its graceful shutdown sequence.
Proposed Fix/Question:
Should pebble explicitly ensure that signals sent during maybe_stop_worker are scoped strictly to the child PID, or should it automatically call os.setpgrp() in forked workers to isolate them from the parent's signal group?
Description:
I am experiencing an issue where the parent process (running a Quart/Hypercorn web server) unexpectedly enters a shutdown sequence whenever a task in the ProcessPool reaches its timeout. This behavior started after upgrading to v5.2.0 and specifically occurs when using the fork multiprocessing context.
Environment:
Multiprocessing Context: fork
Steps to Reproduce:
Create a ProcessPool with context=multiprocessing.get_context("fork").
Schedule a long-running task with a short timeout (e.g., 15s).
Use pebble.asynchronous.process or a manual bridge to asyncio.
Observe that when the TimeoutError is raised, the parent process receives a signal (likely SIGTERM) and triggers its own shutdown hooks (@app.after_serving in Quart).
Analysis:
Based on the logs and call stacks, when a timeout occurs, PoolManager.update_tasks calls self.worker_manager.maybe_stop_worker(task.worker_id), which eventually invokes stop_process(worker).
In a fork environment, the worker process belongs to the same Process Group as the parent. It appears that the signal sent to terminate the worker is either leaking to the parent process or being treated as a group-wide signal, causing the parent (web server) to initiate its graceful shutdown sequence.
Proposed Fix/Question:
Should pebble explicitly ensure that signals sent during maybe_stop_worker are scoped strictly to the child PID, or should it automatically call os.setpgrp() in forked workers to isolate them from the parent's signal group?