Skip to content

Commit f55d235

Browse files
committed
fix: empty the daemon log in place when registration publishes
The daemon's stdout and stderr hold append descriptors on daemon.log, so an atomic rename gave the log a new inode and later output went to the unlinked file. Publication now truncates the existing inode. The held-lock provider shutdown test now loses to a real registration instead of a removed helper.
1 parent 1f3cf0e commit f55d235

3 files changed

Lines changed: 36 additions & 4 deletions

File tree

‎src/__tests__/daemon-registration-owner.test.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,20 @@ test('publication is atomic, binds identity and paths, and excludes a second acq
5454
assert.deepEqual(readDaemonShutdownReport(paths.baseDir), report);
5555
});
5656

57+
test('publication truncates the log the daemon is already appending to', async () => {
58+
const { paths, owner } = await acquire();
59+
const daemonOutput = fs.openSync(paths.logPath, 'a');
60+
try {
61+
fs.writeSync(daemonOutput, 'previous run\n');
62+
owner.publish(fields);
63+
fs.writeSync(daemonOutput, 'listening\n');
64+
} finally {
65+
fs.closeSync(daemonOutput);
66+
}
67+
assert.equal(fs.readFileSync(paths.logPath, 'utf8'), 'listening\n');
68+
await owner.finish(report);
69+
});
70+
5771
for (const [pid, startTime, reason] of [
5872
[999_999_999, 'successor-start', 'replaced'],
5973
[process.pid, 'recycled-start', 'replaced'],

‎src/daemon-registration-owner.ts‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export async function tryAcquireDaemonRegistration(
6464
owner: Object.freeze({
6565
publish({ socketPort, httpPort, ...fields }: DaemonRegistrationFields) {
6666
acquisition.assertHeld();
67-
publishFileSync({ destination: boundPaths.logPath, contents: '', mode: 0o600 });
67+
truncateDaemonLog(boundPaths.logPath);
6868
const transport = socketPort && httpPort ? 'dual' : httpPort ? 'http' : 'socket';
6969
acquisition.assertHeld();
7070
publishFileSync({
@@ -153,3 +153,13 @@ function writeShutdownReport(
153153
return;
154154
}
155155
}
156+
157+
/** The daemon's stdout and stderr append to this inode, so it is emptied in place, never replaced. */
158+
function truncateDaemonLog(logPath: string): void {
159+
const descriptor = fs.openSync(logPath, 'a', 0o600);
160+
try {
161+
fs.ftruncateSync(descriptor, 0);
162+
} finally {
163+
fs.closeSync(descriptor);
164+
}
165+
}

‎src/daemon/server/daemon-runtime-interactor-composition.test.ts‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ import { setActiveProviderDeviceRuntimes } from '../../provider-device-runtime.t
66
import { IOS_SIMULATOR } from '../../__tests__/test-utils/device-fixtures.ts';
77
import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts';
88
import { createDaemonProviderRuntimeComposition } from '../../provider-device-runtimes.ts';
9+
import {
10+
DAEMON_STARTUP_EXIT_CODES,
11+
tryAcquireDaemonRegistration,
12+
} from '../../daemon-registration-owner.ts';
13+
import { resolveDaemonPaths } from '../../daemon-resolution.ts';
914
import { interactorResolution } from '../interactor-resolution.ts';
1015

1116
vi.mock('../../platform-runtime.ts', () => ({
@@ -85,7 +90,9 @@ test('daemon startup composes the interactor resolution the daemon resolves thro
8590
});
8691

8792
test('a daemon attempt losing the lock shuts down every constructed provider', async () => {
88-
vi.spyOn(await import('./server-lifecycle.ts'), 'acquireDaemonLock').mockReturnValueOnce(false);
93+
const stateDir = mkdtempForTestSync('daemon-held-lock-');
94+
const held = await tryAcquireDaemonRegistration(resolveDaemonPaths(stateDir));
95+
if (held.status !== 'acquired') throw new Error('registration fixture refused');
8996
const shutdown = vi.fn(() => {
9097
throw new Error('cleanup failed');
9198
});
@@ -103,7 +110,7 @@ test('a daemon attempt losing the lock shuts down every constructed provider', a
103110
});
104111
const exit = vi.fn();
105112
const runtime = await startDaemonRuntime({
106-
env: { AGENT_DEVICE_STATE_DIR: mkdtempForTestSync('daemon-held-lock-') },
113+
env: { AGENT_DEVICE_STATE_DIR: stateDir },
107114
exit,
108115
registerProcessHandlers: false,
109116
stderr: { write: () => {} },
@@ -112,5 +119,6 @@ test('a daemon attempt losing the lock shuts down every constructed provider', a
112119
expect(runtime).toBeNull();
113120
expect(shutdown).toHaveBeenCalledOnce();
114121
expect(otherShutdown).toHaveBeenCalledOnce();
115-
expect(exit).toHaveBeenCalledWith(0);
122+
expect(exit).toHaveBeenCalledWith(DAEMON_STARTUP_EXIT_CODES.busy);
123+
await held.owner.finish();
116124
});

0 commit comments

Comments
 (0)