OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / lib / sidecar-wait.js

56 lines · javascript ref:
/**
 * Wait for the pgh-sidecar UDS to appear before a daemon registers with
 * it / starts OTA.
 *
 * On `pgh app boot` the daemon can start before the sidecar's socket is
 * created. A single existsSync at daemon boot loses that race
 * permanently: the daemon stays unregistered until a manual restart, and
 * `app-lookup` (used by every pgh daemon command — clone, repo, issue…)
 * returns not-found. Polling for the socket closes the race without
 * blocking the already-serving daemon.
 */

import fs from '#node:fs'
import path from '#node:path'
import { homedir } from '#node:os'
import process from '#node:process'

export function sidecarSockPath () {
  return process.env.PGH_SIDECAR_SOCK || path.join(homedir(), '.pgh', 'svc.sock')
}

// Resolves true once the socket exists, false if the deadline passes or
// shouldStop() returns true (e.g. the daemon began shutting down). All
// of sock/exists/clock/sleep are injectable so the race can be tested
// without real filesystem timing.
export async function waitForSidecarSock (opts = {}) {
  const sock = opts.sock || sidecarSockPath()
  const timeoutMs = opts.timeoutMs == null ? 120000 : opts.timeoutMs
  const intervalMs = opts.intervalMs == null ? 1000 : opts.intervalMs
  const exists = opts.exists || fs.existsSync
  const shouldStop = opts.shouldStop || (() => false)
  const now = opts.now || (() => Date.now())
  const sleep = opts.sleep || ((ms) => new Promise((r) => setTimeout(r, ms)))

  const deadline = now() + timeoutMs
  while (!exists(sock)) {
    if (shouldStop()) return false
    if (now() >= deadline) return false
    await sleep(intervalMs)
  }
  return true
}

// Decide whether the daemon must re-register with the sidecar, given the
// sidecar's current app-lookup result for our name. The sidecar holds
// its app registry in memory, so a sidecar restart silently drops our
// entry (found:false) — every pgh daemon command then fails until we
// re-register. We also re-register if the recorded pid isn't ours (a
// stale entry from a previous daemon instance). app-register is
// last-writer-wins, so re-registering is always safe.
export function needsReregister (entry, selfPid) {
  if (!entry || !entry.found) return true
  if (selfPid != null && entry.pid != null && entry.pid !== selfPid) return true
  return false
}