Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions context.c
Original file line number Diff line number Diff line change
Expand Up @@ -365,18 +365,30 @@ static zend_object *context_object_create(zend_class_entry *class_entry)
return &context->std;
}

static void context_object_destroy(zend_object *object)
static void context_free(zend_object *object)
{
async_context_t *context = ZEND_OBJECT_TO_CONTEXT(object);

// Destroy hash tables
zend_hash_destroy(&context->values);
zend_hash_destroy(&context->keys);
zend_object_std_dtor(object);
}

static void context_free(zend_object *object)
static HashTable *context_get_gc(zend_object *object, zval **table, int *num)
{
zend_object_std_dtor(object);
async_context_t *context = ZEND_OBJECT_TO_CONTEXT(object);
zend_get_gc_buffer *buf = zend_get_gc_buffer_create();
zval *value;

ZEND_HASH_FOREACH_VAL(&context->values, value) {
zend_get_gc_buffer_add_zval(buf, value);
} ZEND_HASH_FOREACH_END();

ZEND_HASH_FOREACH_VAL(&context->keys, value) {
zend_get_gc_buffer_add_zval(buf, value);
} ZEND_HASH_FOREACH_END();
zend_get_gc_buffer_use(buf, table, num);
return NULL;
}

void async_register_context_ce(void)
Expand All @@ -391,8 +403,8 @@ void async_register_context_ce(void)
context_handlers = std_object_handlers;
context_handlers.offset = offsetof(async_context_t, std);
context_handlers.clone_obj = NULL;
context_handlers.dtor_obj = context_object_destroy;
context_handlers.free_obj = context_free;
context_handlers.get_gc = context_get_gc;

async_ce_context->default_object_handlers = &context_handlers;
}
}
19 changes: 2 additions & 17 deletions coroutine.c
Original file line number Diff line number Diff line change
Expand Up @@ -329,25 +329,10 @@ static HashTable *async_coroutine_object_gc(zend_object *object, zval **table, i
}
}

/* Add context ZVALs if present */
/* The coroutine owns the Context object, which exposes its values and keys to GC. */
if (coroutine->coroutine.context) {
/* Cast to actual context implementation to access HashTables */
async_context_t *context = (async_context_t *) coroutine->coroutine.context;

/* Add all values from context->values HashTable */
zval *val;
ZEND_HASH_FOREACH_VAL(&context->values, val)
{
zend_get_gc_buffer_add_zval(buf, val);
}
ZEND_HASH_FOREACH_END();

/* Add all object keys from context->keys HashTable */
ZEND_HASH_FOREACH_VAL(&context->keys, val)
{
zend_get_gc_buffer_add_zval(buf, val);
}
ZEND_HASH_FOREACH_END();
zend_get_gc_buffer_add_obj(buf, &context->std);
}

async_fiber_context_t *fiber_context = coroutine->fiber_context;
Expand Down
21 changes: 3 additions & 18 deletions scope.c
Original file line number Diff line number Diff line change
Expand Up @@ -1448,25 +1448,10 @@ static HashTable *scope_object_gc(zend_object *object, zval **table, int *num)
ZEND_HASH_FOREACH_END();
}

/* Add context ZVALs if present */
/* The scope owns the Context object, which exposes its values and keys to GC. */
if (scope->scope.context) {
/* Cast to actual context implementation to access HashTables */
async_context_t *context = (async_context_t *) scope->scope.context;

/* Add all values from context->values HashTable */
zval *val;
ZEND_HASH_FOREACH_VAL(&context->values, val)
{
zend_get_gc_buffer_add_zval(buf, val);
}
ZEND_HASH_FOREACH_END();

/* Add all object keys from context->keys HashTable */
ZEND_HASH_FOREACH_VAL(&context->keys, val)
{
zend_get_gc_buffer_add_zval(buf, val);
}
ZEND_HASH_FOREACH_END();
zend_get_gc_buffer_add_obj(buf, &context->std);
}

zend_get_gc_buffer_use(buf, table, num);
Expand Down Expand Up @@ -1751,4 +1736,4 @@ static bool async_scope_call_finally_handlers(async_scope_t *scope)
} else {
return true;
}
}
}
61 changes: 61 additions & 0 deletions tests/context/014-context_cycles_gc.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
--TEST--
Context values and object keys are visible to normal cycle collection
--FILE--
<?php

function abandonContext(bool $asKey): WeakReference
{
$context = new Async\Context();

if ($asKey) {
$context->set($context, 'value');
} else {
$context->set('key', $context);
}

return WeakReference::create($context);
}

$valueCycle = abandonContext(false);
$keyCycle = abandonContext(true);

// Fill the GC root buffer and let the collector run on its normal schedule.
$threshold = gc_status()['threshold'];
for ($i = 0; $i < $threshold + 64; $i++) {
abandonContext(($i & 1) !== 0);
}

var_dump($valueCycle->get() === null);
var_dump($keyCycle->get() === null);

$coroutineContext = null;
Async\await(Async\spawn(function () use (&$coroutineContext): void {
$context = Async\coroutine_context();
$coroutineContext = WeakReference::create($context);
$context->set('self', $context);
}));

gc_collect_cycles();
Async\delay(1);
var_dump($coroutineContext->get() === null);

$scope = new Async\Scope();
$scopeContext = null;
Async\await($scope->spawn(function () use (&$scopeContext): void {
$context = Async\current_context();
$scopeContext = WeakReference::create($context);
$context->set($context, 'self');
}));
$scope->dispose();
unset($scope);

gc_collect_cycles();
Async\delay(1);
var_dump($scopeContext->get() === null);

?>
--EXPECT--
bool(true)
bool(true)
bool(true)
bool(true)
Loading