Merge fiber switching functions (#7106)

Co-authored-by: Martin Schröder <m.schroeder2007@gmail.com>
This commit is contained in:
Aaron Piotrowski 2021-06-08 08:23:56 -05:00 committed by GitHub
parent 291d8db7c5
commit 2184422adc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 175 additions and 160 deletions

View file

@ -10,6 +10,18 @@ $fiber = new Fiber(function (): void {
var_dump($fiber->isRunning());
var_dump($fiber->isSuspended());
var_dump($fiber->isTerminated());
$nested = new Fiber(function () use ($fiber): void {
echo "\nWithin Nested Fiber:\n";
var_dump($fiber->isStarted());
var_dump($fiber->isRunning());
var_dump($fiber->isSuspended());
var_dump($fiber->isTerminated());
Fiber::suspend();
});
$nested->start();
Fiber::suspend();
});
@ -49,6 +61,12 @@ bool(true)
bool(false)
bool(false)
Within Nested Fiber:
bool(true)
bool(true)
bool(false)
bool(false)
After Start:
bool(true)
bool(false)

View file

@ -0,0 +1,28 @@
--TEST--
Resume previous fiber
--FILE--
<?php
$fiber = new Fiber(function (): void {
$fiber1 = Fiber::this();
$fiber2 = new Fiber(function () use ($fiber1): void {
$fiber1->resume();
});
$fiber2->start();
});
$fiber->start();
?>
--EXPECTF--
Fatal error: Uncaught FiberError: Cannot resume a fiber that is not suspended in %sresume-previous-fiber.php:%d
Stack trace:
#0 %sresume-previous-fiber.php(%d): Fiber->resume()
#1 [internal function]: {closure}()
#2 %sresume-previous-fiber.php(%d): Fiber->start()
#3 [internal function]: {closure}()
#4 %sresume-previous-fiber.php(%d): Fiber->start()
#5 {main}
thrown in %sresume-previous-fiber.php on line %d

View file

@ -192,8 +192,12 @@ static ZEND_NORETURN void zend_fiber_trampoline(transfer_t transfer)
__sanitizer_finish_switch_fiber(NULL, &from->stack.prior_pointer, &from->stack.prior_size);
#endif
zend_fiber_context *to = context->function(context);
context->status = ZEND_FIBER_STATUS_DEAD;
/* Final context switch, the fiber must not be resumed afterwards! */
zend_fiber_switch_context(context->function(context));
zend_fiber_switch_context(to);
/* Abort here because we are in an inconsistent program state. */
abort();
@ -222,14 +226,27 @@ ZEND_API void zend_fiber_destroy_context(zend_fiber_context *context)
zend_fiber_stack_free(&context->stack);
}
ZEND_API void zend_fiber_switch_context(zend_fiber_context *to)
ZEND_API zend_fiber_context *zend_fiber_switch_context(zend_fiber_context *to)
{
zend_fiber_context *from = EG(current_fiber);
zend_fiber_vm_state state;
ZEND_ASSERT(to && to->handle && "Invalid fiber context");
ZEND_ASSERT(from && "From fiber context must be present");
ZEND_ASSERT(to != from && "Cannot switch into the running fiber context");
zend_observer_fiber_switch_notify(from, to);
zend_fiber_capture_vm_state(&state);
to->status = ZEND_FIBER_STATUS_RUNNING;
if (from->status == ZEND_FIBER_STATUS_RUNNING) {
from->status = ZEND_FIBER_STATUS_SUSPENDED;
}
EG(current_fiber) = to;
#ifdef __SANITIZE_ADDRESS__
void *fake_stack = NULL;
__sanitizer_start_switch_fiber(
@ -238,50 +255,30 @@ ZEND_API void zend_fiber_switch_context(zend_fiber_context *to)
to->stack.prior_size);
#endif
EG(current_fiber) = to;
transfer_t transfer = jump_fcontext(to->handle, from);
((zend_fiber_context *) transfer.data)->handle = transfer.context;
EG(current_fiber) = from;
#ifdef __SANITIZE_ADDRESS__
__sanitizer_finish_switch_fiber(fake_stack, &to->stack.prior_pointer, &to->stack.prior_size);
#endif
}
static void zend_fiber_suspend_from(zend_fiber *fiber)
{
zend_fiber_vm_state state;
EG(current_fiber) = from;
ZEND_ASSERT(fiber->caller && "Fiber has no caller");
zend_fiber_context *previous = transfer.data;
previous->handle = transfer.context;
zend_fiber_capture_vm_state(&state);
zend_fiber_switch_context(fiber->caller);
zend_fiber_restore_vm_state(&state);
}
static void zend_fiber_switch_to(zend_fiber *fiber)
{
zend_fiber_context *context = zend_fiber_get_context(fiber);
zend_fiber_vm_state state;
zend_observer_fiber_switch_notify(EG(current_fiber), context);
fiber->caller = EG(current_fiber);
zend_fiber_capture_vm_state(&state);
zend_fiber_switch_context(context);
zend_fiber_restore_vm_state(&state);
fiber->caller = NULL;
/* Destroy context first to ensure it does not leak if some extension does custom bailout handling. */
if (previous->status == ZEND_FIBER_STATUS_DEAD) {
zend_fiber_destroy_context(previous);
}
zend_observer_fiber_switch_notify(context, EG(current_fiber));
if (UNEXPECTED(fiber->flags & ZEND_FIBER_FLAG_BAILOUT)) {
// zend_bailout() was called in the fiber, so call it again in the previous fiber or {main}.
/* Propagate bailout to current fiber / main. */
if (UNEXPECTED(previous->flags & ZEND_FIBER_FLAG_BAILOUT)) {
zend_bailout();
}
return previous;
}
@ -328,8 +325,6 @@ static ZEND_STACK_ALIGNED zend_fiber_context *zend_fiber_execute(zend_fiber_cont
fiber->fci.retval = &fiber->value;
fiber->status = ZEND_FIBER_STATUS_RUNNING;
zend_call_function(&fiber->fci, &fiber->fci_cache);
zval_ptr_dtor(&fiber->fci.function_name);
@ -347,13 +342,14 @@ static ZEND_STACK_ALIGNED zend_fiber_context *zend_fiber_execute(zend_fiber_cont
fiber->flags |= ZEND_FIBER_FLAG_BAILOUT;
} zend_end_try();
fiber->status = ZEND_FIBER_STATUS_DEAD;
zend_vm_stack_destroy();
fiber->execute_data = NULL;
fiber->stack_bottom = NULL;
return fiber->caller;
zend_fiber_context *caller = fiber->caller;
fiber->caller = NULL;
return caller;
}
static zend_object *zend_fiber_object_create(zend_class_entry *ce)
@ -380,10 +376,10 @@ static void zend_fiber_object_destroy(zend_object *object)
zend_object *exception = EG(exception);
EG(exception) = NULL;
fiber->status = ZEND_FIBER_STATUS_RUNNING;
fiber->caller = EG(current_fiber);
fiber->flags |= ZEND_FIBER_FLAG_DESTROYED;
zend_fiber_switch_to(fiber);
zend_fiber_switch_context(zend_fiber_get_context(fiber));
if (EG(exception)) {
if (!exception && EG(current_execute_data) && EG(current_execute_data)->func
@ -412,23 +408,9 @@ static void zend_fiber_object_free(zend_object *object)
zval_ptr_dtor(&fiber->value);
zend_fiber_destroy_context(zend_fiber_get_context(fiber));
zend_object_std_dtor(&fiber->std);
}
ZEND_API zend_fiber *zend_fiber_create(const zend_fcall_info *fci, const zend_fcall_info_cache *fci_cache)
{
zend_fiber *fiber = (zend_fiber *) zend_fiber_object_create(zend_ce_fiber);
fiber->fci = *fci;
fiber->fci_cache = *fci_cache;
Z_TRY_ADDREF(fiber->fci.function_name);
return fiber;
}
ZEND_METHOD(Fiber, __construct)
{
zend_fiber *fiber = (zend_fiber *) Z_OBJ_P(getThis());
@ -441,31 +423,6 @@ ZEND_METHOD(Fiber, __construct)
Z_TRY_ADDREF(fiber->fci.function_name);
}
ZEND_API void zend_fiber_start(zend_fiber *fiber, zval *params, uint32_t param_count, zend_array *named_params, zval *return_value)
{
if (fiber->status != ZEND_FIBER_STATUS_INIT) {
zend_throw_error(zend_ce_fiber_error, "Cannot start a fiber that has already been started");
RETURN_THROWS();
}
fiber->fci.params = params;
fiber->fci.param_count = param_count;
fiber->fci.named_params = named_params;
if (!zend_fiber_init_context(zend_fiber_get_context(fiber), zend_ce_fiber, zend_fiber_execute, EG(fiber_stack_size))) {
RETURN_THROWS();
}
zend_fiber_switch_to(fiber);
if (fiber->status == ZEND_FIBER_STATUS_DEAD) {
RETURN_NULL();
}
RETVAL_COPY_VALUE(&fiber->value);
ZVAL_UNDEF(&fiber->value);
}
ZEND_METHOD(Fiber, start)
{
zend_fiber *fiber = (zend_fiber *) Z_OBJ_P(getThis());
@ -477,51 +434,27 @@ ZEND_METHOD(Fiber, start)
Z_PARAM_VARIADIC_WITH_NAMED(params, param_count, named_params);
ZEND_PARSE_PARAMETERS_END();
zend_fiber_start(fiber, params, param_count, named_params, return_value);
}
ZEND_API void zend_fiber_suspend(zval *value, zval *return_value)
{
if (UNEXPECTED(EG(current_fiber)->kind != zend_ce_fiber)) {
zend_throw_error(zend_ce_fiber_error, "Cannot suspend outside of a fiber");
if (fiber->status != ZEND_FIBER_STATUS_INIT) {
zend_throw_error(zend_ce_fiber_error, "Cannot start a fiber that has already been started");
RETURN_THROWS();
}
zend_fiber *fiber = zend_fiber_from_context(EG(current_fiber));
fiber->fci.params = params;
fiber->fci.param_count = param_count;
fiber->fci.named_params = named_params;
if (UNEXPECTED(fiber->flags & ZEND_FIBER_FLAG_DESTROYED)) {
zend_throw_error(zend_ce_fiber_error, "Cannot suspend in a force-closed fiber");
zend_fiber_context *context = zend_fiber_get_context(fiber);
if (!zend_fiber_init_context(context, zend_ce_fiber, zend_fiber_execute, EG(fiber_stack_size))) {
RETURN_THROWS();
}
ZEND_ASSERT(fiber->status == ZEND_FIBER_STATUS_RUNNING);
fiber->caller = EG(current_fiber);
if (value) {
ZVAL_COPY(&fiber->value, value);
} else {
ZVAL_NULL(&fiber->value);
}
zend_fiber_switch_context(context);
fiber->execute_data = EG(current_execute_data);
fiber->status = ZEND_FIBER_STATUS_SUSPENDED;
fiber->stack_bottom->prev_execute_data = NULL;
zend_fiber_suspend_from(fiber);
if (fiber->flags & ZEND_FIBER_FLAG_DESTROYED) {
// This occurs when the fiber is GC'ed while suspended.
zend_throw_graceful_exit();
RETURN_THROWS();
}
fiber->status = ZEND_FIBER_STATUS_RUNNING;
if (fiber->exception) {
zval *exception = fiber->exception;
fiber->exception = NULL;
zend_throw_exception_object(exception);
RETURN_THROWS();
if (fiber->status == ZEND_FIBER_STATUS_DEAD) {
RETURN_NULL();
}
RETVAL_COPY_VALUE(&fiber->value);
@ -537,29 +470,46 @@ ZEND_METHOD(Fiber, suspend)
Z_PARAM_ZVAL(value);
ZEND_PARSE_PARAMETERS_END();
zend_fiber_suspend(value, return_value);
}
ZEND_API void zend_fiber_resume(zend_fiber *fiber, zval *value, zval *return_value)
{
if (UNEXPECTED(fiber->status != ZEND_FIBER_STATUS_SUSPENDED)) {
zend_throw_error(zend_ce_fiber_error, "Cannot resume a fiber that is not suspended");
if (UNEXPECTED(EG(current_fiber)->kind != zend_ce_fiber)) {
zend_throw_error(zend_ce_fiber_error, "Cannot suspend outside of a fiber");
RETURN_THROWS();
}
zend_fiber *fiber = zend_fiber_from_context(EG(current_fiber));
zend_fiber_context *caller = fiber->caller;
if (UNEXPECTED(fiber->flags & ZEND_FIBER_FLAG_DESTROYED)) {
zend_throw_error(zend_ce_fiber_error, "Cannot suspend in a force-closed fiber");
RETURN_THROWS();
}
ZEND_ASSERT(fiber->status == ZEND_FIBER_STATUS_RUNNING);
ZEND_ASSERT(caller != NULL);
if (value) {
ZVAL_COPY(&fiber->value, value);
} else {
ZVAL_NULL(&fiber->value);
}
fiber->status = ZEND_FIBER_STATUS_RUNNING;
fiber->stack_bottom->prev_execute_data = EG(current_execute_data);
fiber->caller = NULL;
fiber->execute_data = EG(current_execute_data);
fiber->stack_bottom->prev_execute_data = NULL;
zend_fiber_switch_to(fiber);
zend_fiber_switch_context(caller);
if (fiber->status == ZEND_FIBER_STATUS_DEAD) {
RETURN_NULL();
if (fiber->flags & ZEND_FIBER_FLAG_DESTROYED) {
// This occurs when the fiber is GC'ed while suspended.
zend_throw_graceful_exit();
RETURN_THROWS();
}
if (fiber->exception) {
zval *exception = fiber->exception;
fiber->exception = NULL;
zend_throw_exception_object(exception);
RETURN_THROWS();
}
RETVAL_COPY_VALUE(&fiber->value);
@ -578,23 +528,21 @@ ZEND_METHOD(Fiber, resume)
fiber = (zend_fiber *) Z_OBJ_P(getThis());
zend_fiber_resume(fiber, value, return_value);
}
ZEND_API void zend_fiber_throw(zend_fiber *fiber, zval *exception, zval *return_value)
{
if (UNEXPECTED(fiber->status != ZEND_FIBER_STATUS_SUSPENDED)) {
if (UNEXPECTED(fiber->status != ZEND_FIBER_STATUS_SUSPENDED || fiber->caller != NULL)) {
zend_throw_error(zend_ce_fiber_error, "Cannot resume a fiber that is not suspended");
RETURN_THROWS();
}
Z_ADDREF_P(exception);
fiber->exception = exception;
if (value) {
ZVAL_COPY(&fiber->value, value);
} else {
ZVAL_NULL(&fiber->value);
}
fiber->status = ZEND_FIBER_STATUS_RUNNING;
fiber->caller = EG(current_fiber);
fiber->stack_bottom->prev_execute_data = EG(current_execute_data);
zend_fiber_switch_to(fiber);
zend_fiber_switch_context(zend_fiber_get_context(fiber));
if (fiber->status == ZEND_FIBER_STATUS_DEAD) {
RETURN_NULL();
@ -615,7 +563,25 @@ ZEND_METHOD(Fiber, throw)
fiber = (zend_fiber *) Z_OBJ_P(getThis());
zend_fiber_throw(fiber, exception, return_value);
if (UNEXPECTED(fiber->status != ZEND_FIBER_STATUS_SUSPENDED || fiber->caller != NULL)) {
zend_throw_error(zend_ce_fiber_error, "Cannot resume a fiber that is not suspended");
RETURN_THROWS();
}
Z_ADDREF_P(exception);
fiber->exception = exception;
fiber->caller = EG(current_fiber);
fiber->stack_bottom->prev_execute_data = EG(current_execute_data);
zend_fiber_switch_context(zend_fiber_get_context(fiber));
if (fiber->status == ZEND_FIBER_STATUS_DEAD) {
RETURN_NULL();
}
RETVAL_COPY_VALUE(&fiber->value);
ZVAL_UNDEF(&fiber->value);
}
ZEND_METHOD(Fiber, isStarted)
@ -637,7 +603,7 @@ ZEND_METHOD(Fiber, isSuspended)
fiber = (zend_fiber *) Z_OBJ_P(getThis());
RETURN_BOOL(fiber->status == ZEND_FIBER_STATUS_SUSPENDED);
RETURN_BOOL(fiber->status == ZEND_FIBER_STATUS_SUSPENDED && fiber->caller == NULL);
}
ZEND_METHOD(Fiber, isRunning)
@ -648,7 +614,7 @@ ZEND_METHOD(Fiber, isRunning)
fiber = (zend_fiber *) Z_OBJ_P(getThis());
RETURN_BOOL(fiber->status == ZEND_FIBER_STATUS_RUNNING);
RETURN_BOOL(fiber->status == ZEND_FIBER_STATUS_RUNNING || fiber->caller != NULL);
}
ZEND_METHOD(Fiber, isTerminated)

View file

@ -89,6 +89,8 @@ struct _zend_fiber_context {
/* Zend VM state that needs to be captured / restored during fiber context switch. */
typedef struct _zend_fiber_vm_state {
zend_vm_stack vm_stack;
zval *vm_stack_top;
zval *vm_stack_end;
size_t vm_stack_page_size;
zend_execute_data *current_execute_data;
int error_reporting;
@ -100,12 +102,12 @@ struct _zend_fiber {
/* PHP object handle. */
zend_object std;
/* Fiber that resumed us. */
zend_fiber_context *caller;
/* Fiber context fields (embedded to avoid memory allocation). */
ZEND_FIBER_CONTEXT_FIELDS;
/* Fiber that resumed us. */
zend_fiber_context *caller;
/* Callback and info / cache to be used when fiber is started. */
zend_fcall_info fci;
zend_fcall_info_cache fci_cache;
@ -123,17 +125,10 @@ struct _zend_fiber {
zval value;
};
/* These functions create and manipulate a Fiber object, allowing any internal function to start, resume, or suspend a fiber. */
ZEND_API zend_fiber *zend_fiber_create(const zend_fcall_info *fci, const zend_fcall_info_cache *fci_cache);
ZEND_API void zend_fiber_start(zend_fiber *fiber, zval *params, uint32_t param_count, zend_array *named_params, zval *return_value);
ZEND_API void zend_fiber_suspend(zval *value, zval *return_value);
ZEND_API void zend_fiber_resume(zend_fiber *fiber, zval *value, zval *return_value);
ZEND_API void zend_fiber_throw(zend_fiber *fiber, zval *exception, zval *return_value);
/* These functions may be used to create custom fibers (coroutines) using the bundled fiber switching context. */
/* These functions may be used to create custom fiber objects using the bundled fiber switching context. */
ZEND_API bool zend_fiber_init_context(zend_fiber_context *context, void *kind, zend_fiber_coroutine coroutine, size_t stack_size);
ZEND_API void zend_fiber_destroy_context(zend_fiber_context *context);
ZEND_API void zend_fiber_switch_context(zend_fiber_context *to);
ZEND_API zend_fiber_context *zend_fiber_switch_context(zend_fiber_context *to);
END_EXTERN_C()
@ -152,8 +147,8 @@ static zend_always_inline zend_fiber_context *zend_fiber_get_context(zend_fiber
static zend_always_inline void zend_fiber_capture_vm_state(zend_fiber_vm_state *state)
{
state->vm_stack = EG(vm_stack);
state->vm_stack->top = EG(vm_stack_top);
state->vm_stack->end = EG(vm_stack_end);
state->vm_stack_top = EG(vm_stack_top);
state->vm_stack_end = EG(vm_stack_end);
state->vm_stack_page_size = EG(vm_stack_page_size);
state->current_execute_data = EG(current_execute_data);
state->error_reporting = EG(error_reporting);
@ -164,8 +159,8 @@ static zend_always_inline void zend_fiber_capture_vm_state(zend_fiber_vm_state *
static zend_always_inline void zend_fiber_restore_vm_state(zend_fiber_vm_state *state)
{
EG(vm_stack) = state->vm_stack;
EG(vm_stack_top) = state->vm_stack->top;
EG(vm_stack_end) = state->vm_stack->end;
EG(vm_stack_top) = state->vm_stack_top;
EG(vm_stack_end) = state->vm_stack_end;
EG(vm_stack_page_size) = state->vm_stack_page_size;
EG(current_execute_data) = state->current_execute_data;
EG(error_reporting) = state->error_reporting;

View file

@ -436,7 +436,12 @@ static void fiber_enter_observer(zend_fiber_context *from, zend_fiber_context *t
if (ZT_G(observer_fiber_switch)) {
if (to->status == ZEND_FIBER_STATUS_INIT) {
php_printf("<init '%p'>\n", to);
} else if (to->status == ZEND_FIBER_STATUS_RUNNING && from->status == ZEND_FIBER_STATUS_RUNNING) {
} else if (to->kind == zend_ce_fiber) {
zend_fiber *fiber = zend_fiber_from_context(to);
if (fiber->caller != from) {
return;
}
if (to->flags & ZEND_FIBER_FLAG_DESTROYED) {
php_printf("<destroying '%p'>\n", to);
} else if (to->status != ZEND_FIBER_STATUS_DEAD) {
@ -449,9 +454,7 @@ static void fiber_enter_observer(zend_fiber_context *from, zend_fiber_context *t
static void fiber_suspend_observer(zend_fiber_context *from, zend_fiber_context *to)
{
if (ZT_G(observer_fiber_switch)) {
if (from->status == ZEND_FIBER_STATUS_SUSPENDED) {
php_printf("<suspend '%p'>\n", from);
} else if (from->status == ZEND_FIBER_STATUS_DEAD) {
if (from->status == ZEND_FIBER_STATUS_DEAD) {
if (from->flags & ZEND_FIBER_FLAG_THREW) {
php_printf("<threw '%p'>\n", from);
} else if (from->flags & ZEND_FIBER_FLAG_DESTROYED) {
@ -459,6 +462,11 @@ static void fiber_suspend_observer(zend_fiber_context *from, zend_fiber_context
} else {
php_printf("<returned '%p'>\n", from);
}
} else if (from->kind == zend_ce_fiber) {
zend_fiber *fiber = zend_fiber_from_context(from);
if (fiber->caller == NULL) {
php_printf("<suspend '%p'>\n", from);
}
}
}
}