Fix task supervisor hang by resetting SIGCHLD in CeleryExecutor#70163
Fix task supervisor hang by resetting SIGCHLD in CeleryExecutor#70163hooiv wants to merge 2 commits into
Conversation
|
Congratulations on your first Pull Request and welcome to the Apache Airflow community! If you have any issues or are unsure about any anything please check our Contributors' Guide
|
4f21720 to
ef1c8a5
Compare
|
Updated the patch and description to match the confirmed failure path in #70117. psutil.Process.wait(timeout=0) can return None after the child wait status has already been consumed; _exit_code then stayed unset, so the supervisor loop never entered socket cleanup. The regression now covers that exact None result and maps it to the existing unknown exit code (-1). The earlier dumb-init/NoSuchProcess explanation was removed because it was not confirmed. Focused Linux test and Ruff checks pass. |
ef1c8a5 to
f922c72
Compare
|
I've pushed a completely new approach that fixes the actual root cause! In #70117, @Andrushika correctly pointed out that It turns out it was Celery's I've force-pushed a fix that simply resets The PR description has been updated . |
|
Thanks for figuring it out! Would it be possible to give a minimal reproducible example for how "Celery's billiard library" triggers this bug? Please also include the necessary tests in your fix. |
|
Thanks for the review! I have pushed a commit that adds a unit test for this fix in Regarding how Here is a minimal reproducible example demonstrating the issue: `python if name == "main": ` If you run this script, |
be7b0ea to
f6e8988
Compare
f6e8988 to
e29d4d0
Compare
What type of change is this?
Bug fix.
What does this PR do?
Fixes a task supervisor hang and memory leak in the
CeleryExecutorwhere supervisors stay alive indefinitely after their subprocess has exited.The Root Cause:
As discussed in #70117,
psutil.Process.wait(timeout=0)was returningNonebecause the child's wait status was already being consumed by something else before the supervisor could retrieve it.The culprit is Celery's
billiardlibrary. When theCeleryExecutorruns, it executes the task supervisor directly inside the Celery worker process.billiardinstalls a globalSIGCHLDsignal handler that performs an out-of-bandwaitpid(-1, WNOHANG)loop. When the Airflow task subprocess exits,billiard's signal handler wakes up and indiscriminately reaps it. This steals the exit status, causing the Task SDK'spsutil.wait()to returnNoneand bypass the socket cleanup logic.The Fix:
This PR explicitly resets
SIGCHLDtoSIG_DFLincelery_executor_utils.pyright before the supervisor process drops intosetproctitleand executes the task. By restoring the default signal disposition,billiardno longer steals the exit status, and the Airflow supervisor can properly reap its own children and gracefully exit.Closes #70117
Note: An AI coding assistant was used to help write pr description and trace the Celery/billiard signal handler interaction .