fix(tests): use process groups for reliable signal handling in PyPy

- Use preexec_fn=os.setsid to create new process group
- Send signals to process group with os.killpg() instead of single process
- Add explicit timeout and graceful-timeout to gunicorn command
- Fixes test failures on PyPy 3.10 where signals weren't propagating properly
This commit is contained in:
Benoit Chesneau 2026-02-13 11:02:10 +01:00
parent cd77bcc941
commit 63df19bd5c

View File

@ -93,15 +93,19 @@ def gunicorn_server(app_module):
'--access-logfile', '-', '--access-logfile', '-',
'--error-logfile', '-', '--error-logfile', '-',
'--log-level', 'info', '--log-level', 'info',
'--timeout', '30',
'--graceful-timeout', '30',
app_name app_name
] ]
# Use setsid to create new process group for proper signal handling
proc = subprocess.Popen( proc = subprocess.Popen(
cmd, cmd,
cwd=app_dir, cwd=app_dir,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
env={**os.environ, 'PYTHONPATH': app_dir} env={**os.environ, 'PYTHONPATH': app_dir},
preexec_fn=os.setsid
) )
# Wait for server to start # Wait for server to start
@ -113,13 +117,19 @@ def gunicorn_server(app_module):
yield proc, port yield proc, port
# Cleanup # Cleanup - use process group kill for better cleanup
if proc.poll() is None: if proc.poll() is None:
proc.terminate() try:
os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
except (ProcessLookupError, OSError):
pass
try: try:
proc.wait(timeout=5) proc.wait(timeout=5)
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
proc.kill() try:
os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
except (ProcessLookupError, OSError):
pass
proc.wait() proc.wait()
@ -141,7 +151,10 @@ class TestSignalHandlingIntegration:
response = make_request('127.0.0.1', port) response = make_request('127.0.0.1', port)
assert b'Hello, World!' in response assert b'Hello, World!' in response
# Send SIGTERM # Send SIGTERM to the process group for reliable signal delivery
try:
os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
except (ProcessLookupError, OSError):
proc.send_signal(signal.SIGTERM) proc.send_signal(signal.SIGTERM)
# Wait for process to exit # Wait for process to exit
@ -160,7 +173,10 @@ class TestSignalHandlingIntegration:
response = make_request('127.0.0.1', port) response = make_request('127.0.0.1', port)
assert b'Hello, World!' in response assert b'Hello, World!' in response
# Send SIGINT # Send SIGINT to the process group for reliable signal delivery
try:
os.killpg(os.getpgid(proc.pid), signal.SIGINT)
except (ProcessLookupError, OSError):
proc.send_signal(signal.SIGINT) proc.send_signal(signal.SIGINT)
# Wait for process to exit # Wait for process to exit
@ -179,7 +195,7 @@ class TestSignalHandlingIntegration:
response = make_request('127.0.0.1', port) response = make_request('127.0.0.1', port)
assert b'Hello, World!' in response assert b'Hello, World!' in response
# Send SIGHUP # Send SIGHUP to the master process (not process group - only master handles reload)
proc.send_signal(signal.SIGHUP) proc.send_signal(signal.SIGHUP)
# Wait a moment for reload # Wait a moment for reload