Commit f11ac0d

Browse files
mcollinaaduh95
authored andcommitted
http2: avoid per-write closures in kWriteGeneric
Every _write()/_writev() on an Http2Stream allocated four closures and an anonymous nextTick callback to coordinate the write callback with the end-of-stream check. Since the stream machinery dispatches at most one write at a time, that coordination state can live on the stream's kState object instead, with shared named functions for the end check and completion logic. When trailers are pending the writable side cannot be shut down early anyway, so the end-of-stream check tick is now skipped entirely for those writes. Also pre-initialize the kState fields that used to be added dynamically (shutdownWritableCalled, fd) so hot-path stores no longer transition the object shape. h2load, 1 KiB response payload, -c 4 -m 100, mean of 6 alternating runs vs main: core API 61.0k -> 70.7k req/s (+15.9% cumulative), compat API 43.7k -> 50.4k req/s (+15.3% cumulative). Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #64265 Backport-PR-URL: #64663 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Tim Perry <pimterry@gmail.com> Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
1 parent 157bd07 commit f11ac0d

1 file changed

Lines changed: 71 additions & 35 deletions

File tree

β€Žlib/internal/http2/core.jsβ€Ž

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,6 +1991,47 @@ function shutdownWritable(callback) {
19911991
returnafterShutdown.call(req,0);
19921992
}
19931993

1994+
// Completes one of the two halves of a dispatched write (the write callback
1995+
// itself and the end-of-stream check); the stream machinery callback runs
1996+
// once both have finished. The state lives on stream[kState] because only a
1997+
// single write may be in flight at any given time.
1998+
functionfinishWrite(stream){
1999+
conststate=stream[kState];
2000+
if(--state.writePending!==0)
2001+
return;
2002+
constcb=state.writeCb;
2003+
state.writeCb=null;
2004+
consterr=aggregateTwoErrors(state.endErr,state.writeErr);
2005+
state.writeErr=null;
2006+
state.endErr=null;
2007+
// writeGeneric does not destroy on error and
2008+
// we cannot enable autoDestroy,
2009+
// so make sure to destroy on error.
2010+
if(err){
2011+
stream.destroy(err);
2012+
}
2013+
cb(err);
2014+
}
2015+
2016+
// Runs on the tick after a write was dispatched: if the write turned out to
2017+
// be the last chunk of an ending writable, shut the writable side down right
2018+
// away so the final DATA frame can include the END_STREAM flag.
2019+
functionendCheckNT(stream){
2020+
conststate=stream[kState];
2021+
if(state.writeErr||
2022+
!stream._writableState.ending||
2023+
stream._writableState.buffered.length||
2024+
(state.flags&STREAM_FLAGS_HAS_TRAILERS)){
2025+
finishWrite(stream);
2026+
return;
2027+
}
2028+
debugStreamObj(stream,'shutting down writable on last write');
2029+
shutdownWritable.call(stream,(err)=>{
2030+
state.endErr=err;
2031+
finishWrite(stream);
2032+
});
2033+
}
2034+
19942035
functionfinishSendTrailers(stream,headersList){
19952036
// The stream might be destroyed and in that case
19962037
// there is nothing to do.
@@ -2098,6 +2139,12 @@ class Http2Stream extends Duplex {
20982139
writeQueueSize: 0,
20992140
trailersReady: false,
21002141
endAfterHeaders: false,
2142+
writeCb: null,
2143+
writeErr: null,
2144+
endErr: null,
2145+
writePending: 0,
2146+
shutdownWritableCalled: false,
2147+
fd: -1,
21012148
};
21022149

21032150
// Fields used by the compat API to avoid megamorphisms.
@@ -2285,45 +2332,34 @@ class Http2Stream extends Duplex {
22852332
if(!this.headersSent)
22862333
this[kProceed]();
22872334

2288-
letreq;
2335+
// The stream machinery dispatches at most one _write()/_writev() at a
2336+
// time, so the coordination state between the write callback and the
2337+
// end-of-stream check below can live on the stream state instead of
2338+
// being captured by per-write closures.
2339+
conststate=this[kState];
2340+
state.writeCb=cb;
2341+
state.writeErr=null;
2342+
state.endErr=null;
2343+
2344+
if(state.flags&STREAM_FLAGS_HAS_TRAILERS){
2345+
// Trailers are pending, so the writable side cannot be shut down
2346+
// early anyway; there is no point in scheduling the end check.
2347+
state.writePending=1;
2348+
}else{
2349+
state.writePending=2;
2350+
// Shutdown write stream right after last chunk is sent
2351+
// so final DATA frame can include END_STREAM flag
2352+
process.nextTick(endCheckNT,this);
2353+
}
22892354

2290-
letwaitingForWriteCallback=true;
2291-
letwaitingForEndCheck=true;
2292-
letwriteCallbackErr;
2293-
letendCheckCallbackErr;
2294-
constdone=()=>{
2295-
if(waitingForEndCheck||waitingForWriteCallback)return;
2296-
consterr=aggregateTwoErrors(endCheckCallbackErr,writeCallbackErr);
2297-
// writeGeneric does not destroy on error and
2298-
// we cannot enable autoDestroy,
2299-
// so make sure to destroy on error.
2300-
if(err){
2301-
this.destroy(err);
2302-
}
2303-
cb(err);
2304-
};
2355+
// This is invoked both as a method on the write req and as a plain
2356+
// call, so the stream has to be captured here.
23052357
constwriteCallback=(err)=>{
2306-
waitingForWriteCallback=false;
2307-
writeCallbackErr=err;
2308-
done();
2309-
};
2310-
constendCheckCallback=(err)=>{
2311-
waitingForEndCheck=false;
2312-
endCheckCallbackErr=err;
2313-
done();
2358+
state.writeErr=err;
2359+
finishWrite(this);
23142360
};
2315-
// Shutdown write stream right after last chunk is sent
2316-
// so final DATA frame can include END_STREAM flag
2317-
process.nextTick(()=>{
2318-
if(writeCallbackErr||
2319-
!this._writableState.ending||
2320-
this._writableState.buffered.length||
2321-
(this[kState].flags&STREAM_FLAGS_HAS_TRAILERS))
2322-
returnendCheckCallback();
2323-
debugStreamObj(this,'shutting down writable on last write');
2324-
shutdownWritable.call(this,endCheckCallback);
2325-
});
23262361

2362+
letreq;
23272363
if(writev)
23282364
req=writevGeneric(this,data,writeCallback);
23292365
else

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Commit f11ac0d

Browse files
mcollinaaduh95
authored andcommitted
http2: avoid per-write closures in kWriteGeneric
Every _write()/_writev() on an Http2Stream allocated four closures and an anonymous nextTick callback to coordinate the write callback with the end-of-stream check. Since the stream machinery dispatches at most one write at a time, that coordination state can live on the stream's kState object instead, with shared named functions for the end check and completion logic. When trailers are pending the writable side cannot be shut down early anyway, so the end-of-stream check tick is now skipped entirely for those writes. Also pre-initialize the kState fields that used to be added dynamically (shutdownWritableCalled, fd) so hot-path stores no longer transition the object shape. h2load, 1 KiB response payload, -c 4 -m 100, mean of 6 alternating runs vs main: core API 61.0k -> 70.7k req/s (+15.9% cumulative), compat API 43.7k -> 50.4k req/s (+15.3% cumulative). Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #64265 Backport-PR-URL: #64663 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Tim Perry <pimterry@gmail.com> Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
1 parent 157bd07 commit f11ac0d

1 file changed

Lines changed: 71 additions & 35 deletions

File tree

β€Žlib/internal/http2/core.jsβ€Ž

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,6 +1991,47 @@ function shutdownWritable(callback) {
19911991
returnafterShutdown.call(req,0);
19921992
}
19931993

1994+
// Completes one of the two halves of a dispatched write (the write callback
1995+
// itself and the end-of-stream check); the stream machinery callback runs
1996+
// once both have finished. The state lives on stream[kState] because only a
1997+
// single write may be in flight at any given time.
1998+
functionfinishWrite(stream){
1999+
conststate=stream[kState];
2000+
if(--state.writePending!==0)
2001+
return;
2002+
constcb=state.writeCb;
2003+
state.writeCb=null;
2004+
consterr=aggregateTwoErrors(state.endErr,state.writeErr);
2005+
state.writeErr=null;
2006+
state.endErr=null;
2007+
// writeGeneric does not destroy on error and
2008+
// we cannot enable autoDestroy,
2009+
// so make sure to destroy on error.
2010+
if(err){
2011+
stream.destroy(err);
2012+
}
2013+
cb(err);
2014+
}
2015+
2016+
// Runs on the tick after a write was dispatched: if the write turned out to
2017+
// be the last chunk of an ending writable, shut the writable side down right
2018+
// away so the final DATA frame can include the END_STREAM flag.
2019+
functionendCheckNT(stream){
2020+
conststate=stream[kState];
2021+
if(state.writeErr||
2022+
!stream._writableState.ending||
2023+
stream._writableState.buffered.length||
2024+
(state.flags&STREAM_FLAGS_HAS_TRAILERS)){
2025+
finishWrite(stream);
2026+
return;
2027+
}
2028+
debugStreamObj(stream,'shutting down writable on last write');
2029+
shutdownWritable.call(stream,(err)=>{
2030+
state.endErr=err;
2031+
finishWrite(stream);
2032+
});
2033+
}
2034+
19942035
functionfinishSendTrailers(stream,headersList){
19952036
// The stream might be destroyed and in that case
19962037
// there is nothing to do.
@@ -2098,6 +2139,12 @@ class Http2Stream extends Duplex {
20982139
writeQueueSize: 0,
20992140
trailersReady: false,
21002141
endAfterHeaders: false,
2142+
writeCb: null,
2143+
writeErr: null,
2144+
endErr: null,
2145+
writePending: 0,
2146+
shutdownWritableCalled: false,
2147+
fd: -1,
21012148
};
21022149

21032150
// Fields used by the compat API to avoid megamorphisms.
@@ -2285,45 +2332,34 @@ class Http2Stream extends Duplex {
22852332
if(!this.headersSent)
22862333
this[kProceed]();
22872334

2288-
letreq;
2335+
// The stream machinery dispatches at most one _write()/_writev() at a
2336+
// time, so the coordination state between the write callback and the
2337+
// end-of-stream check below can live on the stream state instead of
2338+
// being captured by per-write closures.
2339+
conststate=this[kState];
2340+
state.writeCb=cb;
2341+
state.writeErr=null;
2342+
state.endErr=null;
2343+
2344+
if(state.flags&STREAM_FLAGS_HAS_TRAILERS){
2345+
// Trailers are pending, so the writable side cannot be shut down
2346+
// early anyway; there is no point in scheduling the end check.
2347+
state.writePending=1;
2348+
}else{
2349+
state.writePending=2;
2350+
// Shutdown write stream right after last chunk is sent
2351+
// so final DATA frame can include END_STREAM flag
2352+
process.nextTick(endCheckNT,this);
2353+
}
22892354

2290-
letwaitingForWriteCallback=true;
2291-
letwaitingForEndCheck=true;
2292-
letwriteCallbackErr;
2293-
letendCheckCallbackErr;
2294-
constdone=()=>{
2295-
if(waitingForEndCheck||waitingForWriteCallback)return;
2296-
consterr=aggregateTwoErrors(endCheckCallbackErr,writeCallbackErr);
2297-
// writeGeneric does not destroy on error and
2298-
// we cannot enable autoDestroy,
2299-
// so make sure to destroy on error.
2300-
if(err){
2301-
this.destroy(err);
2302-
}
2303-
cb(err);
2304-
};
2355+
// This is invoked both as a method on the write req and as a plain
2356+
// call, so the stream has to be captured here.
23052357
constwriteCallback=(err)=>{
2306-
waitingForWriteCallback=false;
2307-
writeCallbackErr=err;
2308-
done();
2309-
};
2310-
constendCheckCallback=(err)=>{
2311-
waitingForEndCheck=false;
2312-
endCheckCallbackErr=err;
2313-
done();
2358+
state.writeErr=err;
2359+
finishWrite(this);
23142360
};
2315-
// Shutdown write stream right after last chunk is sent
2316-
// so final DATA frame can include END_STREAM flag
2317-
process.nextTick(()=>{
2318-
if(writeCallbackErr||
2319-
!this._writableState.ending||
2320-
this._writableState.buffered.length||
2321-
(this[kState].flags&STREAM_FLAGS_HAS_TRAILERS))
2322-
returnendCheckCallback();
2323-
debugStreamObj(this,'shutting down writable on last write');
2324-
shutdownWritable.call(this,endCheckCallback);
2325-
});
23262361

2362+
letreq;
23272363
if(writev)
23282364
req=writevGeneric(this,data,writeCallback);
23292365
else

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit f11ac0d

Browse files
mcollinaaduh95
authored andcommitted
http2: avoid per-write closures in kWriteGeneric
Every _write()/_writev() on an Http2Stream allocated four closures and an anonymous nextTick callback to coordinate the write callback with the end-of-stream check. Since the stream machinery dispatches at most one write at a time, that coordination state can live on the stream's kState object instead, with shared named functions for the end check and completion logic. When trailers are pending the writable side cannot be shut down early anyway, so the end-of-stream check tick is now skipped entirely for those writes. Also pre-initialize the kState fields that used to be added dynamically (shutdownWritableCalled, fd) so hot-path stores no longer transition the object shape. h2load, 1 KiB response payload, -c 4 -m 100, mean of 6 alternating runs vs main: core API 61.0k -> 70.7k req/s (+15.9% cumulative), compat API 43.7k -> 50.4k req/s (+15.3% cumulative). Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #64265 Backport-PR-URL: #64663 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Tim Perry <pimterry@gmail.com> Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
1 parent 157bd07 commit f11ac0d

1 file changed

Lines changed: 71 additions & 35 deletions

File tree

β€Žlib/internal/http2/core.jsβ€Ž

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,6 +1991,47 @@ function shutdownWritable(callback) {
19911991
returnafterShutdown.call(req,0);
19921992
}
19931993

1994+
// Completes one of the two halves of a dispatched write (the write callback
1995+
// itself and the end-of-stream check); the stream machinery callback runs
1996+
// once both have finished. The state lives on stream[kState] because only a
1997+
// single write may be in flight at any given time.
1998+
functionfinishWrite(stream){
1999+
conststate=stream[kState];
2000+
if(--state.writePending!==0)
2001+
return;
2002+
constcb=state.writeCb;
2003+
state.writeCb=null;
2004+
consterr=aggregateTwoErrors(state.endErr,state.writeErr);
2005+
state.writeErr=null;
2006+
state.endErr=null;
2007+
// writeGeneric does not destroy on error and
2008+
// we cannot enable autoDestroy,
2009+
// so make sure to destroy on error.
2010+
if(err){
2011+
stream.destroy(err);
2012+
}
2013+
cb(err);
2014+
}
2015+
2016+
// Runs on the tick after a write was dispatched: if the write turned out to
2017+
// be the last chunk of an ending writable, shut the writable side down right
2018+
// away so the final DATA frame can include the END_STREAM flag.
2019+
functionendCheckNT(stream){
2020+
conststate=stream[kState];
2021+
if(state.writeErr||
2022+
!stream._writableState.ending||
2023+
stream._writableState.buffered.length||
2024+
(state.flags&STREAM_FLAGS_HAS_TRAILERS)){
2025+
finishWrite(stream);
2026+
return;
2027+
}
2028+
debugStreamObj(stream,'shutting down writable on last write');
2029+
shutdownWritable.call(stream,(err)=>{
2030+
state.endErr=err;
2031+
finishWrite(stream);
2032+
});
2033+
}
2034+
19942035
functionfinishSendTrailers(stream,headersList){
19952036
// The stream might be destroyed and in that case
19962037
// there is nothing to do.
@@ -2098,6 +2139,12 @@ class Http2Stream extends Duplex {
20982139
writeQueueSize: 0,
20992140
trailersReady: false,
21002141
endAfterHeaders: false,
2142+
writeCb: null,
2143+
writeErr: null,
2144+
endErr: null,
2145+
writePending: 0,
2146+
shutdownWritableCalled: false,
2147+
fd: -1,
21012148
};
21022149

21032150
// Fields used by the compat API to avoid megamorphisms.
@@ -2285,45 +2332,34 @@ class Http2Stream extends Duplex {
22852332
if(!this.headersSent)
22862333
this[kProceed]();
22872334

2288-
letreq;
2335+
// The stream machinery dispatches at most one _write()/_writev() at a
2336+
// time, so the coordination state between the write callback and the
2337+
// end-of-stream check below can live on the stream state instead of
2338+
// being captured by per-write closures.
2339+
conststate=this[kState];
2340+
state.writeCb=cb;
2341+
state.writeErr=null;
2342+
state.endErr=null;
2343+
2344+
if(state.flags&STREAM_FLAGS_HAS_TRAILERS){
2345+
// Trailers are pending, so the writable side cannot be shut down
2346+
// early anyway; there is no point in scheduling the end check.
2347+
state.writePending=1;
2348+
}else{
2349+
state.writePending=2;
2350+
// Shutdown write stream right after last chunk is sent
2351+
// so final DATA frame can include END_STREAM flag
2352+
process.nextTick(endCheckNT,this);
2353+
}
22892354

2290-
letwaitingForWriteCallback=true;
2291-
letwaitingForEndCheck=true;
2292-
letwriteCallbackErr;
2293-
letendCheckCallbackErr;
2294-
constdone=()=>{
2295-
if(waitingForEndCheck||waitingForWriteCallback)return;
2296-
consterr=aggregateTwoErrors(endCheckCallbackErr,writeCallbackErr);
2297-
// writeGeneric does not destroy on error and
2298-
// we cannot enable autoDestroy,
2299-
// so make sure to destroy on error.
2300-
if(err){
2301-
this.destroy(err);
2302-
}
2303-
cb(err);
2304-
};
2355+
// This is invoked both as a method on the write req and as a plain
2356+
// call, so the stream has to be captured here.
23052357
constwriteCallback=(err)=>{
2306-
waitingForWriteCallback=false;
2307-
writeCallbackErr=err;
2308-
done();
2309-
};
2310-
constendCheckCallback=(err)=>{
2311-
waitingForEndCheck=false;
2312-
endCheckCallbackErr=err;
2313-
done();
2358+
state.writeErr=err;
2359+
finishWrite(this);
23142360
};
2315-
// Shutdown write stream right after last chunk is sent
2316-
// so final DATA frame can include END_STREAM flag
2317-
process.nextTick(()=>{
2318-
if(writeCallbackErr||
2319-
!this._writableState.ending||
2320-
this._writableState.buffered.length||
2321-
(this[kState].flags&STREAM_FLAGS_HAS_TRAILERS))
2322-
returnendCheckCallback();
2323-
debugStreamObj(this,'shutting down writable on last write');
2324-
shutdownWritable.call(this,endCheckCallback);
2325-
});
23262361

2362+
letreq;
23272363
if(writev)
23282364
req=writevGeneric(this,data,writeCallback);
23292365
else

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit f11ac0d

Browse files
mcollinaaduh95
authored andcommitted
http2: avoid per-write closures in kWriteGeneric
Every _write()/_writev() on an Http2Stream allocated four closures and an anonymous nextTick callback to coordinate the write callback with the end-of-stream check. Since the stream machinery dispatches at most one write at a time, that coordination state can live on the stream's kState object instead, with shared named functions for the end check and completion logic. When trailers are pending the writable side cannot be shut down early anyway, so the end-of-stream check tick is now skipped entirely for those writes. Also pre-initialize the kState fields that used to be added dynamically (shutdownWritableCalled, fd) so hot-path stores no longer transition the object shape. h2load, 1 KiB response payload, -c 4 -m 100, mean of 6 alternating runs vs main: core API 61.0k -> 70.7k req/s (+15.9% cumulative), compat API 43.7k -> 50.4k req/s (+15.3% cumulative). Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #64265 Backport-PR-URL: #64663 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Tim Perry <pimterry@gmail.com> Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
1 parent 157bd07 commit f11ac0d

1 file changed

Lines changed: 71 additions & 35 deletions

File tree

β€Žlib/internal/http2/core.jsβ€Ž

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,6 +1991,47 @@ function shutdownWritable(callback) {
19911991
returnafterShutdown.call(req,0);
19921992
}
19931993

1994+
// Completes one of the two halves of a dispatched write (the write callback
1995+
// itself and the end-of-stream check); the stream machinery callback runs
1996+
// once both have finished. The state lives on stream[kState] because only a
1997+
// single write may be in flight at any given time.
1998+
functionfinishWrite(stream){
1999+
conststate=stream[kState];
2000+
if(--state.writePending!==0)
2001+
return;
2002+
constcb=state.writeCb;
2003+
state.writeCb=null;
2004+
consterr=aggregateTwoErrors(state.endErr,state.writeErr);
2005+
state.writeErr=null;
2006+
state.endErr=null;
2007+
// writeGeneric does not destroy on error and
2008+
// we cannot enable autoDestroy,
2009+
// so make sure to destroy on error.
2010+
if(err){
2011+
stream.destroy(err);
2012+
}
2013+
cb(err);
2014+
}
2015+
2016+
// Runs on the tick after a write was dispatched: if the write turned out to
2017+
// be the last chunk of an ending writable, shut the writable side down right
2018+
// away so the final DATA frame can include the END_STREAM flag.
2019+
functionendCheckNT(stream){
2020+
conststate=stream[kState];
2021+
if(state.writeErr||
2022+
!stream._writableState.ending||
2023+
stream._writableState.buffered.length||
2024+
(state.flags&STREAM_FLAGS_HAS_TRAILERS)){
2025+
finishWrite(stream);
2026+
return;
2027+
}
2028+
debugStreamObj(stream,'shutting down writable on last write');
2029+
shutdownWritable.call(stream,(err)=>{
2030+
state.endErr=err;
2031+
finishWrite(stream);
2032+
});
2033+
}
2034+
19942035
functionfinishSendTrailers(stream,headersList){
19952036
// The stream might be destroyed and in that case
19962037
// there is nothing to do.
@@ -2098,6 +2139,12 @@ class Http2Stream extends Duplex {
20982139
writeQueueSize: 0,
20992140
trailersReady: false,
21002141
endAfterHeaders: false,
2142+
writeCb: null,
2143+
writeErr: null,
2144+
endErr: null,
2145+
writePending: 0,
2146+
shutdownWritableCalled: false,
2147+
fd: -1,
21012148
};
21022149

21032150
// Fields used by the compat API to avoid megamorphisms.
@@ -2285,45 +2332,34 @@ class Http2Stream extends Duplex {
22852332
if(!this.headersSent)
22862333
this[kProceed]();
22872334

2288-
letreq;
2335+
// The stream machinery dispatches at most one _write()/_writev() at a
2336+
// time, so the coordination state between the write callback and the
2337+
// end-of-stream check below can live on the stream state instead of
2338+
// being captured by per-write closures.
2339+
conststate=this[kState];
2340+
state.writeCb=cb;
2341+
state.writeErr=null;
2342+
state.endErr=null;
2343+
2344+
if(state.flags&STREAM_FLAGS_HAS_TRAILERS){
2345+
// Trailers are pending, so the writable side cannot be shut down
2346+
// early anyway; there is no point in scheduling the end check.
2347+
state.writePending=1;
2348+
}else{
2349+
state.writePending=2;
2350+
// Shutdown write stream right after last chunk is sent
2351+
// so final DATA frame can include END_STREAM flag
2352+
process.nextTick(endCheckNT,this);
2353+
}
22892354

2290-
letwaitingForWriteCallback=true;
2291-
letwaitingForEndCheck=true;
2292-
letwriteCallbackErr;
2293-
letendCheckCallbackErr;
2294-
constdone=()=>{
2295-
if(waitingForEndCheck||waitingForWriteCallback)return;
2296-
consterr=aggregateTwoErrors(endCheckCallbackErr,writeCallbackErr);
2297-
// writeGeneric does not destroy on error and
2298-
// we cannot enable autoDestroy,
2299-
// so make sure to destroy on error.
2300-
if(err){
2301-
this.destroy(err);
2302-
}
2303-
cb(err);
2304-
};
2355+
// This is invoked both as a method on the write req and as a plain
2356+
// call, so the stream has to be captured here.
23052357
constwriteCallback=(err)=>{
2306-
waitingForWriteCallback=false;
2307-
writeCallbackErr=err;
2308-
done();
2309-
};
2310-
constendCheckCallback=(err)=>{
2311-
waitingForEndCheck=false;
2312-
endCheckCallbackErr=err;
2313-
done();
2358+
state.writeErr=err;
2359+
finishWrite(this);
23142360
};
2315-
// Shutdown write stream right after last chunk is sent
2316-
// so final DATA frame can include END_STREAM flag
2317-
process.nextTick(()=>{
2318-
if(writeCallbackErr||
2319-
!this._writableState.ending||
2320-
this._writableState.buffered.length||
2321-
(this[kState].flags&STREAM_FLAGS_HAS_TRAILERS))
2322-
returnendCheckCallback();
2323-
debugStreamObj(this,'shutting down writable on last write');
2324-
shutdownWritable.call(this,endCheckCallback);
2325-
});
23262361

2362+
letreq;
23272363
if(writev)
23282364
req=writevGeneric(this,data,writeCallback);
23292365
else

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Commit f11ac0d

Browse files
mcollinaaduh95
authored andcommitted
http2: avoid per-write closures in kWriteGeneric
Every _write()/_writev() on an Http2Stream allocated four closures and an anonymous nextTick callback to coordinate the write callback with the end-of-stream check. Since the stream machinery dispatches at most one write at a time, that coordination state can live on the stream's kState object instead, with shared named functions for the end check and completion logic. When trailers are pending the writable side cannot be shut down early anyway, so the end-of-stream check tick is now skipped entirely for those writes. Also pre-initialize the kState fields that used to be added dynamically (shutdownWritableCalled, fd) so hot-path stores no longer transition the object shape. h2load, 1 KiB response payload, -c 4 -m 100, mean of 6 alternating runs vs main: core API 61.0k -> 70.7k req/s (+15.9% cumulative), compat API 43.7k -> 50.4k req/s (+15.3% cumulative). Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #64265 Backport-PR-URL: #64663 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Tim Perry <pimterry@gmail.com> Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
1 parent 157bd07 commit f11ac0d

1 file changed

Lines changed: 71 additions & 35 deletions

File tree

β€Žlib/internal/http2/core.jsβ€Ž

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,6 +1991,47 @@ function shutdownWritable(callback) {
19911991
returnafterShutdown.call(req,0);
19921992
}
19931993

1994+
// Completes one of the two halves of a dispatched write (the write callback
1995+
// itself and the end-of-stream check); the stream machinery callback runs
1996+
// once both have finished. The state lives on stream[kState] because only a
1997+
// single write may be in flight at any given time.
1998+
functionfinishWrite(stream){
1999+
conststate=stream[kState];
2000+
if(--state.writePending!==0)
2001+
return;
2002+
constcb=state.writeCb;
2003+
state.writeCb=null;
2004+
consterr=aggregateTwoErrors(state.endErr,state.writeErr);
2005+
state.writeErr=null;
2006+
state.endErr=null;
2007+
// writeGeneric does not destroy on error and
2008+
// we cannot enable autoDestroy,
2009+
// so make sure to destroy on error.
2010+
if(err){
2011+
stream.destroy(err);
2012+
}
2013+
cb(err);
2014+
}
2015+
2016+
// Runs on the tick after a write was dispatched: if the write turned out to
2017+
// be the last chunk of an ending writable, shut the writable side down right
2018+
// away so the final DATA frame can include the END_STREAM flag.
2019+
functionendCheckNT(stream){
2020+
conststate=stream[kState];
2021+
if(state.writeErr||
2022+
!stream._writableState.ending||
2023+
stream._writableState.buffered.length||
2024+
(state.flags&STREAM_FLAGS_HAS_TRAILERS)){
2025+
finishWrite(stream);
2026+
return;
2027+
}
2028+
debugStreamObj(stream,'shutting down writable on last write');
2029+
shutdownWritable.call(stream,(err)=>{
2030+
state.endErr=err;
2031+
finishWrite(stream);
2032+
});
2033+
}
2034+
19942035
functionfinishSendTrailers(stream,headersList){
19952036
// The stream might be destroyed and in that case
19962037
// there is nothing to do.
@@ -2098,6 +2139,12 @@ class Http2Stream extends Duplex {
20982139
writeQueueSize: 0,
20992140
trailersReady: false,
21002141
endAfterHeaders: false,
2142+
writeCb: null,
2143+
writeErr: null,
2144+
endErr: null,
2145+
writePending: 0,
2146+
shutdownWritableCalled: false,
2147+
fd: -1,
21012148
};
21022149

21032150
// Fields used by the compat API to avoid megamorphisms.
@@ -2285,45 +2332,34 @@ class Http2Stream extends Duplex {
22852332
if(!this.headersSent)
22862333
this[kProceed]();
22872334

2288-
letreq;
2335+
// The stream machinery dispatches at most one _write()/_writev() at a
2336+
// time, so the coordination state between the write callback and the
2337+
// end-of-stream check below can live on the stream state instead of
2338+
// being captured by per-write closures.
2339+
conststate=this[kState];
2340+
state.writeCb=cb;
2341+
state.writeErr=null;
2342+
state.endErr=null;
2343+
2344+
if(state.flags&STREAM_FLAGS_HAS_TRAILERS){
2345+
// Trailers are pending, so the writable side cannot be shut down
2346+
// early anyway; there is no point in scheduling the end check.
2347+
state.writePending=1;
2348+
}else{
2349+
state.writePending=2;
2350+
// Shutdown write stream right after last chunk is sent
2351+
// so final DATA frame can include END_STREAM flag
2352+
process.nextTick(endCheckNT,this);
2353+
}
22892354

2290-
letwaitingForWriteCallback=true;
2291-
letwaitingForEndCheck=true;
2292-
letwriteCallbackErr;
2293-
letendCheckCallbackErr;
2294-
constdone=()=>{
2295-
if(waitingForEndCheck||waitingForWriteCallback)return;
2296-
consterr=aggregateTwoErrors(endCheckCallbackErr,writeCallbackErr);
2297-
// writeGeneric does not destroy on error and
2298-
// we cannot enable autoDestroy,
2299-
// so make sure to destroy on error.
2300-
if(err){
2301-
this.destroy(err);
2302-
}
2303-
cb(err);
2304-
};
2355+
// This is invoked both as a method on the write req and as a plain
2356+
// call, so the stream has to be captured here.
23052357
constwriteCallback=(err)=>{
2306-
waitingForWriteCallback=false;
2307-
writeCallbackErr=err;
2308-
done();
2309-
};
2310-
constendCheckCallback=(err)=>{
2311-
waitingForEndCheck=false;
2312-
endCheckCallbackErr=err;
2313-
done();
2358+
state.writeErr=err;
2359+
finishWrite(this);
23142360
};
2315-
// Shutdown write stream right after last chunk is sent
2316-
// so final DATA frame can include END_STREAM flag
2317-
process.nextTick(()=>{
2318-
if(writeCallbackErr||
2319-
!this._writableState.ending||
2320-
this._writableState.buffered.length||
2321-
(this[kState].flags&STREAM_FLAGS_HAS_TRAILERS))
2322-
returnendCheckCallback();
2323-
debugStreamObj(this,'shutting down writable on last write');
2324-
shutdownWritable.call(this,endCheckCallback);
2325-
});
23262361

2362+
letreq;
23272363
if(writev)
23282364
req=writevGeneric(this,data,writeCallback);
23292365
else

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit f11ac0d

Browse files
mcollinaaduh95
authored andcommitted
http2: avoid per-write closures in kWriteGeneric
Every _write()/_writev() on an Http2Stream allocated four closures and an anonymous nextTick callback to coordinate the write callback with the end-of-stream check. Since the stream machinery dispatches at most one write at a time, that coordination state can live on the stream's kState object instead, with shared named functions for the end check and completion logic. When trailers are pending the writable side cannot be shut down early anyway, so the end-of-stream check tick is now skipped entirely for those writes. Also pre-initialize the kState fields that used to be added dynamically (shutdownWritableCalled, fd) so hot-path stores no longer transition the object shape. h2load, 1 KiB response payload, -c 4 -m 100, mean of 6 alternating runs vs main: core API 61.0k -> 70.7k req/s (+15.9% cumulative), compat API 43.7k -> 50.4k req/s (+15.3% cumulative). Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #64265 Backport-PR-URL: #64663 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Tim Perry <pimterry@gmail.com> Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
1 parent 157bd07 commit f11ac0d

1 file changed

Lines changed: 71 additions & 35 deletions

File tree

β€Žlib/internal/http2/core.jsβ€Ž

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,6 +1991,47 @@ function shutdownWritable(callback) {
19911991
returnafterShutdown.call(req,0);
19921992
}
19931993

1994+
// Completes one of the two halves of a dispatched write (the write callback
1995+
// itself and the end-of-stream check); the stream machinery callback runs
1996+
// once both have finished. The state lives on stream[kState] because only a
1997+
// single write may be in flight at any given time.
1998+
functionfinishWrite(stream){
1999+
conststate=stream[kState];
2000+
if(--state.writePending!==0)
2001+
return;
2002+
constcb=state.writeCb;
2003+
state.writeCb=null;
2004+
consterr=aggregateTwoErrors(state.endErr,state.writeErr);
2005+
state.writeErr=null;
2006+
state.endErr=null;
2007+
// writeGeneric does not destroy on error and
2008+
// we cannot enable autoDestroy,
2009+
// so make sure to destroy on error.
2010+
if(err){
2011+
stream.destroy(err);
2012+
}
2013+
cb(err);
2014+
}
2015+
2016+
// Runs on the tick after a write was dispatched: if the write turned out to
2017+
// be the last chunk of an ending writable, shut the writable side down right
2018+
// away so the final DATA frame can include the END_STREAM flag.
2019+
functionendCheckNT(stream){
2020+
conststate=stream[kState];
2021+
if(state.writeErr||
2022+
!stream._writableState.ending||
2023+
stream._writableState.buffered.length||
2024+
(state.flags&STREAM_FLAGS_HAS_TRAILERS)){
2025+
finishWrite(stream);
2026+
return;
2027+
}
2028+
debugStreamObj(stream,'shutting down writable on last write');
2029+
shutdownWritable.call(stream,(err)=>{
2030+
state.endErr=err;
2031+
finishWrite(stream);
2032+
});
2033+
}
2034+
19942035
functionfinishSendTrailers(stream,headersList){
19952036
// The stream might be destroyed and in that case
19962037
// there is nothing to do.
@@ -2098,6 +2139,12 @@ class Http2Stream extends Duplex {
20982139
writeQueueSize: 0,
20992140
trailersReady: false,
21002141
endAfterHeaders: false,
2142+
writeCb: null,
2143+
writeErr: null,
2144+
endErr: null,
2145+
writePending: 0,
2146+
shutdownWritableCalled: false,
2147+
fd: -1,
21012148
};
21022149

21032150
// Fields used by the compat API to avoid megamorphisms.
@@ -2285,45 +2332,34 @@ class Http2Stream extends Duplex {
22852332
if(!this.headersSent)
22862333
this[kProceed]();
22872334

2288-
letreq;
2335+
// The stream machinery dispatches at most one _write()/_writev() at a
2336+
// time, so the coordination state between the write callback and the
2337+
// end-of-stream check below can live on the stream state instead of
2338+
// being captured by per-write closures.
2339+
conststate=this[kState];
2340+
state.writeCb=cb;
2341+
state.writeErr=null;
2342+
state.endErr=null;
2343+
2344+
if(state.flags&STREAM_FLAGS_HAS_TRAILERS){
2345+
// Trailers are pending, so the writable side cannot be shut down
2346+
// early anyway; there is no point in scheduling the end check.
2347+
state.writePending=1;
2348+
}else{
2349+
state.writePending=2;
2350+
// Shutdown write stream right after last chunk is sent
2351+
// so final DATA frame can include END_STREAM flag
2352+
process.nextTick(endCheckNT,this);
2353+
}
22892354

2290-
letwaitingForWriteCallback=true;
2291-
letwaitingForEndCheck=true;
2292-
letwriteCallbackErr;
2293-
letendCheckCallbackErr;
2294-
constdone=()=>{
2295-
if(waitingForEndCheck||waitingForWriteCallback)return;
2296-
consterr=aggregateTwoErrors(endCheckCallbackErr,writeCallbackErr);
2297-
// writeGeneric does not destroy on error and
2298-
// we cannot enable autoDestroy,
2299-
// so make sure to destroy on error.
2300-
if(err){
2301-
this.destroy(err);
2302-
}
2303-
cb(err);
2304-
};
2355+
// This is invoked both as a method on the write req and as a plain
2356+
// call, so the stream has to be captured here.
23052357
constwriteCallback=(err)=>{
2306-
waitingForWriteCallback=false;
2307-
writeCallbackErr=err;
2308-
done();
2309-
};
2310-
constendCheckCallback=(err)=>{
2311-
waitingForEndCheck=false;
2312-
endCheckCallbackErr=err;
2313-
done();
2358+
state.writeErr=err;
2359+
finishWrite(this);
23142360
};
2315-
// Shutdown write stream right after last chunk is sent
2316-
// so final DATA frame can include END_STREAM flag
2317-
process.nextTick(()=>{
2318-
if(writeCallbackErr||
2319-
!this._writableState.ending||
2320-
this._writableState.buffered.length||
2321-
(this[kState].flags&STREAM_FLAGS_HAS_TRAILERS))
2322-
returnendCheckCallback();
2323-
debugStreamObj(this,'shutting down writable on last write');
2324-
shutdownWritable.call(this,endCheckCallback);
2325-
});
23262361

2362+
letreq;
23272363
if(writev)
23282364
req=writevGeneric(this,data,writeCallback);
23292365
else

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit f11ac0d

Browse files
mcollinaaduh95
authored andcommitted
http2: avoid per-write closures in kWriteGeneric
Every _write()/_writev() on an Http2Stream allocated four closures and an anonymous nextTick callback to coordinate the write callback with the end-of-stream check. Since the stream machinery dispatches at most one write at a time, that coordination state can live on the stream's kState object instead, with shared named functions for the end check and completion logic. When trailers are pending the writable side cannot be shut down early anyway, so the end-of-stream check tick is now skipped entirely for those writes. Also pre-initialize the kState fields that used to be added dynamically (shutdownWritableCalled, fd) so hot-path stores no longer transition the object shape. h2load, 1 KiB response payload, -c 4 -m 100, mean of 6 alternating runs vs main: core API 61.0k -> 70.7k req/s (+15.9% cumulative), compat API 43.7k -> 50.4k req/s (+15.3% cumulative). Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #64265 Backport-PR-URL: #64663 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Tim Perry <pimterry@gmail.com> Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
1 parent 157bd07 commit f11ac0d

1 file changed

Lines changed: 71 additions & 35 deletions

File tree

β€Žlib/internal/http2/core.jsβ€Ž

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,6 +1991,47 @@ function shutdownWritable(callback) {
19911991
returnafterShutdown.call(req,0);
19921992
}
19931993

1994+
// Completes one of the two halves of a dispatched write (the write callback
1995+
// itself and the end-of-stream check); the stream machinery callback runs
1996+
// once both have finished. The state lives on stream[kState] because only a
1997+
// single write may be in flight at any given time.
1998+
functionfinishWrite(stream){
1999+
conststate=stream[kState];
2000+
if(--state.writePending!==0)
2001+
return;
2002+
constcb=state.writeCb;
2003+
state.writeCb=null;
2004+
consterr=aggregateTwoErrors(state.endErr,state.writeErr);
2005+
state.writeErr=null;
2006+
state.endErr=null;
2007+
// writeGeneric does not destroy on error and
2008+
// we cannot enable autoDestroy,
2009+
// so make sure to destroy on error.
2010+
if(err){
2011+
stream.destroy(err);
2012+
}
2013+
cb(err);
2014+
}
2015+
2016+
// Runs on the tick after a write was dispatched: if the write turned out to
2017+
// be the last chunk of an ending writable, shut the writable side down right
2018+
// away so the final DATA frame can include the END_STREAM flag.
2019+
functionendCheckNT(stream){
2020+
conststate=stream[kState];
2021+
if(state.writeErr||
2022+
!stream._writableState.ending||
2023+
stream._writableState.buffered.length||
2024+
(state.flags&STREAM_FLAGS_HAS_TRAILERS)){
2025+
finishWrite(stream);
2026+
return;
2027+
}
2028+
debugStreamObj(stream,'shutting down writable on last write');
2029+
shutdownWritable.call(stream,(err)=>{
2030+
state.endErr=err;
2031+
finishWrite(stream);
2032+
});
2033+
}
2034+
19942035
functionfinishSendTrailers(stream,headersList){
19952036
// The stream might be destroyed and in that case
19962037
// there is nothing to do.
@@ -2098,6 +2139,12 @@ class Http2Stream extends Duplex {
20982139
writeQueueSize: 0,
20992140
trailersReady: false,
21002141
endAfterHeaders: false,
2142+
writeCb: null,
2143+
writeErr: null,
2144+
endErr: null,
2145+
writePending: 0,
2146+
shutdownWritableCalled: false,
2147+
fd: -1,
21012148
};
21022149

21032150
// Fields used by the compat API to avoid megamorphisms.
@@ -2285,45 +2332,34 @@ class Http2Stream extends Duplex {
22852332
if(!this.headersSent)
22862333
this[kProceed]();
22872334

2288-
letreq;
2335+
// The stream machinery dispatches at most one _write()/_writev() at a
2336+
// time, so the coordination state between the write callback and the
2337+
// end-of-stream check below can live on the stream state instead of
2338+
// being captured by per-write closures.
2339+
conststate=this[kState];
2340+
state.writeCb=cb;
2341+
state.writeErr=null;
2342+
state.endErr=null;
2343+
2344+
if(state.flags&STREAM_FLAGS_HAS_TRAILERS){
2345+
// Trailers are pending, so the writable side cannot be shut down
2346+
// early anyway; there is no point in scheduling the end check.
2347+
state.writePending=1;
2348+
}else{
2349+
state.writePending=2;
2350+
// Shutdown write stream right after last chunk is sent
2351+
// so final DATA frame can include END_STREAM flag
2352+
process.nextTick(endCheckNT,this);
2353+
}
22892354

2290-
letwaitingForWriteCallback=true;
2291-
letwaitingForEndCheck=true;
2292-
letwriteCallbackErr;
2293-
letendCheckCallbackErr;
2294-
constdone=()=>{
2295-
if(waitingForEndCheck||waitingForWriteCallback)return;
2296-
consterr=aggregateTwoErrors(endCheckCallbackErr,writeCallbackErr);
2297-
// writeGeneric does not destroy on error and
2298-
// we cannot enable autoDestroy,
2299-
// so make sure to destroy on error.
2300-
if(err){
2301-
this.destroy(err);
2302-
}
2303-
cb(err);
2304-
};
2355+
// This is invoked both as a method on the write req and as a plain
2356+
// call, so the stream has to be captured here.
23052357
constwriteCallback=(err)=>{
2306-
waitingForWriteCallback=false;
2307-
writeCallbackErr=err;
2308-
done();
2309-
};
2310-
constendCheckCallback=(err)=>{
2311-
waitingForEndCheck=false;
2312-
endCheckCallbackErr=err;
2313-
done();
2358+
state.writeErr=err;
2359+
finishWrite(this);
23142360
};
2315-
// Shutdown write stream right after last chunk is sent
2316-
// so final DATA frame can include END_STREAM flag
2317-
process.nextTick(()=>{
2318-
if(writeCallbackErr||
2319-
!this._writableState.ending||
2320-
this._writableState.buffered.length||
2321-
(this[kState].flags&STREAM_FLAGS_HAS_TRAILERS))
2322-
returnendCheckCallback();
2323-
debugStreamObj(this,'shutting down writable on last write');
2324-
shutdownWritable.call(this,endCheckCallback);
2325-
});
23262361

2362+
letreq;
23272363
if(writev)
23282364
req=writevGeneric(this,data,writeCallback);
23292365
else

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Commit f11ac0d

Browse files
mcollinaaduh95
authored andcommitted
http2: avoid per-write closures in kWriteGeneric
Every _write()/_writev() on an Http2Stream allocated four closures and an anonymous nextTick callback to coordinate the write callback with the end-of-stream check. Since the stream machinery dispatches at most one write at a time, that coordination state can live on the stream's kState object instead, with shared named functions for the end check and completion logic. When trailers are pending the writable side cannot be shut down early anyway, so the end-of-stream check tick is now skipped entirely for those writes. Also pre-initialize the kState fields that used to be added dynamically (shutdownWritableCalled, fd) so hot-path stores no longer transition the object shape. h2load, 1 KiB response payload, -c 4 -m 100, mean of 6 alternating runs vs main: core API 61.0k -> 70.7k req/s (+15.9% cumulative), compat API 43.7k -> 50.4k req/s (+15.3% cumulative). Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #64265 Backport-PR-URL: #64663 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Tim Perry <pimterry@gmail.com> Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
1 parent 157bd07 commit f11ac0d

1 file changed

Lines changed: 71 additions & 35 deletions

File tree

β€Žlib/internal/http2/core.jsβ€Ž

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,6 +1991,47 @@ function shutdownWritable(callback) {
19911991
returnafterShutdown.call(req,0);
19921992
}
19931993

1994+
// Completes one of the two halves of a dispatched write (the write callback
1995+
// itself and the end-of-stream check); the stream machinery callback runs
1996+
// once both have finished. The state lives on stream[kState] because only a
1997+
// single write may be in flight at any given time.
1998+
functionfinishWrite(stream){
1999+
conststate=stream[kState];
2000+
if(--state.writePending!==0)
2001+
return;
2002+
constcb=state.writeCb;
2003+
state.writeCb=null;
2004+
consterr=aggregateTwoErrors(state.endErr,state.writeErr);
2005+
state.writeErr=null;
2006+
state.endErr=null;
2007+
// writeGeneric does not destroy on error and
2008+
// we cannot enable autoDestroy,
2009+
// so make sure to destroy on error.
2010+
if(err){
2011+
stream.destroy(err);
2012+
}
2013+
cb(err);
2014+
}
2015+
2016+
// Runs on the tick after a write was dispatched: if the write turned out to
2017+
// be the last chunk of an ending writable, shut the writable side down right
2018+
// away so the final DATA frame can include the END_STREAM flag.
2019+
functionendCheckNT(stream){
2020+
conststate=stream[kState];
2021+
if(state.writeErr||
2022+
!stream._writableState.ending||
2023+
stream._writableState.buffered.length||
2024+
(state.flags&STREAM_FLAGS_HAS_TRAILERS)){
2025+
finishWrite(stream);
2026+
return;
2027+
}
2028+
debugStreamObj(stream,'shutting down writable on last write');
2029+
shutdownWritable.call(stream,(err)=>{
2030+
state.endErr=err;
2031+
finishWrite(stream);
2032+
});
2033+
}
2034+
19942035
functionfinishSendTrailers(stream,headersList){
19952036
// The stream might be destroyed and in that case
19962037
// there is nothing to do.
@@ -2098,6 +2139,12 @@ class Http2Stream extends Duplex {
20982139
writeQueueSize: 0,
20992140
trailersReady: false,
21002141
endAfterHeaders: false,
2142+
writeCb: null,
2143+
writeErr: null,
2144+
endErr: null,
2145+
writePending: 0,
2146+
shutdownWritableCalled: false,
2147+
fd: -1,
21012148
};
21022149

21032150
// Fields used by the compat API to avoid megamorphisms.
@@ -2285,45 +2332,34 @@ class Http2Stream extends Duplex {
22852332
if(!this.headersSent)
22862333
this[kProceed]();
22872334

2288-
letreq;
2335+
// The stream machinery dispatches at most one _write()/_writev() at a
2336+
// time, so the coordination state between the write callback and the
2337+
// end-of-stream check below can live on the stream state instead of
2338+
// being captured by per-write closures.
2339+
conststate=this[kState];
2340+
state.writeCb=cb;
2341+
state.writeErr=null;
2342+
state.endErr=null;
2343+
2344+
if(state.flags&STREAM_FLAGS_HAS_TRAILERS){
2345+
// Trailers are pending, so the writable side cannot be shut down
2346+
// early anyway; there is no point in scheduling the end check.
2347+
state.writePending=1;
2348+
}else{
2349+
state.writePending=2;
2350+
// Shutdown write stream right after last chunk is sent
2351+
// so final DATA frame can include END_STREAM flag
2352+
process.nextTick(endCheckNT,this);
2353+
}
22892354

2290-
letwaitingForWriteCallback=true;
2291-
letwaitingForEndCheck=true;
2292-
letwriteCallbackErr;
2293-
letendCheckCallbackErr;
2294-
constdone=()=>{
2295-
if(waitingForEndCheck||waitingForWriteCallback)return;
2296-
consterr=aggregateTwoErrors(endCheckCallbackErr,writeCallbackErr);
2297-
// writeGeneric does not destroy on error and
2298-
// we cannot enable autoDestroy,
2299-
// so make sure to destroy on error.
2300-
if(err){
2301-
this.destroy(err);
2302-
}
2303-
cb(err);
2304-
};
2355+
// This is invoked both as a method on the write req and as a plain
2356+
// call, so the stream has to be captured here.
23052357
constwriteCallback=(err)=>{
2306-
waitingForWriteCallback=false;
2307-
writeCallbackErr=err;
2308-
done();
2309-
};
2310-
constendCheckCallback=(err)=>{
2311-
waitingForEndCheck=false;
2312-
endCheckCallbackErr=err;
2313-
done();
2358+
state.writeErr=err;
2359+
finishWrite(this);
23142360
};
2315-
// Shutdown write stream right after last chunk is sent
2316-
// so final DATA frame can include END_STREAM flag
2317-
process.nextTick(()=>{
2318-
if(writeCallbackErr||
2319-
!this._writableState.ending||
2320-
this._writableState.buffered.length||
2321-
(this[kState].flags&STREAM_FLAGS_HAS_TRAILERS))
2322-
returnendCheckCallback();
2323-
debugStreamObj(this,'shutting down writable on last write');
2324-
shutdownWritable.call(this,endCheckCallback);
2325-
});
23262361

2362+
letreq;
23272363
if(writev)
23282364
req=writevGeneric(this,data,writeCallback);
23292365
else

0 commit comments

Comments
Β (0)