[njs] Modules: improved reporting of unhandled promise rejections.

noreply at nginx.com noreply at nginx.com
Tue Feb 25 00:50:02 UTC 2025


details:   https://github.com/nginx/njs/commit/24633b3766df99de3c138ca6dc37b0cb403e619d
branches:  master
commit:    24633b3766df99de3c138ca6dc37b0cb403e619d
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Fri, 21 Feb 2025 20:40:14 -0800
description:
Modules: improved reporting of unhandled promise rejections.

Previously, some promise rejections were not reported.

For example:

async function timeout(ms) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(new Error('timeout'));
        }, ms);
    });
}

async function handler(r) {
    let v = await timeout(1000);
    r.return(200);
}

---
 nginx/ngx_js.c     | 72 +++++++++++++++++-------------------------------------
 nginx/t/js_async.t | 24 ++++++++++++++++--
 2 files changed, 45 insertions(+), 51 deletions(-)

diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c
index 7f7b7362..383d2304 100644
--- a/nginx/ngx_js.c
+++ b/nginx/ngx_js.c
@@ -731,13 +731,6 @@ ngx_engine_njs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname,
         }
     }
 
-    if (ngx_js_unhandled_rejection(ctx)) {
-        ngx_js_exception(vm, &exception);
-
-        ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js exception: %V", &exception);
-        return NGX_ERROR;
-    }
-
     return njs_rbtree_is_empty(&ctx->waiting_events) ? NGX_OK : NGX_AGAIN;
 }
 
@@ -775,6 +768,7 @@ static void
 ngx_engine_njs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx,
     ngx_js_loc_conf_t *conf)
 {
+    ngx_str_t           exception;
     ngx_js_event_t     *event;
     njs_rbtree_node_t  *node;
 
@@ -791,6 +785,12 @@ ngx_engine_njs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx,
 
             node = njs_rbtree_node_successor(&ctx->waiting_events, node);
         }
+
+        if (ngx_js_unhandled_rejection(ctx)) {
+            ngx_js_exception(e->u.njs.vm, &exception);
+            ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+                          "js unhandled rejection: %V", &exception);
+        }
     }
 
     njs_vm_destroy(e->u.njs.vm);
@@ -1071,13 +1071,6 @@ ngx_engine_qjs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname,
         }
     }
 
-    if (ngx_qjs_unhandled_rejection(ctx)) {
-        ngx_qjs_exception(ctx->engine, &exception);
-
-        ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js exception: %V", &exception);
-        return NGX_ERROR;
-    }
-
     return njs_rbtree_is_empty(&ctx->waiting_events) ? NGX_OK : NGX_AGAIN;
 }
 
@@ -1129,16 +1122,16 @@ void
 ngx_engine_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx,
     ngx_js_loc_conf_t *conf)
 {
-    uint32_t                    i, length;
-    JSRuntime                  *rt;
-    JSContext                  *cx;
-    JSClassID                   class_id;
-    ngx_qjs_event_t            *event;
-    ngx_js_opaque_t            *opaque;
-    njs_rbtree_node_t          *node;
-    ngx_pool_cleanup_t         *cln;
-    ngx_js_code_entry_t        *pc;
-    ngx_js_rejected_promise_t  *rejected_promise;
+    uint32_t              i, length;
+    ngx_str_t             exception;
+    JSRuntime            *rt;
+    JSContext            *cx;
+    JSClassID             class_id;
+    ngx_qjs_event_t      *event;
+    ngx_js_opaque_t      *opaque;
+    njs_rbtree_node_t    *node;
+    ngx_pool_cleanup_t   *cln;
+    ngx_js_code_entry_t  *pc;
 
     cx = e->u.qjs.ctx;
 
@@ -1156,13 +1149,10 @@ ngx_engine_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx,
             node = njs_rbtree_node_successor(&ctx->waiting_events, node);
         }
 
-        if (ctx->rejected_promises != NULL) {
-            rejected_promise = ctx->rejected_promises->start;
-
-            for (i = 0; i < ctx->rejected_promises->items; i++) {
-                JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].promise));
-                JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].message));
-            }
+        if (ngx_qjs_unhandled_rejection(ctx)) {
+            ngx_qjs_exception(ctx->engine, &exception);
+            ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+                          "js unhandled rejection: %V", &exception);
         }
 
         class_id = JS_GetClassID(ngx_qjs_arg(ctx->args[0]));
@@ -2037,10 +2027,8 @@ ngx_qjs_module_loader(JSContext *cx, const char *module_name, void *opaque)
 static int
 ngx_qjs_unhandled_rejection(ngx_js_ctx_t *ctx)
 {
-    size_t                      len;
     uint32_t                    i;
     JSContext                  *cx;
-    const char                 *str;
     ngx_js_rejected_promise_t  *rejected_promise;
 
     if (ctx->rejected_promises == NULL
@@ -2052,13 +2040,7 @@ ngx_qjs_unhandled_rejection(ngx_js_ctx_t *ctx)
     cx = ctx->engine->u.qjs.ctx;
     rejected_promise = ctx->rejected_promises->start;
 
-    str = JS_ToCStringLen(cx, &len, ngx_qjs_arg(rejected_promise->message));
-    if (njs_slow_path(str == NULL)) {
-        return -1;
-    }
-
-    JS_ThrowTypeError(cx, "unhandled promise rejection: %.*s", (int) len, str);
-    JS_FreeCString(cx, str);
+    JS_Throw(cx, JS_DupValue(cx, ngx_qjs_arg(rejected_promise->message)));
 
     for (i = 0; i < ctx->rejected_promises->items; i++) {
         JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].promise));
@@ -3890,8 +3872,6 @@ static njs_int_t
 ngx_js_unhandled_rejection(ngx_js_ctx_t *ctx)
 {
     njs_vm_t                   *vm;
-    njs_int_t                   ret;
-    njs_str_t                   message;
     ngx_js_rejected_promise_t  *rejected_promise;
 
     if (ctx->rejected_promises == NULL
@@ -3903,13 +3883,7 @@ ngx_js_unhandled_rejection(ngx_js_ctx_t *ctx)
     vm = ctx->engine->u.njs.vm;
     rejected_promise = ctx->rejected_promises->start;
 
-    ret = njs_vm_value_to_string(vm, &message,
-                                 njs_value_arg(&rejected_promise->message));
-    if (njs_slow_path(ret != NJS_OK)) {
-        return -1;
-    }
-
-    njs_vm_error(vm, "unhandled promise rejection: %V", &message);
+    njs_vm_throw(vm, njs_value_arg(&rejected_promise->message));
 
     njs_arr_destroy(ctx->rejected_promises);
     ctx->rejected_promises = NULL;
diff --git a/nginx/t/js_async.t b/nginx/t/js_async.t
index 73e71851..32d1e0a4 100644
--- a/nginx/t/js_async.t
+++ b/nginx/t/js_async.t
@@ -84,6 +84,10 @@ http {
         location /set_rv_var {
             return 200 $test_set_rv_var;
         }
+
+        location /await_reject {
+            js_content test.await_reject;
+        }
     }
 }
 
@@ -194,13 +198,26 @@ $t->write_file('test.js', <<EOF);
         r.setReturnValue(`retval: \${a1 + a2}`);
     }
 
+    async function timeout(ms) {
+        return new Promise((resolve, reject) => {
+            setTimeout(() => {
+                reject(new Error('timeout'));
+            }, ms);
+        });
+    }
+
+    async function await_reject(r) {
+        let v = await timeout(1);
+        r.return(200);
+    }
+
     export default {njs:test_njs, set_timeout, set_timeout_data,
                     set_timeout_many, context_var, shared_ctx, limit_rate,
-                    async_content, set_rv_var};
+                    async_content, set_rv_var, await_reject};
 
 EOF
 
-$t->try_run('no njs available')->plan(9);
+$t->try_run('no njs available')->plan(10);
 
 ###############################################################################
 
@@ -214,6 +231,7 @@ like(http_get('/async_content'), qr/retval: AB/, 'async content');
 like(http_get('/set_rv_var'), qr/retval: 30/, 'set return value variable');
 
 http_get('/async_var');
+http_get('/await_reject');
 
 $t->stop();
 
@@ -221,5 +239,7 @@ ok(index($t->read_file('error.log'), 'pending events') > 0,
    'pending js events');
 ok(index($t->read_file('error.log'), 'async operation inside') > 0,
    'async op in var handler');
+ok(index($t->read_file('error.log'), 'js unhandled rejection') > 0,
+   'unhandled rejection');
 
 ###############################################################################


More information about the nginx-devel mailing list