Skip to content

tray: a live core with a saturated listen backlog can be launched over, producing doomed spawns and a misleading error #933

Description

@Dumbris

Found by cross-model review (round 6) of #927, which introduced the stale-socket escalation. Not a merge blocker for #927 — the single-writer invariant still holds — but the failure mode is user-visible and confusing.

What happens

onlyEverRefused is used as evidence that a socket file is stale (refusals > 0 and nothing ever accepted during the episode). That is sound for a crashed core, but a live core whose listen backlog is saturated before the episode begins refuses every probe too. Since it never accepts once, the evidence never records somethingAccepted, and after the 60s deadline reportUnresponsiveCore escalates to a launch (CoreProcessManager.swift:792). The escalation's own confirmation and the launch preflight also only ever see refusals, so neither vetoes it.

Consequence

The tray launches cores that are doomed: each new core blocks ~10s on bbolt's exclusive lock, fails, and the ladder retries. The attach watch has already been cancelled. The user eventually sees a retry-exhaustion / database-locked error about a core that is actually alive and healthy — just briefly saturated.

Data integrity is fine throughout: bbolt's exclusive flock is acquired before socket cleanup, so there is never a second writer.

Why the existing test does not cover it

CoreLivenessTests.swift:861 (testACoreThatAcceptedEarlierInTheWindowIsNeverEscalatedOver) models a core that accepts early and then refuses — but it stops the listener entirely after that acceptance, which makes the socket genuinely stale. The uncovered case is a listener that is live for the whole episode and refuses every probe from the start.

Possible directions

  • Treat a socket file whose inode/mtime is changing, or which a process still holds open, as evidence against staleness (lsof-equivalent via proc_pidfdinfo, or check the file is still bound).
  • Have the spawned core distinguish "database locked" from "failed to start" and, on lock contention, tell the tray to go back to attaching rather than climbing the retry ladder.
  • Cap escalation at one attempt per episode, so the worst case is one doomed spawn rather than a ladder.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions