mirror of
https://github.com/stablyai/orca.git
synced 2026-10-23 16:03:49 +00:00
fix(daemon): send the pid-publish degrade to the log the detached daemon keeps
The degrade warning exists so an operator on a volume without hard links can tell torn-write protection is off, but publishDaemonPidFile runs inside the detached daemon: daemon-init spawns it with stdio 'ignore' and destroys the startup stderr pipe once it reports ready, so console alone reaches nobody on exactly the path the warning was written for -- the constraint daemon-file-log states in its header comment. Take an optional log and emit both, matching daemon-endpoint-lifecycle's handling of the sibling endpoint degrade three lines from this call site.
This commit is contained in:
@@ -274,14 +274,18 @@ async function main(): Promise<void> {
|
||||
...(pidPath && launchNonce
|
||||
? {
|
||||
publishEndpointOwnership: () =>
|
||||
publishDaemonPidFile(pidPath, {
|
||||
pid: process.pid,
|
||||
...readyIdentity,
|
||||
...(entryPath ? { entryPath } : {}),
|
||||
...(appVersion ? { appVersion } : {}),
|
||||
...(spawnerExecPath ? { spawnerExecPath } : {}),
|
||||
launchNonce
|
||||
})
|
||||
publishDaemonPidFile(
|
||||
pidPath,
|
||||
{
|
||||
pid: process.pid,
|
||||
...readyIdentity,
|
||||
...(entryPath ? { entryPath } : {}),
|
||||
...(appVersion ? { appVersion } : {}),
|
||||
...(spawnerExecPath ? { spawnerExecPath } : {}),
|
||||
launchNonce
|
||||
},
|
||||
daemonLog
|
||||
)
|
||||
}
|
||||
: {}),
|
||||
log: daemonLog,
|
||||
|
||||
@@ -149,6 +149,27 @@ describe('publishDaemonPidFile atomicity', () => {
|
||||
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('non-atomic'))
|
||||
})
|
||||
|
||||
it('records the degrade on the daemon file log, which is the only channel the detached daemon keeps', () => {
|
||||
// The daemon runs with stdio 'ignore' and its startup stderr pipe is destroyed once
|
||||
// it is up, so a console-only warning never reaches the operator it is written for.
|
||||
const entries: { event: string; details?: Record<string, unknown> }[] = []
|
||||
fsFaults.linkError = Object.assign(new Error('injected ENOTSUP'), { code: 'ENOTSUP' })
|
||||
|
||||
publishDaemonPidFile(pidPath, RECORD, {
|
||||
log: (event, details) => entries.push({ event, ...(details ? { details } : {}) })
|
||||
})
|
||||
|
||||
expect(entries).toEqual([{ event: 'pid-publish-degraded', details: { errno: 'ENOTSUP' } }])
|
||||
})
|
||||
|
||||
it('leaves the daemon file log untouched when the atomic path succeeds', () => {
|
||||
const entries: string[] = []
|
||||
|
||||
publishDaemonPidFile(pidPath, RECORD, { log: (event) => entries.push(event) })
|
||||
|
||||
expect(entries).toEqual([])
|
||||
})
|
||||
|
||||
it('keeps exclusive-create semantics on the degraded path too', () => {
|
||||
publishDaemonPidFile(pidPath, { pid: 1, startedAtMs: 5, launchNonce: 'other' })
|
||||
fsFaults.linkError = Object.assign(new Error('injected ENOTSUP'), { code: 'ENOTSUP' })
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { fsyncFileSync } from '../../shared/secure-file'
|
||||
import type { DaemonFileLog } from './daemon-file-log'
|
||||
import { PROTOCOL_VERSION } from './types'
|
||||
import { reapOrphanedDaemonPidPublishClaims } from './daemon-pid-publish-claim-reap'
|
||||
import { DaemonCrashLoopError, DaemonRespawnThrottle } from './daemon-respawn-throttle'
|
||||
@@ -176,7 +177,11 @@ export function getDaemonPidPublishClaimPath(pidPath: string): string {
|
||||
return `${pidPath}.publish-${process.pid}-${randomUUID()}`
|
||||
}
|
||||
|
||||
export function publishDaemonPidFile(pidPath: string, pidFile: DaemonPidFile): void {
|
||||
export function publishDaemonPidFile(
|
||||
pidPath: string,
|
||||
pidFile: DaemonPidFile,
|
||||
log?: Pick<DaemonFileLog, 'log'>
|
||||
): void {
|
||||
const claimPath = getDaemonPidPublishClaimPath(pidPath)
|
||||
try {
|
||||
writeFileSync(claimPath, serializeDaemonPidFile(pidFile), { mode: 0o600, flag: 'wx' })
|
||||
@@ -199,6 +204,10 @@ export function publishDaemonPidFile(pidPath: string, pidFile: DaemonPidFile): v
|
||||
// instead of trading a torn-write window for no pid record at all. Warn because the
|
||||
// degrade is otherwise invisible: an operator on such a volume (network mount,
|
||||
// container overlay) should be able to tell the atomic path is not in use.
|
||||
// Both channels, matching daemon-endpoint-lifecycle.ts: inside the detached
|
||||
// daemon stdio is 'ignore' and its startup stderr pipe is destroyed once it is
|
||||
// up, so console alone reaches nobody on the path this warning exists for.
|
||||
log?.log('pid-publish-degraded', { errno: describeErrnoCode(error) })
|
||||
console.warn(
|
||||
`[daemon] pid-record hard-link publish failed (${describeErrnoCode(error)}); ` +
|
||||
'degrading to the non-atomic exclusive write — torn-write protection is off for this record'
|
||||
|
||||
Reference in New Issue
Block a user