72
72
#include "zend_errors.h"
73
73
#include "zend_fibers.h"
74
74
#include "zend_hrtime.h"
75
+ #include "zend_portability.h"
75
76
#include "zend_types.h"
76
77
#include "zend_weakrefs.h"
77
78
#include "zend_string.h"
@@ -270,6 +271,7 @@ typedef struct _zend_gc_globals {
270
271
zend_hrtime_t free_time;
271
272
272
273
uint32_t dtor_idx; /* root buffer index */
274
+ uint32_t dtor_end;
273
275
zend_fiber *dtor_fiber;
274
276
bool dtor_fiber_running;
275
277
@@ -498,6 +500,7 @@ static void gc_globals_ctor_ex(zend_gc_globals *gc_globals)
498
500
gc_globals->activated_at = 0;
499
501
500
502
gc_globals->dtor_idx = GC_FIRST_ROOT;
503
+ gc_globals->dtor_end = 0;
501
504
gc_globals->dtor_fiber = NULL;
502
505
gc_globals->dtor_fiber_running = false;
503
506
@@ -545,6 +548,7 @@ void gc_reset(void)
545
548
GC_G(free_time) = 0;
546
549
547
550
GC_G(dtor_idx) = GC_FIRST_ROOT;
551
+ GC_G(dtor_end) = 0;
548
552
GC_G(dtor_fiber) = NULL;
549
553
GC_G(dtor_fiber_running) = false;
550
554
@@ -1792,7 +1796,61 @@ static void zend_get_gc_buffer_release(void);
1792
1796
static void zend_gc_check_root_tmpvars(void);
1793
1797
static void zend_gc_remove_root_tmpvars(void);
1794
1798
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
+ }
1796
1854
1797
1855
static zend_fiber *gc_create_destructor_fiber(void)
1798
1856
{
@@ -1801,46 +1859,48 @@ static zend_fiber *gc_create_destructor_fiber(void)
1801
1859
1802
1860
GC_TRACE("starting destructor fiber");
1803
1861
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( );
1806
1864
}
1807
1865
1808
1866
fiber = (zend_fiber *)Z_OBJ(zobj);
1809
1867
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 ;
1811
1869
1812
1870
GC_G(dtor_fiber) = fiber;
1813
1871
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( );
1816
1874
}
1817
1875
1818
1876
return fiber;
1819
1877
}
1820
1878
1821
- static void gc_call_destructors(void )
1879
+ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end )
1822
1880
{
1823
1881
ZEND_ASSERT(!GC_G(dtor_fiber_running));
1824
1882
1825
1883
zend_fiber *fiber = GC_G(dtor_fiber);
1826
1884
1827
1885
GC_G(dtor_idx) = GC_FIRST_ROOT;
1886
+ GC_G(dtor_end) = GC_G(first_unused);
1828
1887
1829
- if (!fiber) {
1888
+ if (UNEXPECTED( !fiber) ) {
1830
1889
fiber = gc_create_destructor_fiber();
1831
1890
} else {
1832
1891
zend_fiber_resume(fiber, NULL, NULL);
1833
1892
}
1834
1893
1835
1894
for (;;) {
1836
1895
/* At this point, fiber has executed until suspension */
1837
- GC_TRACE("returned from destructor fiber");
1896
+ GC_TRACE("resumed from destructor fiber");
1838
1897
1839
1898
if (UNEXPECTED(GC_G(dtor_fiber_running))) {
1840
1899
/* Fiber was suspended by a destructor. Start a new one for the
1841
1900
* remaining destructors. */
1842
1901
GC_TRACE("destructor fiber suspended by destructor");
1843
1902
GC_G(dtor_fiber) = NULL;
1903
+ GC_G(dtor_idx)++;
1844
1904
fiber = gc_create_destructor_fiber();
1845
1905
continue;
1846
1906
} else {
@@ -1951,7 +2011,11 @@ ZEND_API int zend_gc_collect_cycles(void)
1951
2011
1952
2012
/* Actually call destructors. */
1953
2013
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
+ }
1955
2019
GC_G(dtor_time) += zend_hrtime() - dtor_start_time;
1956
2020
1957
2021
if (GC_G(gc_protected)) {
@@ -2167,13 +2231,12 @@ size_t zend_gc_globals_size(void)
2167
2231
}
2168
2232
#endif
2169
2233
2170
- ZEND_FUNCTION(gc_call_destructors )
2234
+ static ZEND_FUNCTION(gc_destructor_fiber )
2171
2235
{
2172
2236
uint32_t idx, end;
2173
- gc_root_buffer *current;
2174
- zend_refcounted *p;
2175
2237
2176
2238
zend_fiber *fiber = GC_G(dtor_fiber);
2239
+ ZEND_ASSERT(fiber != NULL);
2177
2240
ZEND_ASSERT(fiber == EG(active_fiber));
2178
2241
2179
2242
for (;;) {
@@ -2182,41 +2245,15 @@ ZEND_FUNCTION(gc_call_destructors)
2182
2245
/* The root buffer might be reallocated during destructors calls,
2183
2246
* make sure to reload pointers as necessary. */
2184
2247
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;
2216
2252
}
2217
2253
2254
+ /* We have called all destructors. Suspend fiber until the next GC run
2255
+ */
2218
2256
GC_G(dtor_fiber_running) = false;
2219
-
2220
2257
zend_fiber_suspend(fiber, NULL, NULL);
2221
2258
2222
2259
if (UNEXPECTED(fiber->flags & ZEND_FIBER_FLAG_DESTROYED)) {
@@ -2228,16 +2265,16 @@ ZEND_FUNCTION(gc_call_destructors)
2228
2265
}
2229
2266
}
2230
2267
2231
- static zend_internal_function gc_call_destructors_fn = {
2268
+ static zend_internal_function gc_destructor_fiber = {
2232
2269
.type = ZEND_INTERNAL_FUNCTION,
2233
2270
.fn_flags = ZEND_ACC_PUBLIC,
2234
- .handler = ZEND_FN(gc_call_destructors ),
2271
+ .handler = ZEND_FN(gc_destructor_fiber ),
2235
2272
};
2236
2273
2237
2274
void gc_init(void)
2238
2275
{
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 "),
2242
2279
true);
2243
2280
}
0 commit comments