OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / lib / web / pipe-file.js

70 lines · javascript ref:
/**
 * Stream a file to an HTTP response without letting a mid-flight failure kill
 * the daemon.
 *
 * `fs.createReadStream(p).pipe(res)` reads as safe when it sits inside a
 * try/catch, but it is not: the catch only guards the SYNCHRONOUS setup. Once
 * `.pipe()` returns, any later stream failure lands on a subsequent tick with
 * no handler attached, and Bare terminates the process on an uncaught error.
 *
 * That is not a theoretical hazard. On celltron (2026-08-07) one click on a
 * partially-synced repo took the whole forge down repeatedly:
 *
 *     Uncaught StreamError: Writable stream closed
 *         at ReadableState.afterDestroy (streamx  index.js:572)
 *         at FileReadStream._destroy    (bare-fs  index.js:2511)
 *       { code: 'PREMATURE_CLOSE' }
 *     [..] pear-git-v3 exited; restart in 60s
 *
 * Each death cost ~2.5 min of downtime — 60 s of supervisor backoff plus ~100 s
 * remounting 36 repos before the HTTP port is bound again — so a user retrying
 * the same link kept the forge in a self-sustaining crash loop.
 *
 * The usual trigger is NOT a corrupt file. It is the reader going away (a
 * browser navigating off a large download, a client abort) or the file being
 * rewritten underneath us by a concurrent sync. Both tear the pipe down and
 * surface as PREMATURE_CLOSE. Serving one truncated response is the correct
 * outcome; killing a daemon that hosts every other repo is not.
 */

/**
 * Pipe `stream` into `res`, guaranteeing both ends are torn down exactly once
 * and that no failure escapes as an uncaught error.
 *
 * Returns true so call sites can keep the `return pipeFile(...)` shape the
 * router uses to signal "request handled".
 *
 * @param {object} stream  a readable file stream (bare-fs / node fs)
 * @param {object} res     the HTTP response being written to
 * @param {string} [label] what is being served, for the log line
 */
export function pipeFile (stream, res, label) {
  let settled = false

  const teardown = (err) => {
    if (settled) return
    settled = true
    // A premature close is the expected shape of "reader hung up" — it is
    // noise, not a fault. Anything else is worth a line in the log.
    if (err && err.code !== 'PREMATURE_CLOSE') {
      console.error('[web] stream failed' + (label ? ' for ' + label : '') + ': ' + (err.message || err))
    }
    try { stream.destroy() } catch {}
    try { res.destroy() } catch {}
  }

  stream.on('error', teardown)
  res.on('error', teardown)
  // Normal completion also closes `res`; destroying an already-ended stream is
  // a no-op, so this doubles as the "client vanished mid-body" cleanup without
  // needing to tell the two cases apart.
  res.on('close', () => {
    if (settled) return
    settled = true
    try { stream.destroy() } catch {}
  })

  stream.pipe(res)
  return true
}