Skip to content

True async api stable #19142

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

Open
wants to merge 179 commits into
base: master
Choose a base branch
from
Open

Conversation

EdmondDantes
Copy link

TrueAsync engine API

The TrueAsync engine API defines a pluggable interface that lets extensions register different async backends while the core supplies standardized primitives.

Key Components

  • Events: Low‑level representation of sockets, timers and other readiness sources
  • Coroutines: Stackful tasks
  • Scopes: Hierarchical lifetime management enabling grouped cancellation
  • Wakers: Event‑completion handlers that resume suspended coroutines

PR for https://wiki.php.net/rfc/true_async_engine_api

EdmondDantes and others added 30 commits May 24, 2025 13:49
Initial version of the asynchronous API for PHP. Includes only the API itself and the necessary core changes required for the API to function.
…ized into a method for retrieving any ClassEntry.

  Now, using this API function, you can obtain the required class entry by a type descriptor type.
Added API functions for coroutine context management:
- zend_async_context_set_t: set value by string or object key
- zend_async_context_get_t: get value by string or object key
- zend_async_context_has_t: check if key exists
- zend_async_context_delete_t: delete key-value pair

Includes convenience macros for easier usage:
ZEND_ASYNC_CONTEXT_SET_STR/OBJ, GET_STR/OBJ, HAS_STR/OBJ, DELETE_STR/OBJ
Added complete Context API implementation:
- Updated zend_async_scheduler_register signature to accept context functions
- Added context function pointers and registration in zend_async_API.c
- Context API supports both string and object keys
- Includes convenience macros for easy usage

This completes the core API infrastructure for coroutine context management.
- Remove separate context_set, context_get, context_has, context_delete functions
- Add single zend_async_new_context_fn function to create context instances
- Move context implementation to ext/async module using zend_async_context_t structure
- Update scheduler registration to include new_context_fn parameter
- Add context field to zend_async_globals_t and zend_coroutine_s
- Simplify Context API macros to ZEND_ASYNC_NEW_CONTEXT and ZEND_ASYNC_CURRENT_CONTEXT
% removal of the current Scope from the global structure
… always needs to be explicitly known. Macros like ZEND_ASYNC_CURRENT_SCOPE were updated.

  A new macro ZEND_ASYNC_MAIN_SCOPE was added for the main Scope.
…main coroutine can correctly return control.
* add macro START_REACTOR_OR_RETURN for reactor autostart
… is now passed to the main coroutine’s finalize method instead of being displayed on screen.

* Fixed an issue with correctly passing the exception into the coroutine.
… captures the exception, marking it as handled in another coroutine.
Copy link
Member

@bukka bukka left a comment

Choose a reason for hiding this comment

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

This is just an initial review without really reviewing the logic - will need to schedule some bigger chunk of time for that later...

@iluuu1994
Copy link
Member

Sorry for not asking this question on the list, but what's the motivation for landing this in 8.5? This seems unnecessary if there isn't an intent to use it until 8.6/9.0.

@EdmondDantes
Copy link
Author

Sorry for not asking this question on the list, but what's the motivation for landing this in 8.5? This seems unnecessary if there isn't an intent to use it until 8.6/9.0.

Good day.

The main goal of these changes is to make PHP 8.5 asynchronous today (for as long as this version remains supported). The idea is not to wait for 8.6 or 9.0, but to make it asynchronous now. And this is possible because the implementation is split into two parts.

Users of PHP 8.5 will be able to use coroutines and non-blocking I/O through an additional extension.

Yes, this extension will remain experimental for some time. This means that, for a while, developers won’t be able to rely on it with 100% certainty. However, once the functions are officially finalized, PHP 8.5 will gain most of the official — and even new —functionality without changing its codebase.

In other words, the main value is the ability to support full-fledged asynchrony starting from this version, while continuing to develop additional functionality that doesn’t depend on PHP itself.


require "zend_constants.stub.php";

class CancellationException implements Throwable
Copy link
Member

Choose a reason for hiding this comment

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

Can this not just inherit from Exception ?

Copy link

Choose a reason for hiding this comment

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

IMO the naming is off. CancellationException should either be an instance of Exception or should become a Cancellation - new root Throwable next to Exception and Error.

This is especially dangerous if catching Exception will not catch the CancellationException.

Copy link
Author

@EdmondDantes EdmondDantes Jul 17, 2025

Choose a reason for hiding this comment

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

Hello.
The CancellationException is intended for situations where execution must be stopped unconditionally.
Such an exception may only be caught for the purpose of rethrowing it.

Therefore, code that attempts to catch Exception must never catch CancellationException.
Incorrect handling of CancellationException is a known issue, and this is a simple way to mitigate it.

The ideal solution would be to introduce a special additional syntax block, similar to finally, but triggered only for CancellationException.

Such a solution would be logically perfect and clean, but it would require far more changes.
Meanwhile, CancellationException is a typical solution to this problem and is widely used in other languages.

Copy link

Choose a reason for hiding this comment

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

I'm not having a problem with its function, just the naming is confusing. I agree that the type of error represented by the CancellationException should not be caught by a } catch (\Exception $e) block. The problem is that, because of the naming clash, reading the code will suggest that it will get caught, due to its name ending with *Exception. Developers will have to remember this special case and I'm wondering if there is a better way of naming it so that we can avoid introducing this memoization requirement.

Choose a reason for hiding this comment

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

I agree with what was exposed here. CancellationException indeed gives the impression that it should be treated as a regular Exception. If this is an exception that is not meant to be treated (if I understood correctly by Edmond's comment) it reminds me more of something closer to a panic() in Golang, for example, which basically shuts the entire application execution due to a very exceptional circunstance (even though you still can recover() from it).

Copy link

Choose a reason for hiding this comment

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

Would changing it to CancellationError make more sense? Or is *Error too much and we'd need something inbetween?

Copy link

@jhonatanjacinto jhonatanjacinto Jul 18, 2025

Choose a reason for hiding this comment

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

By author's description it would need to be something in between. What is confusing to me is that he says "The CancellationException is intended for situations where execution must be stopped unconditionally. Such an exception may only be caught for the purpose of rethrowing it" and what it translates to me is that it is something that should be, in some sense, "throwable" (like an Exception/Error) but not totally "treateable" (such a Fatal Error or a panic() in golang which is recoverable in some situations, but not all of them).

However, at the same time, the CancellationException class implements Throwable which, theoretically, allows a try...catch block to capture it with a catch(Throwable $e), right? Which makes it "treateable". So, in order to name it properly, I think we should understand what the author expects from this implementation:

  1. Should "CancellationException" be treatable/catchable in a try...catch block or not?
  2. If not, definitelly it must be something else cause anything that is throwable can be captured in a try...catch block. Fatal Error? A panic equivalent new category of error in PHP? Don't know... but definitely not a "Throwable" implementation.
  3. If it's not a big deal to capture and handle this exception and there's only some weird edge cases where it would be bad to handle it (or it should be handled in a very particular way) I think it should inherit from Exception and the proper behaviour be document to let the developers know what needs to be done when handling this particular type.

Another thing is that I'm truly curious why handle this CancellationException as a normal Exception would be a problem. From the outside, I cannot imagine a reason for not being a regular exception in the language.

Copy link
Author

Choose a reason for hiding this comment

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

We can use CancelledError, like in Python. However, I’m not aware of any research showing that one clear name makes code better than another equally clear name.

Although modern languages don’t have an explicit division between catchable and uncatchable exceptions, such a distinction exists implicitly by convention.

From a semantic standpoint, operation cancellation has all the characteristics of an exception: it interrupts code execution, carries a stack trace, and so on.

Copy link

Choose a reason for hiding this comment

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

The thing is that the name CancellationException suggests that it inherits from the root Exception class, which is not true. This is far from being clear, at least for me :)

Coming from Java, I tend to lean to their understanding of the division between Exceptions and Errors (both originating from Throwable).

Exception is generally a kind of error you can recover from. Error, on the other hand, is generally not recoverable. You can intercept both of them by catching a Throwable, but in case of an Error there is usually very little we can do, except logging it and (if it's even possible at this point) notifying external systems about it, and then rethrowing it. Usually at this point JVM is cooked.

Copy link
Author

Choose a reason for hiding this comment

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

I understand your logic.
But if we follow this path, then we might as well not implement this class here at all, and I can remove it from the PR.
Instead, we inherit CancellationError from the Error class.
What do you think?

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.

6 participants