Skip to content

Flexible fiber bailout handling #7163

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

Merged
merged 1 commit into from
Jun 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 16 additions & 14 deletions Zend/zend_fibers.c
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ ZEND_API void zend_fiber_switch_context(zend_fiber_transfer *transfer)

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;
}

Expand Down Expand Up @@ -342,11 +342,6 @@ ZEND_API void zend_fiber_switch_context(zend_fiber_transfer *transfer)
if (to->status == ZEND_FIBER_STATUS_DEAD) {
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)
Expand All @@ -361,6 +356,7 @@ static ZEND_STACK_ALIGNED void zend_fiber_execute(zend_fiber_transfer *transfer)
}

EG(vm_stack) = NULL;
transfer->flags = 0;

zend_first_try {
zend_vm_stack stack = zend_vm_stack_new_page(ZEND_FIBER_VM_STACK_SIZE, NULL);
Expand Down Expand Up @@ -388,10 +384,10 @@ static ZEND_STACK_ALIGNED void zend_fiber_execute(zend_fiber_transfer *transfer)
zval_ptr_dtor(&fiber->fci.function_name);

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)))
) {
fiber->context.flags |= ZEND_FIBER_FLAG_THREW;
fiber->flags |= ZEND_FIBER_FLAG_THREW;
transfer->flags = ZEND_FIBER_TRANSFER_FLAG_ERROR;

ZVAL_OBJ_COPY(&transfer->value, EG(exception));
Expand All @@ -402,7 +398,8 @@ static ZEND_STACK_ALIGNED void zend_fiber_execute(zend_fiber_transfer *transfer)
ZVAL_COPY(&transfer->value, &fiber->result);
}
} 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();

transfer->context = fiber->caller;
Expand Down Expand Up @@ -447,6 +444,11 @@ static zend_always_inline zend_fiber_transfer zend_fiber_switch_to(

zend_fiber_switch_context(&transfer);

/* Forward bailout into current fiber. */
if (UNEXPECTED(transfer.flags & ZEND_FIBER_TRANSFER_FLAG_BAILOUT)) {
zend_bailout();
}

return transfer;
}

Expand Down Expand Up @@ -499,7 +501,7 @@ static void zend_fiber_object_destroy(zend_object *object)
zend_object *exception = EG(exception);
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);

Expand Down Expand Up @@ -600,7 +602,7 @@ ZEND_METHOD(Fiber, suspend)
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");
RETURN_THROWS();
}
Expand All @@ -617,7 +619,7 @@ ZEND_METHOD(Fiber, suspend)

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.
zval_ptr_dtor(&transfer.value);
zend_throw_graceful_exit();
Expand Down Expand Up @@ -738,9 +740,9 @@ ZEND_METHOD(Fiber, getReturn)
fiber = (zend_fiber *) Z_OBJ_P(getThis());

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";
} 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";
} else {
RETURN_COPY(&fiber->result);
Expand Down
15 changes: 14 additions & 1 deletion Zend/zend_fibers.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ typedef enum {

typedef enum {
ZEND_FIBER_TRANSFER_FLAG_ERROR = 1 << 0,
ZEND_FIBER_TRANSFER_FLAG_BAILOUT = 1 << 1
} zend_fiber_transfer_flag;

void zend_register_fiber_ce(void);
Expand All @@ -61,8 +62,10 @@ typedef struct _zend_fiber_stack zend_fiber_stack;
typedef struct _zend_fiber_transfer {
/* Fiber that will be switched to / has resumed us. */
zend_fiber_context *context;

/* Value to that should be send to (or was received from) a fiber. */
zval value;

/* Bitmask of flags defined in enum zend_fiber_transfer_flag. */
uint8_t flags;
} zend_fiber_transfer;
Expand All @@ -74,12 +77,18 @@ typedef void (*zend_fiber_coroutine)(zend_fiber_transfer *transfer);
struct _zend_fiber_context {
/* Handle to fiber state as needed by boost.context */
void *handle;

/* Pointer that identifies the fiber type. */
void *kind;

/* Entrypoint function of the fiber. */
zend_fiber_coroutine function;

/* Assigned C stack. */
zend_fiber_stack *stack;

/* Fiber status. */
zend_fiber_status status;
uint8_t flags;
};

/* Zend VM state that needs to be captured / restored during fiber context switch. */
Expand All @@ -99,6 +108,10 @@ struct _zend_fiber {
/* PHP object handle. */
zend_object std;

/* Flags are defined in enum zend_fiber_flag. */
uint8_t flags;

/* Native C fiber context. */
zend_fiber_context context;

/* Fiber that resumed us. */
Expand Down
17 changes: 12 additions & 5 deletions ext/zend_test/fiber.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ static zend_fiber_transfer zend_test_fiber_switch_to(zend_fiber_context *context

zend_fiber_switch_context(&transfer);

/* Forward bailout into current fiber. */
if (UNEXPECTED(transfer.flags & ZEND_FIBER_TRANSFER_FLAG_BAILOUT)) {
zend_bailout();
}

return transfer;
}

Expand Down Expand Up @@ -74,6 +79,7 @@ static ZEND_STACK_ALIGNED void zend_test_fiber_execute(zend_fiber_transfer *tran
zend_execute_data *execute_data;

EG(vm_stack) = NULL;
transfer->flags = 0;

zend_first_try {
zend_vm_stack stack = zend_vm_stack_new_page(ZEND_FIBER_VM_STACK_SIZE, NULL);
Expand All @@ -97,10 +103,10 @@ static ZEND_STACK_ALIGNED void zend_test_fiber_execute(zend_fiber_transfer *tran
zval_ptr_dtor(&fiber->fci.function_name);

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)))
) {
fiber->context.flags |= ZEND_FIBER_FLAG_THREW;
fiber->flags |= ZEND_FIBER_FLAG_THREW;
transfer->flags = ZEND_FIBER_TRANSFER_FLAG_ERROR;

ZVAL_OBJ_COPY(&transfer->value, EG(exception));
Expand All @@ -112,7 +118,8 @@ static ZEND_STACK_ALIGNED void zend_test_fiber_execute(zend_fiber_transfer *tran
ZVAL_COPY(&transfer->value, &fiber->result);
}
} 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_vm_stack_destroy();
Expand Down Expand Up @@ -159,7 +166,7 @@ static void zend_test_fiber_object_destroy(zend_object *object)
zend_object *exception = EG(exception);
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);

Expand Down Expand Up @@ -274,7 +281,7 @@ static ZEND_METHOD(_ZendTestFiber, suspend)

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.
zval_ptr_dtor(&transfer.value);
zend_throw_graceful_exit();
Expand Down
1 change: 1 addition & 0 deletions ext/zend_test/fiber.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ typedef struct _zend_test_fiber zend_test_fiber;

struct _zend_test_fiber {
zend_object std;
uint8_t flags;
zend_fiber_context context;
zend_fiber_context *caller;
zend_fiber_context *previous;
Expand Down
8 changes: 5 additions & 3 deletions ext/zend_test/observer.c
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ static void fiber_enter_observer(zend_fiber_context *from, zend_fiber_context *t
return;
}

if (to->flags & ZEND_FIBER_FLAG_DESTROYED) {
if (fiber->flags & ZEND_FIBER_FLAG_DESTROYED) {
php_printf("<destroying '%p'>\n", to);
} else if (to->status != ZEND_FIBER_STATUS_DEAD) {
php_printf("<resume '%p'>\n", to);
Expand All @@ -220,9 +220,11 @@ static void fiber_suspend_observer(zend_fiber_context *from, zend_fiber_context
{
if (ZT_G(observer_fiber_switch)) {
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);
} else if (from->flags & ZEND_FIBER_FLAG_DESTROYED) {
} else if (fiber && fiber->flags & ZEND_FIBER_FLAG_DESTROYED) {
php_printf("<destroyed '%p'>\n", from);
} else {
php_printf("<returned '%p'>\n", from);
Expand Down