Skip to content

Commit ccc069d

Browse files
committed
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}.
1 parent ca82476 commit ccc069d

File tree

6 files changed

+84
-64
lines changed

6 files changed

+84
-64
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
Out of Memory in a fiber
3+
--INI--
4+
memory_limit=10K
5+
--SKIPIF--
6+
<?php
7+
if (getenv("USE_ZEND_ALLOC") === "0") {
8+
die("skip Zend MM disabled");
9+
}
10+
?>
11+
--FILE--
12+
<?php
13+
14+
$fiber = new Fiber(function (): void {
15+
$buffer = '';
16+
while (true) {
17+
$buffer .= str_repeat('.', 1 << 10);
18+
}
19+
});
20+
21+
$fiber->start();
22+
23+
?>
24+
--EXPECTF--
25+
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
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
Out of Memory in a nested fiber
3+
--INI--
4+
memory_limit=10K
5+
--SKIPIF--
6+
<?php
7+
if (getenv("USE_ZEND_ALLOC") === "0") {
8+
die("skip Zend MM disabled");
9+
}
10+
?>
11+
--FILE--
12+
<?php
13+
14+
$fiber = new Fiber(function (): void {
15+
$fiber = new Fiber(function (): void {
16+
$buffer = '';
17+
while (true) {
18+
$buffer .= str_repeat('.', 1 << 10);
19+
}
20+
});
21+
22+
$fiber->start();
23+
});
24+
25+
$fiber->start();
26+
27+
?>
28+
--EXPECTF--
29+
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

Zend/zend.c

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,6 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{
775775
executor_globals->exception = NULL;
776776
executor_globals->objects_store.object_buckets = NULL;
777777
executor_globals->current_fiber = NULL;
778-
executor_globals->fiber_error = NULL;
779778
#ifdef ZEND_WIN32
780779
zend_get_windows_version_info(&executor_globals->windows_version_info);
781780
#endif
@@ -1348,11 +1347,6 @@ ZEND_API ZEND_COLD void zend_error_zstr_at(
13481347
zend_stack delayed_oplines_stack;
13491348
int type = orig_type & E_ALL;
13501349

1351-
/* Fatal errors must be handled in {main} */
1352-
if (type & E_FATAL_ERRORS && EG(current_fiber)) {
1353-
zend_error_suspend_fiber(orig_type, error_filename, error_lineno, message);
1354-
}
1355-
13561350
/* If we're executing a function during SCCP, count any warnings that may be emitted,
13571351
* but don't perform any other error handling. */
13581352
if (EG(capture_warnings_during_sccp)) {

Zend/zend_fibers.c

Lines changed: 28 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -58,24 +58,26 @@ extern transfer_t jump_fcontext(fcontext_t to, void *vp);
5858

5959
#define ZEND_FIBER_DEFAULT_PAGE_SIZE 4096
6060

61-
#define ZEND_FIBER_BACKUP_EG(stack, stack_page_size, execute_data, error_reporting, trace_num) do { \
61+
#define ZEND_FIBER_BACKUP_EG(stack, stack_page_size, execute_data, error_reporting, trace_num, bailout) do { \
6262
stack = EG(vm_stack); \
6363
stack->top = EG(vm_stack_top); \
6464
stack->end = EG(vm_stack_end); \
6565
stack_page_size = EG(vm_stack_page_size); \
6666
execute_data = EG(current_execute_data); \
6767
error_reporting = EG(error_reporting); \
6868
trace_num = EG(jit_trace_num); \
69+
bailout = EG(bailout); \
6970
} while (0)
7071

71-
#define ZEND_FIBER_RESTORE_EG(stack, stack_page_size, execute_data, error_reporting, trace_num) do { \
72+
#define ZEND_FIBER_RESTORE_EG(stack, stack_page_size, execute_data, error_reporting, trace_num, bailout) do { \
7273
EG(vm_stack) = stack; \
7374
EG(vm_stack_top) = stack->top; \
7475
EG(vm_stack_end) = stack->end; \
7576
EG(vm_stack_page_size) = stack_page_size; \
7677
EG(current_execute_data) = execute_data; \
7778
EG(error_reporting) = error_reporting; \
7879
EG(jit_trace_num) = trace_num; \
80+
EG(bailout) = bailout; \
7981
} while (0)
8082

8183
#if defined(MAP_STACK) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__)
@@ -241,12 +243,13 @@ static void zend_fiber_suspend(zend_fiber *fiber)
241243
zend_execute_data *execute_data;
242244
int error_reporting;
243245
uint32_t jit_trace_num;
246+
JMP_BUF *bailout;
244247

245-
ZEND_FIBER_BACKUP_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num);
248+
ZEND_FIBER_BACKUP_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num, bailout);
246249

247250
zend_fiber_suspend_context(&fiber->context);
248251

249-
ZEND_FIBER_RESTORE_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num);
252+
ZEND_FIBER_RESTORE_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num, bailout);
250253
}
251254

252255
static void zend_fiber_switch_to(zend_fiber *fiber)
@@ -257,53 +260,30 @@ static void zend_fiber_switch_to(zend_fiber *fiber)
257260
zend_execute_data *execute_data;
258261
int error_reporting;
259262
uint32_t jit_trace_num;
263+
JMP_BUF *bailout;
260264

261265
previous = EG(current_fiber);
262266

263267
zend_observer_fiber_switch_notify(previous, fiber);
264268

265-
ZEND_FIBER_BACKUP_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num);
269+
ZEND_FIBER_BACKUP_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num, bailout);
266270

267271
EG(current_fiber) = fiber;
268272

269273
zend_fiber_switch_context(&fiber->context);
270274

271275
EG(current_fiber) = previous;
272276

273-
ZEND_FIBER_RESTORE_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num);
277+
ZEND_FIBER_RESTORE_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num, bailout);
274278

275279
zend_observer_fiber_switch_notify(fiber, previous);
276280

277-
if (UNEXPECTED(EG(fiber_error)) && fiber->status != ZEND_FIBER_STATUS_SHUTDOWN) {
278-
if (previous) {
279-
zend_fiber_suspend(previous); // Still in fiber, suspend again until in {main}.
280-
abort(); // This fiber should never be resumed.
281-
}
282-
283-
zend_error_info *error = EG(fiber_error);
284-
zend_error_zstr_at(error->type, error->filename, error->lineno, error->message);
281+
if (UNEXPECTED(fiber->status == ZEND_FIBER_STATUS_BAILOUT)) {
282+
// zend_bailout() was called in the fiber, so call it again in the previous fiber or {main}.
283+
zend_bailout();
285284
}
286285
}
287286

288-
ZEND_COLD void zend_error_suspend_fiber(
289-
int orig_type, zend_string *error_filename, uint32_t error_lineno, zend_string *message)
290-
{
291-
ZEND_ASSERT(EG(current_fiber) && "Must be within an active fiber!");
292-
ZEND_ASSERT(orig_type & E_FATAL_ERRORS && "Error type must be fatal");
293-
294-
zend_error_info *error = emalloc(sizeof(zend_error_info));
295-
296-
error->type = orig_type;
297-
error->filename = error_filename;
298-
error->lineno = error_lineno;
299-
error->message = message;
300-
301-
EG(fiber_error) = error;
302-
303-
zend_fiber_suspend(EG(current_fiber));
304-
305-
abort(); // This fiber should never be resumed.
306-
}
307287

308288
static zend_always_inline zend_vm_stack zend_fiber_vm_stack_alloc(size_t size)
309289
{
@@ -348,21 +328,25 @@ static void ZEND_STACK_ALIGNED zend_fiber_execute(zend_fiber_context *context)
348328

349329
fiber->status = ZEND_FIBER_STATUS_RUNNING;
350330

351-
zend_call_function(&fiber->fci, &fiber->fci_cache);
331+
zend_first_try {
332+
zend_call_function(&fiber->fci, &fiber->fci_cache);
352333

353-
zval_ptr_dtor(&fiber->fci.function_name);
334+
zval_ptr_dtor(&fiber->fci.function_name);
354335

355-
if (EG(exception)) {
356-
if (fiber->status == ZEND_FIBER_STATUS_SHUTDOWN) {
357-
if (EXPECTED(zend_is_graceful_exit(EG(exception)) || zend_is_unwind_exit(EG(exception)))) {
358-
zend_clear_exception();
336+
if (EG(exception)) {
337+
if (fiber->status == ZEND_FIBER_STATUS_SHUTDOWN) {
338+
if (EXPECTED(zend_is_graceful_exit(EG(exception)) || zend_is_unwind_exit(EG(exception)))) {
339+
zend_clear_exception();
340+
}
341+
} else {
342+
fiber->status = ZEND_FIBER_STATUS_THREW;
359343
}
360344
} else {
361-
fiber->status = ZEND_FIBER_STATUS_THREW;
345+
fiber->status = ZEND_FIBER_STATUS_RETURNED;
362346
}
363-
} else {
364-
fiber->status = ZEND_FIBER_STATUS_RETURNED;
365-
}
347+
} zend_catch {
348+
fiber->status = ZEND_FIBER_STATUS_BAILOUT;
349+
} zend_end_try();
366350

367351
zend_vm_stack_destroy();
368352
fiber->execute_data = NULL;
@@ -512,13 +496,7 @@ ZEND_METHOD(Fiber, suspend)
512496

513497
if (fiber->status == ZEND_FIBER_STATUS_SHUTDOWN) {
514498
// This occurs when the fiber is GC'ed while suspended.
515-
if (EG(fiber_error)) {
516-
// Throw UnwindExit so finally blocks are not executed on fatal error.
517-
zend_throw_unwind_exit();
518-
} else {
519-
// Otherwise throw GracefulExit to execute finally blocks.
520-
zend_throw_graceful_exit();
521-
}
499+
zend_throw_graceful_exit();
522500
RETURN_THROWS();
523501
}
524502

@@ -718,5 +696,4 @@ void zend_register_fiber_ce(void)
718696
void zend_fiber_init(void)
719697
{
720698
EG(current_fiber) = NULL;
721-
EG(fiber_error) = NULL;
722699
}

Zend/zend_fibers.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,13 @@ static const zend_uchar ZEND_FIBER_STATUS_RUNNING = 0x2;
8383
static const zend_uchar ZEND_FIBER_STATUS_RETURNED = 0x4;
8484
static const zend_uchar ZEND_FIBER_STATUS_THREW = 0x8;
8585
static const zend_uchar ZEND_FIBER_STATUS_SHUTDOWN = 0x10;
86+
static const zend_uchar ZEND_FIBER_STATUS_BAILOUT = 0x20;
8687

87-
static const zend_uchar ZEND_FIBER_STATUS_FINISHED = 0x1c;
88+
static const zend_uchar ZEND_FIBER_STATUS_FINISHED = 0x2c;
8889

8990
ZEND_API zend_bool zend_fiber_init_context(zend_fiber_context *context, zend_fiber_coroutine coroutine, size_t stack_size);
9091
ZEND_API void zend_fiber_destroy_context(zend_fiber_context *context);
9192

92-
ZEND_COLD void zend_error_suspend_fiber(
93-
int orig_type, zend_string *error_filename, uint32_t error_lineno, zend_string *message);
94-
9593
ZEND_API void zend_fiber_switch_context(zend_fiber_context *to);
9694
ZEND_API void zend_fiber_suspend_context(zend_fiber_context *current);
9795

Zend/zend_globals.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -257,9 +257,6 @@ struct _zend_executor_globals {
257257
/* Default fiber C stack size. */
258258
zend_long fiber_stack_size;
259259

260-
/* Pointer to fatal error that occurred in a fiber while switching to {main}. */
261-
zend_error_info *fiber_error;
262-
263260
/* If record_errors is enabled, all emitted diagnostics will be recorded,
264261
* in addition to being processed as usual. */
265262
bool record_errors;

0 commit comments

Comments
 (0)