OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-sync commits / a0ff34e

pull: fix mid-transfer stall on large repos
Two changes to _spawnGitService: 1. proc.on('exit') → proc.on('close') 'exit' fires when the process terminates but BEFORE all of proc.stdout has been emitted to our 'data' listener. Calling conn.end() at that point truncates the tail of the pack — every byte git-upload-pack wrote that was still in the kernel pipe buffer or queued by backpressure gets dropped on the floor. The client then sits in epoll forever waiting for the missing tail. 'close' fires only after all stdio streams have emitted their data and ended, so conn.end() runs with nothing left in flight. 2. Add error teardown that mirrors the hyperbeam Duplex pattern (lib/index.js:81,96 — destroys self on conn error rather than swallowing). conn / proc.stdin / proc.stdout errors all funnel through a teardown() that SIGTERMs the proc and destroys the conn, so the client's retry loop sees a fast failure instead of a 30s idle ghost. Replaces three silent safetyCatch handlers. Verified end-to-end against tabertron→stortron with hyper-tps (765 MB) and hyper-booking (106 MB) clones over punch://. Pre-fix: both stalled mid-transfer (95% / 92%). Post-fix: both clone clean. A 95-repo heal sweep dropped from 19 stalls to 0. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
a0ff34eac09cd43c00623ebf54c89da0cd690a15
by plankatron <fleeky@gmail.com> · 2026-04-27T19:44:28.000Z

Changed files (1) +27 -3

index.js +27 -3
……@@ -424,10 +424,34 @@ module.exports = class HyperSync extends ReadyResource {
424424 if (typeof conn.resume === 'function') conn.resume();
425425
426426 conn.on('end', () => { try { proc.stdin.end(); } catch {} });
427 proc.stdin.on('error', safetyCatch);
428 proc.stdout.on('error', safetyCatch);
429427
430 proc.on('exit', () => {
428 // Error teardown — mirrors hyperbeam (canonical Duplex-over-DHT
429 // pattern): conn errors destroy the parent stream rather than
430 // getting silently swallowed. Without this, a transient libudx /
431 // secret-stream error mid-transfer leaves proc.stdout paused
432 // awaiting a 'drain' that the broken conn will never emit → 30s
433 // idle stall on the client side. With teardown, the error
434 // promptly tears down both ends and the client's retry loop can
435 // act on it.
436 const teardown = (err) => {
437 if (proc.exitCode === null) {
438 try { proc.kill('SIGTERM'); } catch {}
439 }
440 if (!conn.destroyed) {
441 try { conn.destroy(err); } catch {}
442 }
443 };
444 conn.on('error', teardown);
445 proc.stdin.on('error', teardown);
446 proc.stdout.on('error', teardown);
447
448 // Use 'close' not 'exit'. 'exit' fires when the process terminates
449 // but BEFORE all of proc.stdout has been emitted to our 'data'
450 // listener — calling conn.end() at that point truncates the tail
451 // of the pack. 'close' fires after all stdio streams have ended,
452 // guaranteeing every byte git-upload-pack wrote has reached
453 // conn.write() before we FIN the conn.
454 proc.on('close', () => {
431455 if (!conn.destroyed) conn.end();
432456 });
433457