Skip to content

Commit 6e955d2

Browse files
committed
Merge branch 'true-async-api' into true-async-api-stable
2 parents b675f11 + 9f3bd23 commit 6e955d2

File tree

1 file changed

+200
-33
lines changed

1 file changed

+200
-33
lines changed

Zend/zend_gc.c

Lines changed: 200 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,39 @@ typedef struct _gc_root_buffer {
251251
zend_refcounted *ref;
252252
} gc_root_buffer;
253253

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+
254287
typedef struct _zend_gc_globals {
255288
gc_root_buffer *buf; /* preallocated arrays of buffers */
256289

@@ -278,9 +311,11 @@ typedef struct _zend_gc_globals {
278311
zend_fiber *dtor_fiber;
279312
bool dtor_fiber_running;
280313
#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;
284319
#endif
285320

286321
#if GC_BENCH
@@ -316,17 +351,6 @@ static zend_gc_globals gc_globals;
316351
# define GC_BENCH_PEAK(peak, counter)
317352
#endif
318353

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-
330354
#define GC_STACK_DCL(init) \
331355
gc_stack *_stack = init; \
332356
size_t _top = 0;
@@ -516,6 +540,8 @@ static void gc_globals_ctor_ex(zend_gc_globals *gc_globals)
516540
gc_globals->dtor_coroutine = NULL;
517541
gc_globals->dtor_scope = NULL;
518542
gc_globals->microtask = NULL;
543+
gc_globals->async_context.state = GC_ASYNC_STATE_NONE;
544+
gc_globals->gc_stack = NULL;
519545
#endif
520546

521547
#if GC_BENCH
@@ -542,6 +568,12 @@ void gc_globals_dtor(void)
542568
#ifndef ZTS
543569
root_buffer_dtor(&gc_globals);
544570
#endif
571+
572+
#ifdef PHP_ASYNC_API
573+
if (GC_G(dtor_scope)) {
574+
GC_G(dtor_scope) = NULL;
575+
}
576+
#endif
545577
}
546578

547579
void gc_reset(void)
@@ -1973,9 +2005,34 @@ static void zend_gc_collect_cycles_coroutine(void)
19732005
GC_G(microtask) = task;
19742006
}
19752007

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+
19762028
ZEND_ASYNC_ADD_MICROTASK(GC_G(microtask));
19772029
zend_gc_collect_cycles();
19782030

2031+
// Coroutines were separated.
2032+
if (GC_G(dtor_coroutine) != ZEND_ASYNC_CURRENT_COROUTINE) {
2033+
return;
2034+
}
2035+
19792036
if (GC_G(microtask) != NULL) {
19802037
GC_G(microtask)->is_cancelled = true;
19812038
ZEND_ASYNC_MICROTASK_RELEASE(GC_G(microtask));
@@ -2006,11 +2063,30 @@ static void coroutine_dispose(zend_coroutine_t *coroutine)
20062063
if (coroutine == GC_G(dtor_coroutine)) {
20072064
GC_TRACE("GC coroutine finished");
20082065
GC_G(dtor_coroutine) = NULL;
2066+
GC_G(dtor_scope) = NULL;
20092067

20102068
if (GC_G(microtask) != NULL) {
20112069
GC_G(microtask)->is_cancelled = true;
20122070
zend_gc_collect_cycles_microtask_dtor(GC_G(microtask));
20132071
}
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+
}
20142090
}
20152091
}
20162092

@@ -2074,48 +2150,115 @@ ZEND_API int zend_gc_collect_cycles(void)
20742150

20752151
#endif
20762152

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+
20772217
int total_count = 0;
20782218
bool should_rerun_gc = 0;
20792219
bool did_rerun_gc = 0;
20802220

20812221
zend_hrtime_t start_time = zend_hrtime();
2222+
#endif
2223+
20822224
if (GC_G(num_roots) && !GC_G(gc_active)) {
20832225
zend_gc_remove_root_tmpvars();
20842226
}
20852227

20862228
rerun_gc:
20872229
if (GC_G(num_roots)) {
2088-
int count;
20892230
gc_root_buffer *current, *last;
20902231
zend_refcounted *p;
20912232
uint32_t gc_flags = 0;
20922233
uint32_t idx, end;
2234+
#ifndef PHP_ASYNC_API
2235+
int count;
20932236
gc_stack stack;
2094-
20952237
stack.prev = NULL;
20962238
stack.next = NULL;
2239+
#endif
20972240

20982241
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;
21012244
}
21022245

21032246
GC_TRACE("Collecting cycles");
21042247
GC_G(gc_runs)++;
21052248
GC_G(gc_active) = 1;
21062249

21072250
GC_TRACE("Marking roots");
2108-
gc_mark_roots(&stack);
2251+
gc_mark_roots(GC_COLLECT_STACK);
21092252
GC_TRACE("Scanning roots");
2110-
gc_scan_roots(&stack);
2253+
gc_scan_roots(GC_COLLECT_STACK);
21112254

21122255
GC_TRACE("Collecting roots");
2113-
count = gc_collect_roots(&gc_flags, &stack);
2256+
GC_COLLECT_COUNT = gc_collect_roots(&gc_flags, GC_COLLECT_STACK);
21142257

21152258
if (!GC_G(num_roots)) {
21162259
/* nothing to free */
21172260
GC_TRACE("Nothing to free");
2118-
gc_stack_free(&stack);
2261+
GC_COLLECT_FREE_STACK;
21192262
GC_G(gc_active) = 0;
21202263
goto finish;
21212264
}
@@ -2130,7 +2273,7 @@ ZEND_API int zend_gc_collect_cycles(void)
21302273
* modify any refcounts, so we have no real way to detect this situation
21312274
* short of rerunning full GC tracing. What we do instead is to only run
21322275
* destructors at this point and automatically re-run GC afterwards. */
2133-
should_rerun_gc = 1;
2276+
GC_COLLECT_SHOULD_RERUN_GC = 1;
21342277

21352278
/* Mark all roots for which a dtor will be invoked as DTOR_GARBAGE. Additionally
21362279
* color them purple. This serves a double purpose: First, they should be
@@ -2164,7 +2307,7 @@ ZEND_API int zend_gc_collect_cycles(void)
21642307
while (idx != end) {
21652308
if (GC_IS_DTOR_GARBAGE(current->ref)) {
21662309
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);
21682311
}
21692312
current++;
21702313
idx++;
@@ -2173,7 +2316,21 @@ ZEND_API int zend_gc_collect_cycles(void)
21732316
/* Actually call destructors. */
21742317
zend_hrtime_t dtor_start_time = zend_hrtime();
21752318
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
21762332
gc_call_destructors(GC_FIRST_ROOT, end, NULL);
2333+
#endif
21772334
} else {
21782335
gc_call_destructors_in_fiber(end);
21792336
}
@@ -2182,12 +2339,16 @@ ZEND_API int zend_gc_collect_cycles(void)
21822339
if (GC_G(gc_protected)) {
21832340
/* something went wrong */
21842341
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;
21872344
}
21882345
}
21892346

2190-
gc_stack_free(&stack);
2347+
GC_COLLECT_FREE_STACK;
2348+
2349+
#ifdef PHP_ASYNC_API
2350+
end = GC_G(first_unused);
2351+
#endif
21912352

21922353
/* Destroy zvals. The root buffer may be reallocated. */
21932354
GC_TRACE("Destroying zvals");
@@ -2245,8 +2406,8 @@ ZEND_API int zend_gc_collect_cycles(void)
22452406
GC_G(free_time) += zend_hrtime() - free_start_time;
22462407

22472408
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;
22502411
GC_G(gc_active) = 0;
22512412
}
22522413

@@ -2255,8 +2416,8 @@ ZEND_API int zend_gc_collect_cycles(void)
22552416
/* Objects with destructors were removed from this GC run. Rerun GC right away to clean them
22562417
* up. We do this only once: If we encounter more destructors on the second run, we'll not
22572418
* 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;
22602421
goto rerun_gc;
22612422
}
22622423

@@ -2269,8 +2430,14 @@ ZEND_API int zend_gc_collect_cycles(void)
22692430
zend_gc_check_root_tmpvars();
22702431
GC_G(gc_active) = 0;
22712432

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;
22742441
}
22752442

22762443
ZEND_API void zend_gc_get_status(zend_gc_status *status)

0 commit comments

Comments
 (0)