Add proper handling to observe functions in fibers

The current_observed_frame is carried along the fiber context and thus always correct now.

Signed-off-by: Bob Weinand <bobwei9@hotmail.com>
This commit is contained in:
Bob Weinand 2022-07-29 13:32:48 +02:00
parent dc5475c191
commit da94baf31a
6 changed files with 210 additions and 2 deletions

View file

@ -88,6 +88,9 @@ struct _zend_fiber_context {
/* Fiber status. */
zend_fiber_status status;
/* Observer state */
zend_execute_data *top_observed_frame;
/* Reserved for extensions */
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
};

View file

@ -39,7 +39,6 @@ zend_llist zend_observer_fiber_destroy;
int zend_observer_fcall_op_array_extension;
ZEND_TLS zend_execute_data *first_observed_frame;
ZEND_TLS zend_execute_data *current_observed_frame;
// Call during minit/startup ONLY
@ -95,7 +94,6 @@ ZEND_API void zend_observer_post_startup(void)
ZEND_API void zend_observer_activate(void)
{
first_observed_frame = NULL;
current_observed_frame = NULL;
}
@ -321,6 +319,8 @@ ZEND_API void ZEND_FASTCALL zend_observer_fiber_init_notify(zend_fiber_context *
zend_llist_element *element;
zend_observer_fiber_init_handler callback;
initializing->top_observed_frame = NULL;
for (element = zend_observer_fiber_init.head; element; element = element->next) {
callback = *(zend_observer_fiber_init_handler *) element->data;
callback(initializing);
@ -332,10 +332,17 @@ ZEND_API void ZEND_FASTCALL zend_observer_fiber_switch_notify(zend_fiber_context
zend_llist_element *element;
zend_observer_fiber_switch_handler callback;
if (from->status == ZEND_FIBER_STATUS_DEAD) {
zend_observer_fcall_end_all(); // fiber is either finished (call will do nothing) or has bailed out
}
for (element = zend_observer_fiber_switch.head; element; element = element->next) {
callback = *(zend_observer_fiber_switch_handler *) element->data;
callback(from, to);
}
from->top_observed_frame = current_observed_frame;
current_observed_frame = to->top_observed_frame;
}
ZEND_API void ZEND_FASTCALL zend_observer_fiber_destroy_notify(zend_fiber_context *destroying)

View file

@ -756,6 +756,7 @@ PHP_MSHUTDOWN_FUNCTION(zend_test)
PHP_RINIT_FUNCTION(zend_test)
{
zend_hash_init(&ZT_G(global_weakmap), 8, NULL, ZVAL_PTR_DTOR, 0);
ZT_G(observer_nesting_depth) = 0;
return SUCCESS;
}

View file

@ -0,0 +1,59 @@
--TEST--
Observer: Basic function observing in fibers
--EXTENSIONS--
zend_test
--INI--
zend_test.observer.enabled=1
zend_test.observer.observe_all=1
zend_test.observer.fiber_init=1
zend_test.observer.fiber_switch=1
zend_test.observer.fiber_destroy=1
--FILE--
<?php
$fiber = new Fiber(function (): void {
var_dump(1);
Fiber::suspend();
var_dump(2);
});
$fiber->start();
$fiber->resume();
?>
--EXPECTF--
<!-- init '%s' -->
<file '%s'>
<!-- init Fiber::__construct() -->
<Fiber::__construct>
</Fiber::__construct>
<!-- init Fiber::start() -->
<Fiber::start>
<!-- alloc: %s -->
<!-- switching from fiber %s to %s -->
<init '%s'>
<!-- init {closure}() -->
<{closure}>
<!-- init var_dump() -->
<var_dump>
int(1)
</var_dump>
<!-- init Fiber::suspend() -->
<Fiber::suspend>
<!-- switching from fiber %s to %s -->
<suspend '%s'>
</Fiber::start>
<!-- init Fiber::resume() -->
<Fiber::resume>
<!-- switching from fiber %s to %s -->
<resume '%s'>
</Fiber::suspend>
<var_dump>
int(2)
</var_dump>
</{closure}>
<!-- switching from fiber %s to %s -->
<returned '%s'>
<!-- destroy: %s -->
</Fiber::resume>
</file '%s'>

View file

@ -0,0 +1,54 @@
--TEST--
Observer: Function observing in fibers with unfinished fiber
--EXTENSIONS--
zend_test
--INI--
zend_test.observer.enabled=1
zend_test.observer.observe_all=1
zend_test.observer.fiber_init=1
zend_test.observer.fiber_switch=1
zend_test.observer.fiber_destroy=1
--FILE--
<?php
$fiber = new Fiber(function (): void {
var_dump(1);
Fiber::suspend();
var_dump(2);
});
$fiber->start();
?>
--EXPECTF--
<!-- init '%s' -->
<file '%s'>
<!-- init Fiber::__construct() -->
<Fiber::__construct>
</Fiber::__construct>
<!-- init Fiber::start() -->
<Fiber::start>
<!-- alloc: %s -->
<!-- switching from fiber %s to %s -->
<init '%s'>
<!-- init {closure}() -->
<{closure}>
<!-- init var_dump() -->
<var_dump>
int(1)
</var_dump>
<!-- init Fiber::suspend() -->
<Fiber::suspend>
<!-- switching from fiber %s to %s -->
<suspend '%s'>
</Fiber::start>
</file '%s'>
<!-- switching from fiber %s to %s -->
<destroying '%s'>
<!-- Exception: GracefulExit -->
</Fiber::suspend>
<!-- Exception: GracefulExit -->
</{closure}>
<!-- switching from fiber %s to %s -->
<destroyed '%s'>
<!-- destroy: %s -->

View file

@ -0,0 +1,84 @@
--TEST--
Observer: Function observing in fibers with bailout in fiber
--EXTENSIONS--
zend_test
--INI--
zend_test.observer.enabled=1
zend_test.observer.observe_all=1
zend_test.observer.fiber_init=1
zend_test.observer.fiber_switch=1
zend_test.observer.fiber_destroy=1
memory_limit=100M
--FILE--
<?php
$notBailedOutFiber = new Fiber(function (): void {
var_dump(1);
Fiber::suspend();
});
$notBailedOutFiber->start();
$fiber = new Fiber(function (): void {
var_dump(2);
Fiber::suspend();
str_repeat('A', 200_000_000);
});
$fiber->start();
$fiber->resume();
?>
--EXPECTF--
<!-- init '%s' -->
<file '%s'>
<!-- init Fiber::__construct() -->
<Fiber::__construct>
</Fiber::__construct>
<!-- init Fiber::start() -->
<Fiber::start>
<!-- alloc: %s -->
<!-- switching from fiber %s to %s -->
<init '%s'>
<!-- init {closure}() -->
<{closure}>
<!-- init var_dump() -->
<var_dump>
int(1)
</var_dump>
<!-- init Fiber::suspend() -->
<Fiber::suspend>
<!-- switching from fiber %s to %s -->
<suspend '%s'>
</Fiber::start>
<Fiber::__construct>
</Fiber::__construct>
<Fiber::start>
<!-- alloc: %s -->
<!-- switching from fiber %s to %s -->
<init '%s'>
<!-- init {closure}() -->
<{closure}>
<var_dump>
int(2)
</var_dump>
<Fiber::suspend>
<!-- switching from fiber %s to %s -->
<suspend '%s'>
</Fiber::start>
<!-- init Fiber::resume() -->
<Fiber::resume>
<!-- switching from fiber %s to %s -->
<resume '%s'>
</Fiber::suspend>
<!-- init str_repeat() -->
<str_repeat>
Fatal error: Allowed memory size of 104857600 bytes exhausted %s on line %d
</str_repeat>
</{closure}>
<!-- switching from fiber %s to %s -->
<returned '%s'>
<!-- destroy: %s -->
</Fiber::resume>
</file '%s'>