Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Introduce new zend_module_entry hook: child_startup_func #13551

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
arnaud-lb wants to merge 4 commits into php:master
base: master
Choose a base branch
Loading
from arnaud-lb:chinit

Conversation

Copy link
Member

@arnaud-lb arnaud-lb commented Feb 28, 2024
edited
Loading

Starting threads in module_startup_func (MINIT) is unsafe because SAPIs may fork after this hook. Yet, some modules may start threads as part of their initialisation and have no easy way to safely do so.

For example, the curl module calls curl_global_init() in request_startup_func to initialize libcurl, but this function starts threads at least on MacOS.

This change introduces a new hook: child_startup_func (CHINIT). This hook is executed once per request-handling process, which are assumed to be non-forking processes. Modules should use this hook for their thread-creating initialization.

This fixes a crash observed in #13468, and follows an idea from @bukka.

NB: Forking a process with threads may leave child processes in a corrupted state and lead to crashes or deadlocks. In the case of #13468, curl was indirectly using libdispatch during MINIT, which created threads, and the library defensively crashed child processes trying to use it again, after that. This problem is well described here and here.

NB2: opcache preloading may execute php code during module startup, in the main process. This requires calling the child startup hook, but this breaks the assumption that processes calling the child startup hook do not fork. It supports executing the preload script in a sub-process when preload_user is specified. In this case it's safe to call the child startup hook (in the sub-process). I've changed preloading so that the preload script is always executed in a sub-process. As a side-effect, FFI::load() can never be used during preloading, and ffi.preload should be used instead. Previously, FFI::load() could be used during preloading when fork wasn't needed.

NB3: We can not guarantee that pcntl_fork() will ever be safe

dunglas reacted with thumbs up emoji
Starting threads in `request_startup_func` is unsafe because SAPIs may fork
after this hook. Yet, some modules may start threads as part of their
initialisation and have no easy way to safely do so.
For example, the `curl` module calls `curl_global_init()` in
`request_startup_func` to initialize `libcurl`, but this function may start
threads at least on MacOS.
This change introduces a new hook: `child_startup_func`. This hook is executed
once per request-handling process, which are assumed to be non-forking
processes. Modules should use this hook for their thread-creating
initialization.
Copy link
Member

bwoebi commented Feb 28, 2024
edited
Loading

Would it make sense to instead have a zend_fork hook?
Which is used by fpm and pcntl_fork() both.

This would probably make pcntl_fork a tiny bit safer, as then extensions can just do some pre-fork cleanup as well, possibly synchronizing on some state to make it safer (like calling curl_global_cleanup too).

Copy link
Member Author

Unfortunately it's not possible to transition the process in a fork-safe state after using libdispatch, for instance. I suspect this will be the case for most libraries using threads.

Copy link
Member

@bukka bukka left a comment
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good in general. I was also thinking maybe to introduce just some registration hook during MINIT but this is probably cleaner.

You might also need to update lightspeed...

/* startup after we get the above ini override se we get things right */
if (sapi_module->startup(sapi_module) == FAILURE) {
if (sapi_module->startup(sapi_module) == FAILURE
|| sapi_module->child_startup(sapi_module) == FAILURE) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's not much point to force SAPI's to call child startup if they don't create any children.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to let SAPI code to call it during startup if no child_startup defined.

Copy link
Member Author

@arnaud-lb arnaud-lb Feb 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, but the separation of startup/child_startup happens to be useful even for these SAPIs in the context of opcache.preload

{
return php_module_child_startup(sapi_module);
}
/* }}} */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: we no longer add /* }}} */ and /* {{{ */ to the new functions

arnaud-lb reacted with hooray emoji
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Reviewers

@bukka bukka bukka left review comments

@dstogov dstogov Awaiting requested review from dstogov dstogov will be requested when the pull request is marked ready for review dstogov is a code owner

@adoy adoy Awaiting requested review from adoy adoy will be requested when the pull request is marked ready for review adoy is a code owner

Assignees
No one assigned
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

AltStyle によって変換されたページ (->オリジナル) /