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