diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aaa30b..40d91ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.7.0] - ### Fixed +- **`Async\signal()` no longer dies in worker threads** (#109). Each call to + `php_request_startup()` in a worker thread ran `zend_signal_activate()`, + which unconditionally re-installed `zend_signal_handler_defer` via + `sigaction()` — clobbering the libuv handler the reactor had installed in + the main thread. The next process-directed signal then hit Zend's defer + path in a worker whose `SIGG(handlers)` was empty, fell through to + `SIG_DFL`, and killed the process. Fixed in `Zend/zend_signal.c`: + `zend_signal_activate()` and `zend_signal_deactivate()` now early-return + when `zend_async_reactor_is_enabled()` (reactor module registered at + MINIT) — the reactor owns the OS-level sigaction process-wide, and the + per-thread libuv signal callback already dispatches via TLS `SIGG(handlers)`. + `zend_sigaction` is unchanged so pcntl-only flows keep working. Tests + `tests/signal/008-009` cover both `ThreadPool` and `spawn_thread` variants. - **PDO MySQL `010-pdo_resource_cleanup` no longer false-fails under parallel test workers** (#114). The test counted leaks against `SHOW STATUS LIKE 'Threads_connected'` — a *server-global* counter that diff --git a/tests/signal/008-signal_thread_pool_multi.phpt b/tests/signal/008-signal_thread_pool_multi.phpt new file mode 100644 index 0000000..ae331bb --- /dev/null +++ b/tests/signal/008-signal_thread_pool_multi.phpt @@ -0,0 +1,59 @@ +--TEST-- +Async\signal() #109: multiple workers in ThreadPool each receive process-directed signal +--SKIPIF-- + +--FILE-- +submit(function () { + try { + $r = await(signal(Signal::SIGUSR1), timeout(2000)); + return "w1:" . $r->name; + } catch (\Throwable $e) { + return "w1:ex:" . $e->getMessage(); + } +}); + +$f2 = $pool->submit(function () { + try { + $r = await(signal(Signal::SIGUSR1), timeout(2000)); + return "w2:" . $r->name; + } catch (\Throwable $e) { + return "w2:ex:" . $e->getMessage(); + } +}); + +spawn(function () { + delay(300); + posix_kill(getmypid(), SIGUSR1); +}); + +$results = [await($f1), await($f2)]; +sort($results); +foreach ($results as $r) echo $r . "\n"; + +$pool->close(); +echo "end\n"; + +?> +--EXPECT-- +start +w1:SIGUSR1 +w2:SIGUSR1 +end diff --git a/tests/signal/009-signal_spawn_thread_multi.phpt b/tests/signal/009-signal_spawn_thread_multi.phpt new file mode 100644 index 0000000..862ed38 --- /dev/null +++ b/tests/signal/009-signal_spawn_thread_multi.phpt @@ -0,0 +1,56 @@ +--TEST-- +Async\signal() #109: multiple spawn_thread workers each receive process-directed signal +--SKIPIF-- + +--FILE-- +name; + } catch (\Throwable $e) { + return "t1:ex:" . $e->getMessage(); + } +}); + +$t2 = spawn_thread(function () { + try { + $r = await(signal(Signal::SIGUSR1), timeout(2000)); + return "t2:" . $r->name; + } catch (\Throwable $e) { + return "t2:ex:" . $e->getMessage(); + } +}); + +spawn(function () { + delay(300); + posix_kill(getmypid(), SIGUSR1); +}); + +$results = [await($t1), await($t2)]; +sort($results); +foreach ($results as $r) echo $r . "\n"; + +echo "end\n"; + +?> +--EXPECT-- +start +t1:SIGUSR1 +t2:SIGUSR1 +end diff --git a/tests/signal/010-signal_unregistered_kills_process.phpt b/tests/signal/010-signal_unregistered_kills_process.phpt new file mode 100644 index 0000000..773b24f --- /dev/null +++ b/tests/signal/010-signal_unregistered_kills_process.phpt @@ -0,0 +1,44 @@ +--TEST-- +Async\signal() #109: unregistered SIGINT/SIGTERM still kill the process at OS level +--SKIPIF-- + +--EXTENSIONS-- +pcntl +posix +--FILE-- + +--EXPECT-- +SIGINT: killed by SIGINT +SIGTERM: killed by SIGTERM +SIGUSR1: killed by SIGUSR1 +done diff --git a/tests/signal/011-signal_pcntl_still_works.phpt b/tests/signal/011-signal_pcntl_still_works.phpt new file mode 100644 index 0000000..6d05d85 --- /dev/null +++ b/tests/signal/011-signal_pcntl_still_works.phpt @@ -0,0 +1,31 @@ +--TEST-- +Async\signal() #109: pcntl_signal still intercepts when TrueAsync is loaded +--SKIPIF-- + +--EXTENSIONS-- +pcntl +posix +--FILE-- + +--EXPECT-- +got=10 +OK diff --git a/tests/signal/012-signal_thread_pool_different_signals.phpt b/tests/signal/012-signal_thread_pool_different_signals.phpt new file mode 100644 index 0000000..a967e24 --- /dev/null +++ b/tests/signal/012-signal_thread_pool_different_signals.phpt @@ -0,0 +1,64 @@ +--TEST-- +Async\signal() #109: ThreadPool workers can register different signals concurrently +--SKIPIF-- + +--FILE-- +submit(function () { + try { + $r = await(signal(Signal::SIGUSR1), timeout(2000)); + return "w1:" . $r->name; + } catch (\Throwable $e) { + return "w1:ex:" . $e->getMessage(); + } +}); + +$f2 = $pool->submit(function () { + try { + $r = await(signal(Signal::SIGUSR2), timeout(2000)); + return "w2:" . $r->name; + } catch (\Throwable $e) { + return "w2:ex:" . $e->getMessage(); + } +}); + +spawn(function () { + delay(300); + posix_kill(getmypid(), SIGUSR1); + posix_kill(getmypid(), SIGUSR2); +}); + +$results = [await($f1), await($f2)]; +sort($results); +foreach ($results as $r) echo $r . "\n"; + +$pool->close(); +echo "end\n"; + +?> +--EXPECT-- +start +w1:SIGUSR1 +w2:SIGUSR2 +end