From eeab9176249200a19b3f89584eace0bec3902096 Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Thu, 12 Oct 2023 22:34:34 +0200 Subject: [PATCH] http: reduce parts in chunked response when corking Refs: https://github.com/nodejs/performance/issues/57 PR-URL: https://github.com/nodejs/node/pull/50167 --- lib/_http_outgoing.js | 60 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index 178a3418dace0a..b248521150ba25 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -82,6 +82,8 @@ let debug = require('internal/util/debuglog').debuglog('http', (fn) => { }); const kCorked = Symbol('corked'); +const kChunkedBuffer = Symbol('kChunkedBuffer'); +const kChunkedLength = Symbol('kChunkedLength'); const kUniqueHeaders = Symbol('kUniqueHeaders'); const kBytesWritten = Symbol('kBytesWritten'); const kErrored = Symbol('errored'); @@ -140,6 +142,8 @@ function OutgoingMessage(options) { this.finished = false; this._headerSent = false; this[kCorked] = 0; + this[kChunkedBuffer] = []; + this[kChunkedLength] = 0; this._closed = false; this.socket = null; @@ -192,7 +196,7 @@ ObjectDefineProperty(OutgoingMessage.prototype, 'writableObjectMode', { ObjectDefineProperty(OutgoingMessage.prototype, 'writableLength', { __proto__: null, get() { - return this.outputSize + (this.socket ? this.socket.writableLength : 0); + return this.outputSize + this[kChunkedLength] + (this.socket ? this.socket.writableLength : 0); }, }); @@ -206,8 +210,7 @@ ObjectDefineProperty(OutgoingMessage.prototype, 'writableHighWaterMark', { ObjectDefineProperty(OutgoingMessage.prototype, 'writableCorked', { __proto__: null, get() { - const corked = this.socket ? this.socket.writableCorked : 0; - return corked + this[kCorked]; + return this[kCorked]; }, }); @@ -299,19 +302,45 @@ OutgoingMessage.prototype._renderHeaders = function _renderHeaders() { }; OutgoingMessage.prototype.cork = function() { + this[kCorked]++; if (this.socket) { this.socket.cork(); - } else { - this[kCorked]++; } }; OutgoingMessage.prototype.uncork = function() { + this[kCorked]--; if (this.socket) { this.socket.uncork(); - } else if (this[kCorked]) { - this[kCorked]--; } + + if (this[kCorked] || this[kChunkedBuffer].length === 0) { + return; + } + + const len = this[kChunkedLength]; + const buf = this[kChunkedBuffer]; + + assert(this.chunkedEncoding); + + let callbacks; + this._send(NumberPrototypeToString(len, 16), 'latin1', null); + this._send(crlf_buf, null, null); + for (let n = 0; n < buf.length; n += 3) { + this._send(buf[n + 0], buf[n + 1], null); + if (buf[n + 2]) { + callbacks ??= []; + callbacks.push(buf[n + 2]); + } + } + this._send(crlf_buf, null, callbacks.length ? (err) => { + for (const callback of callbacks) { + callback(err); + } + } : null); + + this[kChunkedBuffer].length = 0; + this[kChunkedLength] = 0; }; OutgoingMessage.prototype.setTimeout = function setTimeout(msecs, callback) { @@ -938,10 +967,16 @@ function write_(msg, chunk, encoding, callback, fromEnd) { let ret; if (msg.chunkedEncoding && chunk.length !== 0) { len ??= typeof chunk === 'string' ? Buffer.byteLength(chunk, encoding) : chunk.byteLength; - msg._send(NumberPrototypeToString(len, 16), 'latin1', null); - msg._send(crlf_buf, null, null); - msg._send(chunk, encoding, null, len); - ret = msg._send(crlf_buf, null, callback); + if (msg[kCorked] && msg._headerSent) { + msg[kChunkedBuffer].push(chunk, encoding, callback); + msg[kChunkedLength] += len; + ret = msg[kChunkedLength] < msg[kHighWaterMark]; + } else { + msg._send(NumberPrototypeToString(len, 16), 'latin1', null); + msg._send(crlf_buf, null, null); + msg._send(chunk, encoding, null, len); + ret = msg._send(crlf_buf, null, callback); + } } else { ret = msg._send(chunk, encoding, callback, len); } @@ -1068,7 +1103,8 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) { this.socket._writableState.corked = 1; this.socket.uncork(); } - this[kCorked] = 0; + this[kCorked] = 1; + this.uncork(); this.finished = true;