46 lines
2.1 KiB
JavaScript
46 lines
2.1 KiB
JavaScript
|
|
// Webserver bridge backpressure probe (audit R2, webserver half): a paused client socket
|
|||
|
|
// must stall the SSE pump (res.write false → await drain) instead of buffering unboundedly.
|
|||
|
|
// Self-contained: starts startWebServer on PORT with a stub apiHandler; no host needed.
|
|||
|
|
// Run: node_modules/.bin/tsx missions/scripts/verify-webserver-backpressure.mjs
|
|||
|
|
import { connect } from 'node:net'
|
|||
|
|
import { once } from 'node:events'
|
|||
|
|
import { startWebServer } from '../../packages/host/webserver/src/index.ts'
|
|||
|
|
|
|||
|
|
const PORT = Number(process.env.PROBE_PORT ?? 3097)
|
|||
|
|
const CHUNK = 64 * 1024
|
|||
|
|
const TOTAL = 200 // 200 × 64KB = 12.5MB — far beyond any socket buffer
|
|||
|
|
let failures = 0
|
|||
|
|
const report = (n, p, d = '') => { failures += p ? 0 : 1; console.log(`${p ? 'PASS' : 'FAIL'} ${n}${d ? ' — ' + d : ''}`) }
|
|||
|
|
|
|||
|
|
let pulled = 0
|
|||
|
|
const apiHandler = {
|
|||
|
|
fetch: async () => new Response(new ReadableStream({
|
|||
|
|
pull(controller) {
|
|||
|
|
if (pulled >= TOTAL) return controller.close()
|
|||
|
|
pulled++
|
|||
|
|
controller.enqueue(new Uint8Array(CHUNK))
|
|||
|
|
},
|
|||
|
|
}), { headers: { 'content-type': 'text/event-stream' } }),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const server = await startWebServer({ port: PORT, distIndex: '/nonexistent/index.html', apiHandler }, (e) => console.error(String(e)))
|
|||
|
|
const socket = connect(PORT, '127.0.0.1')
|
|||
|
|
await once(socket, 'connect')
|
|||
|
|
socket.write(`GET /api/events.host HTTP/1.1\r\nHost: x\r\nConnection: keep-alive\r\n\r\n`)
|
|||
|
|
socket.pause() // stop reading: kernel+node buffers fill, then res.write must return false
|
|||
|
|
|
|||
|
|
await new Promise(r => setTimeout(r, 1500))
|
|||
|
|
const stalled = pulled
|
|||
|
|
// Without drain-await the pump races through all chunks regardless of the paused reader.
|
|||
|
|
report('R2 暂停读的慢客户端使泵停在低水位(非全量吞入内存)', stalled < TOTAL / 2, `pulled=${stalled}/${TOTAL}`)
|
|||
|
|
|
|||
|
|
socket.resume() // drain: the pump must resume and finish
|
|||
|
|
const t0 = Date.now()
|
|||
|
|
while (pulled < TOTAL && Date.now() - t0 < 10_000) await new Promise(r => setTimeout(r, 100))
|
|||
|
|
report('R2b 恢复读后泵继续推进到完成', pulled === TOTAL, `pulled=${pulled}/${TOTAL}`)
|
|||
|
|
|
|||
|
|
socket.destroy()
|
|||
|
|
await server.close()
|
|||
|
|
console.log(failures === 0 ? 'ALL PASS' : `${failures} FAILURE(S)`)
|
|||
|
|
process.exit(failures === 0 ? 0 : 1)
|