Skip to content

feat: expose thread API for Go-based PHP extensions - #2305

Merged
dunglas merged 5 commits into
php:mainfrom
johanjanssens:feat/thread-api
Sep 17, 2026
Merged

dunglas merged 5 commits into
php:mainfrom
johanjanssens:feat/thread-api

Conversation

@johanjanssens

Copy link
Copy Markdown
Contributor

Add APIs that enable Go-based PHP extensions to execute on existing FrankenPHP threads and access per-request state:

  • Thread(index): retrieves a PHP thread by its index, returning its *http.Request which carries the request context
  • PHPThread.Pin(): pins a Go object to prevent GC collection
  • PHPThread.IsRequestDone(): checks if the request has been closed
  • frankenphp_thread_index(): C function returning the current thread index so C extension methods can call back into Go

This makes it possible to build PHP extensions where PHP calls into C, C resolves the current thread index, and Go handles the logic with full access to the request context.

@johanjanssens

Copy link
Copy Markdown
Contributor Author

@dunglas These are the key changes required for:

@dunglas dunglas left a comment

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.

LGTM, but could you add some tests please?

Comment thread frankenphp.go
}

// PHPThread exposes a PHP thread's request context.
type PHPThread struct {

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.

Maybe we could use this new type internally too, for code clarity?

@johanjanssens johanjanssens Mar 26, 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.

Agreed. Kept it minimal to avoid a large refactor.

We can definitely take this further. Need to define scope a bit. For example, if you push this deeper I would suggest to consider addressing worker context, at the moment a worker carries a dummy context at boot time.

There's no way to inject persistent context from outside (e.g. middleware-style state that lives across requests on a worker thread). Could look into making PHPThread carry context consistently for both regular and worker threads.

Happy to expand this PR, or follow up in a new one. A context refactor would touch quite a bit more code.

@johanjanssens

Copy link
Copy Markdown
Contributor Author

Tests added.

@dunglas

dunglas commented Mar 30, 2026

Copy link
Copy Markdown
Member

Can you rebase please?

@johanjanssens

Copy link
Copy Markdown
Contributor Author

@dunglas Done.

Comment thread frankenphp.go Resolved
@johanjanssens

Copy link
Copy Markdown
Contributor Author

One extra note. I put the tests in thread_api_tests.go, they could also go in the frankenphp_test.go wasn't sure where to best put them.

@dunglas
dunglas force-pushed the feat/thread-api branch from 9cfce15 to 8d3aa73 Compare July 1, 2026 13:29
Comment thread frankenphp.go Outdated

// Thread retrieves a PHP thread by its index.
// Returns nil and false if the system is not running or no thread exists at the given index.
func Thread(index int) (*PHPThread, bool) {

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.

Suggested change
func Thread(index int) (*PHPThread, bool) {
func Thread(index uint) (*PHPThread, bool) {

This would prevent errors when passing a negative number and match the internal C api. WDYT @johanjanssens?

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.

Something i missed indeed.

johanjanssens and others added 3 commits July 18, 2026 16:38
Add APIs that enable Go-based PHP extensions to execute on existing
FrankenPHP threads and access per-request state:

- Thread(index) — retrieves a PHP thread by its index, returning its
  *http.Request which carries the request context
- PHPThread.Pin() — pins a Go object to prevent GC collection
- PHPThread.IsRequestDone() — checks if the request has been closed
- frankenphp_thread_index() — C function returning the current thread
  index so C extension methods can call back into Go

This makes it possible to build PHP extensions where PHP calls into C,
C resolves the current thread index, and Go handles the logic with
full access to the request context.
Test Thread(), PHPThread.Pin(), and request handling. Fix nil pointer
dereference in Thread() when no request is active or index is out of
bounds.
@dunglas
dunglas merged commit 5efe762 into php:main Sep 17, 2026
13 checks passed
@dunglas

dunglas commented Sep 17, 2026

Copy link
Copy Markdown
Member

Thanks @johanjanssens!

dunglas added a commit that referenced this pull request Sep 17, 2026
PR #2305 was written against the pre-atomic isRunning flag and a
frankenPHPContext() method on phpThread; both moved before it landed.
dunglas pushed a commit that referenced this pull request Oct 21, 2026
commit
[52e1932](52e1932)
in #2305 removed the duplicate `frankenphp_thread_index` from both
`frankenphp.c` and `frankenphp.h`.

However, there wasn't actually a duplicate in `frankenphp.h`, so there's
no export for
[frankenstate](https://github.com/johanjanssens/frankenstate),
[frankenasync](https://github.com/johanjanssens/frankenasync) etc to use

Also, the `frankenphp_thread_index` that stayed in `frankenphp.c` has
`static inline`, whereas the PR originally did not. So, nothing outside
that file can call it.

This PR makes the changes that #2305 originally intended. Not sure if
that conflicts with anything though.

---------

Signed-off-by: nickchomey <88559987+nickchomey@users.noreply.github.com>
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.

3 participants