]> git.kaiwu.me - njs.git/commitdiff
Modules: clear QuickJS hooks before a context is reused master
authorDmitry Volyntsev <xeioex@nginx.com>
Thu, 12 Nov 2026 01:37:06 +0000 (17:37 -0800)
committerDmitry Volyntsev <xeioexception@gmail.com>
Thu, 12 Nov 2026 22:29:31 +0000 (14:29 -0800)
Previously, an exit hook installed during context teardown, for example
by a promise job queued by the exit hook, was kept in the context.  When
the context was reused, the hook was called during the teardown of a
later request.

Now hooks are cleared together with the pending events, after the last
user code of the request has run.

The issue was introduced in 04f6dfb9 (0.9.2).

nginx/ngx_js.c
nginx/t/js_teardown.t
src/qjs.c
src/qjs.h

index a7ec295e02a49e25f586988a306bed8bd4ce2401..d3ffd4bcdcae73e7bbd496e0d654fdb7ad5d7c1e 100644 (file)
@@ -1406,6 +1406,7 @@ ngx_engine_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx,
             ngx_qjs_log_exception(e, ctx->log, "unhandled rejection");
         }
 
+        qjs_clear_hooks(cx);
         ngx_qjs_clear_events(ctx);
 
         ngx_qjs_detach_ctx(ctx, cx);
index f8c2f7ac4f55afb258cfc30cef0453b88c13eaea..193bac316447fbc2f02834f9eba7bcb8a325b549 100644 (file)
@@ -87,6 +87,13 @@ $t->write_file('test.js', <<'EOF');
                 ngx.log(ngx.ERR, 'exit hook ' + r.uri);
             });
 
+        } else if (mode === 'reinstall') {
+            njs.on('exit', () => {
+                Promise.resolve().then(() => {
+                    njs.on('exit', late('reinstalled exit hook'));
+                });
+            });
+
         }
 
         r.return(200, 'ok');
@@ -95,12 +102,12 @@ $t->write_file('test.js', <<'EOF');
     export default {content};
 EOF
 
-$t->try_run('no njs available')->plan(9);
+$t->try_run('no njs available')->plan(11);
 
 ###############################################################################
 
 for my $scope ('zero', 'reuse') {
-    for my $mode ('timer', 'fetch', 'exit') {
+    for my $mode ('timer', 'fetch', 'exit', 'reinstall') {
         like(http_get("/$scope?mode=$mode"), qr/ok$/, "$scope $mode");
     }
 }
index 8de2ebe45eafb49e25c86c3e9b1a40940add045e..4393065737dc37c6c6b65e80b1d81b9660600eeb 100644 (file)
--- a/src/qjs.c
+++ b/src/qjs.c
@@ -401,6 +401,24 @@ qjs_call_exit_hook(JSContext *ctx)
 }
 
 
+void
+qjs_clear_hooks(JSContext *ctx)
+{
+    unsigned   i;
+    qjs_njs_t  *njs;
+
+    njs = qjs_njs(ctx);
+    if (njs == NULL) {
+        return;
+    }
+
+    for (i = 0; i < njs_nitems(njs->hooks); i++) {
+        JS_FreeValue(ctx, njs->hooks[i]);
+        njs->hooks[i] = JS_UNDEFINED;
+    }
+}
+
+
 static int
 qjs_add_intrinsic_njs(JSContext *cx, JSValueConst global)
 {
index a24c8a808657f2fd4c10d96cf47dc87ade005e42..4336455b01d1becc06857a64792725aa7aca83f1 100644 (file)
--- a/src/qjs.h
+++ b/src/qjs.h
@@ -62,6 +62,7 @@ typedef struct {
 
 JSContext *qjs_new_context(JSRuntime *rt, qjs_module_t **addons);
 JSValue qjs_call_exit_hook(JSContext *ctx);
+void qjs_clear_hooks(JSContext *ctx);
 void *qjs_chb_ctx_alloc(void *ctx, size_t size);
 void qjs_chb_ctx_free(void *ctx, void *p);
 void *qjs_chb_rt_alloc(void *rt, size_t size);