Skip to content

Commit f316b3d

Browse files
committed
Rework promise rejection tracker
This reverts a75498b and a4a5a17 (except the tests) and adapts bellard/quickjs@3c39307 which also passes the tests and feels like a better solution overall. Ref: #1038
1 parent 6b07a4e commit f316b3d

File tree

2 files changed

+86
-56
lines changed

2 files changed

+86
-56
lines changed

quickjs-libc.c

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ typedef struct {
128128
JSValue func;
129129
} JSOSTimer;
130130

131+
typedef struct {
132+
struct list_head link;
133+
JSValue promise;
134+
JSValue reason;
135+
} JSRejectedPromiseEntry;
136+
131137
#ifdef USE_WORKER
132138

133139
typedef struct {
@@ -168,6 +174,7 @@ typedef struct JSThreadState {
168174
struct list_head os_signal_handlers; /* list JSOSSignalHandler.link */
169175
struct list_head os_timers; /* list of JSOSTimer.link */
170176
struct list_head port_list; /* list of JSWorkerMessageHandler.link */
177+
struct list_head rejected_promise_list; /* list of JSRejectedPromiseEntry.link */
171178
int eval_script_recurse; /* only used in the main thread */
172179
int64_t next_timer_id; /* for setTimeout / setInterval */
173180
bool can_js_os_poll;
@@ -4280,6 +4287,7 @@ void js_std_init_handlers(JSRuntime *rt)
42804287
init_list_head(&ts->os_signal_handlers);
42814288
init_list_head(&ts->os_timers);
42824289
init_list_head(&ts->port_list);
4290+
init_list_head(&ts->rejected_promise_list);
42834291

42844292
ts->next_timer_id = 1;
42854293

@@ -4299,6 +4307,14 @@ void js_std_init_handlers(JSRuntime *rt)
42994307
#endif
43004308
}
43014309

4310+
static void free_rp(JSRuntime *rt, JSRejectedPromiseEntry *rp)
4311+
{
4312+
list_del(&rp->link);
4313+
JS_FreeValueRT(rt, rp->promise);
4314+
JS_FreeValueRT(rt, rp->reason);
4315+
js_free_rt(rt, rp);
4316+
}
4317+
43024318
void js_std_free_handlers(JSRuntime *rt)
43034319
{
43044320
JSThreadState *ts = js_get_thread_state(rt);
@@ -4319,6 +4335,11 @@ void js_std_free_handlers(JSRuntime *rt)
43194335
free_timer(rt, th);
43204336
}
43214337

4338+
list_for_each_safe(el, el1, &ts->rejected_promise_list) {
4339+
JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link);
4340+
free_rp(rt, rp);
4341+
}
4342+
43224343
#ifdef USE_WORKER
43234344
/* XXX: free port_list ? */
43244345
js_free_message_pipe(ts->recv_pipe);
@@ -4366,14 +4387,62 @@ void js_std_dump_error(JSContext *ctx)
43664387
JS_FreeValue(ctx, exception_val);
43674388
}
43684389

4390+
static JSRejectedPromiseEntry *find_rejected_promise(JSContext *ctx, JSThreadState *ts,
4391+
JSValueConst promise)
4392+
{
4393+
struct list_head *el;
4394+
4395+
list_for_each(el, &ts->rejected_promise_list) {
4396+
JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link);
4397+
if (JS_IsSameValue(ctx, rp->promise, promise))
4398+
return rp;
4399+
}
4400+
return NULL;
4401+
}
4402+
43694403
void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise,
43704404
JSValueConst reason,
43714405
bool is_handled, void *opaque)
43724406
{
4373-
if (!is_handled) {
4374-
fprintf(stderr, "Possibly unhandled promise rejection: ");
4375-
js_std_dump_error1(ctx, reason);
4376-
fflush(stderr);
4407+
4408+
JSRuntime *rt = JS_GetRuntime(ctx);
4409+
JSThreadState *ts = js_get_thread_state(rt);
4410+
JSRejectedPromiseEntry *rp = find_rejected_promise(ctx, ts, promise);
4411+
4412+
if (is_handled) {
4413+
/* the rejection is handled, so the entry can be removed if present */
4414+
if (rp)
4415+
free_rp(rt, rp);
4416+
} else {
4417+
/* add a new entry if needed */
4418+
if (!rp) {
4419+
rp = js_malloc_rt(rt, sizeof(*rp));
4420+
if (rp) {
4421+
rp->promise = JS_DupValue(ctx, promise);
4422+
rp->reason = JS_DupValue(ctx, reason);
4423+
list_add_tail(&rp->link, &ts->rejected_promise_list);
4424+
}
4425+
}
4426+
}
4427+
}
4428+
4429+
/* check if there are pending promise rejections. It must be done
4430+
asynchrously in case a rejected promise is handled later. Currently
4431+
we do it once the application is about to sleep. It could be done
4432+
more often if needed. */
4433+
static void js_std_promise_rejection_check(JSContext *ctx)
4434+
{
4435+
JSRuntime *rt = JS_GetRuntime(ctx);
4436+
JSThreadState *ts = js_get_thread_state(rt);
4437+
struct list_head *el;
4438+
4439+
if (unlikely(!list_empty(&ts->rejected_promise_list))) {
4440+
list_for_each(el, &ts->rejected_promise_list) {
4441+
JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link);
4442+
fprintf(stderr, "Possibly unhandled promise rejection: ");
4443+
js_std_dump_error1(ctx, rp->reason);
4444+
fflush(stderr);
4445+
}
43774446
exit(1);
43784447
}
43794448
}
@@ -4397,6 +4466,8 @@ int js_std_loop(JSContext *ctx)
43974466
}
43984467
}
43994468

4469+
js_std_promise_rejection_check(ctx);
4470+
44004471
if (!ts->can_js_os_poll || js_os_poll(ctx))
44014472
break;
44024473
}
@@ -4431,6 +4502,8 @@ JSValue js_std_await(JSContext *ctx, JSValue obj)
44314502
if (err < 0) {
44324503
js_std_dump_error(ctx1);
44334504
}
4505+
if (err == 0)
4506+
js_std_promise_rejection_check(ctx);
44344507
if (ts->can_js_os_poll)
44354508
js_os_poll(ctx);
44364509
} else {

quickjs.c

Lines changed: 9 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -50208,51 +50208,6 @@ static JSValue promise_reaction_job(JSContext *ctx, int argc,
5020850208
return res2;
5020950209
}
5021050210

50211-
static JSValue promise_rejection_tracker_job(JSContext *ctx, int argc,
50212-
JSValueConst *argv)
50213-
{
50214-
JSRuntime *rt;
50215-
JSPromiseData *s;
50216-
JSValueConst promise;
50217-
struct list_head *el, *el1;
50218-
JSJobEntry *job;
50219-
bool has_other_promise_jobs;
50220-
50221-
assert(argc == 1);
50222-
50223-
rt = ctx->rt;
50224-
promise = argv[0];
50225-
s = JS_GetOpaque(promise, JS_CLASS_PROMISE);
50226-
50227-
if (!s || s->promise_state != JS_PROMISE_REJECTED)
50228-
return JS_UNDEFINED; /* should never happen */
50229-
50230-
promise_trace(ctx, "promise_rejection_tracker_job\n");
50231-
50232-
// Push the rejection tracker jobs to the end of the queue if there are other jobs.
50233-
// This allows us to handle rejections that get added later and thus would handle the
50234-
// rejection _after_ we check for it.
50235-
has_other_promise_jobs = false;
50236-
list_for_each_safe(el, el1, &rt->job_list) {
50237-
job = list_entry(el, JSJobEntry, link);
50238-
if (job->job_func == promise_reaction_job || job->job_func == js_promise_resolve_thenable_job) {
50239-
has_other_promise_jobs = true;
50240-
break;
50241-
}
50242-
}
50243-
if (has_other_promise_jobs) {
50244-
JS_EnqueueJob(ctx, promise_rejection_tracker_job, 1, &promise);
50245-
return JS_UNDEFINED;
50246-
}
50247-
50248-
// Check again in case the hook was removed.
50249-
if (rt->host_promise_rejection_tracker)
50250-
rt->host_promise_rejection_tracker(
50251-
ctx, promise, s->promise_result, s->is_handled, rt->host_promise_rejection_tracker_opaque);
50252-
50253-
return JS_UNDEFINED;
50254-
}
50255-
5025650211
void JS_SetPromiseHook(JSRuntime *rt, JSPromiseHook promise_hook, void *opaque)
5025750212
{
5025850213
rt->promise_hook = promise_hook;
@@ -50290,6 +50245,13 @@ static void fulfill_or_reject_promise(JSContext *ctx, JSValueConst promise,
5029050245
}
5029150246
}
5029250247

50248+
if (s->promise_state == JS_PROMISE_REJECTED && !s->is_handled) {
50249+
JSRuntime *rt = ctx->rt;
50250+
if (rt->host_promise_rejection_tracker)
50251+
rt->host_promise_rejection_tracker(ctx, promise, value, false,
50252+
rt->host_promise_rejection_tracker_opaque);
50253+
}
50254+
5029350255
list_for_each_safe(el, el1, &s->promise_reactions[is_reject]) {
5029450256
rd = list_entry(el, JSPromiseReactionData, link);
5029550257
args[0] = rd->resolving_funcs[0];
@@ -50307,12 +50269,6 @@ static void fulfill_or_reject_promise(JSContext *ctx, JSValueConst promise,
5030750269
list_del(&rd->link);
5030850270
promise_reaction_data_free(ctx->rt, rd);
5030950271
}
50310-
50311-
if (s->promise_state == JS_PROMISE_REJECTED && !s->is_handled) {
50312-
JSRuntime *rt = ctx->rt;
50313-
if (rt->host_promise_rejection_tracker)
50314-
JS_EnqueueJob(ctx, promise_rejection_tracker_job, 1, &promise);
50315-
}
5031650272
}
5031750273

5031850274
static JSValue js_promise_resolve_thenable_job(JSContext *ctx,
@@ -51060,7 +51016,8 @@ static __exception int perform_promise_then(JSContext *ctx,
5106051016
if (s->promise_state == JS_PROMISE_REJECTED && !s->is_handled) {
5106151017
JSRuntime *rt = ctx->rt;
5106251018
if (rt->host_promise_rejection_tracker)
51063-
JS_EnqueueJob(ctx, promise_rejection_tracker_job, 1, &promise);
51019+
rt->host_promise_rejection_tracker(ctx, promise, s->promise_result,
51020+
true, rt->host_promise_rejection_tracker_opaque);
5106451021
}
5106551022
i = s->promise_state - JS_PROMISE_FULFILLED;
5106651023
rd = rd_array[i];

0 commit comments

Comments
 (0)