Skip to content

Commit c651c63

Browse files
committed
fix: acquire daemon heartbeat atomically
1 parent 52ad495 commit c651c63

2 files changed

Lines changed: 64 additions & 11 deletions

File tree

src/daemon.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -495,18 +495,16 @@ function stop() {
495495
function start() {
496496
store.ensureDirs();
497497

498-
if (store.isAlive()) {
499-
console.log('AgentLoop daemon is already running.');
500-
process.exitCode = 1;
501-
return;
502-
}
503-
504498
daemonInfo = {
505499
pid: process.pid,
506500
port: store.config.dashboardPort,
507501
startedAt: new Date().toISOString(),
508502
};
509-
store.writeHeartbeat(daemonInfo);
503+
504+
if (!store.acquireHeartbeat(daemonInfo)) {
505+
console.log('AgentLoop daemon is already running.');
506+
return;
507+
}
510508

511509
server = http.createServer((req, res) => {
512510
handleRequest(req, res).catch((error) => {

src/store.js

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,11 +169,66 @@ function appendEvent(type, dataObj = {}) {
169169
return event;
170170
}
171171

172-
function writeHeartbeat(heartbeat) {
173-
const value = {
172+
function heartbeatValue(heartbeat) {
173+
return {
174174
...heartbeat,
175175
ts: new Date().toISOString(),
176176
};
177+
}
178+
179+
function writeExclusiveHeartbeat(value) {
180+
let fd;
181+
182+
try {
183+
fd = fs.openSync(paths.daemon, 'wx');
184+
fs.writeFileSync(fd, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
185+
} finally {
186+
if (fd !== undefined) {
187+
fs.closeSync(fd);
188+
}
189+
}
190+
}
191+
192+
function acquireHeartbeat(heartbeat) {
193+
ensureDirs();
194+
const value = heartbeatValue(heartbeat);
195+
196+
try {
197+
writeExclusiveHeartbeat(value);
198+
return true;
199+
} catch (error) {
200+
if (error.code !== 'EEXIST') {
201+
throw error;
202+
}
203+
}
204+
205+
const existing = readHeartbeat();
206+
207+
if (isAlive(existing)) {
208+
return false;
209+
}
210+
211+
try {
212+
fs.unlinkSync(paths.daemon);
213+
} catch (error) {
214+
if (error.code !== 'ENOENT') {
215+
throw error;
216+
}
217+
}
218+
219+
try {
220+
writeExclusiveHeartbeat(value);
221+
return true;
222+
} catch (error) {
223+
if (error.code === 'EEXIST') {
224+
return false;
225+
}
226+
throw error;
227+
}
228+
}
229+
230+
function writeHeartbeat(heartbeat) {
231+
const value = heartbeatValue(heartbeat);
177232

178233
ensureDirs();
179234
writeJsonAtomic(paths.daemon, value);
@@ -188,8 +243,7 @@ function readHeartbeat() {
188243
}
189244
}
190245

191-
function isAlive() {
192-
const heartbeat = readHeartbeat();
246+
function isAlive(heartbeat = readHeartbeat()) {
193247
const timestamp = heartbeat && Date.parse(heartbeat.ts);
194248
const pid = heartbeat && Number(heartbeat.pid);
195249

@@ -220,6 +274,7 @@ module.exports = {
220274
listTasks,
221275
writeResult,
222276
appendEvent,
277+
acquireHeartbeat,
223278
writeHeartbeat,
224279
readHeartbeat,
225280
isAlive,

0 commit comments

Comments
 (0)