Skip to content

TSRM: attach pthread_key/fls callbacks for thread cleanup - #24428

Open
henderkes wants to merge 4 commits into
php:masterfrom
henderkes:perf/1-tsrm-lifecycle
Open

henderkes wants to merge 4 commits into
php:masterfrom
henderkes:perf/1-tsrm-lifecycle

Conversation

@henderkes

Copy link
Copy Markdown
Contributor

... to automatically call ts_free_thread at thread exit

SAPI's should already been doing this on their own before, but the apache2handler doesn't get control over threads, so it didn't call ts_free_thread and therefore started leaking resources with the move of per-thread globals from heap storage to native __thread storage

@shivammathur's requests in #23227
Will need minimal adjusting in the eg/cg move later, but it can already be merged standalone.

cc @arnaud-lb

…free_thread` at thread exit

SAPI's should already been doing this on their own before, but the apache2handler doesn't get control over threads, so it didn't call ts_free_thread and therefore started leaking resources with the move of per-thread globals from heap storage to native __thread storage
Comment thread TSRM/TSRM.c Outdated
}
if (thread_resources && !tsrm_thread_exit_registered) {
tsrm_thread_exit_registered = true;
_tlv_atexit(tsrm_thread_exit_handler, NULL);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: Could this we moved next to the pthread_key_create() call? Purely for clarity / consistency

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No, https://github.com/apple-oss-distributions/dyld/blob/main/include/mach-o/dyld.h#L104 documents that _tlv_atexit is for the calling thread, while the pthread_key_create is once for the process. We don't want to register for the main thread, but for the other threads spawned later.

Comment thread TSRM/TSRM.c Outdated
Comment thread TSRM/TSRM.c Outdated
Comment thread TSRM/TSRM.c Outdated
Comment thread TSRM/TSRM.c Outdated
Comment thread TSRM/TSRM.c Outdated
free(data);
return;
}
if (FlsGetValue(tsrm_exit_key) != data) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I may we wrong, but I think that we can simplify this to if (!data) {: If we are called because a Fiber exited, we receive that Fiber's specific data in arg, so it should be NULL?

Comment thread TSRM/TSRM.c Outdated
return;
}
if (FlsGetValue(tsrm_exit_key) != data) {
free(data);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think that freeing data in this case is wrong

Comment thread Zend/zend.c
Comment on lines +863 to +868
do {
zend_try {
zend_destroy_rsrc_list(&EG(persistent_list));
completed = true;
} zend_end_try();
} while (!completed);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The catch/loop seem unrelated to the current PR?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hah I recall this one, had to add it because the persistent_list destructors can bailout.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you split this out of this PR? Is this necessary at all? Not a fan of catching bailouts.

@henderkes henderkes Oct 19, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Could apply it later, but not before this PR, because persistent_list cleanup just doesn't happen on threads calling ts_free_thread on master now. I think it belongs in the PR, because it's really part of its handling.

Comment thread TSRM/TSRM.c Outdated
#elif defined(__APPLE__)
tsrm_thread_exit_armed = pthread_key_create(&tsrm_exit_key, NULL) == 0;
#else
tsrm_thread_exit_armed = pthread_key_create(&tsrm_exit_key, tsrm_thread_exit_handler) == 0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should print something to stderr when these fail, and maybe abort()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

See comment below, it's deliberate (at least for now) because I'm not sure under what circumstances (if any) the setup could fail.

We currently don't have any such cleanup, so I prefer to keep the current behaviour if something goes wrong, rather than exiting the process. The stderr should be enough to draw attention to it.

Comment thread UPGRADING.INTERNALS
Comment on lines +95 to +97
- Per-thread TSRM resources are now released best-effort at normal thread exit,
because native TLS resource destructors can only run on their owning thread and
tsrm_shutdown() cannot release them afterwards. Prefer explicitly calling ts_free_thread().

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is that still true? In which case are resources not released?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's best-effort, pthread_key_create of FlsAlloc could fail and we don't want to fail php if a callback registration fails. Someone could also deliberately detach exit handlers before a thread exits... but yeah that's probably not worth mentioning.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants