Skip to content

Handle calling functions from global scope#355

Merged
realFlowControl merged 4 commits into
developfrom
florian/handle-undefined-functions
Dec 17, 2025
Merged

Handle calling functions from global scope#355
realFlowControl merged 4 commits into
developfrom
florian/handle-undefined-functions

Conversation

@realFlowControl

@realFlowControl realFlowControl commented Dec 12, 2025

Copy link
Copy Markdown
Collaborator

When calling a function from the global scope of the main thread in a task, parallel crashes depending on the PHP version and if you have OPcache enabled or not (see #317).

Copying the function_table from the parent thread seems possible, but it is complex, error-prone, and, due to the highly dynamic nature of PHP, nearly impossible to perform with 100% accuracy.

How does the crashing code look:

<?php
function foo() { return "OK"; }
$future = \parallel\run(function(){ 
    return foo(); 
}); 
var_dump($future->value()); 

parallel does not copy functions from the main threads function_table to the task's thread. You need to make those functions available using a bootstrap file:

<?php 
\parallel\bootstrap('bootstrap.php'); 
$future = \parallel\run(function(){ 
    return foo(); 
}); 
var_dump($future->value()); 

// bootstrap.php
<?php 
function foo() { return "OK"; }

This PR adds a SIGSEGV handler that catches this crash (and other segmentation faults) and raises an exception that get's thrown when the $future->value() function is called. Additionally this runtime get's shutdown, so for parallel managed runtimes through \parallel\run() you will not notice any difference (just in CPU cycles, as parallel has to create a new runtime if you schedule a new task), for runtimes created using new \parallel\Runtime() you will notice that after a crash you can't schedule a new task on that runtime anymore. When calling $runtime->run() it'll throw a \parallel\Runtime\Error\Closed() exception instead of scheduling the task to be run.

Fixes #317

@realFlowControl realFlowControl self-assigned this Dec 12, 2025
@realFlowControl
realFlowControl force-pushed the florian/handle-undefined-functions branch 3 times, most recently from b90f408 to fc53265 Compare December 12, 2025 16:33
@realFlowControl
realFlowControl force-pushed the florian/handle-undefined-functions branch from fc53265 to fad9905 Compare December 12, 2025 16:40
@realFlowControl realFlowControl changed the title Handle undefined functions Handle calling functions from global scope Dec 15, 2025
@realFlowControl
realFlowControl marked this pull request as ready for review December 15, 2025 13:27
@realFlowControl
realFlowControl force-pushed the florian/handle-undefined-functions branch from cc03255 to 72e191d Compare December 17, 2025 20:07
@realFlowControl
realFlowControl merged commit 573121d into develop Dec 17, 2025
59 of 78 checks passed
@realFlowControl
realFlowControl deleted the florian/handle-undefined-functions branch December 22, 2025 11:42
realFlowControl added a commit that referenced this pull request Jun 16, 2026
The Windows VEH is a process-wide first-chance handler and #355 used it as
a catch-all for every access violation on a parallel worker. Narrowing it
unmasked a latent shutdown access violation in functional/007 (bootstrap
set inside a task) that crashes the process. That is a separate, Windows-only
issue that needs a Windows repro to fix properly, so restore the VEH to its
original catch-all behavior.

The undefined-function narrowing (and the debugging benefit of not masking
real crashes under sanitizers/gdb/core dumps) remains on the POSIX SIGSEGV
handler, which is where it matters.
realFlowControl added a commit that referenced this pull request Jun 16, 2026
The SIGSEGV/VEH handler added in #355 caught *every* fault on a parallel
worker thread and converted it into a catchable IllegalInstruction error
via zend_bailout(). This masked genuine bugs (no backtrace, no core dump)
and longjmp-ing out of a signal handler is not async-signal-safe.

Restrict the conversion to the single case it was built for: a worker
faulting on a ZEND_INIT_FCALL to a function that is not in the worker's
function table (i.e. not provided via a bootstrap file). Any other fault
now falls through to the previous handler so it crashes properly with a
backtrace / core dump.

Detection is factored into php_parallel_scheduler_recover_missing_function()
shared by both the POSIX and Windows handlers.
realFlowControl added a commit that referenced this pull request Jun 16, 2026
The SIGSEGV/VEH handler added in #355 caught *every* fault on a parallel
worker thread and converted it into a catchable IllegalInstruction error
via zend_bailout(). This masked genuine bugs (no backtrace, no core dump)
and longjmp-ing out of a signal handler is not async-signal-safe.

Restrict the conversion to the single case it was built for: a worker
faulting on a ZEND_INIT_FCALL to a function that is not in the worker's
function table (i.e. not provided via a bootstrap file). Any other fault
now falls through to the previous handler so it crashes properly with a
backtrace / core dump.
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.

segfault when calling a function defined in global scope

1 participant