Report async failures via exit code

Fixes: https://github.com/quickjs-ng/quickjs/issues/340
This commit is contained in:
Saúl Ibarra Corretgé 2024-09-09 23:14:22 +02:00
parent 5bf35450cd
commit 7ad980704c
4 changed files with 52 additions and 31 deletions

7
qjs.c
View file

@ -321,6 +321,7 @@ int main(int argc, char **argv)
{ {
JSRuntime *rt; JSRuntime *rt;
JSContext *ctx; JSContext *ctx;
JSValue ret;
struct trace_malloc_data trace_data = { NULL }; struct trace_malloc_data trace_data = { NULL };
int optind; int optind;
char *expr = NULL; char *expr = NULL;
@ -531,7 +532,11 @@ int main(int argc, char **argv)
if (interactive) { if (interactive) {
js_std_eval_binary(ctx, qjsc_repl, qjsc_repl_size, 0); js_std_eval_binary(ctx, qjsc_repl, qjsc_repl_size, 0);
} }
js_std_loop(ctx); ret = js_std_loop(ctx);
if (!JS_IsUndefined(ret)) {
js_std_dump_error1(ctx, ret);
goto fail;
}
} }
if (dump_memory) { if (dump_memory) {

View file

@ -153,6 +153,7 @@ typedef struct JSThreadState {
struct list_head port_list; /* list of JSWorkerMessageHandler.link */ struct list_head port_list; /* list of JSWorkerMessageHandler.link */
int eval_script_recurse; /* only used in the main thread */ int eval_script_recurse; /* only used in the main thread */
int64_t next_timer_id; /* for setTimeout / setInterval */ int64_t next_timer_id; /* for setTimeout / setInterval */
JSValue exc; /* current exception from one of our handlers */
/* not used in the main thread */ /* not used in the main thread */
JSWorkerMessagePipe *recv_pipe, *send_pipe; JSWorkerMessagePipe *recv_pipe, *send_pipe;
} JSThreadState; } JSThreadState;
@ -2133,51 +2134,61 @@ static JSValue js_os_sleepAsync(JSContext *ctx, JSValueConst this_val,
return promise; return promise;
} }
static void call_handler(JSContext *ctx, JSValue func) static int call_handler(JSContext *ctx, JSValue func)
{ {
int r;
JSValue ret, func1; JSValue ret, func1;
/* 'func' might be destroyed when calling itself (if it frees the /* 'func' might be destroyed when calling itself (if it frees the
handler), so must take extra care */ handler), so must take extra care */
func1 = JS_DupValue(ctx, func); func1 = JS_DupValue(ctx, func);
ret = JS_Call(ctx, func1, JS_UNDEFINED, 0, NULL); ret = JS_Call(ctx, func1, JS_UNDEFINED, 0, NULL);
JS_FreeValue(ctx, func1); JS_FreeValue(ctx, func1);
if (JS_IsException(ret)) r = 0;
js_std_dump_error(ctx); if (JS_IsException(ret)) {
JSRuntime *rt = JS_GetRuntime(ctx);
JSThreadState *ts = JS_GetRuntimeOpaque(rt);
ts->exc = JS_GetException(ctx);
r = -1;
}
JS_FreeValue(ctx, ret); JS_FreeValue(ctx, ret);
return r;
} }
static int js_os_run_timers(JSRuntime *rt, JSContext *ctx, JSThreadState *ts) static int js_os_run_timers(JSRuntime *rt, JSContext *ctx, JSThreadState *ts, int *min_delay)
{ {
JSValue func; JSValue func;
JSOSTimer *th; JSOSTimer *th;
int min_delay;
int64_t cur_time, delay; int64_t cur_time, delay;
struct list_head *el; struct list_head *el;
int r;
if (list_empty(&ts->os_timers)) if (list_empty(&ts->os_timers)) {
return -1; *min_delay = -1;
return 0;
}
cur_time = js__hrtime_ms(); cur_time = js__hrtime_ms();
min_delay = 10000; *min_delay = INT32_MAX;
list_for_each(el, &ts->os_timers) { list_for_each(el, &ts->os_timers) {
th = list_entry(el, JSOSTimer, link); th = list_entry(el, JSOSTimer, link);
delay = th->timeout - cur_time; delay = th->timeout - cur_time;
if (delay > 0) { if (delay > 0) {
min_delay = min_int(min_delay, delay); *min_delay = min_int(*min_delay, delay);
} else { } else {
*min_delay = 0;
func = JS_DupValueRT(rt, th->func); func = JS_DupValueRT(rt, th->func);
if (th->repeats) if (th->repeats)
th->timeout = cur_time + th->delay; th->timeout = cur_time + th->delay;
else else
free_timer(rt, th); free_timer(rt, th);
call_handler(ctx, func); r = call_handler(ctx, func);
JS_FreeValueRT(rt, func); JS_FreeValueRT(rt, func);
return 0; return r;
} }
} }
return min_delay; return 0;
} }
#if defined(_WIN32) #if defined(_WIN32)
@ -2192,7 +2203,8 @@ static int js_os_poll(JSContext *ctx)
/* XXX: handle signals if useful */ /* XXX: handle signals if useful */
min_delay = js_os_run_timers(rt, ctx, ts); if (js_os_run_timers(rt, ctx, ts, &min_delay))
return -1;
if (min_delay == 0) if (min_delay == 0)
return 0; // expired timer return 0; // expired timer
if (min_delay < 0) if (min_delay < 0)
@ -2221,9 +2233,8 @@ static int js_os_poll(JSContext *ctx)
list_for_each(el, &ts->os_rw_handlers) { list_for_each(el, &ts->os_rw_handlers) {
rh = list_entry(el, JSOSRWHandler, link); rh = list_entry(el, JSOSRWHandler, link);
if (rh->fd == console_fd && !JS_IsNull(rh->rw_func[0])) { if (rh->fd == console_fd && !JS_IsNull(rh->rw_func[0])) {
call_handler(ctx, rh->rw_func[0]); return call_handler(ctx, rh->rw_func[0]);
/* must stop because the list may have been modified */ /* must stop because the list may have been modified */
break;
} }
} }
} }
@ -2332,13 +2343,13 @@ static int js_os_poll(JSContext *ctx)
mask = (uint64_t)1 << sh->sig_num; mask = (uint64_t)1 << sh->sig_num;
if (os_pending_signals & mask) { if (os_pending_signals & mask) {
os_pending_signals &= ~mask; os_pending_signals &= ~mask;
call_handler(ctx, sh->func); return call_handler(ctx, sh->func);
return 0;
} }
} }
} }
min_delay = js_os_run_timers(rt, ctx, ts); if (js_os_run_timers(rt, ctx, ts, &min_delay))
return -1;
if (min_delay == 0) if (min_delay == 0)
return 0; // expired timer return 0; // expired timer
if (min_delay < 0) if (min_delay < 0)
@ -2379,15 +2390,13 @@ static int js_os_poll(JSContext *ctx)
rh = list_entry(el, JSOSRWHandler, link); rh = list_entry(el, JSOSRWHandler, link);
if (!JS_IsNull(rh->rw_func[0]) && if (!JS_IsNull(rh->rw_func[0]) &&
FD_ISSET(rh->fd, &rfds)) { FD_ISSET(rh->fd, &rfds)) {
call_handler(ctx, rh->rw_func[0]); return call_handler(ctx, rh->rw_func[0]);
/* must stop because the list may have been modified */ /* must stop because the list may have been modified */
goto done;
} }
if (!JS_IsNull(rh->rw_func[1]) && if (!JS_IsNull(rh->rw_func[1]) &&
FD_ISSET(rh->fd, &wfds)) { FD_ISSET(rh->fd, &wfds)) {
call_handler(ctx, rh->rw_func[1]); return call_handler(ctx, rh->rw_func[1]);
/* must stop because the list may have been modified */ /* must stop because the list may have been modified */
goto done;
} }
} }
@ -3879,6 +3888,7 @@ void js_std_init_handlers(JSRuntime *rt)
init_list_head(&ts->port_list); init_list_head(&ts->port_list);
ts->next_timer_id = 1; ts->next_timer_id = 1;
ts->exc = JS_UNDEFINED;
JS_SetRuntimeOpaque(rt, ts); JS_SetRuntimeOpaque(rt, ts);
@ -3938,7 +3948,7 @@ static void js_dump_obj(JSContext *ctx, FILE *f, JSValue val)
} }
} }
static void js_std_dump_error1(JSContext *ctx, JSValue exception_val) void js_std_dump_error1(JSContext *ctx, JSValue exception_val)
{ {
JSValue val; JSValue val;
BOOL is_error; BOOL is_error;
@ -3974,8 +3984,10 @@ void js_std_promise_rejection_tracker(JSContext *ctx, JSValue promise,
} }
/* main loop which calls the user JS callbacks */ /* main loop which calls the user JS callbacks */
void js_std_loop(JSContext *ctx) JSValue js_std_loop(JSContext *ctx)
{ {
JSRuntime *rt = JS_GetRuntime(ctx);
JSThreadState *ts = JS_GetRuntimeOpaque(rt);
JSContext *ctx1; JSContext *ctx1;
int err; int err;
@ -3985,7 +3997,8 @@ void js_std_loop(JSContext *ctx)
err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1); err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
if (err <= 0) { if (err <= 0) {
if (err < 0) { if (err < 0) {
js_std_dump_error(ctx1); ts->exc = JS_GetException(ctx1);
goto done;
} }
break; break;
} }
@ -3994,6 +4007,8 @@ void js_std_loop(JSContext *ctx)
if (!os_poll_func || os_poll_func(ctx)) if (!os_poll_func || os_poll_func(ctx))
break; break;
} }
done:
return ts->exc;
} }
/* Wait for a promise and execute pending jobs while waiting for /* Wait for a promise and execute pending jobs while waiting for

View file

@ -37,11 +37,12 @@ JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name);
JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name); JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name);
JSModuleDef *js_init_module_bjson(JSContext *ctx, const char *module_name); JSModuleDef *js_init_module_bjson(JSContext *ctx, const char *module_name);
void js_std_add_helpers(JSContext *ctx, int argc, char **argv); void js_std_add_helpers(JSContext *ctx, int argc, char **argv);
void js_std_loop(JSContext *ctx); JSValue js_std_loop(JSContext *ctx);
JSValue js_std_await(JSContext *ctx, JSValue obj); JSValue js_std_await(JSContext *ctx, JSValue obj);
void js_std_init_handlers(JSRuntime *rt); void js_std_init_handlers(JSRuntime *rt);
void js_std_free_handlers(JSRuntime *rt); void js_std_free_handlers(JSRuntime *rt);
void js_std_dump_error(JSContext *ctx); void js_std_dump_error(JSContext *ctx);
void js_std_dump_error1(JSContext *ctx, JSValue exception_val);
uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename); uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename);
int js_module_set_import_meta(JSContext *ctx, JSValue func_val, int js_module_set_import_meta(JSContext *ctx, JSValue func_val,
JS_BOOL use_realpath, JS_BOOL is_main); JS_BOOL use_realpath, JS_BOOL is_main);

View file

@ -280,10 +280,10 @@ function test_timeout_order()
if (globalThis.__running_with_sanitizer__) return; if (globalThis.__running_with_sanitizer__) return;
var s = ""; var s = "";
os.setTimeout(a, 100); os.setTimeout(a, 0);
os.setTimeout(b, 200); os.setTimeout(b, 100);
os.setTimeout(d, 500); os.setTimeout(d, 700);
function a() { s += "a"; os.setTimeout(c, 200); } function a() { s += "a"; os.setTimeout(c, 300); }
function b() { s += "b"; } function b() { s += "b"; }
function c() { s += "c"; } function c() { s += "c"; }
function d() { assert(s === "abc"); } // not "acb" function d() { assert(s === "abc"); } // not "acb"