engine: crash safe local state, the journal, and the branch lock
Adds the three packages the "nothing outlives its environment" guarantee rests on. internal/state is SQLite in write ahead logging mode under .antifailure, opened with a pure Go driver so the shipped binary needs no C toolchain and keeps CGO_ENABLED=0. It holds references, never secrets and never customer data. A database that fails its integrity check is moved aside and rebuilt rather than refused, because refusing to start would leave a user holding resources they can no longer tear down, which is the worst outcome for a tool whose whole promise is teardown. internal/journal records every external resource as an intent before it is created and compensates it on teardown. Two choices make replay survive reality: the compensating action is stored as data, never as code, so a binary built six months later can tear down what this one made; and every create uses a deterministic idempotency key, so a provider call that times out after the resource was created finds the existing intent instead of orphaning it. Replay never stops at the first failure. A provider being unreachable must not strand the other twelve resources, so each record is attempted, failures are recorded with an attempt count for the next teardown, and the result says what is left. Records for a provider this binary has no deleter for are skipped rather than dropped, so a downgrade cannot orphan anything. internal/lock is the per branch advisory lock. The interesting half is telling an active process from a corpse holding the door: the lock file carries the owner's process identifier, host, and start time, and a stale lock is reclaimed by liveness check rather than by timeout. A lock written on another host is treated as live, because wrongly waiting costs a wait and wrongly proceeding costs two processes fighting over one environment. One real defect, found by a test rather than by reading: state.Close set the handle to nil, so every journal call after it panicked. Those calls are exactly the ones that race Close during teardown, so the panic would have crashed the process while it was deleting resources. Close now keeps the handle and the driver returns an error instead. Failure paths covered: crash at any point in the create sequence: TestReplay_ConvergesFromACrashAtEveryStep any interleaving of intent, create, commit, crash: TestReplay_AnyInterleavingConvergesToZeroLiveResources (property) a retry after a timeout duplicates a resource: TestIntent_SameKeyReturnsTheExistingRecord concurrent intents for one key: TestIntent_ConcurrentSameKeyYieldsOneRecord a provider is unreachable during teardown: TestReplay_ContinuesPastAFailureAndReportsWhatIsLeft a downgrade orphans resources: TestReplay_LeavesRecordsWithNoDeleterAlone teardown is cancelled midway: TestReplay_StopsOnACancelledContextAndLeavesTheRest the state database is closed mid teardown: TestJournal_EveryMethodReportsAClosedDatabase the state database is corrupt: TestOpen_RebuildsACorruptDatabaseAndKeepsTheOriginal a stale lock blocks forever: TestAcquire_ReclaimsALockHeldByADeadProcess a stale holder deletes a newer lock: TestRelease_DoesNotRemoveALockANewerProcessHolds Coverage: journal 94.9 percent, state 83.3 percent, lock 77.5 percent. Signed-off-by: Vir Sanghavi <Virrsanghavi@gmail.com>
V
Vir Sanghavi committed
86a59e3d1a630b5a077bfb600981d0b79e0cc32b
Parent: 7c292fc