Mercurial > njs
changeset 2607:362ef716eabc
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.
| author | Dmitry Volyntsev <xeioex@nginx.com> |
|---|---|
| date | Mon, 18 Aug 2025 21:21:09 -0700 |
| parents | caf716d4e693 |
| children | 76ccd4e7d5d7 |
| files | nginx/ngx_http_js_module.c nginx/ngx_stream_js_module.c |
| diffstat | 2 files changed, 211 insertions(+), 56 deletions(-) [+] |
line wrap: on
line diff
--- a/nginx/ngx_http_js_module.c Wed Aug 13 23:19:46 2025 -0700 +++ b/nginx/ngx_http_js_module.c Mon Aug 18 21:21:09 2025 -0700 @@ -5601,10 +5601,12 @@ 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 @@ 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 @@ JS_FreeValue(cx, value); } - cl = ngx_chain_get_free_buf(r->pool, &ctx->free); - if (cl == NULL) { - return JS_ThrowOutOfMemory(cx); - } - - b = cl->buf; - - b->flush = flush; - b->last_buf = last_buf; - - b->memory = (buffer.len ? 1 : 0); - b->sync = (buffer.len ? 0 : 1); - b->tag = (ngx_buf_tag_t) &ngx_http_js_module; - - b->start = buffer.data; - b->end = buffer.data + buffer.len; - b->pos = b->start; - b->last = b->end; - - *ctx->last_out = cl; - ctx->last_out = &cl->next; + val = argv[0]; + + if (JS_IsNullOrUndefined(val)) { + buffer.len = 0; + buffer.data = NULL; + } + + str = NULL; + buf = JS_UNDEFINED; + + if (JS_IsString(val)) { + goto string; + } + + 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; + } + + buffer.data += byte_offset; + buffer.len = byte_length; + + } 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); }
--- a/nginx/ngx_stream_js_module.c Wed Aug 13 23:19:46 2025 -0700 +++ b/nginx/ngx_stream_js_module.c Mon Aug 18 21:21:09 2025 -0700 @@ -2273,10 +2273,12 @@ 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 @@ 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 @@ } } - 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; - - b->flush = flush; - b->last_buf = last_buf; - - b->memory = (buffer.len ? 1 : 0); - b->sync = (buffer.len ? 0 : 1); - b->tag = (ngx_buf_tag_t) &ngx_stream_js_module; - - b->start = buffer.data; - b->end = buffer.data + buffer.len; - - b->pos = b->start; - b->last = b->end; - - if (from_upstream == NGX_JS_BOOL_UNSET) { - *ctx->last_out = cl; - ctx->last_out = &cl->next; + str = NULL; + buf = JS_UNDEFINED; + + if (JS_IsString(val)) { + goto string; + } + + 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; + } + + buffer.data += byte_offset; + buffer.len = byte_length; } else { - - if (ngx_stream_js_next_filter(s, ctx, cl, from_upstream) == NGX_ERROR) { - return JS_ThrowInternalError(cx, "ngx_stream_js_next_filter() " - "failed"); +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(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"); }
