Flexible fiber bailout handling (#7163)

This commit is contained in:
Martin Schröder 2021-06-18 18:01:10 +02:00 committed by GitHub
parent e9e06279c1
commit c5f9cdedd6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 48 additions and 23 deletions

View file

@ -343,7 +343,7 @@ ZEND_API void zend_fiber_switch_context(zend_fiber_transfer *transfer)
to->status = ZEND_FIBER_STATUS_RUNNING; to->status = ZEND_FIBER_STATUS_RUNNING;
if (from->status == ZEND_FIBER_STATUS_RUNNING) { if (EXPECTED(from->status == ZEND_FIBER_STATUS_RUNNING)) {
from->status = ZEND_FIBER_STATUS_SUSPENDED; from->status = ZEND_FIBER_STATUS_SUSPENDED;
} }
@ -381,11 +381,6 @@ ZEND_API void zend_fiber_switch_context(zend_fiber_transfer *transfer)
if (to->status == ZEND_FIBER_STATUS_DEAD) { if (to->status == ZEND_FIBER_STATUS_DEAD) {
zend_fiber_destroy_context(to); zend_fiber_destroy_context(to);
} }
/* Propagate bailout to current fiber / main. */
if (UNEXPECTED(to->flags & ZEND_FIBER_FLAG_BAILOUT)) {
zend_bailout();
}
} }
static ZEND_STACK_ALIGNED void zend_fiber_execute(zend_fiber_transfer *transfer) static ZEND_STACK_ALIGNED void zend_fiber_execute(zend_fiber_transfer *transfer)
@ -400,6 +395,7 @@ static ZEND_STACK_ALIGNED void zend_fiber_execute(zend_fiber_transfer *transfer)
} }
EG(vm_stack) = NULL; EG(vm_stack) = NULL;
transfer->flags = 0;
zend_first_try { zend_first_try {
zend_vm_stack stack = zend_vm_stack_new_page(ZEND_FIBER_VM_STACK_SIZE, NULL); zend_vm_stack stack = zend_vm_stack_new_page(ZEND_FIBER_VM_STACK_SIZE, NULL);
@ -427,10 +423,10 @@ static ZEND_STACK_ALIGNED void zend_fiber_execute(zend_fiber_transfer *transfer)
zval_ptr_dtor(&fiber->fci.function_name); zval_ptr_dtor(&fiber->fci.function_name);
if (EG(exception)) { if (EG(exception)) {
if (!(fiber->context.flags & ZEND_FIBER_FLAG_DESTROYED) if (!(fiber->flags & ZEND_FIBER_FLAG_DESTROYED)
|| !(zend_is_graceful_exit(EG(exception)) || zend_is_unwind_exit(EG(exception))) || !(zend_is_graceful_exit(EG(exception)) || zend_is_unwind_exit(EG(exception)))
) { ) {
fiber->context.flags |= ZEND_FIBER_FLAG_THREW; fiber->flags |= ZEND_FIBER_FLAG_THREW;
transfer->flags = ZEND_FIBER_TRANSFER_FLAG_ERROR; transfer->flags = ZEND_FIBER_TRANSFER_FLAG_ERROR;
ZVAL_OBJ_COPY(&transfer->value, EG(exception)); ZVAL_OBJ_COPY(&transfer->value, EG(exception));
@ -441,7 +437,8 @@ static ZEND_STACK_ALIGNED void zend_fiber_execute(zend_fiber_transfer *transfer)
ZVAL_COPY(&transfer->value, &fiber->result); ZVAL_COPY(&transfer->value, &fiber->result);
} }
} zend_catch { } zend_catch {
fiber->context.flags |= ZEND_FIBER_FLAG_BAILOUT; fiber->flags |= ZEND_FIBER_FLAG_BAILOUT;
transfer->flags = ZEND_FIBER_TRANSFER_FLAG_BAILOUT;
} zend_end_try(); } zend_end_try();
transfer->context = fiber->caller; transfer->context = fiber->caller;
@ -486,6 +483,11 @@ static zend_always_inline zend_fiber_transfer zend_fiber_switch_to(
zend_fiber_switch_context(&transfer); zend_fiber_switch_context(&transfer);
/* Forward bailout into current fiber. */
if (UNEXPECTED(transfer.flags & ZEND_FIBER_TRANSFER_FLAG_BAILOUT)) {
zend_bailout();
}
return transfer; return transfer;
} }
@ -538,7 +540,7 @@ static void zend_fiber_object_destroy(zend_object *object)
zend_object *exception = EG(exception); zend_object *exception = EG(exception);
EG(exception) = NULL; EG(exception) = NULL;
fiber->context.flags |= ZEND_FIBER_FLAG_DESTROYED; fiber->flags |= ZEND_FIBER_FLAG_DESTROYED;
zend_fiber_transfer transfer = zend_fiber_resume(fiber, NULL, false); zend_fiber_transfer transfer = zend_fiber_resume(fiber, NULL, false);
@ -639,7 +641,7 @@ ZEND_METHOD(Fiber, suspend)
RETURN_THROWS(); RETURN_THROWS();
} }
if (UNEXPECTED(fiber->context.flags & ZEND_FIBER_FLAG_DESTROYED)) { if (UNEXPECTED(fiber->flags & ZEND_FIBER_FLAG_DESTROYED)) {
zend_throw_error(zend_ce_fiber_error, "Cannot suspend in a force-closed fiber"); zend_throw_error(zend_ce_fiber_error, "Cannot suspend in a force-closed fiber");
RETURN_THROWS(); RETURN_THROWS();
} }
@ -656,7 +658,7 @@ ZEND_METHOD(Fiber, suspend)
zend_fiber_transfer transfer = zend_fiber_suspend(fiber, value); zend_fiber_transfer transfer = zend_fiber_suspend(fiber, value);
if (fiber->context.flags & ZEND_FIBER_FLAG_DESTROYED) { if (fiber->flags & ZEND_FIBER_FLAG_DESTROYED) {
// This occurs when the fiber is GC'ed while suspended. // This occurs when the fiber is GC'ed while suspended.
zval_ptr_dtor(&transfer.value); zval_ptr_dtor(&transfer.value);
zend_throw_graceful_exit(); zend_throw_graceful_exit();
@ -777,9 +779,9 @@ ZEND_METHOD(Fiber, getReturn)
fiber = (zend_fiber *) Z_OBJ_P(getThis()); fiber = (zend_fiber *) Z_OBJ_P(getThis());
if (fiber->context.status == ZEND_FIBER_STATUS_DEAD) { if (fiber->context.status == ZEND_FIBER_STATUS_DEAD) {
if (fiber->context.flags & ZEND_FIBER_FLAG_THREW) { if (fiber->flags & ZEND_FIBER_FLAG_THREW) {
message = "The fiber threw an exception"; message = "The fiber threw an exception";
} else if (fiber->context.flags & ZEND_FIBER_FLAG_BAILOUT) { } else if (fiber->flags & ZEND_FIBER_FLAG_BAILOUT) {
message = "The fiber exited with a fatal error"; message = "The fiber exited with a fatal error";
} else { } else {
RETURN_COPY(&fiber->result); RETURN_COPY(&fiber->result);

View file

@ -45,6 +45,7 @@ typedef enum {
typedef enum { typedef enum {
ZEND_FIBER_TRANSFER_FLAG_ERROR = 1 << 0, ZEND_FIBER_TRANSFER_FLAG_ERROR = 1 << 0,
ZEND_FIBER_TRANSFER_FLAG_BAILOUT = 1 << 1
} zend_fiber_transfer_flag; } zend_fiber_transfer_flag;
void zend_register_fiber_ce(void); void zend_register_fiber_ce(void);
@ -61,8 +62,10 @@ typedef struct _zend_fiber_stack zend_fiber_stack;
typedef struct _zend_fiber_transfer { typedef struct _zend_fiber_transfer {
/* Fiber that will be switched to / has resumed us. */ /* Fiber that will be switched to / has resumed us. */
zend_fiber_context *context; zend_fiber_context *context;
/* Value to that should be send to (or was received from) a fiber. */ /* Value to that should be send to (or was received from) a fiber. */
zval value; zval value;
/* Bitmask of flags defined in enum zend_fiber_transfer_flag. */ /* Bitmask of flags defined in enum zend_fiber_transfer_flag. */
uint8_t flags; uint8_t flags;
} zend_fiber_transfer; } zend_fiber_transfer;
@ -74,18 +77,28 @@ typedef void (*zend_fiber_coroutine)(zend_fiber_transfer *transfer);
struct _zend_fiber_context { struct _zend_fiber_context {
/* Handle to fiber state as needed by boost.context */ /* Handle to fiber state as needed by boost.context */
void *handle; void *handle;
/* Pointer that identifies the fiber type. */ /* Pointer that identifies the fiber type. */
void *kind; void *kind;
/* Entrypoint function of the fiber. */
zend_fiber_coroutine function; zend_fiber_coroutine function;
/* Assigned C stack. */
zend_fiber_stack *stack; zend_fiber_stack *stack;
/* Fiber status. */
zend_fiber_status status; zend_fiber_status status;
uint8_t flags;
}; };
struct _zend_fiber { struct _zend_fiber {
/* PHP object handle. */ /* PHP object handle. */
zend_object std; zend_object std;
/* Flags are defined in enum zend_fiber_flag. */
uint8_t flags;
/* Native C fiber context. */
zend_fiber_context context; zend_fiber_context context;
/* Fiber that resumed us. */ /* Fiber that resumed us. */

View file

@ -38,6 +38,11 @@ static zend_fiber_transfer zend_test_fiber_switch_to(zend_fiber_context *context
zend_fiber_switch_context(&transfer); zend_fiber_switch_context(&transfer);
/* Forward bailout into current fiber. */
if (UNEXPECTED(transfer.flags & ZEND_FIBER_TRANSFER_FLAG_BAILOUT)) {
zend_bailout();
}
return transfer; return transfer;
} }
@ -74,6 +79,7 @@ static ZEND_STACK_ALIGNED void zend_test_fiber_execute(zend_fiber_transfer *tran
zend_execute_data *execute_data; zend_execute_data *execute_data;
EG(vm_stack) = NULL; EG(vm_stack) = NULL;
transfer->flags = 0;
zend_first_try { zend_first_try {
zend_vm_stack stack = zend_vm_stack_new_page(ZEND_FIBER_VM_STACK_SIZE, NULL); zend_vm_stack stack = zend_vm_stack_new_page(ZEND_FIBER_VM_STACK_SIZE, NULL);
@ -97,10 +103,10 @@ static ZEND_STACK_ALIGNED void zend_test_fiber_execute(zend_fiber_transfer *tran
zval_ptr_dtor(&fiber->fci.function_name); zval_ptr_dtor(&fiber->fci.function_name);
if (EG(exception)) { if (EG(exception)) {
if (!(fiber->context.flags & ZEND_FIBER_FLAG_DESTROYED) if (!(fiber->flags & ZEND_FIBER_FLAG_DESTROYED)
|| !(zend_is_graceful_exit(EG(exception)) || zend_is_unwind_exit(EG(exception))) || !(zend_is_graceful_exit(EG(exception)) || zend_is_unwind_exit(EG(exception)))
) { ) {
fiber->context.flags |= ZEND_FIBER_FLAG_THREW; fiber->flags |= ZEND_FIBER_FLAG_THREW;
transfer->flags = ZEND_FIBER_TRANSFER_FLAG_ERROR; transfer->flags = ZEND_FIBER_TRANSFER_FLAG_ERROR;
ZVAL_OBJ_COPY(&transfer->value, EG(exception)); ZVAL_OBJ_COPY(&transfer->value, EG(exception));
@ -112,7 +118,8 @@ static ZEND_STACK_ALIGNED void zend_test_fiber_execute(zend_fiber_transfer *tran
ZVAL_COPY(&transfer->value, &fiber->result); ZVAL_COPY(&transfer->value, &fiber->result);
} }
} zend_catch { } zend_catch {
fiber->context.flags |= ZEND_FIBER_FLAG_BAILOUT; fiber->flags |= ZEND_FIBER_FLAG_BAILOUT;
transfer->flags = ZEND_FIBER_TRANSFER_FLAG_BAILOUT;
} zend_end_try(); } zend_end_try();
zend_vm_stack_destroy(); zend_vm_stack_destroy();
@ -159,7 +166,7 @@ static void zend_test_fiber_object_destroy(zend_object *object)
zend_object *exception = EG(exception); zend_object *exception = EG(exception);
EG(exception) = NULL; EG(exception) = NULL;
fiber->context.flags |= ZEND_FIBER_FLAG_DESTROYED; fiber->flags |= ZEND_FIBER_FLAG_DESTROYED;
zend_fiber_transfer transfer = zend_test_fiber_resume(fiber, NULL, false); zend_fiber_transfer transfer = zend_test_fiber_resume(fiber, NULL, false);
@ -274,7 +281,7 @@ static ZEND_METHOD(_ZendTestFiber, suspend)
zend_fiber_transfer transfer = zend_test_fiber_suspend(fiber, value); zend_fiber_transfer transfer = zend_test_fiber_suspend(fiber, value);
if (fiber->context.flags & ZEND_FIBER_FLAG_DESTROYED) { if (fiber->flags & ZEND_FIBER_FLAG_DESTROYED) {
// This occurs when the test fiber is GC'ed while suspended. // This occurs when the test fiber is GC'ed while suspended.
zval_ptr_dtor(&transfer.value); zval_ptr_dtor(&transfer.value);
zend_throw_graceful_exit(); zend_throw_graceful_exit();

View file

@ -23,6 +23,7 @@ typedef struct _zend_test_fiber zend_test_fiber;
struct _zend_test_fiber { struct _zend_test_fiber {
zend_object std; zend_object std;
uint8_t flags;
zend_fiber_context context; zend_fiber_context context;
zend_fiber_context *caller; zend_fiber_context *caller;
zend_fiber_context *previous; zend_fiber_context *previous;

View file

@ -207,7 +207,7 @@ static void fiber_enter_observer(zend_fiber_context *from, zend_fiber_context *t
return; return;
} }
if (to->flags & ZEND_FIBER_FLAG_DESTROYED) { if (fiber->flags & ZEND_FIBER_FLAG_DESTROYED) {
php_printf("<destroying '%p'>\n", to); php_printf("<destroying '%p'>\n", to);
} else if (to->status != ZEND_FIBER_STATUS_DEAD) { } else if (to->status != ZEND_FIBER_STATUS_DEAD) {
php_printf("<resume '%p'>\n", to); php_printf("<resume '%p'>\n", to);
@ -220,9 +220,11 @@ static void fiber_suspend_observer(zend_fiber_context *from, zend_fiber_context
{ {
if (ZT_G(observer_fiber_switch)) { if (ZT_G(observer_fiber_switch)) {
if (from->status == ZEND_FIBER_STATUS_DEAD) { if (from->status == ZEND_FIBER_STATUS_DEAD) {
if (from->flags & ZEND_FIBER_FLAG_THREW) { zend_fiber *fiber = (from->kind == zend_ce_fiber) ? zend_fiber_from_context(from) : NULL;
if (fiber && fiber->flags & ZEND_FIBER_FLAG_THREW) {
php_printf("<threw '%p'>\n", from); php_printf("<threw '%p'>\n", from);
} else if (from->flags & ZEND_FIBER_FLAG_DESTROYED) { } else if (fiber && fiber->flags & ZEND_FIBER_FLAG_DESTROYED) {
php_printf("<destroyed '%p'>\n", from); php_printf("<destroyed '%p'>\n", from);
} else { } else {
php_printf("<returned '%p'>\n", from); php_printf("<returned '%p'>\n", from);