Skip to content

Commit 4e76bdf

Browse files
authored
Merge pull request #63 from true-async/59-optimize-the-execution-path-for-true-async-api-error-cases
#59: TrueAsync API performance optimization: void→bool refactoring
2 parents 8b40d1b + 5cd0fc2 commit 4e76bdf

File tree

9 files changed

+269
-218
lines changed

9 files changed

+269
-218
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3535
- Optimized `network_async_accept_incoming` to try `accept()` before waiting
3636
- Enhanced stream_select functionality with event-driven architecture
3737
- Improved blocking operation handling with boolean return values
38+
- **TrueAsync API Performance**: Optimized execution paths by replacing expensive `EG(exception)` checks with direct `bool` return values across all async functions
3839
- Upgrade `LibUV` to version `1.45` due to a timer bug that causes the application to hang
3940

4041
## [0.3.0] - 2025-07-16

async.c

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,7 @@ static zend_object *async_timeout_create(zend_ulong ms, bool is_periodic);
5454

5555
#define SCHEDULER_LAUNCH \
5656
if (UNEXPECTED(ZEND_ASYNC_CURRENT_COROUTINE == NULL)) { \
57-
async_scheduler_launch(); \
58-
if (UNEXPECTED(EG(exception) != NULL)) { \
57+
if (!async_scheduler_launch()) { \
5958
RETURN_THROWS(); \
6059
} \
6160
}
@@ -79,7 +78,7 @@ PHP_FUNCTION(Async_spawn)
7978

8079
async_coroutine_t *coroutine = (async_coroutine_t *) ZEND_ASYNC_SPAWN();
8180

82-
if (UNEXPECTED(EG(exception))) {
81+
if (UNEXPECTED(coroutine == NULL)) {
8382
return;
8483
}
8584

@@ -275,9 +274,7 @@ PHP_FUNCTION(Async_await)
275274
}
276275
}
277276

278-
ZEND_ASYNC_SUSPEND();
279-
280-
if (UNEXPECTED(EG(exception) != NULL)) {
277+
if (!ZEND_ASYNC_SUSPEND()) {
281278
zend_async_waker_clean(coroutine);
282279
RETURN_THROWS();
283280
}

async_API.c

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -58,34 +58,34 @@ zend_coroutine_t *spawn(zend_async_scope_t *scope, zend_object *scope_provider,
5858
async_throw_error("Cannot spawn a coroutine when async is disabled");
5959
return NULL;
6060
} else if (UNEXPECTED(ZEND_ASYNC_IS_READY)) {
61-
async_scheduler_launch();
62-
if (UNEXPECTED(EG(exception) != NULL)) {
61+
if (UNEXPECTED(!async_scheduler_launch())) {
6362
return NULL;
6463
}
6564
}
6665

6766
if (scope == NULL && scope_provider != NULL) {
6867
scope = async_provide_scope(scope_provider);
69-
70-
if (UNEXPECTED(EG(exception) != NULL)) {
68+
if (UNEXPECTED(EG(exception))) {
7169
return NULL;
7270
}
7371
}
7472

7573
if (scope == NULL) {
7674

77-
if (UNEXPECTED(ZEND_ASYNC_CURRENT_SCOPE == NULL && ZEND_ASYNC_MAIN_SCOPE == NULL)) {
78-
ZEND_ASYNC_MAIN_SCOPE = ZEND_ASYNC_NEW_SCOPE(NULL);
75+
zend_async_scope_t **main_scope_ptr = &ZEND_ASYNC_MAIN_SCOPE;
7976

80-
if (UNEXPECTED(EG(exception))) {
77+
if (UNEXPECTED(ZEND_ASYNC_CURRENT_SCOPE == NULL && *main_scope_ptr == NULL)) {
78+
*main_scope_ptr = ZEND_ASYNC_NEW_SCOPE(NULL);
79+
80+
if (UNEXPECTED(*main_scope_ptr == NULL)) {
8181
return NULL;
8282
}
8383
}
8484

8585
if (EXPECTED(ZEND_ASYNC_CURRENT_SCOPE != NULL)) {
8686
scope = ZEND_ASYNC_CURRENT_SCOPE;
8787
} else {
88-
scope = ZEND_ASYNC_MAIN_SCOPE;
88+
scope = *main_scope_ptr;
8989
}
9090
}
9191

@@ -100,21 +100,20 @@ zend_coroutine_t *spawn(zend_async_scope_t *scope, zend_object *scope_provider,
100100
}
101101

102102
async_coroutine_t *coroutine = (async_coroutine_t *) async_new_coroutine(scope);
103-
if (UNEXPECTED(EG(exception))) {
103+
if (UNEXPECTED(coroutine == NULL)) {
104104
return NULL;
105105
}
106106

107107
zend_apply_current_filename_and_line(&coroutine->coroutine.filename, &coroutine->coroutine.lineno);
108108

109109
zval options;
110110
ZVAL_UNDEF(&options);
111-
scope->before_coroutine_enqueue(&coroutine->coroutine, scope, &options);
112-
zval_dtor(&options);
113-
114-
if (UNEXPECTED(EG(exception))) {
111+
if (!scope->before_coroutine_enqueue(&coroutine->coroutine, scope, &options)) {
112+
zval_dtor(&options);
115113
coroutine->coroutine.event.dispose(&coroutine->coroutine.event);
116114
return NULL;
117115
}
116+
zval_dtor(&options);
118117

119118
const bool is_spawn_strategy =
120119
scope_provider != NULL && instanceof_function(scope_provider->ce, async_ce_spawn_strategy);
@@ -141,7 +140,7 @@ zend_coroutine_t *spawn(zend_async_scope_t *scope, zend_object *scope_provider,
141140
}
142141

143142
zend_async_waker_t *waker = zend_async_waker_new(&coroutine->coroutine);
144-
if (UNEXPECTED(EG(exception))) {
143+
if (UNEXPECTED(waker == NULL)) {
145144
coroutine->coroutine.event.dispose(&coroutine->coroutine.event);
146145
return NULL;
147146
}
@@ -202,7 +201,7 @@ zend_coroutine_t *spawn(zend_async_scope_t *scope, zend_object *scope_provider,
202201
return &coroutine->coroutine;
203202
}
204203

205-
static void engine_shutdown(void)
204+
static bool engine_shutdown(void)
206205
{
207206
ZEND_ASYNC_REACTOR_SHUTDOWN();
208207

@@ -218,25 +217,27 @@ static void engine_shutdown(void)
218217
}
219218

220219
// async_host_name_list_dtor();
220+
return true;
221221
}
222222

223223
zend_array *get_coroutines(void)
224224
{
225225
return &ASYNC_G(coroutines);
226226
}
227227

228-
void add_microtask(zend_async_microtask_t *microtask)
228+
bool add_microtask(zend_async_microtask_t *microtask)
229229
{
230230
if (microtask->is_cancelled) {
231-
return;
231+
return true;
232232
}
233233

234234
if (UNEXPECTED(circular_buffer_push(&ASYNC_G(microtasks), &microtask, true) == FAILURE)) {
235235
async_throw_error("Failed to enqueue microtask");
236-
return;
236+
return false;
237237
}
238238

239239
microtask->ref_count++;
240+
return true;
240241
}
241242

242243
zend_array *get_awaiting_info(zend_coroutine_t *coroutine)
@@ -351,7 +352,9 @@ static void async_waiting_callback(zend_async_event_t *event,
351352
// We remove the callback because we treat all events
352353
// as FUTURE-type objects, where the trigger can be activated only once.
353354
ZEND_ASYNC_EVENT_CALLBACK_ADD_REF(callback);
354-
event->del_callback(event, callback);
355+
if (!event->del_callback(event, callback)) {
356+
// Optimized: ignore del_callback failure in callback context
357+
}
355358
ZEND_ASYNC_EVENT_CALLBACK_DEC_REF(callback);
356359

357360
if (exception != NULL) {
@@ -446,7 +449,9 @@ static void async_waiting_cancellation_callback(zend_async_event_t *event,
446449

447450
await_context->resolved_count++;
448451
ZEND_ASYNC_EVENT_CALLBACK_ADD_REF(callback);
449-
event->del_callback(event, callback);
452+
if (!event->del_callback(event, callback)) {
453+
// Optimized: ignore del_callback failure in callback context
454+
}
450455
ZEND_ASYNC_EVENT_CALLBACK_DEC_REF(callback);
451456

452457
if (exception != NULL) {
@@ -1009,15 +1014,15 @@ void async_await_futures(zval *iterable,
10091014
// which ensures that all child tasks are stopped if the main task is cancelled.
10101015
zend_async_scope_t *scope = ZEND_ASYNC_NEW_SCOPE(ZEND_ASYNC_CURRENT_SCOPE);
10111016

1012-
if (UNEXPECTED(scope == NULL || EG(exception))) {
1017+
if (UNEXPECTED(scope == NULL)) {
10131018
zend_iterator_dtor(zend_iterator);
10141019
await_context->dtor(await_context);
10151020
return;
10161021
}
10171022

10181023
zend_coroutine_t *iterator_coroutine = ZEND_ASYNC_SPAWN_WITH_SCOPE_EX(scope, ZEND_COROUTINE_NORMAL);
10191024

1020-
if (UNEXPECTED(iterator_coroutine == NULL || EG(exception))) {
1025+
if (UNEXPECTED(iterator_coroutine == NULL)) {
10211026
zend_iterator_dtor(zend_iterator);
10221027
await_context->dtor(await_context);
10231028
scope->try_to_dispose(scope);

coroutine.c

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,16 @@ static void coroutine_call_finally_handlers(async_coroutine_t *coroutine);
4545
static void finally_context_dtor(finally_handlers_context_t *context);
4646

4747
// Forward declarations for event system
48-
static void coroutine_event_start(zend_async_event_t *event);
49-
static void coroutine_event_stop(zend_async_event_t *event);
50-
static void coroutine_add_callback(zend_async_event_t *event, zend_async_event_callback_t *callback);
51-
static void coroutine_del_callback(zend_async_event_t *event, zend_async_event_callback_t *callback);
48+
static bool coroutine_event_start(zend_async_event_t *event);
49+
static bool coroutine_event_stop(zend_async_event_t *event);
50+
static bool coroutine_add_callback(zend_async_event_t *event, zend_async_event_callback_t *callback);
51+
static bool coroutine_del_callback(zend_async_event_t *event, zend_async_event_callback_t *callback);
5252
static bool coroutine_replay(zend_async_event_t *event,
5353
zend_async_event_callback_t *callback,
5454
zval *result,
5555
zend_object **exception);
5656
static zend_string *coroutine_info(zend_async_event_t *event);
57-
static void coroutine_dispose(zend_async_event_t *event);
57+
static bool coroutine_dispose(zend_async_event_t *event);
5858

5959
///////////////////////////////////////////////////////////
6060
/// 2. Object Lifecycle Management
@@ -645,29 +645,28 @@ void async_coroutine_finalize(async_coroutine_t *coroutine)
645645
*
646646
* @param from_main For main coroutine
647647
*/
648-
void async_coroutine_suspend(const bool from_main)
648+
bool async_coroutine_suspend(const bool from_main)
649649
{
650650
if (UNEXPECTED(from_main)) {
651651
// If the Scheduler was never used, it means no coroutines were created,
652652
// so execution can be finished without doing anything.
653653
if (circular_buffer_is_empty(&ASYNC_G(microtasks)) && zend_hash_num_elements(&ASYNC_G(coroutines)) == 0) {
654-
return;
654+
return true;
655655
}
656656

657-
async_scheduler_main_coroutine_suspend();
658-
return;
657+
return async_scheduler_main_coroutine_suspend();
659658
}
660659

661-
async_scheduler_coroutine_suspend();
660+
return async_scheduler_coroutine_suspend();
662661
}
663662

664-
void async_coroutine_resume(zend_coroutine_t *coroutine, zend_object *error, const bool transfer_error)
663+
bool async_coroutine_resume(zend_coroutine_t *coroutine, zend_object *error, const bool transfer_error)
665664
{
666665
zend_async_waker_t *waker = coroutine->waker;
667666

668667
if (UNEXPECTED(waker == NULL || waker->status == ZEND_ASYNC_WAKER_NO_STATUS)) {
669668
async_throw_error("Cannot resume a coroutine that has not been suspended");
670-
return;
669+
return false;
671670
}
672671

673672
if (error != NULL) {
@@ -695,7 +694,7 @@ void async_coroutine_resume(zend_coroutine_t *coroutine, zend_object *error, con
695694
}
696695

697696
if (UNEXPECTED(waker->status == ZEND_ASYNC_WAKER_QUEUED)) {
698-
return;
697+
return true;
699698
}
700699

701700
const bool in_scheduler_context = ZEND_ASYNC_SCHEDULER_CONTEXT;
@@ -707,12 +706,12 @@ void async_coroutine_resume(zend_coroutine_t *coroutine, zend_object *error, con
707706
// we will execute it immediately!
708707
if (UNEXPECTED(in_scheduler_context && coroutine == ZEND_ASYNC_CURRENT_COROUTINE)) {
709708
waker->status = ZEND_ASYNC_WAKER_RESULT;
710-
return;
709+
return true;
711710
}
712711

713712
if (UNEXPECTED(circular_buffer_push_ptr_with_resize(&ASYNC_G(coroutine_queue), coroutine)) == FAILURE) {
714713
async_throw_error("Failed to enqueue coroutine");
715-
return;
714+
return false;
716715
}
717716

718717
waker->status = ZEND_ASYNC_WAKER_QUEUED;
@@ -721,9 +720,11 @@ void async_coroutine_resume(zend_coroutine_t *coroutine, zend_object *error, con
721720
if (in_scheduler_context) {
722721
circular_buffer_push_ptr_with_resize(&ASYNC_G(resumed_coroutines), coroutine);
723722
}
723+
724+
return true;
724725
}
725726

726-
void async_coroutine_cancel(zend_coroutine_t *zend_coroutine,
727+
bool async_coroutine_cancel(zend_coroutine_t *zend_coroutine,
727728
zend_object *error,
728729
bool transfer_error,
729730
const bool is_safely)
@@ -734,7 +735,7 @@ void async_coroutine_cancel(zend_coroutine_t *zend_coroutine,
734735
OBJ_RELEASE(error);
735736
}
736737

737-
return;
738+
return true;
738739
}
739740

740741
// An attempt to cancel a coroutine that is currently running.
@@ -757,7 +758,7 @@ void async_coroutine_cancel(zend_coroutine_t *zend_coroutine,
757758
zend_coroutine->exception = async_new_exception(async_ce_cancellation_exception, "Coroutine cancelled");
758759
}
759760

760-
return;
761+
return true;
761762
}
762763

763764
zend_async_waker_t *waker = zend_async_waker_define(zend_coroutine);
@@ -768,7 +769,7 @@ void async_coroutine_cancel(zend_coroutine_t *zend_coroutine,
768769
error = async_new_exception(async_ce_cancellation_exception, "Coroutine cancelled");
769770
transfer_error = true;
770771
if (UNEXPECTED(EG(exception))) {
771-
return;
772+
return false;
772773
}
773774
}
774775

@@ -786,7 +787,7 @@ void async_coroutine_cancel(zend_coroutine_t *zend_coroutine,
786787
OBJ_RELEASE(error);
787788
}
788789

789-
return;
790+
return true;
790791
}
791792

792793
bool was_cancelled = ZEND_COROUTINE_IS_CANCELLED(zend_coroutine);
@@ -815,8 +816,7 @@ void async_coroutine_cancel(zend_coroutine_t *zend_coroutine,
815816
// In any other case, the cancellation exception overrides the existing exception.
816817
//
817818
ZEND_ASYNC_WAKER_APPLY_CANCELLATION(waker, error, transfer_error);
818-
async_scheduler_coroutine_enqueue(zend_coroutine);
819-
return;
819+
return async_scheduler_coroutine_enqueue(zend_coroutine);
820820
}
821821

822822
// In safely mode, we don't forcibly terminate the coroutine,
@@ -827,7 +827,8 @@ void async_coroutine_cancel(zend_coroutine_t *zend_coroutine,
827827
if (transfer_error && error != NULL) {
828828
OBJ_RELEASE(error);
829829
}
830-
return;
830+
831+
return true;
831832
}
832833

833834
if (was_cancelled && waker->error != NULL &&
@@ -839,29 +840,32 @@ void async_coroutine_cancel(zend_coroutine_t *zend_coroutine,
839840
ZEND_ASYNC_WAKER_APPLY_CANCELLATION(waker, error, transfer_error);
840841
}
841842

842-
async_scheduler_coroutine_enqueue(zend_coroutine);
843+
return async_scheduler_coroutine_enqueue(zend_coroutine);
843844
}
844845

845846
///////////////////////////////////////////////////////////
846847
/// 4. Event System Interface
847848
///////////////////////////////////////////////////////////
848849

849-
static void coroutine_event_start(zend_async_event_t *event)
850+
static bool coroutine_event_start(zend_async_event_t *event)
850851
{
852+
return true;
851853
}
852854

853-
static void coroutine_event_stop(zend_async_event_t *event)
855+
static bool coroutine_event_stop(zend_async_event_t *event)
854856
{
857+
// Empty implementation - coroutines don't need explicit stop
858+
return true;
855859
}
856860

857-
static void coroutine_add_callback(zend_async_event_t *event, zend_async_event_callback_t *callback)
861+
static bool coroutine_add_callback(zend_async_event_t *event, zend_async_event_callback_t *callback)
858862
{
859-
zend_async_callbacks_push(event, callback);
863+
return zend_async_callbacks_push(event, callback);
860864
}
861865

862-
static void coroutine_del_callback(zend_async_event_t *event, zend_async_event_callback_t *callback)
866+
static bool coroutine_del_callback(zend_async_event_t *event, zend_async_event_callback_t *callback)
863867
{
864-
zend_async_callbacks_remove(event, callback);
868+
return zend_async_callbacks_remove(event, callback);
865869
}
866870

867871
/**
@@ -930,10 +934,11 @@ static zend_string *coroutine_info(zend_async_event_t *event)
930934
}
931935
}
932936

933-
static void coroutine_dispose(zend_async_event_t *event)
937+
static bool coroutine_dispose(zend_async_event_t *event)
934938
{
935939
async_coroutine_t *coroutine = (async_coroutine_t *) event;
936940
OBJ_RELEASE(&coroutine->std);
941+
return true;
937942
}
938943

939944
///////////////////////////////////////////////////////////

0 commit comments

Comments
 (0)