Skip to content

fix(thread): keep main-thread signal handlers when other threads exit - #6410

Open
binaryfire wants to merge 2 commits into
swoole:6.1from
binaryfire:fix/thread-signal-callback-cleanup
Open

binaryfire wants to merge 2 commits into
swoole:6.1from
binaryfire:fix/thread-signal-callback-cleanup

Conversation

@binaryfire

Copy link
Copy Markdown
Member

In builds with --enable-swoole-thread, if the main thread has a Process::signal() handler, the process crashes as soon as any other thread ends:

use Swoole\Process;
use Swoole\Thread;

if (Thread::getArguments()) {
    return;
}
Process::signal(SIGTERM, function () {});
$thread = new Thread(__FILE__, 'child');
$thread->join();
echo "joined\n";

This aborts with zend_mm_heap corrupted before joined. A SWOOLE_THREAD server that sets a handler in onStart crashes the same way when its worker threads exit at shutdown.

Cause

Process::signal() only works on the main thread, but its callbacks are stored in the process-wide signal_fci_caches. Every thread's request shutdown calls php_swoole_process_signal_clean(), which frees all of them through that thread's executor and resets the shared listener count.

Process::start() called from a child thread has the same problem in the new process: the after-fork cleanup frees the inherited callbacks from the wrong heap, and the process aborts before running its callback.

Fix

  • Request shutdown skips the signal cleanup outside the main thread. The main thread still frees the callbacks at its own shutdown.
  • A process forked from a non-main thread drops the inherited callbacks instead of freeing them. swoole_fork() has already removed their signal handlers.

Non-ZTS builds are unchanged, since sw_is_main_thread() is always true there. php_swoole_runtime_rshutdown() already returns early outside the main thread in the same way.

Tests

tests/swoole_thread/signal_handler_thread_exit.phpt: the main thread registers a SIGTERM handler, a child thread starts a process that must exit cleanly, and after the join the main thread checks its handler still runs. It fails on 6.1, passes with this change, and fails if either change is removed. I also ran the swoole_thread, swoole_process_pool and swoole_process signal and fork tests on a PHP 8.4 ZTS thread build, with no new failures.

Process::signal() only runs on the main thread, but its callbacks live in
a process-wide array. Every thread's request shutdown freed all of them
through its own executor, so a main thread with a signal handler aborted
with "zend_mm_heap corrupted" as soon as a child thread or SWOOLE_THREAD
worker ended.

A process forked from a child thread hit the same problem: the after-fork
cleanup freed the inherited callbacks from the wrong heap, and the child
aborted before running its callback.

Skip the cleanup in non-main threads, and have a process forked from a
non-main thread drop the inherited callbacks instead of freeing them.
Builds without thread support are unchanged.
The main thread registers a SIGTERM handler, a child thread starts a
process that must exit cleanly and then returns, and the main thread
checks the handler still runs.
@codecov

codecov Bot commented Nov 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.80%. Comparing base (31c8fc6) to head (99bf94c).

Additional details and impacted files
@@            Coverage Diff             @@
##              6.1    #6410      +/-   ##
==========================================
- Coverage   85.81%   85.80%   -0.02%     
==========================================
  Files         110      110              
  Lines       17113    17113              
  Branches     3062     3063       +1     
==========================================
- Hits        14685    14683       -2     
- Misses       2428     2430       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant