Rebuild: XOVER replies are joined once, when complete
XoverClient joined all received pieces into one buffer on every arrival (quadratic for a 2.6 MB reply), and turned the inflated check into text the raw path doesn't use. The coordinator under load (3 workers, 4 connections): busy 40% → 13% of a core, Buffer.concat gone from the profile. (master's relay uses the same client.) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ead2790515
commit
08c876b690
1 file changed
+25
-8
@@ -118,7 +118,7 @@ export class XoverClient {
|
||||
const reply = await this.command(`XOVER ${from}-${to}`)
|
||||
if (reply.startsWith('420') || reply.startsWith('423')) return deflateSync(Buffer.alloc(0))
|
||||
await this.expect(reply, ['224'])
|
||||
if (reply.includes('COMPRESS=GZIP')) return (await this.compressedRaw()).raw
|
||||
if (reply.includes('COMPRESS=GZIP')) return (await this.compressedRaw(false)).raw
|
||||
// Plain replies (the relay's default): compressed off the event loop, at the fastest level.
|
||||
return deflateAsync(Buffer.from(await this.plainBody()), { level: 1 })
|
||||
})
|
||||
@@ -214,19 +214,36 @@ export class XoverClient {
|
||||
}
|
||||
|
||||
private async compressedBody(): Promise<string> {
|
||||
return (await this.compressedRaw()).text
|
||||
return (await this.compressedRaw(true)).text
|
||||
}
|
||||
|
||||
/** A zlib/gzip stream followed by a "." line: complete once it ends in ".\r\n" and inflates. */
|
||||
private async compressedRaw(): Promise<{ raw: Buffer; text: string }> {
|
||||
/** Whether what was received so far ends in `tail`, without joining the pieces. */
|
||||
private endsWith(tail: Buffer): boolean {
|
||||
if (this.length < tail.length) return false
|
||||
let need = tail.length
|
||||
for (let i = this.chunks.length - 1; i >= 0 && need > 0; i--) {
|
||||
const chunk = this.chunks[i]
|
||||
const n = Math.min(need, chunk.length)
|
||||
if (!chunk.subarray(chunk.length - n).equals(tail.subarray(need - n, need))) return false
|
||||
need -= n
|
||||
}
|
||||
return need === 0
|
||||
}
|
||||
|
||||
/**
|
||||
* A zlib/gzip stream followed by a "." line: complete once it ends in
|
||||
* ".\r\n" and inflates. The pieces are joined only then: joining them on
|
||||
* every arrival was quadratic, ~15% of a prefetcher's time (LOG.md 2026-10-09).
|
||||
*/
|
||||
private async compressedRaw(wantText: boolean): Promise<{ raw: Buffer; text: string }> {
|
||||
while (true) {
|
||||
const all = this.buffer()
|
||||
if (all.length > DOT_LINE.length && all.subarray(all.length - DOT_LINE.length).equals(DOT_LINE)) {
|
||||
if (this.length > DOT_LINE.length && this.endsWith(DOT_LINE)) {
|
||||
const all = this.buffer()
|
||||
const raw = all.subarray(0, all.length - DOT_LINE.length)
|
||||
try {
|
||||
const text = unzipSync(raw).toString('utf8')
|
||||
const inflated = unzipSync(raw)
|
||||
this.take(all.length)
|
||||
return { raw: Buffer.from(raw), text }
|
||||
return { raw: Buffer.from(raw), text: wantText ? inflated.toString('utf8') : '' }
|
||||
} catch {
|
||||
// ".\r\n" inside the compressed data: wait for more.
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user