fix(cli): harden election edge cases

This commit is contained in:
Kit Langton 2026-07-07 22:11:14 -04:00
parent 68c62774ac
commit c61e93a1f3
2 changed files with 12 additions and 7 deletions

View file

@ -29,7 +29,7 @@ type ManagedServiceOptions = Service.Options & { readonly file: string }
export const run = Effect.fn("cli.server-process.run")((options: Options) =>
processEffect(options).pipe(
Effect.catchTag("ServiceAlreadyOwned", () => Effect.void),
Effect.catchTag("ServiceAlreadyOwned", () => Effect.logInfo("another process owns the managed service, exiting")),
Effect.provide(Updater.layer),
Effect.provide(AppNodeBuilder.build(LayerNode.group([Global.node, AppProcess.node, EffectFlock.node]))),
Effect.provide(NodeServices.layer),
@ -55,7 +55,11 @@ const processEffect = Effect.fnUntraced(function* (options: Options) {
(acquired) => acquired,
() => ({ _tag: "ServiceAlreadyOwned" }) as const,
),
Effect.retry(Schedule.spaced("100 millis").pipe(Schedule.both(Schedule.recurs(20)))),
// Retry only lost elections: a displaced owner may take a moment to release.
Effect.retry({
while: (error) => error._tag === "ServiceAlreadyOwned",
schedule: Schedule.spaced("100 millis").pipe(Schedule.both(Schedule.recurs(20))),
}),
)
}
const password =

View file

@ -164,11 +164,12 @@ export namespace EffectFlock {
process.kill(owner.pid, 0)
return true
},
catch: (cause) => {
const code = cause && typeof cause === "object" && "code" in cause ? cause.code : undefined
return code !== "ESRCH"
},
}).pipe(Effect.orElseSucceed(() => false))
catch: (cause) => (cause && typeof cause === "object" && "code" in cause ? cause.code : undefined),
}).pipe(
// Only ESRCH proves the pid is gone; other errors (e.g. EPERM) mean a
// process exists, so never break a possibly live owner's lock.
Effect.catch((code) => Effect.succeed(code !== "ESRCH")),
)
if (!alive) return true
}