1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Catch and repeat zend_bailout in fibers

This removes switching to main for fatal errors in fibers in favor of catching any zend_bailout in a fiber and calling zend_bailout again after switching to the previous fiber or {main}.
This commit is contained in:
Aaron Piotrowski
2021-05-06 16:56:42 -05:00
parent ca8247654c
commit ccc069d0bb
6 changed files with 84 additions and 64 deletions

View File

@@ -0,0 +1,25 @@
--TEST--
Out of Memory in a fiber
--INI--
memory_limit=10K
--SKIPIF--
<?php
if (getenv("USE_ZEND_ALLOC") === "0") {
die("skip Zend MM disabled");
}
?>
--FILE--
<?php
$fiber = new Fiber(function (): void {
$buffer = '';
while (true) {
$buffer .= str_repeat('.', 1 << 10);
}
});
$fiber->start();
?>
--EXPECTF--
Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %sout-of-memory-in-fiber.php on line %d

View File

@@ -0,0 +1,29 @@
--TEST--
Out of Memory in a nested fiber
--INI--
memory_limit=10K
--SKIPIF--
<?php
if (getenv("USE_ZEND_ALLOC") === "0") {
die("skip Zend MM disabled");
}
?>
--FILE--
<?php
$fiber = new Fiber(function (): void {
$fiber = new Fiber(function (): void {
$buffer = '';
while (true) {
$buffer .= str_repeat('.', 1 << 10);
}
});
$fiber->start();
});
$fiber->start();
?>
--EXPECTF--
Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %sout-of-memory-in-nested-fiber.php on line %d

View File

@@ -775,7 +775,6 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{
executor_globals->exception = NULL;
executor_globals->objects_store.object_buckets = NULL;
executor_globals->current_fiber = NULL;
executor_globals->fiber_error = NULL;
#ifdef ZEND_WIN32
zend_get_windows_version_info(&executor_globals->windows_version_info);
#endif
@@ -1348,11 +1347,6 @@ ZEND_API ZEND_COLD void zend_error_zstr_at(
zend_stack delayed_oplines_stack;
int type = orig_type & E_ALL;
/* Fatal errors must be handled in {main} */
if (type & E_FATAL_ERRORS && EG(current_fiber)) {
zend_error_suspend_fiber(orig_type, error_filename, error_lineno, message);
}
/* If we're executing a function during SCCP, count any warnings that may be emitted,
* but don't perform any other error handling. */
if (EG(capture_warnings_during_sccp)) {

View File

@@ -58,7 +58,7 @@ extern transfer_t jump_fcontext(fcontext_t to, void *vp);
#define ZEND_FIBER_DEFAULT_PAGE_SIZE 4096
#define ZEND_FIBER_BACKUP_EG(stack, stack_page_size, execute_data, error_reporting, trace_num) do { \
#define ZEND_FIBER_BACKUP_EG(stack, stack_page_size, execute_data, error_reporting, trace_num, bailout) do { \
stack = EG(vm_stack); \
stack->top = EG(vm_stack_top); \
stack->end = EG(vm_stack_end); \
@@ -66,9 +66,10 @@ extern transfer_t jump_fcontext(fcontext_t to, void *vp);
execute_data = EG(current_execute_data); \
error_reporting = EG(error_reporting); \
trace_num = EG(jit_trace_num); \
bailout = EG(bailout); \
} while (0)
#define ZEND_FIBER_RESTORE_EG(stack, stack_page_size, execute_data, error_reporting, trace_num) do { \
#define ZEND_FIBER_RESTORE_EG(stack, stack_page_size, execute_data, error_reporting, trace_num, bailout) do { \
EG(vm_stack) = stack; \
EG(vm_stack_top) = stack->top; \
EG(vm_stack_end) = stack->end; \
@@ -76,6 +77,7 @@ extern transfer_t jump_fcontext(fcontext_t to, void *vp);
EG(current_execute_data) = execute_data; \
EG(error_reporting) = error_reporting; \
EG(jit_trace_num) = trace_num; \
EG(bailout) = bailout; \
} while (0)
#if defined(MAP_STACK) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__)
@@ -241,12 +243,13 @@ static void zend_fiber_suspend(zend_fiber *fiber)
zend_execute_data *execute_data;
int error_reporting;
uint32_t jit_trace_num;
JMP_BUF *bailout;
ZEND_FIBER_BACKUP_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num);
ZEND_FIBER_BACKUP_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num, bailout);
zend_fiber_suspend_context(&fiber->context);
ZEND_FIBER_RESTORE_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num);
ZEND_FIBER_RESTORE_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num, bailout);
}
static void zend_fiber_switch_to(zend_fiber *fiber)
@@ -257,12 +260,13 @@ static void zend_fiber_switch_to(zend_fiber *fiber)
zend_execute_data *execute_data;
int error_reporting;
uint32_t jit_trace_num;
JMP_BUF *bailout;
previous = EG(current_fiber);
zend_observer_fiber_switch_notify(previous, fiber);
ZEND_FIBER_BACKUP_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num);
ZEND_FIBER_BACKUP_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num, bailout);
EG(current_fiber) = fiber;
@@ -270,40 +274,16 @@ static void zend_fiber_switch_to(zend_fiber *fiber)
EG(current_fiber) = previous;
ZEND_FIBER_RESTORE_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num);
ZEND_FIBER_RESTORE_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num, bailout);
zend_observer_fiber_switch_notify(fiber, previous);
if (UNEXPECTED(EG(fiber_error)) && fiber->status != ZEND_FIBER_STATUS_SHUTDOWN) {
if (previous) {
zend_fiber_suspend(previous); // Still in fiber, suspend again until in {main}.
abort(); // This fiber should never be resumed.
}
zend_error_info *error = EG(fiber_error);
zend_error_zstr_at(error->type, error->filename, error->lineno, error->message);
if (UNEXPECTED(fiber->status == ZEND_FIBER_STATUS_BAILOUT)) {
// zend_bailout() was called in the fiber, so call it again in the previous fiber or {main}.
zend_bailout();
}
}
ZEND_COLD void zend_error_suspend_fiber(
int orig_type, zend_string *error_filename, uint32_t error_lineno, zend_string *message)
{
ZEND_ASSERT(EG(current_fiber) && "Must be within an active fiber!");
ZEND_ASSERT(orig_type & E_FATAL_ERRORS && "Error type must be fatal");
zend_error_info *error = emalloc(sizeof(zend_error_info));
error->type = orig_type;
error->filename = error_filename;
error->lineno = error_lineno;
error->message = message;
EG(fiber_error) = error;
zend_fiber_suspend(EG(current_fiber));
abort(); // This fiber should never be resumed.
}
static zend_always_inline zend_vm_stack zend_fiber_vm_stack_alloc(size_t size)
{
@@ -348,21 +328,25 @@ static void ZEND_STACK_ALIGNED zend_fiber_execute(zend_fiber_context *context)
fiber->status = ZEND_FIBER_STATUS_RUNNING;
zend_call_function(&fiber->fci, &fiber->fci_cache);
zend_first_try {
zend_call_function(&fiber->fci, &fiber->fci_cache);
zval_ptr_dtor(&fiber->fci.function_name);
zval_ptr_dtor(&fiber->fci.function_name);
if (EG(exception)) {
if (fiber->status == ZEND_FIBER_STATUS_SHUTDOWN) {
if (EXPECTED(zend_is_graceful_exit(EG(exception)) || zend_is_unwind_exit(EG(exception)))) {
zend_clear_exception();
if (EG(exception)) {
if (fiber->status == ZEND_FIBER_STATUS_SHUTDOWN) {
if (EXPECTED(zend_is_graceful_exit(EG(exception)) || zend_is_unwind_exit(EG(exception)))) {
zend_clear_exception();
}
} else {
fiber->status = ZEND_FIBER_STATUS_THREW;
}
} else {
fiber->status = ZEND_FIBER_STATUS_THREW;
fiber->status = ZEND_FIBER_STATUS_RETURNED;
}
} else {
fiber->status = ZEND_FIBER_STATUS_RETURNED;
}
} zend_catch {
fiber->status = ZEND_FIBER_STATUS_BAILOUT;
} zend_end_try();
zend_vm_stack_destroy();
fiber->execute_data = NULL;
@@ -512,13 +496,7 @@ ZEND_METHOD(Fiber, suspend)
if (fiber->status == ZEND_FIBER_STATUS_SHUTDOWN) {
// This occurs when the fiber is GC'ed while suspended.
if (EG(fiber_error)) {
// Throw UnwindExit so finally blocks are not executed on fatal error.
zend_throw_unwind_exit();
} else {
// Otherwise throw GracefulExit to execute finally blocks.
zend_throw_graceful_exit();
}
zend_throw_graceful_exit();
RETURN_THROWS();
}
@@ -718,5 +696,4 @@ void zend_register_fiber_ce(void)
void zend_fiber_init(void)
{
EG(current_fiber) = NULL;
EG(fiber_error) = NULL;
}

View File

@@ -83,15 +83,13 @@ static const zend_uchar ZEND_FIBER_STATUS_RUNNING = 0x2;
static const zend_uchar ZEND_FIBER_STATUS_RETURNED = 0x4;
static const zend_uchar ZEND_FIBER_STATUS_THREW = 0x8;
static const zend_uchar ZEND_FIBER_STATUS_SHUTDOWN = 0x10;
static const zend_uchar ZEND_FIBER_STATUS_BAILOUT = 0x20;
static const zend_uchar ZEND_FIBER_STATUS_FINISHED = 0x1c;
static const zend_uchar ZEND_FIBER_STATUS_FINISHED = 0x2c;
ZEND_API zend_bool zend_fiber_init_context(zend_fiber_context *context, zend_fiber_coroutine coroutine, size_t stack_size);
ZEND_API void zend_fiber_destroy_context(zend_fiber_context *context);
ZEND_COLD void zend_error_suspend_fiber(
int orig_type, zend_string *error_filename, uint32_t error_lineno, zend_string *message);
ZEND_API void zend_fiber_switch_context(zend_fiber_context *to);
ZEND_API void zend_fiber_suspend_context(zend_fiber_context *current);

View File

@@ -257,9 +257,6 @@ struct _zend_executor_globals {
/* Default fiber C stack size. */
zend_long fiber_stack_size;
/* Pointer to fatal error that occurred in a fiber while switching to {main}. */
zend_error_info *fiber_error;
/* If record_errors is enabled, all emitted diagnostics will be recorded,
* in addition to being processed as usual. */
bool record_errors;