Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions Zend/tests/fibers/destructors_013.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
--TEST--
Fibers in destructors 013: Exception thrown by a destructor called by the GC is thrown by the statement that triggered the GC
--INI--
zend.enable_gc=1
--FILE--
<?php

class Cycle {
public $self;
public function __construct() {
$this->self = $this;
}
public function __destruct() {
echo "Cycle::__destruct\n";
throw new Exception('Cycle::__destruct');
}
}

$objects = [];
for ($i = 0; $i < 20000; $i++) {
$objects[] = new stdClass();
}

function f() {
global $objects;
try {
$copies = [...$objects];
new Cycle();
// Releasing the copies fills the GC root buffer
$copies = null;
echo "Not reached\n";
} catch (Exception $e) {
echo 'Caught: ', $e->getMessage(), "\n";
}
}

$fiber = new Fiber(function () {
try {
f();
} catch (Exception $e) {
echo 'Escaped: ', $e->getMessage(), "\n";
}
});
$fiber->start();

?>
--EXPECT--
Cycle::__destruct
Caught: Cycle::__destruct
55 changes: 55 additions & 0 deletions Zend/tests/fibers/destructors_014.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
--TEST--
Fibers in destructors 014: Exception thrown by a destructor called by the GC during exception unwinding is chained
--INI--
zend.enable_gc=1
--FILE--
<?php

class Cycle {
public $self;
public function __construct() {
$this->self = $this;
}
public function __destruct() {
echo "Cycle::__destruct\n";
throw new Exception('Cycle::__destruct');
}
}

$objects = [];
for ($i = 0; $i < 20000; $i++) {
$objects[] = new stdClass();
}

function f() {
global $objects;
// Releasing these copies during unwinding fills the GC root buffer
$copies = [...$objects];
new Cycle();
throw new Exception('f');
}

function g() {
try {
f();
echo "Not reached\n";
} catch (Exception $e) {
echo 'Caught: ', $e->getMessage(), "\n";
echo 'Previous: ', $e->getPrevious()->getMessage(), "\n";
}
}

$fiber = new Fiber(function () {
try {
g();
} catch (Exception $e) {
echo 'Escaped: ', $e->getMessage(), "\n";
}
});
$fiber->start();

?>
--EXPECT--
Cycle::__destruct
Caught: Cycle::__destruct
Previous: f
60 changes: 60 additions & 0 deletions Zend/tests/fibers/destructors_015.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
--TEST--
Fibers in destructors 015: Exception thrown by a destructor called by the GC from internal code
--INI--
zend.enable_gc=1
--FILE--
<?php

class Cycle {
public $self;
public function __construct() {
$this->self = $this;
}
public function __destruct() {
echo "Cycle::__destruct\n";
throw new Exception('Cycle::__destruct');
}
}

$fiber = new Fiber(function () {
try {
new Cycle();
gc_collect_cycles();
echo "Not reached\n";
} catch (Exception $e) {
echo 'Caught: ', $e->getMessage(), "\n";
}
});
$fiber->start();

$objects = [];
for ($i = 0; $i < 20000; $i++) {
$objects[] = new stdClass();
}

function create_fiber() {
global $objects;
$copies = [...$objects];

// The fiber releases the copies after its function returned, which fills the GC root buffer
return new Fiber(function () use ($copies) {
new Cycle();
echo "Return\n";
});
}

$fiber = create_fiber();
try {
$fiber->start();
echo "Not reached\n";
} catch (Exception $e) {
echo 'Caught: ', $e->getMessage(), "\n";
}

?>
--EXPECT--
Cycle::__destruct
Caught: Cycle::__destruct
Return
Cycle::__destruct
Caught: Cycle::__destruct
7 changes: 7 additions & 0 deletions Zend/zend_gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1898,6 +1898,7 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)

zend_object *exception = NULL;
remember_prev_exception(&exception);
zend_object *old_exception = exception;

if (UNEXPECTED(!fiber)) {
fiber = gc_create_destructor_fiber();
Expand Down Expand Up @@ -1932,6 +1933,12 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
}

EG(exception) = exception;

/* Destructors ran in another fiber, out of reach of zend_call_function()'s rethrow */
if (exception && !old_exception && EG(current_execute_data) && EG(current_execute_data)->func
&& ZEND_USER_CODE(EG(current_execute_data)->func->common.type)) {
zend_rethrow_exception(EG(current_execute_data));
}
}

ZEND_API int zend_gc_collect_cycles(void)
Expand Down
Loading