Skip to content

Commit

Permalink
Report async failures via exit code
Browse files Browse the repository at this point in the history
Fixes: #340
  • Loading branch information
saghul committed Sep 10, 2024
1 parent 5bf3545 commit 7ad9807
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 31 deletions.
7 changes: 6 additions & 1 deletion qjs.c
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ int main(int argc, char **argv)
{
JSRuntime *rt;
JSContext *ctx;
JSValue ret;
struct trace_malloc_data trace_data = { NULL };
int optind;
char *expr = NULL;
Expand Down Expand Up @@ -531,7 +532,11 @@ int main(int argc, char **argv)
if (interactive) {
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) {
Expand Down
65 changes: 40 additions & 25 deletions quickjs-libc.c
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ typedef struct JSThreadState {
struct list_head port_list; /* list of JSWorkerMessageHandler.link */
int eval_script_recurse; /* only used in the main thread */
int64_t next_timer_id; /* for setTimeout / setInterval */
JSValue exc; /* current exception from one of our handlers */
/* not used in the main thread */
JSWorkerMessagePipe *recv_pipe, *send_pipe;
} JSThreadState;
Expand Down Expand Up @@ -2133,51 +2134,61 @@ static JSValue js_os_sleepAsync(JSContext *ctx, JSValueConst this_val,
return promise;
}

static void call_handler(JSContext *ctx, JSValue func)
static int call_handler(JSContext *ctx, JSValue func)
{
int r;
JSValue ret, func1;
/* 'func' might be destroyed when calling itself (if it frees the
handler), so must take extra care */
func1 = JS_DupValue(ctx, func);
ret = JS_Call(ctx, func1, JS_UNDEFINED, 0, NULL);
JS_FreeValue(ctx, func1);
if (JS_IsException(ret))
js_std_dump_error(ctx);
r = 0;
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);
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;
JSOSTimer *th;
int min_delay;
int64_t cur_time, delay;
struct list_head *el;
int r;

if (list_empty(&ts->os_timers))
return -1;
if (list_empty(&ts->os_timers)) {
*min_delay = -1;
return 0;
}

cur_time = js__hrtime_ms();
min_delay = 10000;
*min_delay = INT32_MAX;

list_for_each(el, &ts->os_timers) {
th = list_entry(el, JSOSTimer, link);
delay = th->timeout - cur_time;
if (delay > 0) {
min_delay = min_int(min_delay, delay);
*min_delay = min_int(*min_delay, delay);
} else {
*min_delay = 0;
func = JS_DupValueRT(rt, th->func);
if (th->repeats)
th->timeout = cur_time + th->delay;
else
free_timer(rt, th);
call_handler(ctx, func);
r = call_handler(ctx, func);
JS_FreeValueRT(rt, func);
return 0;
return r;
}
}

return min_delay;
return 0;
}

#if defined(_WIN32)
Expand All @@ -2192,7 +2203,8 @@ static int js_os_poll(JSContext *ctx)

/* 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)
return 0; // expired timer
if (min_delay < 0)
Expand Down Expand Up @@ -2221,9 +2233,8 @@ static int js_os_poll(JSContext *ctx)
list_for_each(el, &ts->os_rw_handlers) {
rh = list_entry(el, JSOSRWHandler, link);
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 */
break;
}
}
}
Expand Down Expand Up @@ -2332,13 +2343,13 @@ static int js_os_poll(JSContext *ctx)
mask = (uint64_t)1 << sh->sig_num;
if (os_pending_signals & mask) {
os_pending_signals &= ~mask;
call_handler(ctx, sh->func);
return 0;
return call_handler(ctx, sh->func);
}
}
}

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)
return 0; // expired timer
if (min_delay < 0)
Expand Down Expand Up @@ -2379,15 +2390,13 @@ static int js_os_poll(JSContext *ctx)
rh = list_entry(el, JSOSRWHandler, link);
if (!JS_IsNull(rh->rw_func[0]) &&
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 */
goto done;
}
if (!JS_IsNull(rh->rw_func[1]) &&
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 */
goto done;
}
}

Expand Down Expand Up @@ -3879,6 +3888,7 @@ void js_std_init_handlers(JSRuntime *rt)
init_list_head(&ts->port_list);

ts->next_timer_id = 1;
ts->exc = JS_UNDEFINED;

JS_SetRuntimeOpaque(rt, ts);

Expand Down Expand Up @@ -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;
BOOL is_error;
Expand Down Expand Up @@ -3974,8 +3984,10 @@ void js_std_promise_rejection_tracker(JSContext *ctx, JSValue promise,
}

/* 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;
int err;

Expand All @@ -3985,7 +3997,8 @@ void js_std_loop(JSContext *ctx)
err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
if (err <= 0) {
if (err < 0) {
js_std_dump_error(ctx1);
ts->exc = JS_GetException(ctx1);
goto done;
}
break;
}
Expand All @@ -3994,6 +4007,8 @@ void js_std_loop(JSContext *ctx)
if (!os_poll_func || os_poll_func(ctx))
break;
}
done:
return ts->exc;
}

/* Wait for a promise and execute pending jobs while waiting for
Expand Down
3 changes: 2 additions & 1 deletion quickjs-libc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_bjson(JSContext *ctx, const char *module_name);
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);
void js_std_init_handlers(JSRuntime *rt);
void js_std_free_handlers(JSRuntime *rt);
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);
int js_module_set_import_meta(JSContext *ctx, JSValue func_val,
JS_BOOL use_realpath, JS_BOOL is_main);
Expand Down
8 changes: 4 additions & 4 deletions tests/test_std.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,10 +280,10 @@ function test_timeout_order()
if (globalThis.__running_with_sanitizer__) return;

var s = "";
os.setTimeout(a, 100);
os.setTimeout(b, 200);
os.setTimeout(d, 500);
function a() { s += "a"; os.setTimeout(c, 200); }
os.setTimeout(a, 0);
os.setTimeout(b, 100);
os.setTimeout(d, 700);
function a() { s += "a"; os.setTimeout(c, 300); }
function b() { s += "b"; }
function c() { s += "c"; }
function d() { assert(s === "abc"); } // not "acb"
Expand Down

0 comments on commit 7ad9807

Please sign in to comment.