Skip to content

Commit 458c34d

Browse files
author
Fabrice Bellard
committed
fixed GC logic so that a module can live after a JSContext is destroyed (#280) - update the reference count for the realm in jobs and FinalizationRegistry
1 parent 2fd48bf commit 458c34d

File tree

3 files changed

+96
-61
lines changed

3 files changed

+96
-61
lines changed

quickjs-libc.c

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4230,17 +4230,15 @@ static void js_std_promise_rejection_check(JSContext *ctx)
42304230
/* main loop which calls the user JS callbacks */
42314231
void js_std_loop(JSContext *ctx)
42324232
{
4233-
JSContext *ctx1;
42344233
int err;
42354234

42364235
for(;;) {
42374236
/* execute the pending jobs */
42384237
for(;;) {
4239-
err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
4238+
err = JS_ExecutePendingJob(JS_GetRuntime(ctx), NULL);
42404239
if (err <= 0) {
4241-
if (err < 0) {
4242-
js_std_dump_error(ctx1);
4243-
}
4240+
if (err < 0)
4241+
js_std_dump_error(ctx);
42444242
break;
42454243
}
42464244
}
@@ -4271,11 +4269,10 @@ JSValue js_std_await(JSContext *ctx, JSValue obj)
42714269
JS_FreeValue(ctx, obj);
42724270
break;
42734271
} else if (state == JS_PROMISE_PENDING) {
4274-
JSContext *ctx1;
42754272
int err;
4276-
err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
4273+
err = JS_ExecutePendingJob(JS_GetRuntime(ctx), NULL);
42774274
if (err < 0) {
4278-
js_std_dump_error(ctx1);
4275+
js_std_dump_error(ctx);
42794276
}
42804277
if (err == 0) {
42814278
js_std_promise_rejection_check(ctx);

quickjs.c

Lines changed: 87 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ typedef enum {
341341
JS_GC_OBJ_TYPE_VAR_REF,
342342
JS_GC_OBJ_TYPE_ASYNC_FUNCTION,
343343
JS_GC_OBJ_TYPE_JS_CONTEXT,
344+
JS_GC_OBJ_TYPE_MODULE,
344345
} JSGCObjectTypeEnum;
345346

346347
/* header for GC objects. GC objects are C data structures with a
@@ -805,7 +806,7 @@ typedef enum {
805806
} JSModuleStatus;
806807

807808
struct JSModuleDef {
808-
JSRefCountHeader header; /* must come first, 32-bit */
809+
JSGCObjectHeader header; /* must come first */
809810
JSAtom module_name;
810811
struct list_head link;
811812

@@ -857,7 +858,7 @@ struct JSModuleDef {
857858

858859
typedef struct JSJobEntry {
859860
struct list_head link;
860-
JSContext *ctx;
861+
JSContext *realm;
861862
JSJobFunc *job_func;
862863
int argc;
863864
JSValue argv[0];
@@ -1222,7 +1223,7 @@ static void js_async_function_resolve_mark(JSRuntime *rt, JSValueConst val,
12221223
static JSValue JS_EvalInternal(JSContext *ctx, JSValueConst this_obj,
12231224
const char *input, size_t input_len,
12241225
const char *filename, int flags, int scope_idx);
1225-
static void js_free_module_def(JSContext *ctx, JSModuleDef *m);
1226+
static void js_free_module_def(JSRuntime *rt, JSModuleDef *m);
12261227
static void js_mark_module_def(JSRuntime *rt, JSModuleDef *m,
12271228
JS_MarkFunc *mark_func);
12281229
static JSValue js_import_meta(JSContext *ctx);
@@ -1782,7 +1783,7 @@ int JS_EnqueueJob(JSContext *ctx, JSJobFunc *job_func,
17821783
e = js_malloc(ctx, sizeof(*e) + argc * sizeof(JSValue));
17831784
if (!e)
17841785
return -1;
1785-
e->ctx = ctx;
1786+
e->realm = JS_DupContext(ctx);
17861787
e->job_func = job_func;
17871788
e->argc = argc;
17881789
for(i = 0; i < argc; i++) {
@@ -1798,7 +1799,10 @@ BOOL JS_IsJobPending(JSRuntime *rt)
17981799
}
17991800

18001801
/* return < 0 if exception, 0 if no job pending, 1 if a job was
1801-
executed successfully. the context of the job is stored in '*pctx' */
1802+
executed successfully. The context of the job is stored in '*pctx'
1803+
if pctx != NULL. It may be NULL if the context was already
1804+
destroyed or if no job was pending. The 'pctx' parameter is now
1805+
absolete. */
18021806
int JS_ExecutePendingJob(JSRuntime *rt, JSContext **pctx)
18031807
{
18041808
JSContext *ctx;
@@ -1807,15 +1811,16 @@ int JS_ExecutePendingJob(JSRuntime *rt, JSContext **pctx)
18071811
int i, ret;
18081812

18091813
if (list_empty(&rt->job_list)) {
1810-
*pctx = NULL;
1814+
if (pctx)
1815+
*pctx = NULL;
18111816
return 0;
18121817
}
18131818

18141819
/* get the first pending job and execute it */
18151820
e = list_entry(rt->job_list.next, JSJobEntry, link);
18161821
list_del(&e->link);
1817-
ctx = e->ctx;
1818-
res = e->job_func(e->ctx, e->argc, (JSValueConst *)e->argv);
1822+
ctx = e->realm;
1823+
res = e->job_func(ctx, e->argc, (JSValueConst *)e->argv);
18191824
for(i = 0; i < e->argc; i++)
18201825
JS_FreeValue(ctx, e->argv[i]);
18211826
if (JS_IsException(res))
@@ -1824,7 +1829,13 @@ int JS_ExecutePendingJob(JSRuntime *rt, JSContext **pctx)
18241829
ret = 1;
18251830
JS_FreeValue(ctx, res);
18261831
js_free(ctx, e);
1827-
*pctx = ctx;
1832+
if (pctx) {
1833+
if (ctx->header.ref_count > 1)
1834+
*pctx = ctx;
1835+
else
1836+
*pctx = NULL;
1837+
}
1838+
JS_FreeContext(ctx);
18281839
return ret;
18291840
}
18301841

@@ -1905,6 +1916,7 @@ void JS_FreeRuntime(JSRuntime *rt)
19051916
JSJobEntry *e = list_entry(el, JSJobEntry, link);
19061917
for(i = 0; i < e->argc; i++)
19071918
JS_FreeValueRT(rt, e->argv[i]);
1919+
JS_FreeContext(e->realm);
19081920
js_free_rt(rt, e);
19091921
}
19101922
init_list_head(&rt->job_list);
@@ -2180,7 +2192,13 @@ static void js_free_modules(JSContext *ctx, JSFreeModuleEnum flag)
21802192
JSModuleDef *m = list_entry(el, JSModuleDef, link);
21812193
if (flag == JS_FREE_MODULE_ALL ||
21822194
(flag == JS_FREE_MODULE_NOT_RESOLVED && !m->resolved)) {
2183-
js_free_module_def(ctx, m);
2195+
/* warning: the module may be referenced elsewhere. It
2196+
could be simpler to use an array instead of a list for
2197+
'ctx->loaded_modules' */
2198+
list_del(&m->link);
2199+
m->link.prev = NULL;
2200+
m->link.next = NULL;
2201+
JS_FreeValue(ctx, JS_MKPTR(JS_TAG_MODULE, m));
21842202
}
21852203
}
21862204
}
@@ -2198,11 +2216,9 @@ static void JS_MarkContext(JSRuntime *rt, JSContext *ctx,
21982216
int i;
21992217
struct list_head *el;
22002218

2201-
/* modules are not seen by the GC, so we directly mark the objects
2202-
referenced by each module */
22032219
list_for_each(el, &ctx->loaded_modules) {
22042220
JSModuleDef *m = list_entry(el, JSModuleDef, link);
2205-
js_mark_module_def(rt, m, mark_func);
2221+
JS_MarkValue(rt, JS_MKPTR(JS_TAG_MODULE, m), mark_func);
22062222
}
22072223

22082224
JS_MarkValue(rt, ctx->global_obj, mark_func);
@@ -5783,6 +5799,9 @@ static void free_gc_object(JSRuntime *rt, JSGCObjectHeader *gp)
57835799
case JS_GC_OBJ_TYPE_ASYNC_FUNCTION:
57845800
__async_func_free(rt, (JSAsyncFunctionState *)gp);
57855801
break;
5802+
case JS_GC_OBJ_TYPE_MODULE:
5803+
js_free_module_def(rt, (JSModuleDef *)gp);
5804+
break;
57865805
default:
57875806
abort();
57885807
}
@@ -5847,6 +5866,7 @@ void __JS_FreeValueRT(JSRuntime *rt, JSValue v)
58475866
break;
58485867
case JS_TAG_OBJECT:
58495868
case JS_TAG_FUNCTION_BYTECODE:
5869+
case JS_TAG_MODULE:
58505870
{
58515871
JSGCObjectHeader *p = JS_VALUE_GET_PTR(v);
58525872
if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) {
@@ -5859,9 +5879,6 @@ void __JS_FreeValueRT(JSRuntime *rt, JSValue v)
58595879
}
58605880
}
58615881
break;
5862-
case JS_TAG_MODULE:
5863-
abort(); /* never freed here */
5864-
break;
58655882
case JS_TAG_BIG_INT:
58665883
{
58675884
JSBigInt *p = JS_VALUE_GET_PTR(v);
@@ -5935,6 +5952,7 @@ void JS_MarkValue(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func)
59355952
switch(JS_VALUE_GET_TAG(val)) {
59365953
case JS_TAG_OBJECT:
59375954
case JS_TAG_FUNCTION_BYTECODE:
5955+
case JS_TAG_MODULE:
59385956
mark_func(rt, JS_VALUE_GET_PTR(val));
59395957
break;
59405958
default:
@@ -6047,6 +6065,12 @@ static void mark_children(JSRuntime *rt, JSGCObjectHeader *gp,
60476065
JS_MarkContext(rt, ctx, mark_func);
60486066
}
60496067
break;
6068+
case JS_GC_OBJ_TYPE_MODULE:
6069+
{
6070+
JSModuleDef *m = (JSModuleDef *)gp;
6071+
js_mark_module_def(rt, m, mark_func);
6072+
}
6073+
break;
60506074
default:
60516075
abort();
60526076
}
@@ -6143,6 +6167,7 @@ static void gc_free_cycles(JSRuntime *rt)
61436167
case JS_GC_OBJ_TYPE_JS_OBJECT:
61446168
case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE:
61456169
case JS_GC_OBJ_TYPE_ASYNC_FUNCTION:
6170+
case JS_GC_OBJ_TYPE_MODULE:
61466171
#ifdef DUMP_GC_FREE
61476172
if (!header_done) {
61486173
printf("Freeing cycles:\n");
@@ -6165,7 +6190,8 @@ static void gc_free_cycles(JSRuntime *rt)
61656190
p = list_entry(el, JSGCObjectHeader, link);
61666191
assert(p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT ||
61676192
p->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE ||
6168-
p->gc_obj_type == JS_GC_OBJ_TYPE_ASYNC_FUNCTION);
6193+
p->gc_obj_type == JS_GC_OBJ_TYPE_ASYNC_FUNCTION ||
6194+
p->gc_obj_type == JS_GC_OBJ_TYPE_MODULE);
61696195
if (p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT &&
61706196
((JSObject *)p)->weakref_count != 0) {
61716197
/* keep the object because there are weak references to it */
@@ -13747,6 +13773,9 @@ static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p)
1374713773
case JS_GC_OBJ_TYPE_JS_CONTEXT:
1374813774
printf("[js_context]");
1374913775
break;
13776+
case JS_GC_OBJ_TYPE_MODULE:
13777+
printf("[module]");
13778+
break;
1375013779
default:
1375113780
printf("[unknown %d]", p->gc_obj_type);
1375213781
break;
@@ -28216,7 +28245,7 @@ static __exception int js_parse_statement_or_decl(JSParseState *s,
2821628245
return -1;
2821728246
}
2821828247

28219-
/* 'name' is freed */
28248+
/* 'name' is freed. The module is referenced by 'ctx->loaded_modules' */
2822028249
static JSModuleDef *js_new_module_def(JSContext *ctx, JSAtom name)
2822128250
{
2822228251
JSModuleDef *m;
@@ -28226,6 +28255,7 @@ static JSModuleDef *js_new_module_def(JSContext *ctx, JSAtom name)
2822628255
return NULL;
2822728256
}
2822828257
m->header.ref_count = 1;
28258+
add_gc_object(ctx->rt, &m->header, JS_GC_OBJ_TYPE_MODULE);
2822928259
m->module_name = name;
2823028260
m->module_ns = JS_UNDEFINED;
2823128261
m->func_obj = JS_UNDEFINED;
@@ -28267,47 +28297,56 @@ static void js_mark_module_def(JSRuntime *rt, JSModuleDef *m,
2826728297
JS_MarkValue(rt, m->private_value, mark_func);
2826828298
}
2826928299

28270-
static void js_free_module_def(JSContext *ctx, JSModuleDef *m)
28300+
static void js_free_module_def(JSRuntime *rt, JSModuleDef *m)
2827128301
{
2827228302
int i;
2827328303

28274-
JS_FreeAtom(ctx, m->module_name);
28304+
JS_FreeAtomRT(rt, m->module_name);
2827528305

2827628306
for(i = 0; i < m->req_module_entries_count; i++) {
2827728307
JSReqModuleEntry *rme = &m->req_module_entries[i];
28278-
JS_FreeAtom(ctx, rme->module_name);
28279-
JS_FreeValue(ctx, rme->attributes);
28308+
JS_FreeAtomRT(rt, rme->module_name);
28309+
JS_FreeValueRT(rt, rme->attributes);
2828028310
}
28281-
js_free(ctx, m->req_module_entries);
28311+
js_free_rt(rt, m->req_module_entries);
2828228312

2828328313
for(i = 0; i < m->export_entries_count; i++) {
2828428314
JSExportEntry *me = &m->export_entries[i];
2828528315
if (me->export_type == JS_EXPORT_TYPE_LOCAL)
28286-
free_var_ref(ctx->rt, me->u.local.var_ref);
28287-
JS_FreeAtom(ctx, me->export_name);
28288-
JS_FreeAtom(ctx, me->local_name);
28316+
free_var_ref(rt, me->u.local.var_ref);
28317+
JS_FreeAtomRT(rt, me->export_name);
28318+
JS_FreeAtomRT(rt, me->local_name);
2828928319
}
28290-
js_free(ctx, m->export_entries);
28320+
js_free_rt(rt, m->export_entries);
2829128321

28292-
js_free(ctx, m->star_export_entries);
28322+
js_free_rt(rt, m->star_export_entries);
2829328323

2829428324
for(i = 0; i < m->import_entries_count; i++) {
2829528325
JSImportEntry *mi = &m->import_entries[i];
28296-
JS_FreeAtom(ctx, mi->import_name);
28326+
JS_FreeAtomRT(rt, mi->import_name);
2829728327
}
28298-
js_free(ctx, m->import_entries);
28299-
js_free(ctx, m->async_parent_modules);
28328+
js_free_rt(rt, m->import_entries);
28329+
js_free_rt(rt, m->async_parent_modules);
2830028330

28301-
JS_FreeValue(ctx, m->module_ns);
28302-
JS_FreeValue(ctx, m->func_obj);
28303-
JS_FreeValue(ctx, m->eval_exception);
28304-
JS_FreeValue(ctx, m->meta_obj);
28305-
JS_FreeValue(ctx, m->promise);
28306-
JS_FreeValue(ctx, m->resolving_funcs[0]);
28307-
JS_FreeValue(ctx, m->resolving_funcs[1]);
28308-
JS_FreeValue(ctx, m->private_value);
28309-
list_del(&m->link);
28310-
js_free(ctx, m);
28331+
JS_FreeValueRT(rt, m->module_ns);
28332+
JS_FreeValueRT(rt, m->func_obj);
28333+
JS_FreeValueRT(rt, m->eval_exception);
28334+
JS_FreeValueRT(rt, m->meta_obj);
28335+
JS_FreeValueRT(rt, m->promise);
28336+
JS_FreeValueRT(rt, m->resolving_funcs[0]);
28337+
JS_FreeValueRT(rt, m->resolving_funcs[1]);
28338+
JS_FreeValueRT(rt, m->private_value);
28339+
/* during the GC the finalizers are called in an arbitrary
28340+
order so the module may no longer be referenced by the JSContext list */
28341+
if (m->link.next) {
28342+
list_del(&m->link);
28343+
}
28344+
remove_gc_object(&m->header);
28345+
if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && m->header.ref_count != 0) {
28346+
list_add_tail(&m->header.link, &rt->gc_zero_ref_count_list);
28347+
} else {
28348+
js_free_rt(rt, m);
28349+
}
2831128350
}
2831228351

2831328352
static int add_req_module_entry(JSContext *ctx, JSModuleDef *m,
@@ -35804,7 +35843,7 @@ static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj,
3580435843
fail1:
3580535844
/* XXX: should free all the unresolved dependencies */
3580635845
if (m)
35807-
js_free_module_def(ctx, m);
35846+
JS_FreeValue(ctx, JS_MKPTR(JS_TAG_MODULE, m));
3580835847
return JS_EXCEPTION;
3580935848
}
3581035849

@@ -37516,7 +37555,7 @@ static JSValue JS_ReadModule(BCReaderState *s)
3751637555
return obj;
3751737556
fail:
3751837557
if (m) {
37519-
js_free_module_def(ctx, m);
37558+
JS_FreeValue(ctx, JS_MKPTR(JS_TAG_MODULE, m));
3752037559
}
3752137560
return JS_EXCEPTION;
3752237561
}
@@ -55663,7 +55702,7 @@ typedef struct JSFinRecEntry {
5566355702
typedef struct JSFinalizationRegistryData {
5566455703
JSWeakRefHeader weakref_header;
5566555704
struct list_head entries; /* list of JSFinRecEntry.link */
55666-
JSContext *ctx;
55705+
JSContext *realm;
5566755706
JSValue cb;
5566855707
} JSFinalizationRegistryData;
5566955708

@@ -55680,6 +55719,7 @@ static void js_finrec_finalizer(JSRuntime *rt, JSValue val)
5568055719
js_free_rt(rt, fre);
5568155720
}
5568255721
JS_FreeValueRT(rt, frd->cb);
55722+
JS_FreeContext(frd->realm);
5568355723
list_del(&frd->weakref_header.link);
5568455724
js_free_rt(rt, frd);
5568555725
}
@@ -55696,6 +55736,7 @@ static void js_finrec_mark(JSRuntime *rt, JSValueConst val,
5569655736
JS_MarkValue(rt, fre->held_val, mark_func);
5569755737
}
5569855738
JS_MarkValue(rt, frd->cb, mark_func);
55739+
mark_func(rt, &frd->realm->header);
5569955740
}
5570055741
}
5570155742

@@ -55721,7 +55762,7 @@ static void finrec_delete_weakref(JSRuntime *rt, JSWeakRefHeader *wh)
5572155762
JSValueConst args[2];
5572255763
args[0] = frd->cb;
5572355764
args[1] = fre->held_val;
55724-
JS_EnqueueJob(frd->ctx, js_finrec_job, 2, args);
55765+
JS_EnqueueJob(frd->realm, js_finrec_job, 2, args);
5572555766

5572655767
js_weakref_free(rt, fre->target);
5572755768
js_weakref_free(rt, fre->token);
@@ -55756,7 +55797,7 @@ static JSValue js_finrec_constructor(JSContext *ctx, JSValueConst new_target,
5575655797
frd->weakref_header.weakref_type = JS_WEAKREF_TYPE_FINREC;
5575755798
list_add_tail(&frd->weakref_header.link, &ctx->rt->weakref_list);
5575855799
init_list_head(&frd->entries);
55759-
frd->ctx = ctx; /* XXX: JS_DupContext() ? */
55800+
frd->realm = JS_DupContext(ctx);
5576055801
frd->cb = JS_DupValue(ctx, cb);
5576155802
JS_SetOpaque(obj, frd);
5576255803
return obj;

0 commit comments

Comments
 (0)