mirror of
https://github.com/bellard/quickjs.git
synced 2025-05-29 01:49:18 +08:00
better promise rejection tracker heuristics (#112)
This commit is contained in:
parent
d7cdfdc8d7
commit
3c39307c22
@ -136,11 +136,18 @@ typedef struct {
|
|||||||
JSValue on_message_func;
|
JSValue on_message_func;
|
||||||
} JSWorkerMessageHandler;
|
} JSWorkerMessageHandler;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
struct list_head link;
|
||||||
|
JSValue promise;
|
||||||
|
JSValue reason;
|
||||||
|
} JSRejectedPromiseEntry;
|
||||||
|
|
||||||
typedef struct JSThreadState {
|
typedef struct JSThreadState {
|
||||||
struct list_head os_rw_handlers; /* list of JSOSRWHandler.link */
|
struct list_head os_rw_handlers; /* list of JSOSRWHandler.link */
|
||||||
struct list_head os_signal_handlers; /* list JSOSSignalHandler.link */
|
struct list_head os_signal_handlers; /* list JSOSSignalHandler.link */
|
||||||
struct list_head os_timers; /* list of JSOSTimer.link */
|
struct list_head os_timers; /* list of JSOSTimer.link */
|
||||||
struct list_head port_list; /* list of JSWorkerMessageHandler.link */
|
struct list_head port_list; /* list of JSWorkerMessageHandler.link */
|
||||||
|
struct list_head rejected_promise_list; /* list of JSRejectedPromiseEntry.link */
|
||||||
int eval_script_recurse; /* only used in the main thread */
|
int eval_script_recurse; /* only used in the main thread */
|
||||||
int next_timer_id; /* for setTimeout() */
|
int next_timer_id; /* for setTimeout() */
|
||||||
/* not used in the main thread */
|
/* not used in the main thread */
|
||||||
@ -3986,6 +3993,7 @@ void js_std_init_handlers(JSRuntime *rt)
|
|||||||
init_list_head(&ts->os_signal_handlers);
|
init_list_head(&ts->os_signal_handlers);
|
||||||
init_list_head(&ts->os_timers);
|
init_list_head(&ts->os_timers);
|
||||||
init_list_head(&ts->port_list);
|
init_list_head(&ts->port_list);
|
||||||
|
init_list_head(&ts->rejected_promise_list);
|
||||||
ts->next_timer_id = 1;
|
ts->next_timer_id = 1;
|
||||||
|
|
||||||
JS_SetRuntimeOpaque(rt, ts);
|
JS_SetRuntimeOpaque(rt, ts);
|
||||||
@ -4023,6 +4031,13 @@ void js_std_free_handlers(JSRuntime *rt)
|
|||||||
free_timer(rt, th);
|
free_timer(rt, th);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
list_for_each_safe(el, el1, &ts->rejected_promise_list) {
|
||||||
|
JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link);
|
||||||
|
JS_FreeValueRT(rt, rp->promise);
|
||||||
|
JS_FreeValueRT(rt, rp->reason);
|
||||||
|
free(rp);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef USE_WORKER
|
#ifdef USE_WORKER
|
||||||
/* XXX: free port_list ? */
|
/* XXX: free port_list ? */
|
||||||
js_free_message_pipe(ts->recv_pipe);
|
js_free_message_pipe(ts->recv_pipe);
|
||||||
@ -4048,13 +4063,66 @@ void js_std_dump_error(JSContext *ctx)
|
|||||||
JS_FreeValue(ctx, exception_val);
|
JS_FreeValue(ctx, exception_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static JSRejectedPromiseEntry *find_rejected_promise(JSContext *ctx, JSThreadState *ts,
|
||||||
|
JSValueConst promise)
|
||||||
|
{
|
||||||
|
struct list_head *el;
|
||||||
|
|
||||||
|
list_for_each(el, &ts->rejected_promise_list) {
|
||||||
|
JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link);
|
||||||
|
if (JS_SameValue(ctx, rp->promise, promise))
|
||||||
|
return rp;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise,
|
void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise,
|
||||||
JSValueConst reason,
|
JSValueConst reason,
|
||||||
BOOL is_handled, void *opaque)
|
BOOL is_handled, void *opaque)
|
||||||
{
|
{
|
||||||
|
JSRuntime *rt = JS_GetRuntime(ctx);
|
||||||
|
JSThreadState *ts = JS_GetRuntimeOpaque(rt);
|
||||||
|
JSRejectedPromiseEntry *rp;
|
||||||
|
|
||||||
if (!is_handled) {
|
if (!is_handled) {
|
||||||
fprintf(stderr, "Possibly unhandled promise rejection: ");
|
/* add a new entry if needed */
|
||||||
js_std_dump_error1(ctx, reason);
|
rp = find_rejected_promise(ctx, ts, promise);
|
||||||
|
if (!rp) {
|
||||||
|
rp = malloc(sizeof(*rp));
|
||||||
|
if (rp) {
|
||||||
|
rp->promise = JS_DupValue(ctx, promise);
|
||||||
|
rp->reason = JS_DupValue(ctx, reason);
|
||||||
|
list_add_tail(&rp->link, &ts->rejected_promise_list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* the rejection is handled, so the entry can be removed if present */
|
||||||
|
rp = find_rejected_promise(ctx, ts, promise);
|
||||||
|
if (rp) {
|
||||||
|
JS_FreeValue(ctx, rp->promise);
|
||||||
|
JS_FreeValue(ctx, rp->reason);
|
||||||
|
list_del(&rp->link);
|
||||||
|
free(rp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check if there are pending promise rejections. It must be done
|
||||||
|
asynchrously in case a rejected promise is handled later. Currently
|
||||||
|
we do it once the application is about to sleep. It could be done
|
||||||
|
more often if needed. */
|
||||||
|
static void js_std_promise_rejection_check(JSContext *ctx)
|
||||||
|
{
|
||||||
|
JSRuntime *rt = JS_GetRuntime(ctx);
|
||||||
|
JSThreadState *ts = JS_GetRuntimeOpaque(rt);
|
||||||
|
struct list_head *el;
|
||||||
|
|
||||||
|
if (unlikely(!list_empty(&ts->rejected_promise_list))) {
|
||||||
|
list_for_each(el, &ts->rejected_promise_list) {
|
||||||
|
JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link);
|
||||||
|
fprintf(stderr, "Possibly unhandled promise rejection: ");
|
||||||
|
js_std_dump_error1(ctx, rp->reason);
|
||||||
|
}
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4077,6 +4145,8 @@ void js_std_loop(JSContext *ctx)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
js_std_promise_rejection_check(ctx);
|
||||||
|
|
||||||
if (!os_poll_func || os_poll_func(ctx))
|
if (!os_poll_func || os_poll_func(ctx))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -4108,6 +4178,8 @@ JSValue js_std_await(JSContext *ctx, JSValue obj)
|
|||||||
js_std_dump_error(ctx1);
|
js_std_dump_error(ctx1);
|
||||||
}
|
}
|
||||||
if (err == 0) {
|
if (err == 0) {
|
||||||
|
js_std_promise_rejection_check(ctx);
|
||||||
|
|
||||||
if (os_poll_func)
|
if (os_poll_func)
|
||||||
os_poll_func(ctx);
|
os_poll_func(ctx);
|
||||||
}
|
}
|
||||||
|
@ -294,6 +294,22 @@ function test_async_gc()
|
|||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* check that the promise async rejection handler is not invoked when
|
||||||
|
the rejection is handled not too late after the promise
|
||||||
|
rejection. */
|
||||||
|
function test_async_promise_rejection()
|
||||||
|
{
|
||||||
|
var counter = 0;
|
||||||
|
var p1, p2, p3;
|
||||||
|
p1 = Promise.reject();
|
||||||
|
p2 = Promise.reject();
|
||||||
|
p3 = Promise.resolve();
|
||||||
|
p1.catch(() => counter++);
|
||||||
|
p2.catch(() => counter++);
|
||||||
|
p3.then(() => counter++)
|
||||||
|
os.setTimeout(() => { assert(counter, 3) }, 10);
|
||||||
|
}
|
||||||
|
|
||||||
test_printf();
|
test_printf();
|
||||||
test_file1();
|
test_file1();
|
||||||
test_file2();
|
test_file2();
|
||||||
@ -304,4 +320,5 @@ test_os_exec();
|
|||||||
test_timer();
|
test_timer();
|
||||||
test_ext_json();
|
test_ext_json();
|
||||||
test_async_gc();
|
test_async_gc();
|
||||||
|
test_async_promise_rejection();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user