diff --git a/src/main/daemon/daemon-entry.ts b/src/main/daemon/daemon-entry.ts index 72537f64cbf..164885e8251 100644 --- a/src/main/daemon/daemon-entry.ts +++ b/src/main/daemon/daemon-entry.ts @@ -274,14 +274,18 @@ async function main(): Promise { ...(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, diff --git a/src/main/daemon/daemon-pid-publish-atomicity.test.ts b/src/main/daemon/daemon-pid-publish-atomicity.test.ts index 774ffcc9d13..086f5432ed2 100644 --- a/src/main/daemon/daemon-pid-publish-atomicity.test.ts +++ b/src/main/daemon/daemon-pid-publish-atomicity.test.ts @@ -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 }[] = [] + 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' }) diff --git a/src/main/daemon/daemon-spawner.ts b/src/main/daemon/daemon-spawner.ts index 644122a8902..4892e758b38 100644 --- a/src/main/daemon/daemon-spawner.ts +++ b/src/main/daemon/daemon-spawner.ts @@ -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 +): 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'