details:
https://github.com/nginx/njs/commit/44744d957b9958f35f18805a5acfa564e66af35f
branches: master
commit: 44744d957b9958f35f18805a5acfa564e66af35f
user: Dmitry Volyntsev <[email protected]>
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");
}
_______________________________________________
nginx-devel mailing list
[email protected]
https://mailman.nginx.org/mailman/listinfo/nginx-devel