From: Dmitry Volyntsev Date: Fri, 30 Oct 2026 05:30:31 +0000 (-0700) Subject: Modules: fix clearTimeout() of a timer from its own callback X-Git-Url: https://git.kaiwu.me/$%7BGITURL%7D/1?a=commitdiff_plain;ds=inline;p=njs.git Modules: fix clearTimeout() of a timer from its own callback Previously, the timer event was removed from the pending events after its callback was called. When the callback called clearTimeout() for its own timer, the event was removed twice, which corrupted the events tree. In QuickJS, the timer function and arguments were also freed while the function was still running, and then freed again. Now, the timer event is removed from the pending events before the callback is called, and its data is freed after the callback returns. The issue was introduced in 9de30135 (0.8.3). --- diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index bf23c5b8..1ba8f712 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -83,6 +83,7 @@ static ngx_int_t ngx_engine_qjs_string(ngx_engine_t *e, njs_opaque_value_t *value, ngx_str_t *str); static void ngx_qjs_clear_events(ngx_js_ctx_t *ctx); static void ngx_qjs_detach_ctx(ngx_js_ctx_t *ctx, JSContext *cx); +static void ngx_qjs_clear_timer(ngx_qjs_event_t *event); static JSValue ngx_qjs_process_getter(JSContext *ctx, JSValueConst this_val); static JSValue ngx_qjs_ext_set_timeout(JSContext *cx, JSValueConst this_val, @@ -1788,9 +1789,11 @@ ngx_qjs_timer_handler(ngx_event_t *ev) external = JS_GetContextOpaque(cx); ctx = ngx_qjs_external_ctx(cx, external); + njs_rbtree_delete(&ctx->waiting_events, &event->node); + rc = ngx_qjs_call(cx, event->function, event->args, event->nargs); - ngx_js_del_event(ctx, event); + ngx_qjs_clear_timer(event); ngx_qjs_external_event_finalize(cx)(external, rc); } @@ -3217,12 +3220,12 @@ ngx_js_timer_handler(ngx_event_t *ev) vm = event->ctx; + ctx = ngx_external_ctx(vm, njs_vm_external_ptr(vm)); + njs_rbtree_delete(&ctx->waiting_events, &event->node); + rc = ngx_js_call(vm, njs_value_function(njs_value_arg(&event->function)), event->args, event->nargs); - ctx = ngx_external_ctx(vm, njs_vm_external_ptr(vm)); - ngx_js_del_event(ctx, event); - ngx_external_event_finalize(vm)(njs_vm_external_ptr(vm), rc); } diff --git a/nginx/t/js_async.t b/nginx/t/js_async.t index c1f209fd..03fbcd83 100644 --- a/nginx/t/js_async.t +++ b/nginx/t/js_async.t @@ -67,6 +67,14 @@ http { js_content test.set_timeout_many; } + location /clear_timeout { + js_content test.clear_timeout; + } + + location /clear_immediate { + js_content test.clear_immediate; + } + location /clear_timeout_fetch { js_content test.clear_timeout_fetch; } @@ -135,6 +143,45 @@ $t->write_file('test.js', < { + for (let i = 0; i < 8; i++) { + ids.push(setTimeout((arg) => { + clearTimeout(ids[arg]); + + if (arg == 2) { + clearTimeout(ids[5]); + clearTimeout(ids[6]); + clearTimeout(ids[7]); + } + + fired.push(arg); + + if (fired.length == 5) { + setTimeout(resolve, 1); + } + }, 10 * i, i)); + } + }); + + clearTimeout(ids[0]); + clearTimeout(2147483647); + r.return(200, fired.join(',')); + } + + async function clear_immediate(r) { + let result = await new Promise(resolve => { + let id = setImmediate(arg => { + clearTimeout(id); + resolve(arg); + }, 'immediate'); + }); + + r.return(200, result); + } + async function clear_timeout_fetch(r) { let id = setTimeout(() => {}, 1); clearTimeout(id); @@ -260,17 +307,21 @@ $t->write_file('test.js', <try_run('no njs available')->plan(13); +$t->try_run('no njs available')->plan(15); ############################################################################### like(http_get('/set_timeout'), qr/Content-Type: foo/, 'setTimeout'); like(http_get('/set_timeout_many'), qr/Content-Type: reply/, 'setTimeout many'); +like(http_get('/clear_timeout'), qr/0,1,2,3,4$/, + 'clearTimeout self and sibling timers'); +like(http_get('/clear_immediate'), qr/immediate$/, 'clearTimeout immediate'); like(http_get('/clear_timeout_fetch'), qr/fetch$/, 'clearTimeout fetch id'); like(http_get('/clear_timeout_unknown'), qr/cleared$/,