Skip to content

Commit bfd8fbd

Browse files
committed
Call destructors in fiber only if necessary
Destructors can not suspend GC if the GC is invoked in the main context. In this case it is not necessary to call destructors in a Fiber.
1 parent 97ceb2d commit bfd8fbd

File tree

3 files changed

+94
-59
lines changed

3 files changed

+94
-59
lines changed

Zend/tests/fibers/destructors_004.phpt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ $f->start();
5151
Exception: 1 exception in %s:%d
5252
Stack trace:
5353
#0 [internal function]: Cycle->__destruct()
54-
#1 [internal function]: gc_call_destructors()
54+
#1 [internal function]: gc_destructor_fiber()
5555
#2 %s(%d): gc_collect_cycles()
5656
#3 [internal function]: {closure}()
5757
#4 %s(%d): Fiber->start()
@@ -60,7 +60,7 @@ Stack trace:
6060
Next Exception: 2 exception in %s:%d
6161
Stack trace:
6262
#0 [internal function]: Cycle->__destruct()
63-
#1 [internal function]: gc_call_destructors()
63+
#1 [internal function]: gc_destructor_fiber()
6464
#2 %s(%d): gc_collect_cycles()
6565
#3 [internal function]: {closure}()
6666
#4 %s(%d): Fiber->start()
@@ -70,7 +70,7 @@ Stack trace:
7070
Fatal error: Uncaught Exception: 0 exception in %s:%d
7171
Stack trace:
7272
#0 [internal function]: Cycle->__destruct()
73-
#1 [internal function]: gc_call_destructors()
73+
#1 [internal function]: gc_destructor_fiber()
7474
#2 %s(%d): Fiber->resume()
7575
#3 [internal function]: {closure}()
7676
#4 %s(%d): Fiber->start()

Zend/tests/gc_030.phpt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,12 @@ gc_collect_cycles();
2323
Fatal error: Uncaught Exception: foobar in %sgc_030.php:%d
2424
Stack trace:
2525
#0 [internal function]: foo->__destruct()
26-
#1 [internal function]: gc_call_destructors()
27-
#2 %sgc_030.php(%d): gc_collect_cycles()
28-
#3 {main}
26+
#1 %sgc_030.php(%d): gc_collect_cycles()
27+
#2 {main}
2928

3029
Next Exception: foobar in %sgc_030.php:%d
3130
Stack trace:
3231
#0 [internal function]: foo->__destruct()
33-
#1 [internal function]: gc_call_destructors()
34-
#2 %sgc_030.php(%d): gc_collect_cycles()
35-
#3 {main}
32+
#1 %sgc_030.php(%d): gc_collect_cycles()
33+
#2 {main}
3634
thrown in %sgc_030.php on line %d

Zend/zend_gc.c

Lines changed: 87 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
#include "zend_errors.h"
7373
#include "zend_fibers.h"
7474
#include "zend_hrtime.h"
75+
#include "zend_portability.h"
7576
#include "zend_types.h"
7677
#include "zend_weakrefs.h"
7778
#include "zend_string.h"
@@ -270,6 +271,7 @@ typedef struct _zend_gc_globals {
270271
zend_hrtime_t free_time;
271272

272273
uint32_t dtor_idx; /* root buffer index */
274+
uint32_t dtor_end;
273275
zend_fiber *dtor_fiber;
274276
bool dtor_fiber_running;
275277

@@ -498,6 +500,7 @@ static void gc_globals_ctor_ex(zend_gc_globals *gc_globals)
498500
gc_globals->activated_at = 0;
499501

500502
gc_globals->dtor_idx = GC_FIRST_ROOT;
503+
gc_globals->dtor_end = 0;
501504
gc_globals->dtor_fiber = NULL;
502505
gc_globals->dtor_fiber_running = false;
503506

@@ -545,6 +548,7 @@ void gc_reset(void)
545548
GC_G(free_time) = 0;
546549

547550
GC_G(dtor_idx) = GC_FIRST_ROOT;
551+
GC_G(dtor_end) = 0;
548552
GC_G(dtor_fiber) = NULL;
549553
GC_G(dtor_fiber_running) = false;
550554

@@ -1792,7 +1796,61 @@ static void zend_get_gc_buffer_release(void);
17921796
static void zend_gc_check_root_tmpvars(void);
17931797
static void zend_gc_remove_root_tmpvars(void);
17941798

1795-
static zend_internal_function gc_call_destructors_fn;
1799+
static zend_internal_function gc_destructor_fiber;
1800+
1801+
static ZEND_COLD ZEND_NORETURN void gc_create_destructor_fiber_error(void)
1802+
{
1803+
zend_error_noreturn(E_ERROR, "Unable to create destructor fiber");
1804+
}
1805+
1806+
static ZEND_COLD ZEND_NORETURN void gc_start_destructor_fiber_error(void)
1807+
{
1808+
zend_error_noreturn(E_ERROR, "Unable to start destructor fiber");
1809+
}
1810+
1811+
static zend_always_inline zend_result gc_call_destructors(uint32_t idx, uint32_t end, zend_fiber *fiber)
1812+
{
1813+
gc_root_buffer *current;
1814+
zend_refcounted *p;
1815+
1816+
/* The root buffer might be reallocated during destructors calls,
1817+
* make sure to reload pointers as necessary. */
1818+
while (idx != end) {
1819+
current = GC_IDX2PTR(idx);
1820+
if (GC_IS_DTOR_GARBAGE(current->ref)) {
1821+
p = GC_GET_PTR(current->ref);
1822+
/* Mark this is as a normal root for the next GC run */
1823+
current->ref = p;
1824+
/* Double check that the destructor hasn't been called yet. It
1825+
* could have already been invoked indirectly by some other
1826+
* destructor. */
1827+
if (!(OBJ_FLAGS(p) & IS_OBJ_DESTRUCTOR_CALLED)) {
1828+
if (fiber != NULL) {
1829+
GC_G(dtor_idx) = idx;
1830+
}
1831+
zend_object *obj = (zend_object*)p;
1832+
GC_TRACE_REF(obj, "calling destructor");
1833+
GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED);
1834+
GC_ADDREF(obj);
1835+
obj->handlers->dtor_obj(obj);
1836+
GC_TRACE_REF(obj, "returned from destructor");
1837+
GC_DELREF(obj);
1838+
if (UNEXPECTED(fiber != NULL && GC_G(dtor_fiber) != fiber)) {
1839+
/* We resumed after suspension */
1840+
gc_check_possible_root((zend_refcounted*)&obj->gc);
1841+
1842+
GC_DELREF(&fiber->std);
1843+
gc_check_possible_root((zend_refcounted*)&fiber->std.gc);
1844+
1845+
return FAILURE;
1846+
}
1847+
}
1848+
}
1849+
idx++;
1850+
}
1851+
1852+
return SUCCESS;
1853+
}
17961854

17971855
static zend_fiber *gc_create_destructor_fiber(void)
17981856
{
@@ -1801,46 +1859,48 @@ static zend_fiber *gc_create_destructor_fiber(void)
18011859

18021860
GC_TRACE("starting destructor fiber");
18031861

1804-
if (object_init_ex(&zobj, zend_ce_fiber) != SUCCESS) {
1805-
zend_error_noreturn(E_ERROR, "Unable to create destructor fiber");
1862+
if (UNEXPECTED(object_init_ex(&zobj, zend_ce_fiber) == FAILURE)) {
1863+
gc_create_destructor_fiber_error();
18061864
}
18071865

18081866
fiber = (zend_fiber *)Z_OBJ(zobj);
18091867
fiber->fci.size = sizeof(fiber->fci);
1810-
fiber->fci_cache.function_handler = (zend_function*) &gc_call_destructors_fn;
1868+
fiber->fci_cache.function_handler = (zend_function*) &gc_destructor_fiber;
18111869

18121870
GC_G(dtor_fiber) = fiber;
18131871

1814-
if (zend_fiber_start(fiber, NULL) == FAILURE) {
1815-
zend_error_noreturn(E_ERROR, "Unable to start destructor fiber");
1872+
if (UNEXPECTED(zend_fiber_start(fiber, NULL) == FAILURE)) {
1873+
gc_start_destructor_fiber_error();
18161874
}
18171875

18181876
return fiber;
18191877
}
18201878

1821-
static void gc_call_destructors(void)
1879+
static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
18221880
{
18231881
ZEND_ASSERT(!GC_G(dtor_fiber_running));
18241882

18251883
zend_fiber *fiber = GC_G(dtor_fiber);
18261884

18271885
GC_G(dtor_idx) = GC_FIRST_ROOT;
1886+
GC_G(dtor_end) = GC_G(first_unused);
18281887

1829-
if (!fiber) {
1888+
if (UNEXPECTED(!fiber)) {
18301889
fiber = gc_create_destructor_fiber();
18311890
} else {
18321891
zend_fiber_resume(fiber, NULL, NULL);
18331892
}
18341893

18351894
for (;;) {
18361895
/* At this point, fiber has executed until suspension */
1837-
GC_TRACE("returned from destructor fiber");
1896+
GC_TRACE("resumed from destructor fiber");
18381897

18391898
if (UNEXPECTED(GC_G(dtor_fiber_running))) {
18401899
/* Fiber was suspended by a destructor. Start a new one for the
18411900
* remaining destructors. */
18421901
GC_TRACE("destructor fiber suspended by destructor");
18431902
GC_G(dtor_fiber) = NULL;
1903+
GC_G(dtor_idx)++;
18441904
fiber = gc_create_destructor_fiber();
18451905
continue;
18461906
} else {
@@ -1951,7 +2011,11 @@ ZEND_API int zend_gc_collect_cycles(void)
19512011

19522012
/* Actually call destructors. */
19532013
zend_hrtime_t dtor_start_time = zend_hrtime();
1954-
gc_call_destructors();
2014+
if (EXPECTED(!EG(active_fiber))) {
2015+
gc_call_destructors(GC_FIRST_ROOT, end, NULL);
2016+
} else {
2017+
gc_call_destructors_in_fiber(end);
2018+
}
19552019
GC_G(dtor_time) += zend_hrtime() - dtor_start_time;
19562020

19572021
if (GC_G(gc_protected)) {
@@ -2167,13 +2231,12 @@ size_t zend_gc_globals_size(void)
21672231
}
21682232
#endif
21692233

2170-
ZEND_FUNCTION(gc_call_destructors)
2234+
static ZEND_FUNCTION(gc_destructor_fiber)
21712235
{
21722236
uint32_t idx, end;
2173-
gc_root_buffer *current;
2174-
zend_refcounted *p;
21752237

21762238
zend_fiber *fiber = GC_G(dtor_fiber);
2239+
ZEND_ASSERT(fiber != NULL);
21772240
ZEND_ASSERT(fiber == EG(active_fiber));
21782241

21792242
for (;;) {
@@ -2182,41 +2245,15 @@ ZEND_FUNCTION(gc_call_destructors)
21822245
/* The root buffer might be reallocated during destructors calls,
21832246
* make sure to reload pointers as necessary. */
21842247
idx = GC_G(dtor_idx);
2185-
end = GC_G(first_unused);
2186-
while (idx != end) {
2187-
current = GC_IDX2PTR(idx);
2188-
if (GC_IS_DTOR_GARBAGE(current->ref)) {
2189-
p = GC_GET_PTR(current->ref);
2190-
/* Mark this is as a normal root for the next GC run */
2191-
current->ref = p;
2192-
/* Double check that the destructor hasn't been called yet. It
2193-
* could have already been invoked indirectly by some other
2194-
* destructor. */
2195-
if (!(OBJ_FLAGS(p) & IS_OBJ_DESTRUCTOR_CALLED)) {
2196-
GC_G(dtor_idx) = idx;
2197-
zend_object *obj = (zend_object*)p;
2198-
GC_TRACE_REF(obj, "calling destructor");
2199-
GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED);
2200-
GC_ADDREF(obj);
2201-
obj->handlers->dtor_obj(obj);
2202-
GC_TRACE_REF(obj, "returned from destructor");
2203-
GC_DELREF(obj);
2204-
if (UNEXPECTED(GC_G(dtor_fiber) != fiber)) {
2205-
/* We resumed after suspension */
2206-
gc_check_possible_root((zend_refcounted*)&obj->gc);
2207-
2208-
GC_DELREF(&fiber->std);
2209-
gc_check_possible_root((zend_refcounted*)&fiber->std.gc);
2210-
2211-
return;
2212-
}
2213-
}
2214-
}
2215-
idx++;
2248+
end = GC_G(dtor_end);
2249+
if (UNEXPECTED(gc_call_destructors(idx, end, fiber) == FAILURE)) {
2250+
/* We resumed after being suspended by a destructor */
2251+
return;
22162252
}
22172253

2254+
/* We have called all destructors. Suspend fiber until the next GC run
2255+
*/
22182256
GC_G(dtor_fiber_running) = false;
2219-
22202257
zend_fiber_suspend(fiber, NULL, NULL);
22212258

22222259
if (UNEXPECTED(fiber->flags & ZEND_FIBER_FLAG_DESTROYED)) {
@@ -2228,16 +2265,16 @@ ZEND_FUNCTION(gc_call_destructors)
22282265
}
22292266
}
22302267

2231-
static zend_internal_function gc_call_destructors_fn = {
2268+
static zend_internal_function gc_destructor_fiber = {
22322269
.type = ZEND_INTERNAL_FUNCTION,
22332270
.fn_flags = ZEND_ACC_PUBLIC,
2234-
.handler = ZEND_FN(gc_call_destructors),
2271+
.handler = ZEND_FN(gc_destructor_fiber),
22352272
};
22362273

22372274
void gc_init(void)
22382275
{
2239-
gc_call_destructors_fn.function_name = zend_string_init_interned(
2240-
"gc_call_destructors",
2241-
strlen("gc_call_destructors"),
2276+
gc_destructor_fiber.function_name = zend_string_init_interned(
2277+
"gc_destructor_fiber",
2278+
strlen("gc_destructor_fiber"),
22422279
true);
22432280
}

0 commit comments

Comments
 (0)