Skip to content

Commit ceeacf3

Browse files
committed
#9: * fix scope_try_to_dispose Fixed cyclic Scope release from children due to missing reentrancy handling.
* Fixed an issue with the set_previous_exception function caused by an extra reference deletion.
1 parent 0e62cd6 commit ceeacf3

File tree

4 files changed

+30
-21
lines changed

4 files changed

+30
-21
lines changed

coroutine.c

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -379,12 +379,11 @@ static void finally_handlers_iterator_dtor(zend_async_iterator_t *zend_iterator)
379379
}
380380

381381
finally_handlers_context_t *context = iterator->extended_data;
382+
async_scope_t *scope = (async_scope_t *) context->scope;
383+
context->scope = NULL;
382384

383385
// Throw CompositeException if any exceptions were collected
384386
if (context->composite_exception != NULL) {
385-
386-
async_scope_t *scope = (async_scope_t *) context->scope;
387-
388387
if (ZEND_ASYNC_SCOPE_CATCH(
389388
&scope->scope,
390389
&context->coroutine->coroutine,
@@ -394,13 +393,13 @@ static void finally_handlers_iterator_dtor(zend_async_iterator_t *zend_iterator)
394393
ZEND_ASYNC_SCOPE_IS_DISPOSE_SAFELY(&scope->scope)
395394
)) {
396395
OBJ_RELEASE(context->composite_exception);
397-
} else {
398-
async_rethrow_exception(context->composite_exception);
396+
context->composite_exception = NULL;
399397
}
400-
401-
context->composite_exception = NULL;
402398
}
403399

400+
zend_object * composite_exception = context->composite_exception;
401+
context->composite_exception = NULL;
402+
404403
if (context->dtor != NULL) {
405404
context->dtor(context);
406405
context->dtor = NULL;
@@ -410,6 +409,18 @@ static void finally_handlers_iterator_dtor(zend_async_iterator_t *zend_iterator)
410409
efree(context);
411410
iterator->extended_data = NULL;
412411

412+
if (ZEND_ASYNC_EVENT_REF(&scope->scope.event) > 1) {
413+
ZEND_ASYNC_EVENT_DEL_REF(&scope->scope.event);
414+
415+
if (ZEND_ASYNC_EVENT_REF(&scope->scope.event) == 1) {
416+
scope->scope.try_to_dispose(&scope->scope);
417+
}
418+
}
419+
420+
if (composite_exception != NULL) {
421+
async_rethrow_exception(composite_exception);
422+
}
423+
413424
//
414425
// If everything is correct,
415426
// the Scope will destroy itself as soon as the coroutine created within it completes execution.
@@ -453,6 +464,7 @@ bool async_call_finally_handlers(HashTable *finally_handlers, finally_handlers_c
453464
iterator->extended_data = context;
454465
iterator->extended_dtor = finally_handlers_iterator_dtor;
455466
async_iterator_run_in_coroutine(iterator, priority);
467+
ZEND_ASYNC_EVENT_ADD_REF(&child_scope->event);
456468

457469
if (UNEXPECTED(EG(exception))) {
458470
return false;

scheduler.c

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,6 @@ void start_graceful_shutdown(void)
471471

472472
if (UNEXPECTED(EG(exception) != NULL)) {
473473
zend_exception_set_previous(EG(exception), ZEND_ASYNC_EXIT_EXCEPTION);
474-
GC_DELREF(ZEND_ASYNC_EXIT_EXCEPTION);
475474
ZEND_ASYNC_EXIT_EXCEPTION = EG(exception);
476475
GC_ADDREF(EG(exception));
477476
zend_clear_exception();
@@ -484,7 +483,6 @@ static void finally_shutdown(void)
484483
{
485484
if (ZEND_ASYNC_EXIT_EXCEPTION != NULL && EG(exception) != NULL) {
486485
zend_exception_set_previous(EG(exception), ZEND_ASYNC_EXIT_EXCEPTION);
487-
GC_DELREF(ZEND_ASYNC_EXIT_EXCEPTION);
488486
ZEND_ASYNC_EXIT_EXCEPTION = EG(exception);
489487
GC_ADDREF(EG(exception));
490488
zend_clear_exception();
@@ -498,7 +496,6 @@ static void finally_shutdown(void)
498496
if (UNEXPECTED(EG(exception))) {
499497
if (ZEND_ASYNC_EXIT_EXCEPTION != NULL) {
500498
zend_exception_set_previous(EG(exception), ZEND_ASYNC_EXIT_EXCEPTION);
501-
GC_DELREF(ZEND_ASYNC_EXIT_EXCEPTION);
502499
ZEND_ASYNC_EXIT_EXCEPTION = EG(exception);
503500
GC_ADDREF(EG(exception));
504501
}
@@ -709,7 +706,6 @@ void async_scheduler_main_coroutine_suspend(void)
709706
//
710707
if (EG(exception) != NULL && exit_exception != NULL) {
711708
zend_exception_set_previous(EG(exception), exit_exception);
712-
GC_DELREF(exit_exception);
713709
} else if (exit_exception != NULL) {
714710
async_rethrow_exception(exit_exception);
715711
}
@@ -855,7 +851,6 @@ void async_scheduler_coroutine_suspend(zend_fiber_transfer *transfer)
855851

856852
if (ZEND_ASYNC_EXIT_EXCEPTION != NULL) {
857853
zend_exception_set_previous(exception, ZEND_ASYNC_EXIT_EXCEPTION);
858-
GC_DELREF(ZEND_ASYNC_EXIT_EXCEPTION);
859854
ZEND_ASYNC_EXIT_EXCEPTION = exception;
860855
} else {
861856
ZEND_ASYNC_EXIT_EXCEPTION = exception;
@@ -982,7 +977,6 @@ void async_scheduler_main_loop(void)
982977

983978
if (EG(exception) != NULL && exit_exception != NULL) {
984979
zend_exception_set_previous(EG(exception), exit_exception);
985-
GC_DELREF(exit_exception);
986980
exit_exception = EG(exception);
987981
GC_ADDREF(exit_exception);
988982
zend_clear_exception();

scope.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -990,10 +990,16 @@ static bool scope_try_to_dispose(zend_async_scope_t *scope)
990990
{
991991
async_scope_t *async_scope = (async_scope_t *) scope;
992992

993+
if (ZEND_ASYNC_SCOPE_IS_DISPOSED(scope)) {
994+
return true;
995+
}
996+
993997
if (false == SCOPE_CAN_BE_DISPOSED(async_scope)) {
994998
return false;
995999
}
9961000

1001+
ZEND_ASYNC_SCOPE_SET_DISPOSED(scope);
1002+
9971003
// Dispose all child scopes
9981004
for (uint32_t i = 0; i < async_scope->scope.scopes.length; ++i) {
9991005
async_scope_t *child_scope = (async_scope_t *) async_scope->scope.scopes.data[i];
@@ -1111,6 +1117,7 @@ static void scope_dispose(zend_async_event_t *scope_event)
11111117
}
11121118

11131119
if (scope->scope.scope_object != NULL) {
1120+
fprintf(stderr, "unlink %p\n", scope->scope.scope_object);
11141121
((async_scope_object_t *) scope->scope.scope_object)->scope = NULL;
11151122
scope->scope.scope_object = NULL;
11161123
}
@@ -1154,6 +1161,7 @@ zend_async_scope_t * async_new_scope(zend_async_scope_t * parent_scope, const bo
11541161

11551162
if (with_zend_object) {
11561163
scope_object = ZEND_OBJECT_ALLOC_EX(sizeof(async_scope_object_t), async_ce_scope);
1164+
fprintf(stderr, "regular new scope_object %p\n", scope_object);
11571165

11581166
zend_object_std_init(&scope_object->std, async_ce_scope);
11591167
object_properties_init(&scope_object->std, async_ce_scope);
@@ -1235,6 +1243,8 @@ static void scope_destroy(zend_object *object)
12351243
scope_object->scope = NULL;
12361244
scope->scope.scope_object = NULL;
12371245

1246+
fprintf(stderr, "scope_destroy %p\n", object);
1247+
12381248
// At this point, the user-defined Scope object is about to be destroyed.
12391249
// This means we are obligated to cancel the Scope and all its child Scopes along with their coroutines.
12401250
// However, the Scope itself will not be destroyed.
@@ -1436,6 +1446,7 @@ static zend_always_inline bool try_to_handle_exception(
14361446
// The PHP Scope object might already be destroyed by the time the internal Scope still exists.
14371447
// To normalize this situation, we’ll create a fake Scope object that will serve as a bridge.
14381448
scope_object = ZEND_OBJECT_ALLOC_EX(sizeof(async_scope_object_t), async_ce_scope);
1449+
fprintf(stderr, "new scope_object %p\n", scope_object);
14391450
zend_object_std_init(&scope_object->std, async_ce_scope);
14401451
object_properties_init(&scope_object->std, async_ce_scope);
14411452

tests/scope/034-scope_cancellation_double_error.phpt

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,6 @@ $child_coroutine = $child_scope->spawn(function() {
3535

3636
$coroutine = \Async\currentCoroutine();
3737
some_function();
38-
39-
$coroutine->onFinally(function() {
40-
echo "child finally handler executed\n";
41-
});
42-
43-
suspend();
44-
echo "child should not complete\n";
45-
return "child_result";
4638
});
4739

4840
echo "coroutines with finally handlers spawned\n";

0 commit comments

Comments
 (0)