Skip to content

Commit 44744d9

Browse files
committed
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.
1 parent 6842e1b commit 44744d9

File tree

2 files changed

+200
-45
lines changed

2 files changed

+200
-45
lines changed

nginx/ngx_http_js_module.c

Lines changed: 95 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5601,10 +5601,12 @@ static JSValue
56015601
ngx_http_qjs_ext_send_buffer(JSContext *cx, JSValueConst this_val,
56025602
int argc, JSValueConst *argv)
56035603
{
5604+
size_t byte_offset, byte_length, len;
56045605
unsigned last_buf, flush;
5605-
JSValue flags, value;
5606+
JSValue flags, value, val, buf;
56065607
ngx_str_t buffer;
56075608
ngx_buf_t *b;
5609+
const char *str;
56085610
ngx_chain_t *cl;
56095611
ngx_http_js_ctx_t *ctx;
56105612
ngx_http_request_t *r;
@@ -5620,10 +5622,6 @@ ngx_http_qjs_ext_send_buffer(JSContext *cx, JSValueConst this_val,
56205622
return JS_ThrowTypeError(cx, "cannot send buffer while not filtering");
56215623
}
56225624

5623-
if (ngx_qjs_string(cx, argv[0], &buffer) != NGX_OK) {
5624-
return JS_ThrowTypeError(cx, "failed get buffer arg");
5625-
}
5626-
56275625
flush = ctx->buf->flush;
56285626
last_buf = ctx->buf->last_buf;
56295627

@@ -5647,29 +5645,106 @@ ngx_http_qjs_ext_send_buffer(JSContext *cx, JSValueConst this_val,
56475645
JS_FreeValue(cx, value);
56485646
}
56495647

5650-
cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
5651-
if (cl == NULL) {
5652-
return JS_ThrowOutOfMemory(cx);
5648+
val = argv[0];
5649+
5650+
if (JS_IsNullOrUndefined(val)) {
5651+
buffer.len = 0;
5652+
buffer.data = NULL;
56535653
}
56545654

5655-
b = cl->buf;
5655+
str = NULL;
5656+
buf = JS_UNDEFINED;
56565657

5657-
b->flush = flush;
5658-
b->last_buf = last_buf;
5658+
if (JS_IsString(val)) {
5659+
goto string;
5660+
}
56595661

5660-
b->memory = (buffer.len ? 1 : 0);
5661-
b->sync = (buffer.len ? 0 : 1);
5662-
b->tag = (ngx_buf_tag_t) &ngx_http_js_module;
5662+
buf = JS_GetTypedArrayBuffer(cx, val, &byte_offset, &byte_length, NULL);
5663+
if (!JS_IsException(buf)) {
5664+
buffer.data = JS_GetArrayBuffer(cx, &buffer.len, buf);
5665+
if (buffer.data == NULL) {
5666+
JS_FreeValue(cx, buf);
5667+
return JS_EXCEPTION;
5668+
}
56635669

5664-
b->start = buffer.data;
5665-
b->end = buffer.data + buffer.len;
5666-
b->pos = b->start;
5667-
b->last = b->end;
5670+
buffer.data += byte_offset;
5671+
buffer.len = byte_length;
56685672

5669-
*ctx->last_out = cl;
5670-
ctx->last_out = &cl->next;
5673+
} else {
5674+
string:
5675+
5676+
str = JS_ToCStringLen(cx, &buffer.len, val);
5677+
if (str == NULL) {
5678+
return JS_EXCEPTION;
5679+
}
5680+
5681+
buffer.data = (u_char *) str;
5682+
}
5683+
5684+
do {
5685+
cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
5686+
if (cl == NULL) {
5687+
goto out_of_memory;
5688+
}
5689+
5690+
b = cl->buf;
5691+
5692+
if (b->start == NULL) {
5693+
b->start = ngx_pnalloc(r->pool, buffer.len);
5694+
if (b->start == NULL) {
5695+
goto out_of_memory;
5696+
}
5697+
5698+
len = buffer.len;
5699+
b->end = b->start + len;
5700+
5701+
} else {
5702+
len = ngx_min(buffer.len, (size_t) (b->end - b->start));
5703+
}
5704+
5705+
memcpy(b->start, buffer.data, len);
5706+
5707+
b->pos = b->start;
5708+
b->last = b->start + len;
5709+
5710+
if (buffer.len == len) {
5711+
b->last_buf = last_buf;
5712+
b->flush = flush;
5713+
5714+
} else {
5715+
b->last_buf = 0;
5716+
b->flush = 0;
5717+
}
5718+
5719+
b->memory = (len ? 1 : 0);
5720+
b->sync = (len ? 0 : 1);
5721+
b->tag = (ngx_buf_tag_t) &ngx_http_js_module;
5722+
5723+
buffer.data += len;
5724+
buffer.len -= len;
5725+
5726+
*ctx->last_out = cl;
5727+
ctx->last_out = &cl->next;
5728+
5729+
} while (buffer.len != 0);
5730+
5731+
if (str != NULL) {
5732+
JS_FreeCString(cx, str);
5733+
}
5734+
5735+
JS_FreeValue(cx, buf);
56715736

56725737
return JS_UNDEFINED;
5738+
5739+
out_of_memory:
5740+
5741+
if (str != NULL) {
5742+
JS_FreeCString(cx, str);
5743+
}
5744+
5745+
JS_FreeValue(cx, buf);
5746+
5747+
return JS_ThrowOutOfMemory(cx);
56735748
}
56745749

56755750

nginx/ngx_stream_js_module.c

Lines changed: 105 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2273,10 +2273,12 @@ static JSValue
22732273
ngx_stream_qjs_ext_send(JSContext *cx, JSValueConst this_val, int argc,
22742274
JSValueConst *argv, int from_upstream)
22752275
{
2276-
JSValue val;
2276+
size_t byte_offset, byte_length, len;
2277+
JSValue val, buf;
22772278
unsigned last_buf, flush;
22782279
ngx_str_t buffer;
22792280
ngx_buf_t *b;
2281+
const char *str;
22802282
ngx_chain_t *cl;
22812283
ngx_connection_t *c;
22822284
ngx_stream_js_ctx_t *ctx;
@@ -2295,10 +2297,6 @@ ngx_stream_qjs_ext_send(JSContext *cx, JSValueConst this_val, int argc,
22952297
return JS_ThrowInternalError(cx, "cannot send buffer in this handler");
22962298
}
22972299

2298-
if (ngx_qjs_string(cx, argv[0], &buffer) != NGX_OK) {
2299-
return JS_EXCEPTION;
2300-
}
2301-
23022300
/*
23032301
* ctx->buf != NULL when s.send() is called while processing incoming
23042302
* data chunks, otherwise s.send() is called asynchronously
@@ -2353,39 +2351,121 @@ ngx_stream_qjs_ext_send(JSContext *cx, JSValueConst this_val, int argc,
23532351
}
23542352
}
23552353

2356-
cl = ngx_chain_get_free_buf(c->pool, &ctx->free);
2357-
if (cl == NULL) {
2358-
return JS_ThrowInternalError(cx, "memory error");
2354+
val = argv[0];
2355+
2356+
if (JS_IsNullOrUndefined(val)) {
2357+
buffer.len = 0;
2358+
buffer.data = NULL;
23592359
}
23602360

2361-
b = cl->buf;
2361+
str = NULL;
2362+
buf = JS_UNDEFINED;
23622363

2363-
b->flush = flush;
2364-
b->last_buf = last_buf;
2364+
if (JS_IsString(val)) {
2365+
goto string;
2366+
}
23652367

2366-
b->memory = (buffer.len ? 1 : 0);
2367-
b->sync = (buffer.len ? 0 : 1);
2368-
b->tag = (ngx_buf_tag_t) &ngx_stream_js_module;
2368+
buf = JS_GetTypedArrayBuffer(cx, val, &byte_offset, &byte_length, NULL);
2369+
if (!JS_IsException(buf)) {
2370+
buffer.data = JS_GetArrayBuffer(cx, &buffer.len, buf);
2371+
if (buffer.data == NULL) {
2372+
JS_FreeValue(cx, buf);
2373+
return JS_EXCEPTION;
2374+
}
23692375

2370-
b->start = buffer.data;
2371-
b->end = buffer.data + buffer.len;
2376+
buffer.data += byte_offset;
2377+
buffer.len = byte_length;
23722378

2373-
b->pos = b->start;
2374-
b->last = b->end;
2379+
} else {
2380+
string:
23752381

2376-
if (from_upstream == NGX_JS_BOOL_UNSET) {
2377-
*ctx->last_out = cl;
2378-
ctx->last_out = &cl->next;
2382+
str = JS_ToCStringLen(cx, &buffer.len, val);
2383+
if (str == NULL) {
2384+
return JS_EXCEPTION;
2385+
}
23792386

2380-
} else {
2387+
buffer.data = (u_char *) str;
2388+
}
23812389

2382-
if (ngx_stream_js_next_filter(s, ctx, cl, from_upstream) == NGX_ERROR) {
2383-
return JS_ThrowInternalError(cx, "ngx_stream_js_next_filter() "
2384-
"failed");
2390+
do {
2391+
cl = ngx_chain_get_free_buf(c->pool, &ctx->free);
2392+
if (cl == NULL) {
2393+
goto out_of_memory;
2394+
}
2395+
2396+
b = cl->buf;
2397+
2398+
if (b->start == NULL) {
2399+
b->start = ngx_pnalloc(c->pool, buffer.len);
2400+
if (b->start == NULL) {
2401+
goto out_of_memory;
2402+
}
2403+
2404+
len = buffer.len;
2405+
b->end = b->start + len;
2406+
2407+
} else {
2408+
len = ngx_min(buffer.len, (size_t) (b->end - b->start));
2409+
}
2410+
2411+
memcpy(b->start, buffer.data, len);
2412+
2413+
b->pos = b->start;
2414+
b->last = b->start + len;
2415+
2416+
if (buffer.len == len) {
2417+
b->last_buf = last_buf;
2418+
b->flush = flush;
2419+
2420+
} else {
2421+
b->last_buf = 0;
2422+
b->flush = 0;
23852423
}
2424+
2425+
b->memory = (len ? 1 : 0);
2426+
b->sync = (len ? 0 : 1);
2427+
b->tag = (ngx_buf_tag_t) &ngx_stream_js_module;
2428+
2429+
buffer.data += len;
2430+
buffer.len -= len;
2431+
2432+
if (from_upstream == NGX_JS_BOOL_UNSET) {
2433+
*ctx->last_out = cl;
2434+
ctx->last_out = &cl->next;
2435+
2436+
} else {
2437+
2438+
if (ngx_stream_js_next_filter(s, ctx, cl, from_upstream)
2439+
== NGX_ERROR)
2440+
{
2441+
if (str != NULL) {
2442+
JS_FreeCString(cx, str);
2443+
}
2444+
2445+
return JS_ThrowInternalError(cx, "ngx_stream_js_next_filter() "
2446+
"failed");
2447+
}
2448+
}
2449+
2450+
} while (buffer.len != 0);
2451+
2452+
if (str != NULL) {
2453+
JS_FreeCString(cx, str);
23862454
}
23872455

2456+
JS_FreeValue(cx, buf);
2457+
23882458
return JS_UNDEFINED;
2459+
2460+
out_of_memory:
2461+
2462+
if (str != NULL) {
2463+
JS_FreeCString(cx, str);
2464+
}
2465+
2466+
JS_FreeValue(cx, buf);
2467+
2468+
return JS_ThrowInternalError(cx, "memory error");
23892469
}
23902470

23912471

0 commit comments

Comments
 (0)