Skip to content

Commit f751613

Browse files
Modules: improved handling of results of async handlers.
Previously, r.setReturnValue() had to be used when returning a value from async js_set handler. async function hash(r) { let hash = await crypto.subtle.digest('SHA-512', r.headersIn.host); r.setReturnValue(Buffer.from(hash).toString('hex')); } Now r.setReturnValue() is not needed: async function hash(r) { let hash = await crypto.subtle.digest('SHA-512', r.headersIn.host); return Buffer.from(hash).toString('hex'); } In addition added: infinite microtask loops detection, promise handling in global qjs code.
1 parent b605a4d commit f751613

12 files changed

+829
-62
lines changed

nginx/ngx_js.c

Lines changed: 178 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
#include "ngx_js.h"
1313

1414

15+
#define NGX_MAX_JOB_ITERATIONS 0x1000
16+
17+
1518
typedef struct {
1619
ngx_queue_t labels;
1720
} ngx_js_console_t;
@@ -683,6 +686,42 @@ ngx_njs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, void *external)
683686
}
684687

685688

689+
static njs_int_t
690+
ngx_njs_execute_pending_jobs(njs_vm_t *vm, ngx_js_ctx_t *ctx)
691+
{
692+
njs_int_t ret, job_count;
693+
ngx_str_t exception;
694+
695+
job_count = 0;
696+
697+
for ( ;; ) {
698+
ret = njs_vm_execute_pending_job(vm);
699+
if (ret <= NJS_OK) {
700+
if (ret == NJS_ERROR) {
701+
ngx_js_exception(vm, &exception);
702+
703+
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
704+
"js job exception: %V", &exception);
705+
return NGX_ERROR;
706+
}
707+
708+
break;
709+
}
710+
711+
job_count++;
712+
713+
if (job_count >= NGX_MAX_JOB_ITERATIONS) {
714+
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
715+
"js job queue processing exceeded %ui iterations",
716+
NGX_MAX_JOB_ITERATIONS);
717+
return NGX_ERROR;
718+
}
719+
}
720+
721+
return NGX_OK;
722+
}
723+
724+
686725
static ngx_int_t
687726
ngx_engine_njs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname,
688727
njs_opaque_value_t *args, njs_uint_t nargs)
@@ -698,6 +737,11 @@ ngx_engine_njs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname,
698737

699738
vm = ctx->engine->u.njs.vm;
700739

740+
ret = ngx_njs_execute_pending_jobs(vm, ctx);
741+
if (ret != NGX_OK) {
742+
return NGX_ERROR;
743+
}
744+
701745
func = njs_vm_function(vm, &name);
702746
if (func == NULL) {
703747
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
@@ -716,18 +760,27 @@ ngx_engine_njs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname,
716760
return NGX_ERROR;
717761
}
718762

719-
for ( ;; ) {
720-
ret = njs_vm_execute_pending_job(vm);
721-
if (ret <= NJS_OK) {
722-
if (ret == NJS_ERROR) {
723-
ngx_js_exception(vm, &exception);
763+
ret = ngx_njs_execute_pending_jobs(vm, ctx);
764+
if (ret != NGX_OK) {
765+
return NGX_ERROR;
766+
}
724767

725-
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
726-
"js job exception: %V", &exception);
727-
return NGX_ERROR;
728-
}
768+
njs_value_t *val = njs_value_arg(&ctx->retval);
769+
if (njs_value_is_promise(val)) {
770+
njs_promise_type_t state = njs_promise_state(val);
729771

730-
break;
772+
if (state == NJS_PROMISE_FULFILL) {
773+
njs_value_assign(val, njs_promise_result(val));
774+
775+
} else if (state == NJS_PROMISE_REJECTED) {
776+
njs_vm_throw(vm, njs_promise_result(val));
777+
778+
} else if (state == NJS_PROMISE_PENDING &&
779+
njs_rbtree_is_empty(&ctx->waiting_events))
780+
{
781+
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
782+
"js promise pending, no jobs, no waiting_events");
783+
return NGX_ERROR;
731784
}
732785
}
733786

@@ -741,6 +794,7 @@ ngx_engine_njs_external(ngx_engine_t *engine)
741794
return njs_vm_external_ptr(engine->u.njs.vm);
742795
}
743796

797+
744798
static ngx_int_t
745799
ngx_engine_njs_pending(ngx_engine_t *e)
746800
{
@@ -754,8 +808,9 @@ ngx_engine_njs_string(ngx_engine_t *e, njs_opaque_value_t *value,
754808
{
755809
ngx_int_t rc;
756810
njs_str_t s;
811+
njs_value_t *val = njs_value_arg(value);
757812

758-
rc = ngx_js_string(e->u.njs.vm, njs_value_arg(value), &s);
813+
rc = ngx_js_string(e->u.njs.vm, val, &s);
759814

760815
str->data = s.start;
761816
str->len = s.length;
@@ -875,38 +930,59 @@ ngx_engine_qjs_compile(ngx_js_loc_conf_t *conf, ngx_log_t *log, u_char *start,
875930

876931

877932
static JSValue
878-
js_std_await(JSContext *ctx, JSValue obj)
933+
ngx_qjs_await(JSContext *ctx, JSValue obj)
879934
{
880-
int state, err;
881-
JSValue ret;
882-
JSContext *ctx1;
935+
int rc, job_count;
936+
JSValue ret;
937+
JSRuntime *rt;
938+
ngx_js_ctx_t *js_ctx;
939+
940+
rt = JS_GetRuntime(ctx);
941+
js_ctx = ngx_qjs_external_ctx(ctx, JS_GetContextOpaque(ctx));
942+
943+
job_count = 0;
944+
945+
for ( ;; ) {
946+
rc = JS_ExecutePendingJob(rt, NULL);
947+
if (rc <= 0) {
948+
if (rc == -1) {
949+
JS_FreeValue(ctx, obj);
950+
return JS_EXCEPTION;
951+
}
952+
break;
953+
}
883954

884-
for (;;) {
885-
state = JS_PromiseState(ctx, obj);
955+
job_count++;
956+
957+
if (job_count >= NGX_MAX_JOB_ITERATIONS) {
958+
JS_FreeValue(ctx, obj);
959+
return JS_ThrowInternalError(ctx, "js job queue processing "
960+
"exceeded %u iterations",
961+
NGX_MAX_JOB_ITERATIONS);
962+
}
963+
}
964+
965+
if (JS_IsObject(obj)) {
966+
JSPromiseStateEnum state = JS_PromiseState(ctx, obj);
886967
if (state == JS_PROMISE_FULFILLED) {
887968
ret = JS_PromiseResult(ctx, obj);
888969
JS_FreeValue(ctx, obj);
889-
break;
890-
970+
return ret;
891971
} else if (state == JS_PROMISE_REJECTED) {
892-
ret = JS_Throw(ctx, JS_PromiseResult(ctx, obj));
972+
JSValue rejection = JS_PromiseResult(ctx, obj);
893973
JS_FreeValue(ctx, obj);
894-
break;
895-
974+
return JS_Throw(ctx, rejection);
896975
} else if (state == JS_PROMISE_PENDING) {
897-
err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
898-
if (err < 0) {
899-
/* js_std_dump_error(ctx1); */
976+
if (js_ctx && njs_rbtree_is_empty(&js_ctx->waiting_events)) {
977+
JS_FreeValue(ctx, obj);
978+
return JS_ThrowInternalError(ctx, "js promise pending, "
979+
"no jobs, no waiting_events");
900980
}
901-
902-
} else {
903-
/* not a promise */
904-
ret = obj;
905-
break;
981+
return obj;
906982
}
907983
}
908984

909-
return ret;
985+
return obj;
910986
}
911987

912988

@@ -1000,12 +1076,13 @@ ngx_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, void *external)
10001076
goto destroy;
10011077
}
10021078

1003-
rv = js_std_await(cx, rv);
1079+
rv = ngx_qjs_await(cx, rv);
10041080
if (JS_IsException(rv)) {
10051081
ngx_qjs_exception(engine, &exception);
10061082

10071083
ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js eval exception: %V",
10081084
&exception);
1085+
10091086
goto destroy;
10101087
}
10111088

@@ -1023,6 +1100,43 @@ ngx_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, void *external)
10231100
}
10241101

10251102

1103+
static ngx_int_t
1104+
ngx_qjs_execute_pending_jobs(ngx_js_ctx_t *ctx, JSRuntime *rt)
1105+
{
1106+
int rc, job_count;
1107+
ngx_str_t exception;
1108+
1109+
job_count = 0;
1110+
1111+
for ( ;; ) {
1112+
rc = JS_ExecutePendingJob(rt, NULL);
1113+
if (rc <= 0) {
1114+
if (rc == -1) {
1115+
ngx_qjs_exception(ctx->engine, &exception);
1116+
1117+
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
1118+
"js job exception: %V", &exception);
1119+
1120+
return NGX_ERROR;
1121+
}
1122+
1123+
break;
1124+
}
1125+
1126+
job_count++;
1127+
1128+
if (job_count >= NGX_MAX_JOB_ITERATIONS) {
1129+
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
1130+
"js job queue processing exceeded %ui iterations",
1131+
NGX_MAX_JOB_ITERATIONS);
1132+
return NGX_ERROR;
1133+
}
1134+
}
1135+
1136+
return NGX_OK;
1137+
}
1138+
1139+
10261140
static ngx_int_t
10271141
ngx_engine_qjs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname,
10281142
njs_opaque_value_t *args, njs_uint_t nargs)
@@ -1031,9 +1145,15 @@ ngx_engine_qjs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname,
10311145
JSValue fn, val;
10321146
ngx_str_t exception;
10331147
JSRuntime *rt;
1034-
JSContext *cx, *cx1;
1148+
JSContext *cx;
10351149

10361150
cx = ctx->engine->u.qjs.ctx;
1151+
rt = JS_GetRuntime(cx);
1152+
1153+
rc = ngx_qjs_execute_pending_jobs(ctx, rt);
1154+
if (rc != NGX_OK) {
1155+
return NGX_ERROR;
1156+
}
10371157

10381158
fn = ngx_qjs_value(cx, fname);
10391159
if (!JS_IsFunction(cx, fn)) {
@@ -1058,21 +1178,31 @@ ngx_engine_qjs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname,
10581178
JS_FreeValue(cx, ngx_qjs_arg(ctx->retval));
10591179
ngx_qjs_arg(ctx->retval) = val;
10601180

1061-
rt = JS_GetRuntime(cx);
1181+
rc = ngx_qjs_execute_pending_jobs(ctx, rt);
1182+
if (rc != NGX_OK) {
1183+
return NGX_ERROR;
1184+
}
10621185

1063-
for ( ;; ) {
1064-
rc = JS_ExecutePendingJob(rt, &cx1);
1065-
if (rc <= 0) {
1066-
if (rc == -1) {
1067-
ngx_qjs_exception(ctx->engine, &exception);
1186+
JSValue retval = ngx_qjs_arg(ctx->retval);
1187+
if (JS_IsObject(retval)) {
1188+
JSPromiseStateEnum state = JS_PromiseState(cx, retval);
10681189

1069-
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
1070-
"js job exception: %V", &exception);
1190+
if (state == JS_PROMISE_FULFILLED) {
1191+
JSValue result = JS_PromiseResult(cx, retval);
1192+
JS_FreeValue(cx, ngx_qjs_arg(ctx->retval));
1193+
ngx_qjs_arg(ctx->retval) = result;
10711194

1072-
return NGX_ERROR;
1073-
}
1195+
} else if (state == JS_PROMISE_REJECTED) {
1196+
JSValue rejection = JS_PromiseResult(cx, retval);
1197+
JS_FreeValue(cx, ngx_qjs_arg(ctx->retval));
1198+
ngx_qjs_arg(ctx->retval) = JS_Throw(cx, rejection);
10741199

1075-
break;
1200+
} else if (state == JS_PROMISE_PENDING &&
1201+
njs_rbtree_is_empty(&ctx->waiting_events))
1202+
{
1203+
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
1204+
"js promise pending, no jobs, no waiting_events");
1205+
return NGX_ERROR;
10761206
}
10771207
}
10781208

@@ -1098,7 +1228,9 @@ static ngx_int_t
10981228
ngx_engine_qjs_string(ngx_engine_t *e, njs_opaque_value_t *value,
10991229
ngx_str_t *str)
11001230
{
1101-
return ngx_qjs_dump_obj(e, ngx_qjs_arg(*value), str);
1231+
JSValue val = ngx_qjs_arg(*value);
1232+
1233+
return ngx_qjs_dump_obj(e, val, str);
11021234
}
11031235

11041236

@@ -1386,7 +1518,6 @@ ngx_qjs_call(JSContext *cx, JSValue fn, JSValue *argv, int argc)
13861518
JSValue ret;
13871519
ngx_str_t exception;
13881520
JSRuntime *rt;
1389-
JSContext *cx1;
13901521
ngx_js_ctx_t *ctx;
13911522

13921523
ctx = ngx_qjs_external_ctx(cx, JS_GetContextOpaque(cx));
@@ -1406,7 +1537,7 @@ ngx_qjs_call(JSContext *cx, JSValue fn, JSValue *argv, int argc)
14061537
rt = JS_GetRuntime(cx);
14071538

14081539
for ( ;; ) {
1409-
rc = JS_ExecutePendingJob(rt, &cx1);
1540+
rc = JS_ExecutePendingJob(rt, NULL);
14101541
if (rc <= 0) {
14111542
if (rc == -1) {
14121543
ngx_qjs_exception(ctx->engine, &exception);

0 commit comments

Comments
 (0)