Skip to content

Commit f30916f

Browse files
sl0thentr0pyclaude
andcommitted
fix(tests): make celery beat killer wait for pidfile
py3.7 celery in CI hangs in test_celery_beat_cron_monitoring::test_explanation. kill_beat slept 1s then opened the pidfile; in the slower py3.7 container startup, the file didn't exist yet, the thread died silently, and beat ran forever (30-min job timeout). Poll for the pidfile up to 30s before starting the kill timer, and dump any future thread exception to stderr so the next failure surfaces a traceback instead of silently hanging. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 273c7c3 commit f30916f

1 file changed

Lines changed: 32 additions & 7 deletions

File tree

tests/integrations/celery/integration_tests/__init__.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import os
22
import signal
3+
import sys
34
import tempfile
45
import threading
56
import time
7+
import traceback
68

79
from celery.beat import Scheduler
810

@@ -24,15 +26,38 @@ def tick(self):
2426
return 1
2527

2628

27-
def kill_beat(beat_pid_file, delay_seconds=1):
29+
def kill_beat(beat_pid_file, delay_seconds=1, pidfile_timeout=30):
2830
"""
29-
Terminates Celery Beat after the given `delay_seconds`.
31+
Terminates Celery Beat after `delay_seconds` of beat-runtime.
32+
33+
Waits up to `pidfile_timeout` seconds for the pidfile to appear before
34+
starting the runtime timer. Without this, slow process startup (e.g. in
35+
a Python 3.7 docker container in CI) caused the killer to race the
36+
pidfile, die with FileNotFoundError, and leave beat running forever.
37+
38+
Any unexpected exception is dumped to stderr so a future hang surfaces
39+
a traceback in the CI log instead of silently leaking a dead thread.
3040
"""
31-
logger.info("Starting Celery Beat killer...")
32-
time.sleep(delay_seconds)
33-
pid = int(open(beat_pid_file, "r").read())
34-
logger.info("Terminating Celery Beat...")
35-
os.kill(pid, signal.SIGTERM)
41+
try:
42+
logger.info("Starting Celery Beat killer...")
43+
deadline = time.monotonic() + pidfile_timeout
44+
while not os.path.exists(beat_pid_file):
45+
if time.monotonic() > deadline:
46+
raise RuntimeError(
47+
"Celery Beat pidfile %s never appeared" % beat_pid_file
48+
)
49+
time.sleep(0.05)
50+
time.sleep(delay_seconds)
51+
pid = int(open(beat_pid_file, "r").read())
52+
logger.info("Terminating Celery Beat...")
53+
os.kill(pid, signal.SIGTERM)
54+
except BaseException:
55+
sys.stderr.write(
56+
"kill_beat thread crashed; beat will hang. pidfile=%s\n" % beat_pid_file
57+
)
58+
traceback.print_exc()
59+
sys.stderr.flush()
60+
raise
3661

3762

3863
def run_beat(celery_app, runtime_seconds=1, loglevel="warning", quiet=True):

0 commit comments

Comments
 (0)