Skip to content

Commit 3d50dfd

Browse files
fix(dev): treat lock file as stale when PID matches current process (#17744) (#17754)
In Docker containers, PID namespaces reset on restart, so the new `astro dev` process often inherits the same PID the old one had. The lock file from the previous run persists, and the process detects itself as the "already running" server. Add a self-PID guard to `isLockFileProcessAlive()`: if the lock file's PID matches `process.pid`, treat it as stale immediately. The current process cannot be the server recorded in the lock file because it hasn't started one yet. Co-authored-by: factory[bot] <factory[bot]@users.noreply.github.com>
1 parent c7811b8 commit 3d50dfd

4 files changed

Lines changed: 53 additions & 5 deletions

File tree

.changeset/wicked-zebras-sip.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Fixes the dev server refusing to start in Docker containers after a restart due to PID reuse in the lock file check

packages/astro/src/core/dev/lockfile.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,14 @@ export async function isLockFileProcessAlive(
124124
data: LockFileData,
125125
find: ProcessLookup = findProcess,
126126
): Promise<boolean> {
127+
// The current process cannot be the server recorded in the lock file — it hasn't
128+
// started one yet. In Docker containers the PID namespace resets on restart, so the
129+
// new `astro dev` process often inherits the same PID the old one had. Without this
130+
// guard, the process detects itself as the "already running" server. (#17744)
131+
if (data.pid === process.pid) {
132+
return false;
133+
}
134+
127135
if (!isProcessAlive(data.pid)) {
128136
return false;
129137
}

packages/astro/test/units/dev/lockfile.test.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,22 @@ describe('isAstroCommand', () => {
220220
});
221221

222222
describe('isLockFileProcessAlive', () => {
223-
it('returns true when the recorded process command is Astro', async () => {
223+
/** A long-lived child process whose PID is alive but is not the current process. */
224+
let child: ReturnType<typeof spawn>;
225+
let childPid: number;
226+
227+
before(() => {
228+
child = spawn(process.execPath, ['-e', 'setInterval(() => {}, 60_000)'], {
229+
stdio: 'ignore',
230+
});
231+
childPid = child.pid!;
232+
});
233+
234+
after(() => {
235+
child.kill('SIGKILL');
236+
});
237+
238+
it('returns false when the lock file PID matches the current process', async () => {
224239
const data = { ...validData, pid: process.pid };
225240
const findProcess = async () => [
226241
{
@@ -231,15 +246,29 @@ describe('isLockFileProcessAlive', () => {
231246
},
232247
];
233248

249+
assert.equal(await isLockFileProcessAlive(data, findProcess), false);
250+
});
251+
252+
it('returns true when the recorded process command is Astro', async () => {
253+
const data = { ...validData, pid: childPid };
254+
const findProcess = async () => [
255+
{
256+
pid: childPid,
257+
ppid: process.pid,
258+
name: 'node',
259+
cmd: 'node /workspace/node_modules/astro/bin/astro.mjs dev',
260+
},
261+
];
262+
234263
assert.equal(await isLockFileProcessAlive(data, findProcess), true);
235264
});
236265

237266
it('returns false when the PID belongs to another command', async () => {
238-
const data = { ...validData, pid: process.pid };
267+
const data = { ...validData, pid: childPid };
239268
const findProcess = async () => [
240269
{
241-
pid: process.pid,
242-
ppid: process.ppid,
270+
pid: childPid,
271+
ppid: process.pid,
243272
name: 'node',
244273
cmd: 'node /app/server.mjs',
245274
},
@@ -249,7 +278,7 @@ describe('isLockFileProcessAlive', () => {
249278
});
250279

251280
it('keeps the PID-only result when the command cannot be inspected', async () => {
252-
const data = { ...validData, pid: process.pid };
281+
const data = { ...validData, pid: childPid };
253282

254283
assert.equal(await isLockFileProcessAlive(data, async () => []), true);
255284
assert.equal(

pnpm-lock.yaml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)