Skip to content

Commit 76213c3

Browse files
committed
fix(logs): use userspace polling for -f follow mode
fs.watchFile (libuv uv_fs_poll_t) does not reliably emit change events for cross-process file appends on Windows, so `bitsocial logs -f` would silently miss new log lines there. Replace it with a self-rescheduling setTimeout that calls fs.promises.stat() + reads new bytes — the same pattern production tail-f libraries use precisely because OS file watchers aren't cross-platform reliable for this scenario. Closes #40
1 parent 6ab08d6 commit 76213c3

1 file changed

Lines changed: 19 additions & 13 deletions

File tree

src/cli/commands/logs.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,6 @@ export default class Logs extends Command {
254254
}
255255
}
256256

257-
// Switch watchers
258-
fs.unwatchFile(currentLogFile, readNewData);
259257
currentLogFile = newestFile;
260258
pendingBuffer = "";
261259

@@ -279,26 +277,34 @@ export default class Logs extends Command {
279277

280278
const newStat = await fsPromise.stat(currentLogFile);
281279
position = newStat.size;
282-
fs.watchFile(currentLogFile, { interval: 300 }, readNewData);
283280
} catch {
284281
// Directory listing failed or file disappeared — retry next cycle
285282
}
286283
};
287284

288-
fs.watchFile(currentLogFile, { interval: 300 }, readNewData);
285+
// Userspace polling instead of fs.watchFile — libuv's uv_fs_poll_t doesn't
286+
// reliably notify on cross-process appends on Windows (see nodejs/node#36888).
287+
let polling = true;
288+
let pollTimer: NodeJS.Timeout | null = null;
289+
const pollLoop = async () => {
290+
if (!polling) return;
291+
try {
292+
await readNewData();
293+
} finally {
294+
if (polling) pollTimer = setTimeout(pollLoop, 300);
295+
}
296+
};
297+
pollTimer = setTimeout(pollLoop, 300);
289298
const newFileCheckInterval = setInterval(checkForNewLogFile, 3000);
290299

291-
// Keep the process alive and clean up on exit
292-
process.on("SIGINT", () => {
293-
clearInterval(newFileCheckInterval);
294-
fs.unwatchFile(currentLogFile, readNewData);
295-
process.exit(0);
296-
});
297-
process.on("SIGTERM", () => {
300+
const shutdown = () => {
301+
polling = false;
302+
if (pollTimer) clearTimeout(pollTimer);
298303
clearInterval(newFileCheckInterval);
299-
fs.unwatchFile(currentLogFile, readNewData);
300304
process.exit(0);
301-
});
305+
};
306+
process.on("SIGINT", shutdown);
307+
process.on("SIGTERM", shutdown);
302308

303309
// Keep process alive
304310
await new Promise(() => {});

0 commit comments

Comments
 (0)