Skip to content

Commit 3c39307

Browse files
author
Fabrice Bellard
committed
better promise rejection tracker heuristics (#112)
1 parent d7cdfdc commit 3c39307

File tree

2 files changed

+91
-2
lines changed

2 files changed

+91
-2
lines changed

quickjs-libc.c

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,18 @@ typedef struct {
136136
JSValue on_message_func;
137137
} JSWorkerMessageHandler;
138138

139+
typedef struct {
140+
struct list_head link;
141+
JSValue promise;
142+
JSValue reason;
143+
} JSRejectedPromiseEntry;
144+
139145
typedef struct JSThreadState {
140146
struct list_head os_rw_handlers; /* list of JSOSRWHandler.link */
141147
struct list_head os_signal_handlers; /* list JSOSSignalHandler.link */
142148
struct list_head os_timers; /* list of JSOSTimer.link */
143149
struct list_head port_list; /* list of JSWorkerMessageHandler.link */
150+
struct list_head rejected_promise_list; /* list of JSRejectedPromiseEntry.link */
144151
int eval_script_recurse; /* only used in the main thread */
145152
int next_timer_id; /* for setTimeout() */
146153
/* not used in the main thread */
@@ -3986,6 +3993,7 @@ void js_std_init_handlers(JSRuntime *rt)
39863993
init_list_head(&ts->os_signal_handlers);
39873994
init_list_head(&ts->os_timers);
39883995
init_list_head(&ts->port_list);
3996+
init_list_head(&ts->rejected_promise_list);
39893997
ts->next_timer_id = 1;
39903998

39913999
JS_SetRuntimeOpaque(rt, ts);
@@ -4023,6 +4031,13 @@ void js_std_free_handlers(JSRuntime *rt)
40234031
free_timer(rt, th);
40244032
}
40254033

4034+
list_for_each_safe(el, el1, &ts->rejected_promise_list) {
4035+
JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link);
4036+
JS_FreeValueRT(rt, rp->promise);
4037+
JS_FreeValueRT(rt, rp->reason);
4038+
free(rp);
4039+
}
4040+
40264041
#ifdef USE_WORKER
40274042
/* XXX: free port_list ? */
40284043
js_free_message_pipe(ts->recv_pipe);
@@ -4048,13 +4063,66 @@ void js_std_dump_error(JSContext *ctx)
40484063
JS_FreeValue(ctx, exception_val);
40494064
}
40504065

4066+
static JSRejectedPromiseEntry *find_rejected_promise(JSContext *ctx, JSThreadState *ts,
4067+
JSValueConst promise)
4068+
{
4069+
struct list_head *el;
4070+
4071+
list_for_each(el, &ts->rejected_promise_list) {
4072+
JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link);
4073+
if (JS_SameValue(ctx, rp->promise, promise))
4074+
return rp;
4075+
}
4076+
return NULL;
4077+
}
4078+
40514079
void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise,
40524080
JSValueConst reason,
40534081
BOOL is_handled, void *opaque)
40544082
{
4083+
JSRuntime *rt = JS_GetRuntime(ctx);
4084+
JSThreadState *ts = JS_GetRuntimeOpaque(rt);
4085+
JSRejectedPromiseEntry *rp;
4086+
40554087
if (!is_handled) {
4056-
fprintf(stderr, "Possibly unhandled promise rejection: ");
4057-
js_std_dump_error1(ctx, reason);
4088+
/* add a new entry if needed */
4089+
rp = find_rejected_promise(ctx, ts, promise);
4090+
if (!rp) {
4091+
rp = malloc(sizeof(*rp));
4092+
if (rp) {
4093+
rp->promise = JS_DupValue(ctx, promise);
4094+
rp->reason = JS_DupValue(ctx, reason);
4095+
list_add_tail(&rp->link, &ts->rejected_promise_list);
4096+
}
4097+
}
4098+
} else {
4099+
/* the rejection is handled, so the entry can be removed if present */
4100+
rp = find_rejected_promise(ctx, ts, promise);
4101+
if (rp) {
4102+
JS_FreeValue(ctx, rp->promise);
4103+
JS_FreeValue(ctx, rp->reason);
4104+
list_del(&rp->link);
4105+
free(rp);
4106+
}
4107+
}
4108+
}
4109+
4110+
/* check if there are pending promise rejections. It must be done
4111+
asynchrously in case a rejected promise is handled later. Currently
4112+
we do it once the application is about to sleep. It could be done
4113+
more often if needed. */
4114+
static void js_std_promise_rejection_check(JSContext *ctx)
4115+
{
4116+
JSRuntime *rt = JS_GetRuntime(ctx);
4117+
JSThreadState *ts = JS_GetRuntimeOpaque(rt);
4118+
struct list_head *el;
4119+
4120+
if (unlikely(!list_empty(&ts->rejected_promise_list))) {
4121+
list_for_each(el, &ts->rejected_promise_list) {
4122+
JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link);
4123+
fprintf(stderr, "Possibly unhandled promise rejection: ");
4124+
js_std_dump_error1(ctx, rp->reason);
4125+
}
40584126
exit(1);
40594127
}
40604128
}
@@ -4077,6 +4145,8 @@ void js_std_loop(JSContext *ctx)
40774145
}
40784146
}
40794147

4148+
js_std_promise_rejection_check(ctx);
4149+
40804150
if (!os_poll_func || os_poll_func(ctx))
40814151
break;
40824152
}
@@ -4108,6 +4178,8 @@ JSValue js_std_await(JSContext *ctx, JSValue obj)
41084178
js_std_dump_error(ctx1);
41094179
}
41104180
if (err == 0) {
4181+
js_std_promise_rejection_check(ctx);
4182+
41114183
if (os_poll_func)
41124184
os_poll_func(ctx);
41134185
}

tests/test_std.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,22 @@ function test_async_gc()
294294
})();
295295
}
296296

297+
/* check that the promise async rejection handler is not invoked when
298+
the rejection is handled not too late after the promise
299+
rejection. */
300+
function test_async_promise_rejection()
301+
{
302+
var counter = 0;
303+
var p1, p2, p3;
304+
p1 = Promise.reject();
305+
p2 = Promise.reject();
306+
p3 = Promise.resolve();
307+
p1.catch(() => counter++);
308+
p2.catch(() => counter++);
309+
p3.then(() => counter++)
310+
os.setTimeout(() => { assert(counter, 3) }, 10);
311+
}
312+
297313
test_printf();
298314
test_file1();
299315
test_file2();
@@ -304,4 +320,5 @@ test_os_exec();
304320
test_timer();
305321
test_ext_json();
306322
test_async_gc();
323+
test_async_promise_rejection();
307324

0 commit comments

Comments
 (0)