| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 0369278 commit ec956f2
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -431,6 +431,7 @@ class DefaultApplication final : public Session::Application { | |||
| 431 | 431 | // The peer granted more flow control for this stream. Re-schedule | |
| 432 | 432 | // it so SendPendingData will resume writing. | |
| 433 | 433 | DCHECK_NOT_NULL(stream); | |
| 434 | + stream->UpdateWriteDesiredSize(); // the stream might be blocked on js side | ||
| 434 | 435 | stream->Schedule(&stream_queue_); | |
| 435 | 436 | } | |
| 436 | 437 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -377,6 +377,7 @@ class Http3ApplicationImpl final : public Session::Application { | |||
| 377 | 377 | Debug(&session(), | |
| 378 | 378 | "HTTP/3 application extending max stream data to %" PRIu64, | |
| 379 | 379 | max_data); | |
| 380 | + stream->UpdateWriteDesiredSize(); // the stream might be blocked on js side | ||
| 380 | 381 | nghttp3_conn_unblock_stream(*this, stream->id()); | |
| 381 | 382 | } | |
| 382 | 383 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,80 @@ | |||
| 1 | + // Flags: --experimental-quic --experimental-stream-iter --no-warnings | ||
| 2 | + | ||
| 3 | + // Test: Quic maxstreamdata updates on http/3 | ||
| 4 | + // Client sends a body that precisely fills the window size, | ||
| 5 | + // and verifies that it is data transfer is not stalled. | ||
| 6 | + | ||
| 7 | + import { hasQuic, skip } from '../common/index.mjs'; | ||
| 8 | + import { readFile } from 'node:fs/promises'; | ||
| 9 | + import { setTimeout as sleep } from 'node:timers/promises'; | ||
| 10 | + | ||
| 11 | + if (!hasQuic) { | ||
| 12 | + skip('QUIC is not enabled'); | ||
| 13 | + } | ||
| 14 | + const { listen, connect } = await import('node:quic'); | ||
| 15 | + const { createPrivateKey } = await import('node:crypto'); | ||
| 16 | + const { drainableProtocol } = await import('stream/iter'); | ||
| 17 | + | ||
| 18 | + const keys = 'test/fixtures/keys'; | ||
| 19 | + const key = createPrivateKey(await readFile(`${keys}/agent1-key.pem`)); | ||
| 20 | + const cert = await readFile(`${keys}/agent1-cert.pem`); | ||
| 21 | + | ||
| 22 | + const WINDOW = 4096; | ||
| 23 | + // Fills the window exactly: HTTP/3 spends 11 of those bytes on framing (8 for | ||
| 24 | + // the HEADERS frame below, 3 for the DATA frame header). The send buffer then | ||
| 25 | + // empties at the same moment the window reaches zero, leaving nothing in | ||
| 26 | + // flight to ack. Any other size leaves bytes queued, and the ack for those | ||
| 27 | + // wakes the writer instead, hiding the bug. | ||
| 28 | + const BODY = WINDOW - 11; | ||
| 29 | + | ||
| 30 | + let letServerRead; | ||
| 31 | + const serverMayRead = new Promise((resolve) => { letServerRead = resolve; }); | ||
| 32 | + | ||
| 33 | + const endpoint = await listen((session) => { | ||
| 34 | + session.onstream = async (stream) => { | ||
| 35 | + await serverMayRead; | ||
| 36 | + // eslint-disable-next-line no-unused-vars | ||
| 37 | + for await (const _ of stream) { /* reading extends the window */ } | ||
| 38 | + }; | ||
| 39 | + }, { | ||
| 40 | + sni: { '*': { keys: [key], certs: [cert] } }, | ||
| 41 | + transportParams: { | ||
| 42 | + initialMaxStreamDataBidiRemote: WINDOW, | ||
| 43 | + initialMaxData: 1024 * 1024, | ||
| 44 | + }, | ||
| 45 | + onheaders() { this.sendHeaders({ ':status': '200' }); }, | ||
| 46 | + }); | ||
| 47 | + | ||
| 48 | + const session = await connect(endpoint.address, { | ||
| 49 | + servername: 'localhost', | ||
| 50 | + verifyPeer: 'manual', | ||
| 51 | + }); | ||
| 52 | + await session.opened; | ||
| 53 | + | ||
| 54 | + // Budget well above the window, so the window is what stops the writer. | ||
| 55 | + const stream = await session.createBidirectionalStream({ budget: 1024 * 1024 }); | ||
| 56 | + stream.sendHeaders({ | ||
| 57 | + ':method': 'POST', | ||
| 58 | + ':path': '/', | ||
| 59 | + ':scheme': 'https', | ||
| 60 | + ':authority': 'localhost', | ||
| 61 | + }, { terminal: false }); | ||
| 62 | + | ||
| 63 | + const writer = stream.writer; | ||
| 64 | + writer.writeSync(new Uint8Array(BODY)); | ||
| 65 | + | ||
| 66 | + // Long enough for every byte to be acked. The peer acks as data arrives, | ||
| 67 | + // whether or not its application has read any of it, so by now the window is | ||
| 68 | + // exhausted, the send buffer is empty, and no further ACK can arrive. | ||
| 69 | + await sleep(500); | ||
| 70 | + | ||
| 71 | + const watchdog = setTimeout(() => { | ||
| 72 | + console.error('STALLED: no drain after MAX_STREAM_DATA'); | ||
| 73 | + process.exit(1); | ||
| 74 | + }, 5000); | ||
| 75 | + | ||
| 76 | + letServerRead(); // Extend the window, with no ack attached | ||
| 77 | + await writer[drainableProtocol](); | ||
| 78 | + | ||
| 79 | + clearTimeout(watchdog); | ||
| 80 | + process.exit(0); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,75 @@ | |||
| 1 | + // Flags: --experimental-quic --experimental-stream-iter --no-warnings | ||
| 2 | + | ||
| 3 | + // Test: Quic maxstreamdata updates on pure quic | ||
| 4 | + // Client sends a body that precisely fills the window size, | ||
| 5 | + // and verifies that it is data transfer is not stalled. | ||
| 6 | + | ||
| 7 | + import { hasQuic, skip } from '../common/index.mjs'; | ||
| 8 | + import { readFile } from 'node:fs/promises'; | ||
| 9 | + import { setTimeout as sleep } from 'node:timers/promises'; | ||
| 10 | + | ||
| 11 | + if (!hasQuic) { | ||
| 12 | + skip('QUIC is not enabled'); | ||
| 13 | + } | ||
| 14 | + const { listen, connect } = await import('node:quic'); | ||
| 15 | + const { createPrivateKey } = await import('node:crypto'); | ||
| 16 | + const { drainableProtocol } = await import('stream/iter'); | ||
| 17 | + | ||
| 18 | + const keys = 'test/fixtures/keys'; | ||
| 19 | + const key = createPrivateKey(await readFile(`${keys}/agent1-key.pem`)); | ||
| 20 | + const cert = await readFile(`${keys}/agent1-cert.pem`); | ||
| 21 | + | ||
| 22 | + const WINDOW = 4096; | ||
| 23 | + // Fills the window exactly: HTTP/3 spends 11 of those bytes on framing (8 for | ||
| 24 | + // the HEADERS frame below, 3 for the DATA frame header). The send buffer then | ||
| 25 | + // empties at the same moment the window reaches zero, leaving nothing in | ||
| 26 | + // flight to ack. Any other size leaves bytes queued, and the ack for those | ||
| 27 | + // wakes the writer instead, hiding the bug. | ||
| 28 | + const BODY = WINDOW; | ||
| 29 | + | ||
| 30 | + let letServerRead; | ||
| 31 | + const serverMayRead = new Promise((resolve) => { letServerRead = resolve; }); | ||
| 32 | + | ||
| 33 | + const endpoint = await listen((session) => { | ||
| 34 | + session.onstream = async (stream) => { | ||
| 35 | + await serverMayRead; | ||
| 36 | + // eslint-disable-next-line no-unused-vars | ||
| 37 | + for await (const _ of stream) { /* reading extends the window */ } | ||
| 38 | + }; | ||
| 39 | + }, { | ||
| 40 | + alpn: 'foo', | ||
| 41 | + sni: { '*': { keys: [key], certs: [cert] } }, | ||
| 42 | + transportParams: { | ||
| 43 | + initialMaxStreamDataBidiRemote: WINDOW, | ||
| 44 | + initialMaxData: 1024 * 1024, | ||
| 45 | + } | ||
| 46 | + }); | ||
| 47 | + | ||
| 48 | + const session = await connect(endpoint.address, { | ||
| 49 | + servername: 'localhost', | ||
| 50 | + verifyPeer: 'manual', | ||
| 51 | + alpn: 'foo' | ||
| 52 | + }); | ||
| 53 | + await session.opened; | ||
| 54 | + | ||
| 55 | + // Budget well above the window, so the window is what stops the writer. | ||
| 56 | + const stream = await session.createBidirectionalStream({ budget: 1024 * 1024 }); | ||
| 57 | + | ||
| 58 | + const writer = stream.writer; | ||
| 59 | + writer.writeSync(new Uint8Array(BODY)); | ||
| 60 | + | ||
| 61 | + // Long enough for every byte to be acked. The peer acks as data arrives, | ||
| 62 | + // whether or not its application has read any of it, so by now the window is | ||
| 63 | + // exhausted, the send buffer is empty, and no further ACK can arrive. | ||
| 64 | + await sleep(500); | ||
| 65 | + | ||
| 66 | + const watchdog = setTimeout(() => { | ||
| 67 | + console.error('STALLED: no drain after MAX_STREAM_DATA'); | ||
| 68 | + process.exit(1); | ||
| 69 | + }, 5000); | ||
| 70 | + | ||
| 71 | + letServerRead(); // Extend the window, with no ack attached | ||
| 72 | + await writer[drainableProtocol](); | ||
| 73 | + | ||
| 74 | + clearTimeout(watchdog); | ||
| 75 | + process.exit(0); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments