Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add user-definable hook to prevent objects from being GC'ed #234

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
77 changes: 74 additions & 3 deletions quickjs.c
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@ struct JSRuntime {
/* list of JSGCObjectHeader.link. Used during JS_FreeValueRT() */
struct list_head gc_zero_ref_count_list;
struct list_head tmp_obj_list; /* used during GC */
/* used during GC (for keeping track of objects with a can_destroy hook) */
struct list_head tmp_hook_obj_list;
JSGCPhaseEnum gc_phase : 8;
size_t malloc_gc_threshold;
#ifdef DUMP_LEAKS
Expand Down Expand Up @@ -284,6 +286,8 @@ struct JSClass {
JSClassCall *call;
/* pointers for exotic behavior, can be NULL if none are present */
const JSClassExoticMethods *exotic;
/* called before object would be destroyed */
JSClassCanDestroy *can_destroy;
};

#define JS_MODE_STRICT (1 << 0)
Expand Down Expand Up @@ -3388,6 +3392,7 @@ static int JS_NewClass1(JSRuntime *rt, JSClassID class_id,
cl->gc_mark = class_def->gc_mark;
cl->call = class_def->call;
cl->exotic = class_def->exotic;
cl->can_destroy = class_def->can_destroy;
return 0;
}

Expand Down Expand Up @@ -5388,6 +5393,28 @@ static void free_gc_object(JSRuntime *rt, JSGCObjectHeader *gp)
}
}

/* Check if object has a can_destroy hook. */
static int gc_has_can_destroy_hook(JSRuntime *rt, JSGCObjectHeader *p)
{
JSObject *obj;

if (p->gc_obj_type != JS_GC_OBJ_TYPE_JS_OBJECT)
return 0;
obj = (JSObject *)p;
return rt->class_array[obj->class_id].can_destroy != NULL;
}

/* User-defined override for object destruction. */
static int gc_can_destroy(JSRuntime *rt, JSGCObjectHeader *p)
{
JSClassCanDestroy *can_destroy;
JSObject *obj;

obj = (JSObject *)p;
can_destroy = rt->class_array[obj->class_id].can_destroy;
return (*can_destroy)(rt, JS_MKPTR(JS_TAG_OBJECT, obj));
}

static void free_zero_refcount(JSRuntime *rt)
{
struct list_head *el;
Expand Down Expand Up @@ -5441,6 +5468,10 @@ void __JS_FreeValueRT(JSRuntime *rt, JSValue v)
{
JSGCObjectHeader *p = JS_VALUE_GET_PTR(v);
if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) {
if (gc_has_can_destroy_hook(rt, p) && !gc_can_destroy(rt, p)) {
p->ref_count++;
break;
}
list_del(&p->link);
list_add(&p->link, &rt->gc_zero_ref_count_list);
if (rt->gc_phase == JS_GC_PHASE_NONE) {
Expand Down Expand Up @@ -5615,7 +5646,10 @@ static void gc_decref_child(JSRuntime *rt, JSGCObjectHeader *p)
p->ref_count--;
if (p->ref_count == 0 && p->mark == 1) {
list_del(&p->link);
list_add_tail(&p->link, &rt->tmp_obj_list);
if (!gc_has_can_destroy_hook(rt, p))
list_add_tail(&p->link, &rt->tmp_obj_list);
else
list_add_tail(&p->link, &rt->tmp_hook_obj_list);
}
}

Expand All @@ -5625,6 +5659,7 @@ static void gc_decref(JSRuntime *rt)
JSGCObjectHeader *p;

init_list_head(&rt->tmp_obj_list);
init_list_head(&rt->tmp_hook_obj_list);

/* decrement the refcount of all the children of all the GC
objects and move the GC objects with zero refcount to
Expand All @@ -5636,7 +5671,10 @@ static void gc_decref(JSRuntime *rt)
p->mark = 1;
if (p->ref_count == 0) {
list_del(&p->link);
list_add_tail(&p->link, &rt->tmp_obj_list);
if (!gc_has_can_destroy_hook(rt, p))
list_add_tail(&p->link, &rt->tmp_obj_list);
else
list_add_tail(&p->link, &rt->tmp_hook_obj_list);
}
}
}
Expand All @@ -5660,8 +5698,9 @@ static void gc_scan_incref_child2(JSRuntime *rt, JSGCObjectHeader *p)

static void gc_scan(JSRuntime *rt)
{
struct list_head *el;
struct list_head *el, *el1, *gc_tail;
JSGCObjectHeader *p;
int redo;

/* keep the objects with a refcount > 0 and their children. */
list_for_each(el, &rt->gc_obj_list) {
Expand All @@ -5671,6 +5710,38 @@ static void gc_scan(JSRuntime *rt)
mark_children(rt, p, gc_scan_incref_child);
}

/* restore objects whose can_destroy hook returns 0 and their children. */
do {
/* save previous tail position of gc_obj_list */
gc_tail = rt->gc_obj_list.prev;
redo = 0;
list_for_each_safe(el, el1, &rt->tmp_hook_obj_list) {
p = list_entry(el, JSGCObjectHeader, link);
list_del(&p->link);
/* gc_has_can_destroy_hook is the condition for objects to be
placed in tmp_hook_obj_list, so it is true here. */
if (gc_can_destroy(rt, p)) {
/* object can be destroyed; move to tmp_obj_list. */
list_add_tail(&p->link, &rt->tmp_obj_list);
} else {
/* hook says we cannot destroy yet; move back to gc_obj_list. */
p->ref_count++;
list_add_tail(&p->link, &rt->gc_obj_list);
redo = 1;
break;
}
}
/* if redo, restore object and all its descendants.
Note: we must do this outside the previous loop, because el/el1
might get moved into gc_obj_list here. */
for (el = gc_tail->next; el != &rt->gc_obj_list; el = el->next) {
p = list_entry(el, JSGCObjectHeader, link);
assert(p->ref_count > 0);
p->mark = 0; /* reset the mark for the next GC call */
mark_children(rt, p, gc_scan_incref_child);
}
} while(redo);

/* restore the refcount of the objects to be deleted. */
list_for_each(el, &rt->tmp_obj_list) {
p = list_entry(el, JSGCObjectHeader, link);
Expand Down
2 changes: 2 additions & 0 deletions quickjs.h
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ typedef void JSClassGCMark(JSRuntime *rt, JSValue val,
typedef JSValue JSClassCall(JSContext *ctx, JSValue func_obj,
JSValue this_val, int argc, JSValue *argv,
int flags);
typedef JS_BOOL JSClassCanDestroy(JSRuntime *rt, JSValue val);

typedef struct JSClassDef {
const char *class_name;
Expand All @@ -448,6 +449,7 @@ typedef struct JSClassDef {
/* XXX: suppress this indirection ? It is here only to save memory
because only a few classes need these methods */
JSClassExoticMethods *exotic;
JSClassCanDestroy *can_destroy;
} JSClassDef;

JS_EXTERN JSClassID JS_NewClassID(JSRuntime *rt, JSClassID *pclass_id);
Expand Down