@@ -251,6 +251,39 @@ typedef struct _gc_root_buffer {
251
251
zend_refcounted * ref ;
252
252
} gc_root_buffer ;
253
253
254
+ typedef struct _gc_stack gc_stack ;
255
+
256
+ #define GC_STACK_SEGMENT_SIZE (((4096 - ZEND_MM_OVERHEAD) / sizeof(void*)) - 2)
257
+
258
+ struct _gc_stack {
259
+ gc_stack * prev ;
260
+ gc_stack * next ;
261
+ zend_refcounted * data [GC_STACK_SEGMENT_SIZE ];
262
+ };
263
+
264
+ #ifdef PHP_ASYNC_API
265
+
266
+ typedef enum {
267
+ GC_ASYNC_STATE_NONE = 0 ,
268
+ GC_ASYNC_STATE_INIT , // initial state
269
+ GC_ASYNC_STATE_RUNNING , // GC is running
270
+ GC_ASYNC_STATE_CONTINUE // GC is called from GC coroutine
271
+ } gc_async_state_t ;
272
+
273
+ typedef struct {
274
+ gc_async_state_t state ; // state
275
+ int total_count ; // total freed objects in this GC run
276
+ int count ; // freed objects in this GC run
277
+
278
+ uint32_t gc_flags ; // GC_HAS_DESTRUCTORS, etc.
279
+ bool should_rerun_gc ; // run GC again after destructors
280
+ bool did_rerun_gc ; // guard against endless reruns
281
+
282
+ zend_hrtime_t start_time ; // start of full GC pass
283
+ zend_hrtime_t dtor_start_time ; // start of dtor phase
284
+ } gc_async_context_t ;
285
+ #endif
286
+
254
287
typedef struct _zend_gc_globals {
255
288
gc_root_buffer * buf ; /* preallocated arrays of buffers */
256
289
@@ -278,9 +311,11 @@ typedef struct _zend_gc_globals {
278
311
zend_fiber * dtor_fiber ;
279
312
bool dtor_fiber_running ;
280
313
#ifdef PHP_ASYNC_API
281
- zend_coroutine_t * dtor_coroutine ;
282
- zend_async_scope_t * dtor_scope ;
283
- zend_async_microtask_t * microtask ;
314
+ gc_async_context_t async_context ; /* async context for gc */
315
+ gc_stack * gc_stack ; /* local mark/scan stack */
316
+ zend_coroutine_t * dtor_coroutine ;
317
+ zend_async_scope_t * dtor_scope ;
318
+ zend_async_microtask_t * microtask ;
284
319
#endif
285
320
286
321
#if GC_BENCH
@@ -316,17 +351,6 @@ static zend_gc_globals gc_globals;
316
351
# define GC_BENCH_PEAK (peak , counter )
317
352
#endif
318
353
319
-
320
- #define GC_STACK_SEGMENT_SIZE (((4096 - ZEND_MM_OVERHEAD) / sizeof(void*)) - 2)
321
-
322
- typedef struct _gc_stack gc_stack ;
323
-
324
- struct _gc_stack {
325
- gc_stack * prev ;
326
- gc_stack * next ;
327
- zend_refcounted * data [GC_STACK_SEGMENT_SIZE ];
328
- };
329
-
330
354
#define GC_STACK_DCL (init ) \
331
355
gc_stack *_stack = init; \
332
356
size_t _top = 0;
@@ -516,6 +540,8 @@ static void gc_globals_ctor_ex(zend_gc_globals *gc_globals)
516
540
gc_globals -> dtor_coroutine = NULL ;
517
541
gc_globals -> dtor_scope = NULL ;
518
542
gc_globals -> microtask = NULL ;
543
+ gc_globals -> async_context .state = GC_ASYNC_STATE_NONE ;
544
+ gc_globals -> gc_stack = NULL ;
519
545
#endif
520
546
521
547
#if GC_BENCH
@@ -542,6 +568,12 @@ void gc_globals_dtor(void)
542
568
#ifndef ZTS
543
569
root_buffer_dtor (& gc_globals );
544
570
#endif
571
+
572
+ #ifdef PHP_ASYNC_API
573
+ if (GC_G (dtor_scope )) {
574
+ GC_G (dtor_scope ) = NULL ;
575
+ }
576
+ #endif
545
577
}
546
578
547
579
void gc_reset (void )
@@ -1973,9 +2005,34 @@ static void zend_gc_collect_cycles_coroutine(void)
1973
2005
GC_G (microtask ) = task ;
1974
2006
}
1975
2007
2008
+ if (GC_ASYNC_STATE_NONE == GC_G (async_context ).state ) {
2009
+ gc_async_context_t * context = & GC_G (async_context );
2010
+ context -> state = GC_ASYNC_STATE_INIT ;
2011
+ context -> total_count = 0 ;
2012
+ context -> count = 0 ;
2013
+ context -> start_time = zend_hrtime ();
2014
+ context -> should_rerun_gc = 0 ;
2015
+ context -> did_rerun_gc = 0 ;
2016
+ context -> gc_flags = 0 ;
2017
+ } else if (GC_G (async_context ).state == GC_ASYNC_STATE_RUNNING ) {
2018
+ GC_G (async_context ).state = GC_ASYNC_STATE_CONTINUE ;
2019
+ }
2020
+
2021
+ if (GC_G (gc_stack ) == NULL ) {
2022
+ gc_stack * stack = ecalloc (1 , sizeof (gc_stack ));
2023
+ stack -> prev = NULL ;
2024
+ stack -> next = NULL ;
2025
+ GC_G (gc_stack ) = stack ;
2026
+ }
2027
+
1976
2028
ZEND_ASYNC_ADD_MICROTASK (GC_G (microtask ));
1977
2029
zend_gc_collect_cycles ();
1978
2030
2031
+ // Coroutines were separated.
2032
+ if (GC_G (dtor_coroutine ) != ZEND_ASYNC_CURRENT_COROUTINE ) {
2033
+ return ;
2034
+ }
2035
+
1979
2036
if (GC_G (microtask ) != NULL ) {
1980
2037
GC_G (microtask )-> is_cancelled = true;
1981
2038
ZEND_ASYNC_MICROTASK_RELEASE (GC_G (microtask ));
@@ -2006,11 +2063,30 @@ static void coroutine_dispose(zend_coroutine_t *coroutine)
2006
2063
if (coroutine == GC_G (dtor_coroutine )) {
2007
2064
GC_TRACE ("GC coroutine finished" );
2008
2065
GC_G (dtor_coroutine ) = NULL ;
2066
+ GC_G (dtor_scope ) = NULL ;
2009
2067
2010
2068
if (GC_G (microtask ) != NULL ) {
2011
2069
GC_G (microtask )-> is_cancelled = true;
2012
2070
zend_gc_collect_cycles_microtask_dtor (GC_G (microtask ));
2013
2071
}
2072
+
2073
+ if (GC_G (gc_stack ) != NULL ) {
2074
+ gc_stack * stack = GC_G (gc_stack );
2075
+ GC_G (gc_stack ) = NULL ;
2076
+ gc_stack_free (stack );
2077
+ efree (stack );
2078
+ }
2079
+
2080
+ if (GC_G (async_context .state )) {
2081
+ gc_async_context_t * context = & GC_G (async_context );
2082
+ context -> state = GC_ASYNC_STATE_NONE ;
2083
+ context -> total_count = 0 ;
2084
+ context -> count = 0 ;
2085
+ context -> start_time = 0 ;
2086
+ context -> should_rerun_gc = 0 ;
2087
+ context -> did_rerun_gc = 0 ;
2088
+ context -> gc_flags = 0 ;
2089
+ }
2014
2090
}
2015
2091
}
2016
2092
@@ -2074,48 +2150,115 @@ ZEND_API int zend_gc_collect_cycles(void)
2074
2150
2075
2151
#endif
2076
2152
2153
+ #ifdef PHP_ASYNC_API
2154
+ #define GC_COLLECT_TOTAL_COUNT (context->total_count)
2155
+ #define GC_COLLECT_COUNT (context->count)
2156
+ #define GC_COLLECT_START_TIME (context->start_time)
2157
+ #define GC_COLLECT_SHOULD_RERUN_GC (context->should_rerun_gc)
2158
+ #define GC_COLLECT_DID_RERUN_GC (context->did_rerun_gc)
2159
+ #define GC_COLLECT_GC_FLAGS (context->gc_flags)
2160
+ #define GC_COLLECT_STACK (stack)
2161
+ #define GC_COLLECT_FREE_STACK GC_G(gc_stack) = NULL;\
2162
+ gc_stack_free(stack); \
2163
+ efree(stack)
2164
+ #define GC_COLLECT_FINISH_0 GC_G(async_context).state = GC_ASYNC_STATE_NONE; \
2165
+ return 0
2166
+
2167
+ // Someone is trying to invoke GC from within a destructor?
2168
+ // We don’t know what that is, but we have nothing to do here.
2169
+ if (UNEXPECTED (GC_G (async_context ).state == GC_ASYNC_STATE_RUNNING )) {
2170
+ return 0 ;
2171
+ }
2172
+
2173
+ //
2174
+ // We might enter this context from different coroutines, so we don’t initialize anything here.
2175
+ //
2176
+ gc_async_context_t * context = & GC_G (async_context );
2177
+ gc_stack * stack = GC_G (gc_stack );
2178
+
2179
+ if (UNEXPECTED (context -> state == GC_ASYNC_STATE_CONTINUE )) {
2180
+ // If we reach this point, it means the destructor call was interrupted by a suspend() operation,
2181
+ // so we continue the process from the next element.
2182
+ GC_G (async_context ).state = GC_ASYNC_STATE_RUNNING ;
2183
+ GC_G (dtor_idx )++ ;
2184
+ goto continue_calling_destructors ;
2185
+ } else {
2186
+ context -> state = GC_ASYNC_STATE_INIT ;
2187
+ context -> total_count = 0 ;
2188
+ context -> count = 0 ;
2189
+ context -> start_time = zend_hrtime ();
2190
+ context -> should_rerun_gc = 0 ;
2191
+ context -> did_rerun_gc = 0 ;
2192
+ context -> gc_flags = 0 ;
2193
+
2194
+ if (GC_G (gc_stack ) == NULL ) {
2195
+ stack = ecalloc (1 , sizeof (gc_stack ));
2196
+ stack -> prev = NULL ;
2197
+ stack -> next = NULL ;
2198
+ GC_G (gc_stack ) = stack ;
2199
+ } else {
2200
+ stack = GC_G (gc_stack );
2201
+ }
2202
+ }
2203
+
2204
+ context -> state = GC_ASYNC_STATE_RUNNING ;
2205
+
2206
+ #else
2207
+ #define GC_COLLECT_COUNT count
2208
+ #define GC_COLLECT_TOTAL_COUNT total_count
2209
+ #define GC_COLLECT_START_TIME start_time
2210
+ #define GC_COLLECT_SHOULD_RERUN_GC should_rerun_gc
2211
+ #define GC_COLLECT_DID_RERUN_GC did_rerun_gc
2212
+ #define GC_COLLECT_GC_FLAGS gc_flags
2213
+ #define GC_COLLECT_STACK (&stack)
2214
+ #define GC_COLLECT_FREE_STACK gc_stack_free(&stack);
2215
+ #define GC_COLLECT_FINISH_0 return 0
2216
+
2077
2217
int total_count = 0 ;
2078
2218
bool should_rerun_gc = 0 ;
2079
2219
bool did_rerun_gc = 0 ;
2080
2220
2081
2221
zend_hrtime_t start_time = zend_hrtime ();
2222
+ #endif
2223
+
2082
2224
if (GC_G (num_roots ) && !GC_G (gc_active )) {
2083
2225
zend_gc_remove_root_tmpvars ();
2084
2226
}
2085
2227
2086
2228
rerun_gc :
2087
2229
if (GC_G (num_roots )) {
2088
- int count ;
2089
2230
gc_root_buffer * current , * last ;
2090
2231
zend_refcounted * p ;
2091
2232
uint32_t gc_flags = 0 ;
2092
2233
uint32_t idx , end ;
2234
+ #ifndef PHP_ASYNC_API
2235
+ int count ;
2093
2236
gc_stack stack ;
2094
-
2095
2237
stack .prev = NULL ;
2096
2238
stack .next = NULL ;
2239
+ #endif
2097
2240
2098
2241
if (GC_G (gc_active )) {
2099
- GC_G (collector_time ) += zend_hrtime () - start_time ;
2100
- return 0 ;
2242
+ GC_G (collector_time ) += zend_hrtime () - GC_COLLECT_START_TIME ;
2243
+ GC_COLLECT_FINISH_0 ;
2101
2244
}
2102
2245
2103
2246
GC_TRACE ("Collecting cycles" );
2104
2247
GC_G (gc_runs )++ ;
2105
2248
GC_G (gc_active ) = 1 ;
2106
2249
2107
2250
GC_TRACE ("Marking roots" );
2108
- gc_mark_roots (& stack );
2251
+ gc_mark_roots (GC_COLLECT_STACK );
2109
2252
GC_TRACE ("Scanning roots" );
2110
- gc_scan_roots (& stack );
2253
+ gc_scan_roots (GC_COLLECT_STACK );
2111
2254
2112
2255
GC_TRACE ("Collecting roots" );
2113
- count = gc_collect_roots (& gc_flags , & stack );
2256
+ GC_COLLECT_COUNT = gc_collect_roots (& gc_flags , GC_COLLECT_STACK );
2114
2257
2115
2258
if (!GC_G (num_roots )) {
2116
2259
/* nothing to free */
2117
2260
GC_TRACE ("Nothing to free" );
2118
- gc_stack_free ( & stack ) ;
2261
+ GC_COLLECT_FREE_STACK ;
2119
2262
GC_G (gc_active ) = 0 ;
2120
2263
goto finish ;
2121
2264
}
@@ -2130,7 +2273,7 @@ ZEND_API int zend_gc_collect_cycles(void)
2130
2273
* modify any refcounts, so we have no real way to detect this situation
2131
2274
* short of rerunning full GC tracing. What we do instead is to only run
2132
2275
* destructors at this point and automatically re-run GC afterwards. */
2133
- should_rerun_gc = 1 ;
2276
+ GC_COLLECT_SHOULD_RERUN_GC = 1 ;
2134
2277
2135
2278
/* Mark all roots for which a dtor will be invoked as DTOR_GARBAGE. Additionally
2136
2279
* color them purple. This serves a double purpose: First, they should be
@@ -2164,7 +2307,7 @@ ZEND_API int zend_gc_collect_cycles(void)
2164
2307
while (idx != end ) {
2165
2308
if (GC_IS_DTOR_GARBAGE (current -> ref )) {
2166
2309
p = GC_GET_PTR (current -> ref );
2167
- count -= gc_remove_nested_data_from_buffer (p , current , & stack );
2310
+ GC_COLLECT_COUNT -= gc_remove_nested_data_from_buffer (p , current , GC_COLLECT_STACK );
2168
2311
}
2169
2312
current ++ ;
2170
2313
idx ++ ;
@@ -2173,7 +2316,21 @@ ZEND_API int zend_gc_collect_cycles(void)
2173
2316
/* Actually call destructors. */
2174
2317
zend_hrtime_t dtor_start_time = zend_hrtime ();
2175
2318
if (EXPECTED (!EG (active_fiber ))) {
2319
+ #ifdef PHP_ASYNC_API
2320
+ continue_calling_destructors :
2321
+ if (UNEXPECTED (FAILURE == gc_call_destructors (GC_G (dtor_idx ), GC_G (first_unused ), NULL ))) {
2322
+ //
2323
+ // gc_call_destructors returns FAILURE when a destructor interrupts execution,
2324
+ // i.e. calls suspend().
2325
+ // At this point, we are inside a foreign coroutine from which we must immediately exit,
2326
+ // because the process continues elsewhere.
2327
+ //
2328
+ GC_G (dtor_time ) += zend_hrtime () - dtor_start_time ;
2329
+ return GC_COLLECT_TOTAL_COUNT ;
2330
+ }
2331
+ #else
2176
2332
gc_call_destructors (GC_FIRST_ROOT , end , NULL );
2333
+ #endif
2177
2334
} else {
2178
2335
gc_call_destructors_in_fiber (end );
2179
2336
}
@@ -2182,12 +2339,16 @@ ZEND_API int zend_gc_collect_cycles(void)
2182
2339
if (GC_G (gc_protected )) {
2183
2340
/* something went wrong */
2184
2341
zend_get_gc_buffer_release ();
2185
- GC_G (collector_time ) += zend_hrtime () - start_time ;
2186
- return 0 ;
2342
+ GC_G (collector_time ) += zend_hrtime () - GC_COLLECT_START_TIME ;
2343
+ GC_COLLECT_FINISH_0 ;
2187
2344
}
2188
2345
}
2189
2346
2190
- gc_stack_free (& stack );
2347
+ GC_COLLECT_FREE_STACK ;
2348
+
2349
+ #ifdef PHP_ASYNC_API
2350
+ end = GC_G (first_unused );
2351
+ #endif
2191
2352
2192
2353
/* Destroy zvals. The root buffer may be reallocated. */
2193
2354
GC_TRACE ("Destroying zvals" );
@@ -2245,8 +2406,8 @@ ZEND_API int zend_gc_collect_cycles(void)
2245
2406
GC_G (free_time ) += zend_hrtime () - free_start_time ;
2246
2407
2247
2408
GC_TRACE ("Collection finished" );
2248
- GC_G (collected ) += count ;
2249
- total_count += count ;
2409
+ GC_G (collected ) += GC_COLLECT_COUNT ;
2410
+ GC_COLLECT_TOTAL_COUNT += GC_COLLECT_COUNT ;
2250
2411
GC_G (gc_active ) = 0 ;
2251
2412
}
2252
2413
@@ -2255,8 +2416,8 @@ ZEND_API int zend_gc_collect_cycles(void)
2255
2416
/* Objects with destructors were removed from this GC run. Rerun GC right away to clean them
2256
2417
* up. We do this only once: If we encounter more destructors on the second run, we'll not
2257
2418
* run GC another time. */
2258
- if (should_rerun_gc && !did_rerun_gc ) {
2259
- did_rerun_gc = 1 ;
2419
+ if (GC_COLLECT_SHOULD_RERUN_GC && !GC_COLLECT_DID_RERUN_GC ) {
2420
+ GC_COLLECT_DID_RERUN_GC = 1 ;
2260
2421
goto rerun_gc ;
2261
2422
}
2262
2423
@@ -2269,8 +2430,14 @@ ZEND_API int zend_gc_collect_cycles(void)
2269
2430
zend_gc_check_root_tmpvars ();
2270
2431
GC_G (gc_active ) = 0 ;
2271
2432
2272
- GC_G (collector_time ) += zend_hrtime () - start_time ;
2273
- return total_count ;
2433
+ GC_G (collector_time ) += zend_hrtime () - GC_COLLECT_START_TIME ;
2434
+ #ifdef PHP_ASYNC_API
2435
+ GC_G (async_context ).state = GC_ASYNC_STATE_NONE ;
2436
+ if (GC_G (gc_stack ) != NULL ) {
2437
+ GC_COLLECT_FREE_STACK ;
2438
+ }
2439
+ #endif
2440
+ return GC_COLLECT_TOTAL_COUNT ;
2274
2441
}
2275
2442
2276
2443
ZEND_API void zend_gc_get_status (zend_gc_status * status )
0 commit comments