]> git.kaiwu.me - njs.git/commitdiff
Fetch: free QuickJS response body chain after reading
authorDmitry Volyntsev <xeioex@nginx.com>
Fri, 30 Oct 2026 03:16:10 +0000 (20:16 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Mon, 2 Nov 2026 14:42:35 +0000 (06:42 -0800)
Previously, Response.text(), json() and arrayBuffer() joined the body
chain into a new block of the engine pool and left both the chain
nodes and the joined copy in the pool.  The memory was released only
with the engine, so with js_context_reuse every response body read
accumulated in the pool, unaccounted by the context reuse memory
check.

Now, the chain is destroyed after the body is read.  For text() the
string is created with qjs_string_create_chb(), and for json() the
joined copy is freed after parsing.

The issue was introduced in b7f76b71 (0.9.1).

nginx/ngx_qjs_fetch.c

index d4b2d536cd7fcb50c91df41ca14fc226d7d690f2..be3f299bfd200a596ba77476bdab4a7433caad20 100644 (file)
@@ -2316,21 +2316,21 @@ ngx_qjs_ext_fetch_response_body(JSContext *cx, JSValueConst this_val,
     response->body_used = 1;
 
     switch (magic) {
-    case NGX_JS_BODY_ARRAY_BUFFER:
     case NGX_JS_BODY_TEXT:
+        result = qjs_string_create_chb(cx, &response->chain);
+        if (JS_IsException(result)) {
+            return JS_EXCEPTION;
+        }
+
+        break;
+
+    case NGX_JS_BODY_ARRAY_BUFFER:
         ret = njs_chb_join(&response->chain, &string);
         if (ret != NJS_OK) {
             return JS_ThrowOutOfMemory(cx);
         }
 
-        if (magic == NGX_JS_BODY_TEXT) {
-            result = qjs_string_create(cx, string.start, string.length);
-            if (JS_IsException(result)) {
-                return JS_ThrowOutOfMemory(cx);
-            }
-
-            break;
-        }
+        njs_chb_destroy(&response->chain);
 
         /*
          * string.start is allocated from e->pool
@@ -2355,9 +2355,9 @@ ngx_qjs_ext_fetch_response_body(JSContext *cx, JSValueConst this_val,
 
         result = JS_ParseJSON(cx, (char *) string.start, string.length - 1,
                               "<input>");
-        if (JS_IsException(result)) {
-            break;
-        }
+
+        njs_chb_destroy(&response->chain);
+        response->chain.free(response->chain.pool, string.start);
     }
 
     return qjs_promise_result(cx, result);