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:
25
Zend/tests/fibers/out-of-memory-in-fiber.phpt
Normal file
25
Zend/tests/fibers/out-of-memory-in-fiber.phpt
Normal 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
|
||||
29
Zend/tests/fibers/out-of-memory-in-nested-fiber.phpt
Normal file
29
Zend/tests/fibers/out-of-memory-in-nested-fiber.phpt
Normal 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
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user