Skip to content

Commit 5f19f81

Browse files
committed
* Fixed errors in handling exceptions using the zend_throw_exception_internal function when there is no active call frame. Replaced with a dedicated function that works correctly in various scenarios (async_rethrow_exception).
* Prevented Scheduler state corruption in case of a critical Zend API crash. * Adjusted the behavior of tests with double repetition.
1 parent 79fc093 commit 5f19f81

File tree

8 files changed

+65
-18
lines changed

8 files changed

+65
-18
lines changed

async.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ PHP_FUNCTION(Async_protect)
183183

184184
if (async_coroutine->deferred_cancellation) {
185185
ZEND_COROUTINE_SET_CANCELLED(coroutine);
186-
zend_throw_exception_internal(async_coroutine->deferred_cancellation);
186+
async_rethrow_exception(async_coroutine->deferred_cancellation);
187187
async_coroutine->deferred_cancellation = NULL;
188188
}
189189
}

coroutine.c

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ static zend_result finally_handlers_iterator_handler(async_iterator_t *iterator,
340340
context->composite_exception = NULL;
341341
}
342342
// Throw graceful/unwind exit and stop iteration
343-
zend_throw_exception_internal(current_exception);
343+
async_rethrow_exception(current_exception);
344344
return SUCCESS;
345345
}
346346

@@ -352,7 +352,7 @@ static zend_result finally_handlers_iterator_handler(async_iterator_t *iterator,
352352
zend_object * composite_exception = async_new_composite_exception();
353353
if (UNEXPECTED(composite_exception == NULL)) {
354354
// If we can't create CompositeException, throw the current one
355-
zend_throw_exception_internal(current_exception);
355+
async_rethrow_exception(current_exception);
356356
return SUCCESS;
357357
}
358358

@@ -393,7 +393,7 @@ static void finally_handlers_iterator_dtor(zend_async_iterator_t *zend_iterator)
393393
)) {
394394
OBJ_RELEASE(context->composite_exception);
395395
} else {
396-
zend_throw_exception_internal(context->composite_exception);
396+
async_rethrow_exception(context->composite_exception);
397397
}
398398

399399
context->composite_exception = NULL;
@@ -615,7 +615,7 @@ void async_coroutine_finalize(zend_fiber_transfer *transfer, async_coroutine_t *
615615

616616
// Otherwise, we rethrow the exception.
617617
if (exception != NULL) {
618-
zend_throw_exception_internal(exception);
618+
async_rethrow_exception(exception);
619619
}
620620

621621
if (EG(exception)) {
@@ -691,11 +691,11 @@ void async_coroutine_finalize_from_scheduler(async_coroutine_t * coroutine)
691691
EG(prev_exception) = prev_exception;
692692

693693
if (UNEXPECTED(new_prev_exception)) {
694-
zend_throw_exception_internal(new_prev_exception);
694+
async_rethrow_exception(new_prev_exception);
695695
}
696696

697697
if (UNEXPECTED(new_exception)) {
698-
zend_throw_exception_internal(new_exception);
698+
async_rethrow_exception(new_exception);
699699
}
700700

701701
if (UNEXPECTED(do_bailout)) {
@@ -844,7 +844,7 @@ static bool coroutine_replay(zend_async_event_t *event, zend_async_event_callbac
844844

845845
if (exception == NULL && coroutine->coroutine.exception != NULL) {
846846
GC_ADDREF(coroutine->coroutine.exception);
847-
zend_throw_exception_internal(coroutine->coroutine.exception);
847+
async_rethrow_exception(coroutine->coroutine.exception);
848848
} else if (exception != NULL && coroutine->coroutine.exception != NULL) {
849849
*exception = coroutine->coroutine.exception;
850850
GC_ADDREF(*exception);

exceptions.c

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ static void exception_coroutine_dispose(zend_coroutine_t *coroutine)
198198
if (coroutine->extended_data != NULL) {
199199
zend_object *exception_obj = coroutine->extended_data;
200200
coroutine->extended_data = NULL;
201-
zend_throw_exception_internal(exception_obj);
201+
async_rethrow_exception(exception_obj);
202202
}
203203
}
204204

@@ -213,7 +213,7 @@ static void exception_coroutine_entry(void)
213213
zend_object *exception = coroutine->extended_data;
214214
coroutine->extended_data = NULL;
215215

216-
zend_throw_exception_internal(exception);
216+
async_rethrow_exception(exception);
217217
}
218218

219219
bool async_spawn_and_throw(zend_object *exception, zend_async_scope_t *scope, int32_t priority)
@@ -244,4 +244,36 @@ bool async_spawn_and_throw(zend_object *exception, zend_async_scope_t *scope, in
244244
GC_ADDREF(exception);
245245

246246
return true;
247+
}
248+
249+
void async_rethrow_exception(zend_object *exception)
250+
{
251+
if (EG(current_execute_data)) {
252+
zend_throw_exception_internal(exception);
253+
} else {
254+
async_apply_exception_to_context(exception);
255+
}
256+
}
257+
258+
void async_apply_exception_to_context(zend_object *exception)
259+
{
260+
if (UNEXPECTED(exception == NULL)) {
261+
return;
262+
}
263+
264+
zend_object *previous = EG(exception);
265+
266+
if (previous && zend_is_unwind_exit(previous)) {
267+
/* Don't replace unwinding exception with different exception. */
268+
OBJ_RELEASE(exception);
269+
return;
270+
}
271+
272+
zend_exception_set_previous(exception, EG(exception));
273+
274+
EG(exception) = exception;
275+
276+
if (previous) {
277+
return;
278+
}
247279
}

exceptions.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ ZEND_API ZEND_COLD zend_object * async_throw_poll(const char *format, ...);
4040
ZEND_API ZEND_COLD zend_object * async_new_composite_exception(void);
4141
ZEND_API void async_composite_exception_add_exception(zend_object *composite, zend_object *exception, bool transfer);
4242
bool async_spawn_and_throw(zend_object *exception, zend_async_scope_t *scope, int32_t priority);
43+
void async_apply_exception_to_context(zend_object *exception);
44+
void async_rethrow_exception(zend_object *exception);
4345

4446
END_EXTERN_C()
4547

libuv_reactor.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1519,7 +1519,7 @@ static zend_async_dns_nameinfo_t * libuv_getnameinfo(const struct sockaddr *addr
15191519
);
15201520

15211521
if (error < 0) {
1522-
zend_throw_exception_internal(async_new_exception(
1522+
async_rethrow_exception(async_new_exception(
15231523
async_ce_dns_exception, "Failed to initialize getnameinfo handle: %s", uv_strerror(error)
15241524
));
15251525
pefree(name_info, 0);
@@ -1626,7 +1626,7 @@ static zend_async_dns_addrinfo_t* libuv_getaddrinfo(
16261626
);
16271627

16281628
if (error < 0) {
1629-
zend_throw_exception_internal(async_new_exception(
1629+
async_rethrow_exception(async_new_exception(
16301630
async_ce_dns_exception, "Failed to initialize getaddrinfo handle: %s", uv_strerror(error)
16311631
));
16321632

scheduler.c

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ static zend_always_inline void switch_context(async_coroutine_t *coroutine, zend
137137

138138
// Transfer the exception to the current coroutine.
139139
if (UNEXPECTED(transfer.flags & ZEND_FIBER_TRANSFER_FLAG_ERROR)) {
140-
zend_throw_exception_internal(Z_OBJ(transfer.value));
140+
async_rethrow_exception(Z_OBJ(transfer.value));
141141
ZVAL_NULL(&transfer.value);
142142
}
143143
}
@@ -216,7 +216,7 @@ static switch_status execute_next_coroutine(zend_fiber_transfer *transfer)
216216
return COROUTINE_SWITCHED;
217217
} else if (ZEND_ASYNC_CURRENT_COROUTINE == coroutine) {
218218
if (error != NULL) {
219-
zend_throw_exception_internal(error);
219+
async_rethrow_exception(error);
220220
}
221221
return COROUTINE_SWITCHED;
222222
} else {
@@ -637,6 +637,8 @@ void async_scheduler_launch(void)
637637
*/
638638
void async_scheduler_main_coroutine_suspend(void)
639639
{
640+
bool do_bailout = false;
641+
640642
if (UNEXPECTED(ZEND_ASYNC_SCHEDULER == NULL)) {
641643
async_scheduler_launch();
642644

@@ -648,6 +650,7 @@ void async_scheduler_main_coroutine_suspend(void)
648650
async_coroutine_t * coroutine = (async_coroutine_t *)ZEND_ASYNC_CURRENT_COROUTINE;
649651
zend_fiber_transfer * transfer = ASYNC_G(main_transfer);
650652

653+
zend_try {
651654
// We reach this point when the main coroutine has completed its execution.
652655
async_coroutine_finalize(transfer, coroutine);
653656

@@ -668,6 +671,10 @@ void async_scheduler_main_coroutine_suspend(void)
668671

669672
switch_to_scheduler(NULL);
670673

674+
} zend_catch {
675+
do_bailout = true;
676+
} zend_end_try();
677+
671678
ZEND_ASYNC_CURRENT_COROUTINE = NULL;
672679
ZEND_ASSERT(ZEND_ASYNC_ACTIVE_COROUTINE_COUNT == 0 && "The active coroutine counter must be 1 at this point");
673680
ZEND_ASYNC_DEACTIVATE;
@@ -689,6 +696,13 @@ void async_scheduler_main_coroutine_suspend(void)
689696
zend_object * exit_exception = ZEND_ASYNC_EXIT_EXCEPTION;
690697
ZEND_ASYNC_EXIT_EXCEPTION = NULL;
691698

699+
if (UNEXPECTED(do_bailout)) {
700+
if (exit_exception != NULL) {
701+
OBJ_RELEASE(exit_exception);
702+
}
703+
zend_bailout();
704+
}
705+
692706
//
693707
// Before exiting completely, we rethrow the exit exception
694708
// that was raised somewhere in other coroutines.
@@ -697,7 +711,7 @@ void async_scheduler_main_coroutine_suspend(void)
697711
zend_exception_set_previous(EG(exception), exit_exception);
698712
GC_DELREF(exit_exception);
699713
} else if (exit_exception != NULL) {
700-
zend_throw_exception_internal(exit_exception);
714+
async_rethrow_exception(exit_exception);
701715
}
702716
}
703717

scope.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -936,7 +936,7 @@ static bool scope_catch_or_cancel(
936936
}
937937

938938
if (UNEXPECTED(critical_exception)) {
939-
zend_throw_exception_internal(critical_exception);
939+
async_rethrow_exception(critical_exception);
940940
goto exit_false;
941941
}
942942

tests/dns/006-dns_timeout_handling.phpt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ $coroutine = spawn(function() {
2020
await($dns_coroutine, timeout(1));
2121

2222
} catch (Async\TimeoutException $e) {
23-
echo "DNS lookup timed out as expected\n";
23+
//echo "DNS lookup timed out as expected\n";
2424
} catch (Throwable $e) {
2525
echo "Other exception: " . get_class($e) . ": " . $e->getMessage() . "\n";
2626
}
@@ -43,5 +43,4 @@ await($coroutine);
4343
?>
4444
--EXPECTF--
4545
Testing DNS timeout handling
46-
%s
4746
Fast DNS lookup completed: 127.0.0.1

0 commit comments

Comments
 (0)