]> git.kaiwu.me - njs.git/commitdiff
QuickJS: fix unhandled rejection tracking in reused contexts master
authorDmitry Volyntsev <xeioex@nginx.com>
Thu, 2 Jul 2026 23:19:32 +0000 (16:19 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Fri, 3 Jul 2026 00:40:33 +0000 (17:40 -0700)
ngx_qjs_destroy() unregisters the host promise rejection tracker on the
runtime, but ngx_qjs_clone() did not re-register it when a context was
popped from the reuse queue.  Every request served by a reused context
then silently dropped unhandled rejections instead of reporting them,
which also defeated any caller relying on ctx->rejected_promises.

nginx/ngx_js.c
nginx/t/js_context_reuse.t [new file with mode: 0644]

index 34fc23a4657859194b729e1a61b620ed3e5cec82..3b69cea5c855516c010edf2d287bd1230c480d9e 100644 (file)
@@ -1062,6 +1062,10 @@ ngx_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, void *external)
             ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
                            "js reused context: %p", engine->u.qjs.ctx);
             JS_SetContextOpaque(engine->u.qjs.ctx, external);
+
+            JS_SetHostPromiseRejectionTracker(
+                                         JS_GetRuntime(engine->u.qjs.ctx),
+                                         ngx_qjs_rejection_tracker, ctx);
             return engine;
         }
     }
diff --git a/nginx/t/js_context_reuse.t b/nginx/t/js_context_reuse.t
new file mode 100644 (file)
index 0000000..97a1a22
--- /dev/null
@@ -0,0 +1,81 @@
+#!/usr/bin/perl
+
+# (C) Dmitry Volyntsev
+# (C) F5, Inc.
+
+# Tests for QuickJS context reuse.  An unhandled rejection must keep being
+# reported after the per-worker context is reused, not only on its first use.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/http/)
+       ->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    js_import test.js;
+
+    server {
+        listen       127.0.0.1:8080;
+        server_name  localhost;
+
+        location /reject {
+            js_content test.reject;
+        }
+    }
+}
+
+EOF
+
+$t->write_file('test.js', <<EOF);
+    function reject(r) {
+        Promise.reject(new Error('unhandled-boom'));
+        r.return(200, 'ok');
+    }
+
+    export default { reject };
+
+EOF
+
+$t->try_run('no njs available')->plan(4);
+
+###############################################################################
+
+# The first request runs in a fresh context, the following ones reuse it
+# (with the QuickJS engine).  Every unhandled rejection must be reported.
+
+like(http_get('/reject'), qr/200 OK/, 'reject 1 (fresh context)');
+like(http_get('/reject'), qr/200 OK/, 'reject 2 (reused context)');
+like(http_get('/reject'), qr/200 OK/, 'reject 3 (reused context)');
+
+$t->stop();
+
+my $log = $t->read_file('error.log');
+my $count = () = $log =~ /js unhandled rejection/g;
+
+is($count, 3, 'unhandled rejection reported for every request');
+
+###############################################################################