[njs] Modules: optimized memory consumption while streaming in qjs.
noreply at nginx.com
noreply at nginx.com
Wed Aug 27 01:59:41 UTC 2025
details: https://github.com/nginx/njs/commit/44744d957b9958f35f18805a5acfa564e66af35f
branches: master
commit: 44744d957b9958f35f18805a5acfa564e66af35f
user: Dmitry Volyntsev <xeioex at nginx.com>
date: Mon, 18 Aug 2025 21:21:09 -0700
description:
Modules: optimized memory consumption while streaming in qjs.
This allows to stream long tcp streams or large http response bodies
with low memory consumption.
This works only for qjs engine, because njs has no GC.
This fixes #943 issue on Github.
---
nginx/ngx_http_js_module.c | 115 +++++++++++++++++++++++++++++++-------
nginx/ngx_stream_js_module.c | 130 ++++++++++++++++++++++++++++++++++---------
2 files changed, 200 insertions(+), 45 deletions(-)
diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c
index 487c8115..3f38f86f 100644
--- a/nginx/ngx_http_js_module.c
+++ b/nginx/ngx_http_js_module.c
@@ -5601,10 +5601,12 @@ static JSValue
ngx_http_qjs_ext_send_buffer(JSContext *cx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
+ size_t byte_offset, byte_length, len;
unsigned last_buf, flush;
- JSValue flags, value;
+ JSValue flags, value, val, buf;
ngx_str_t buffer;
ngx_buf_t *b;
+ const char *str;
ngx_chain_t *cl;
ngx_http_js_ctx_t *ctx;
ngx_http_request_t *r;
@@ -5620,10 +5622,6 @@ ngx_http_qjs_ext_send_buffer(JSContext *cx, JSValueConst this_val,
return JS_ThrowTypeError(cx, "cannot send buffer while not filtering");
}
- if (ngx_qjs_string(cx, argv[0], &buffer) != NGX_OK) {
- return JS_ThrowTypeError(cx, "failed get buffer arg");
- }
-
flush = ctx->buf->flush;
last_buf = ctx->buf->last_buf;
@@ -5647,29 +5645,106 @@ ngx_http_qjs_ext_send_buffer(JSContext *cx, JSValueConst this_val,
JS_FreeValue(cx, value);
}
- cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
- if (cl == NULL) {
- return JS_ThrowOutOfMemory(cx);
+ val = argv[0];
+
+ if (JS_IsNullOrUndefined(val)) {
+ buffer.len = 0;
+ buffer.data = NULL;
}
- b = cl->buf;
+ str = NULL;
+ buf = JS_UNDEFINED;
- b->flush = flush;
- b->last_buf = last_buf;
+ if (JS_IsString(val)) {
+ goto string;
+ }
- b->memory = (buffer.len ? 1 : 0);
- b->sync = (buffer.len ? 0 : 1);
- b->tag = (ngx_buf_tag_t) &ngx_http_js_module;
+ buf = JS_GetTypedArrayBuffer(cx, val, &byte_offset, &byte_length, NULL);
+ if (!JS_IsException(buf)) {
+ buffer.data = JS_GetArrayBuffer(cx, &buffer.len, buf);
+ if (buffer.data == NULL) {
+ JS_FreeValue(cx, buf);
+ return JS_EXCEPTION;
+ }
- b->start = buffer.data;
- b->end = buffer.data + buffer.len;
- b->pos = b->start;
- b->last = b->end;
+ buffer.data += byte_offset;
+ buffer.len = byte_length;
- *ctx->last_out = cl;
- ctx->last_out = &cl->next;
+ } else {
+string:
+
+ str = JS_ToCStringLen(cx, &buffer.len, val);
+ if (str == NULL) {
+ return JS_EXCEPTION;
+ }
+
+ buffer.data = (u_char *) str;
+ }
+
+ do {
+ cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+ if (cl == NULL) {
+ goto out_of_memory;
+ }
+
+ b = cl->buf;
+
+ if (b->start == NULL) {
+ b->start = ngx_pnalloc(r->pool, buffer.len);
+ if (b->start == NULL) {
+ goto out_of_memory;
+ }
+
+ len = buffer.len;
+ b->end = b->start + len;
+
+ } else {
+ len = ngx_min(buffer.len, (size_t) (b->end - b->start));
+ }
+
+ memcpy(b->start, buffer.data, len);
+
+ b->pos = b->start;
+ b->last = b->start + len;
+
+ if (buffer.len == len) {
+ b->last_buf = last_buf;
+ b->flush = flush;
+
+ } else {
+ b->last_buf = 0;
+ b->flush = 0;
+ }
+
+ b->memory = (len ? 1 : 0);
+ b->sync = (len ? 0 : 1);
+ b->tag = (ngx_buf_tag_t) &ngx_http_js_module;
+
+ buffer.data += len;
+ buffer.len -= len;
+
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ } while (buffer.len != 0);
+
+ if (str != NULL) {
+ JS_FreeCString(cx, str);
+ }
+
+ JS_FreeValue(cx, buf);
return JS_UNDEFINED;
+
+out_of_memory:
+
+ if (str != NULL) {
+ JS_FreeCString(cx, str);
+ }
+
+ JS_FreeValue(cx, buf);
+
+ return JS_ThrowOutOfMemory(cx);
}
diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c
index a7446c49..13b685e5 100644
--- a/nginx/ngx_stream_js_module.c
+++ b/nginx/ngx_stream_js_module.c
@@ -2273,10 +2273,12 @@ static JSValue
ngx_stream_qjs_ext_send(JSContext *cx, JSValueConst this_val, int argc,
JSValueConst *argv, int from_upstream)
{
- JSValue val;
+ size_t byte_offset, byte_length, len;
+ JSValue val, buf;
unsigned last_buf, flush;
ngx_str_t buffer;
ngx_buf_t *b;
+ const char *str;
ngx_chain_t *cl;
ngx_connection_t *c;
ngx_stream_js_ctx_t *ctx;
@@ -2295,10 +2297,6 @@ ngx_stream_qjs_ext_send(JSContext *cx, JSValueConst this_val, int argc,
return JS_ThrowInternalError(cx, "cannot send buffer in this handler");
}
- if (ngx_qjs_string(cx, argv[0], &buffer) != NGX_OK) {
- return JS_EXCEPTION;
- }
-
/*
* ctx->buf != NULL when s.send() is called while processing incoming
* data chunks, otherwise s.send() is called asynchronously
@@ -2353,39 +2351,121 @@ ngx_stream_qjs_ext_send(JSContext *cx, JSValueConst this_val, int argc,
}
}
- cl = ngx_chain_get_free_buf(c->pool, &ctx->free);
- if (cl == NULL) {
- return JS_ThrowInternalError(cx, "memory error");
+ val = argv[0];
+
+ if (JS_IsNullOrUndefined(val)) {
+ buffer.len = 0;
+ buffer.data = NULL;
}
- b = cl->buf;
+ str = NULL;
+ buf = JS_UNDEFINED;
- b->flush = flush;
- b->last_buf = last_buf;
+ if (JS_IsString(val)) {
+ goto string;
+ }
- b->memory = (buffer.len ? 1 : 0);
- b->sync = (buffer.len ? 0 : 1);
- b->tag = (ngx_buf_tag_t) &ngx_stream_js_module;
+ buf = JS_GetTypedArrayBuffer(cx, val, &byte_offset, &byte_length, NULL);
+ if (!JS_IsException(buf)) {
+ buffer.data = JS_GetArrayBuffer(cx, &buffer.len, buf);
+ if (buffer.data == NULL) {
+ JS_FreeValue(cx, buf);
+ return JS_EXCEPTION;
+ }
- b->start = buffer.data;
- b->end = buffer.data + buffer.len;
+ buffer.data += byte_offset;
+ buffer.len = byte_length;
- b->pos = b->start;
- b->last = b->end;
+ } else {
+string:
- if (from_upstream == NGX_JS_BOOL_UNSET) {
- *ctx->last_out = cl;
- ctx->last_out = &cl->next;
+ str = JS_ToCStringLen(cx, &buffer.len, val);
+ if (str == NULL) {
+ return JS_EXCEPTION;
+ }
- } else {
+ buffer.data = (u_char *) str;
+ }
- if (ngx_stream_js_next_filter(s, ctx, cl, from_upstream) == NGX_ERROR) {
- return JS_ThrowInternalError(cx, "ngx_stream_js_next_filter() "
- "failed");
+ do {
+ cl = ngx_chain_get_free_buf(c->pool, &ctx->free);
+ if (cl == NULL) {
+ goto out_of_memory;
+ }
+
+ b = cl->buf;
+
+ if (b->start == NULL) {
+ b->start = ngx_pnalloc(c->pool, buffer.len);
+ if (b->start == NULL) {
+ goto out_of_memory;
+ }
+
+ len = buffer.len;
+ b->end = b->start + len;
+
+ } else {
+ len = ngx_min(buffer.len, (size_t) (b->end - b->start));
+ }
+
+ memcpy(b->start, buffer.data, len);
+
+ b->pos = b->start;
+ b->last = b->start + len;
+
+ if (buffer.len == len) {
+ b->last_buf = last_buf;
+ b->flush = flush;
+
+ } else {
+ b->last_buf = 0;
+ b->flush = 0;
}
+
+ b->memory = (len ? 1 : 0);
+ b->sync = (len ? 0 : 1);
+ b->tag = (ngx_buf_tag_t) &ngx_stream_js_module;
+
+ buffer.data += len;
+ buffer.len -= len;
+
+ if (from_upstream == NGX_JS_BOOL_UNSET) {
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ } else {
+
+ if (ngx_stream_js_next_filter(s, ctx, cl, from_upstream)
+ == NGX_ERROR)
+ {
+ if (str != NULL) {
+ JS_FreeCString(cx, str);
+ }
+
+ return JS_ThrowInternalError(cx, "ngx_stream_js_next_filter() "
+ "failed");
+ }
+ }
+
+ } while (buffer.len != 0);
+
+ if (str != NULL) {
+ JS_FreeCString(cx, str);
}
+ JS_FreeValue(cx, buf);
+
return JS_UNDEFINED;
+
+out_of_memory:
+
+ if (str != NULL) {
+ JS_FreeCString(cx, str);
+ }
+
+ JS_FreeValue(cx, buf);
+
+ return JS_ThrowInternalError(cx, "memory error");
}
More information about the nginx-devel
mailing list