quic: buffer stalls, as maxdata updates do not triggern an update of writeDesired sizes
Dieses Issue hat noch niemand übernommen.
- Vorherrschende Sprache
- JavaScript
- Sterne
- 122k
- Forks
- 37.4k
- Ø Merge
- 4 T. 3 Std.
- Gemergte PRs (30 T.)
- 272
Beschreibung
ngtcp2 has no callback to inform us about an arrived maxdata frame.
If the buffering is only external and not within ngtcp2, this can cause a stall.
The equivalent for maxstreamdata is already addressed in https://github.com/nodejs/node/pull/64768 , as here ngtcp2 provides a callback.
Here is a reproduction code, based on the test of @pimterry for the PR of maxstreamdata:
// Flags: --experimental-quic --experimental-stream-iter --no-warnings
// Test: Quic maxdata updates on http/3
// Client sends a body that precisely fills the session window size,
// and verifies that it is data transfer is not stalled.
import { hasQuic, skip } from '../common/index.mjs';
import { readFile } from 'node:fs/promises';
import { setTimeout as sleep } from 'node:timers/promises';
if (!hasQuic) {
skip('QUIC is not enabled');
}
const { listen, connect } = await import('node:quic');
const { createPrivateKey } = await import('node:crypto');
const { drainableProtocol } = await import('stream/iter');
const keys = 'test/fixtures/keys';
const key = createPrivateKey(await readFile(`${keys}/agent1-key.pem`));
const cert = await readFile(`${keys}/agent1-cert.pem`);
const WINDOW = 4096;
// Fills the window exactly:
// considers all framing including some initial session capsules
const BODY = WINDOW - 38;
let letServerRead;
const serverMayRead = new Promise((resolve) => { letServerRead = resolve; });
const endpoint = await listen((session) => {
session.onstream = async (stream) => {
await serverMayRead;
// eslint-disable-next-line no-unused-vars
for await (const _ of stream) { /* reading extends the window */ }
};
}, {
sni: { '*': { keys: [key], certs: [cert] } },
transportParams: {
initialMaxStreamDataBidiRemote: 1024 * 1024, // make sure maxstreamdata does not block
initialMaxData: WINDOW,
},
onheaders() { this.sendHeaders({ ':status': '200' }); },
});
const session = await connect(endpoint.address, {
servername: 'localhost',
verifyPeer: 'manual',
});
await session.opened;
// Budget well above the window, so the window is what stops the writer.
const stream = await session.createBidirectionalStream({ budget: 1024 * 1024 });
stream.sendHeaders({
':method': 'POST',
':path': '/',
':scheme': 'https',
':authority': 'localhost',
}, { terminal: false });
const writer = stream.writer;
writer.writeSync(new Uint8Array(BODY));
// Long enough for every byte to be acked. The peer acks as data arrives,
// whether or not its application has read any of it, so by now the window is
// exhausted, the send buffer is empty, and no further ACK can arrive.
await sleep(500);
const watchdog = setTimeout(() => {
console.error('STALLED: no drain after MAX_STREAM_DATA');
process.exit(1);
}, 5000);
letServerRead(); // Extend the window, with no ack attached
await writer[drainableProtocol]();
clearTimeout(watchdog);
process.exit(0);
This is the ngtcp2 issue:
https://github.com/ngtcp2/ngtcp2/issues/2243 .
Or is there another way without a ngtcp2 callback? (@pimterry @jasnell )
Beitragsleitfaden
Erste Schritte
- Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
- Forke das Repository und arbeite in einem Branch.
- Öffne einen Pull Request, der die Issue-Nummer nennt.
Rechercherichtung
Führe zunächst die bereitgestellte QUIC-Reproduktion aus und untersuche den Write/Drain-Pfad, der vom Session- und Stream-Writer verwendet wird. Vergleiche das maxdata-Verhalten mit dem maxstreamdata-Fix in Pull Request 64768 und prüfe ngtcp2 issue 2243; abgeschlossen ist die Aufgabe, wenn der Writer-Drain beendet wird, nachdem der Server das Session-Fenster erweitert hat, ohne ins Stocken zu geraten.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Bewertung
- Tech-Stack
- javascript
- Bereich
- networking
- Issue-Typ
- Bug
- Schwierigkeit
- 4/5
- Geschätzter Aufwand
- 3-5 Tage
- Aktivitätsstatus
- Ruhig
- Klarheit
- Größtenteils klar
- Anfängerfreundlichkeit
- 45/100