Skip to content

Commit a92720a

Browse files
fix(credentials): create lock file atomically via rename
Write PID and timestamp to a temp file and renameSync into place, matching writeCredentialsAtomic. Prevents a contender from reclaiming an empty/partial lock during the old open-then-write window. Only reclaim locks with complete PID+timestamp content; treat incomplete locks as in-flight acquisition.
1 parent f607eaf commit a92720a

1 file changed

Lines changed: 16 additions & 11 deletions

File tree

src/lib/credentials.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import {
22
chmodSync,
3-
closeSync,
43
existsSync,
54
mkdirSync,
6-
openSync,
75
readFileSync,
86
renameSync,
97
statSync,
@@ -222,15 +220,17 @@ function acquireCredentialsLock(credentialsPath: string): void {
222220
mkdirSync(dirname(lockPath), { recursive: true, mode: 0o700 });
223221
const deadline = Date.now() + CREDENTIALS_LOCK_MAX_WAIT_MS;
224222
while (Date.now() < deadline) {
223+
const tmp = `${lockPath}.tmp.${process.pid}.${Date.now()}`;
224+
writeFileSync(tmp, `${process.pid}\n${Date.now()}\n`, 'utf8');
225225
try {
226-
const fd = openSync(lockPath, 'wx');
227-
try {
228-
writeFileSync(fd, `${process.pid}\n${Date.now()}\n`, 'utf8');
229-
} finally {
230-
closeSync(fd);
231-
}
226+
renameSync(tmp, lockPath);
232227
return;
233228
} catch (err) {
229+
try {
230+
unlinkSync(tmp);
231+
} catch {
232+
// Best-effort cleanup of the unclaimed temp file.
233+
}
234234
const code = (err as NodeJS.ErrnoException).code;
235235
if (code !== 'EEXIST') throw err;
236236
if (isStaleCredentialsLock(lockPath)) {
@@ -257,11 +257,16 @@ function releaseCredentialsLock(credentialsPath: string): void {
257257

258258
function isStaleCredentialsLock(lockPath: string): boolean {
259259
try {
260+
const content = readFileSync(lockPath, 'utf8');
261+
const [pidLine = '', tsLine = ''] = content.split('\n');
262+
const pid = Number.parseInt(pidLine.trim(), 10);
263+
const ts = Number.parseInt(tsLine.trim(), 10);
264+
// Incomplete lock — another process may be creating it; never reclaim.
265+
if (!Number.isFinite(pid) || pid <= 0 || !Number.isFinite(ts)) return false;
266+
260267
const stat = statSync(lockPath);
261268
if (Date.now() - stat.mtimeMs > CREDENTIALS_LOCK_STALE_MS) return true;
262-
const firstLine = readFileSync(lockPath, 'utf8').split('\n')[0] ?? '';
263-
const pid = Number.parseInt(firstLine, 10);
264-
if (!Number.isFinite(pid) || pid <= 0) return true;
269+
if (Date.now() - ts > CREDENTIALS_LOCK_STALE_MS) return true;
265270
try {
266271
process.kill(pid, 0);
267272
return false;

0 commit comments

Comments
 (0)