Skip to content

Release a transit shell's C state through the new transfer kind - #232

Merged
EdmondDantes merged 1 commit into
mainfrom
feat/transfer-release-kind
Aug 14, 2026
Merged

Release a transit shell's C state through the new transfer kind#232
EdmondDantes merged 1 commit into
mainfrom
feat/transfer-release-kind

Conversation

@EdmondDantes

Copy link
Copy Markdown
Contributor

Needs true-async/php-src#20, which adds the kind this dispatches.

thread_release_transferred_object walked the shell's properties, dropped the class name and pefreed the allocation. Anything a transfer_obj handler had put in the shell's C prefix was never reached, because free_obj does not run for a shell. The TrueAsync server leaked a topic-hub reference for every room transferred into a pool task — 21,608 bytes of hub plus the persistent topic string, per hub.

The walk now resolves the shell's class by name and calls its handler with ZEND_OBJECT_TRANSFER_RELEASE before the generic cleanup. No autoload: a release runs outside a request, and a class that is not loaded cannot own anything here anyway.

The three handlers in this extension refuse the new kind explicitly. Each branches on TRANSFER and treats everything else as LOAD, so reaching that branch with a NULL default_fn would crash. Closure's snapshot is already freed by the walk itself, through the properties marker. The channel reference and the future's shared state move under protocols of their own — untangling those is a change of its own, not a side effect of this one.

Measurements

ext/async: 3 failed, all three pre-existing (__DIR__ resolves the symlink, so their include of a php-src helper four levels up misses). tests/phpt of true-async/server against a build with this change: 1 failed, pre-existing and unrelated (an h3 test whose --EXPECTF-- ends in %A).

The leak this exists for, measured on a room transferred into a ThreadPool task: definitely lost: 21,648 bytes in 2 blocks before, 0 bytes in 0 blocks after.

thread_release_transferred_object walked the shell's properties, dropped the
class name and pefreed the allocation. Anything a transfer_obj handler had put
in the shell's C prefix was never reached, because free_obj does not run for a
shell — the TrueAsync server leaked a topic-hub reference per transferred room
this way, 21,608 bytes of hub plus the persistent topic string.

The walk now resolves the shell's class by name and calls its handler with
ZEND_OBJECT_TRANSFER_RELEASE before the generic cleanup. No autoload: a release
runs outside a request, and a class that is not loaded owns nothing here.

The three handlers in this extension refuse the new kind explicitly, because
each branches on TRANSFER and treats everything else as LOAD — reaching that
branch with a NULL default_fn would crash. Closure's snapshot is already freed
by the walk itself; the channel reference and the future's shared state move
under protocols of their own, and reworking those is a separate change.
@EdmondDantes

Copy link
Copy Markdown
Contributor Author

CI is red on the build step for the stated dependency, not for a defect here: the runners build php-src from the true-async branch, where the kind does not exist yet.

ext/async/future.c:768:21: error: 'ZEND_OBJECT_TRANSFER_RELEASE' undeclared

It turns green once true-async/php-src#20 lands and this branch is re-run.

@codecov

codecov Bot commented Aug 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@EdmondDantes
EdmondDantes merged commit 11f6ef1 into main Aug 14, 2026
9 of 17 checks passed
@EdmondDantes
EdmondDantes deleted the feat/transfer-release-kind branch August 14, 2026 10:44
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