Fix bug which caused startup to hang if the clock was wound back since a previous session (#29558)

* SessionLock: reduce the stale time

30 seconds staring at a spinner while we wait for a stale lock to expire is
rather painful. Pretty sure 15 seconds will be fine.

* SessionLock: deal with the clock having been wound back

If a previous session terminated uncleanly, and then the clock is wound back,
we could be waiting a very long time for the previous session's claim to
expire.

We can fix this by simply treating a future claim the same as "now", and
waiting for the normal stale timeout.

* fixup! SessionLock: deal with the clock having been wound back
This commit is contained in:
Richard van der Hoff
2025-03-24 16:43:53 +00:00
committed by GitHub
parent a6e8d512d0
commit f3653abe92
2 changed files with 53 additions and 6 deletions

View File

@@ -71,15 +71,15 @@ describe("SessionLock", () => {
jest.advanceTimersByTime(5000);
expect(checkSessionLockFree()).toBe(false);
// second instance tries to start. This should block for 25 more seconds
// second instance tries to start. This should block for 10 more seconds
const onNewInstance2 = jest.fn();
let session2Result: boolean | undefined;
getSessionLock(onNewInstance2).then((res) => {
session2Result = res;
});
// after another 24.5 seconds, we are still waiting
jest.advanceTimersByTime(24500);
// after another 9.5 seconds, we are still waiting
jest.advanceTimersByTime(9500);
expect(session2Result).toBe(undefined);
expect(checkSessionLockFree()).toBe(false);
@@ -92,6 +92,40 @@ describe("SessionLock", () => {
expect(onNewInstance2).not.toHaveBeenCalled();
});
it("A second instance starts up when the first terminated uncleanly and the clock was wound back", async () => {
// first instance starts...
expect(await getSessionLock(() => Promise.resolve())).toBe(true);
expect(checkSessionLockFree()).toBe(false);
// oops, now it dies. We simulate this by forcibly clearing the timers.
const time = Date.now();
jest.clearAllTimers();
expect(checkSessionLockFree()).toBe(false);
// Now, the clock gets wound back an hour.
jest.setSystemTime(time - 3600 * 1000);
expect(checkSessionLockFree()).toBe(false);
// second instance tries to start. This should block for 15 seconds
const onNewInstance2 = jest.fn();
let session2Result: boolean | undefined;
getSessionLock(onNewInstance2).then((res) => {
session2Result = res;
});
// after another 14.5 seconds, we are still waiting
jest.advanceTimersByTime(14500);
expect(session2Result).toBe(undefined);
expect(checkSessionLockFree()).toBe(false);
// another 500ms and we get the lock
await jest.advanceTimersByTimeAsync(500);
expect(session2Result).toBe(true);
expect(checkSessionLockFree()).toBe(false); // still false, because the new session has claimed it
expect(onNewInstance2).not.toHaveBeenCalled();
});
it("A second instance waits for the first to shut down", async () => {
// first instance starts. Once it gets the shutdown signal, it will wait two seconds and then release the lock.
await getSessionLock(