Skip to content

Commit 73bd1b1

Browse files
committed
fix(agents): fence yield abort lock release
1 parent e9efdb7 commit 73bd1b1

2 files changed

Lines changed: 41 additions & 22 deletions

File tree

src/agents/pi-embedded-runner/run/attempt.session-lock.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,26 @@ describe("embedded attempt session lock lifecycle", () => {
128128
expect(events).toEqual(["prep-release", "yield-cleanup-write", "cleanup-release"]);
129129
});
130130

131+
it("keeps the session fence active after releasing for sessions_yield abort cleanup", async () => {
132+
const sessionFile = await createTempSessionFile();
133+
const release = vi.fn(async () => {});
134+
const acquireSessionWriteLock = vi.fn(async () => ({ release }));
135+
const controller = await createEmbeddedAttemptSessionLockController({
136+
acquireSessionWriteLock,
137+
lockOptions: { ...lockOptions, sessionFile },
138+
});
139+
140+
await controller.releaseHeldLockForAbort();
141+
await fs.appendFile(sessionFile, '{"type":"message","id":"abort-takeover"}\n', "utf8");
142+
143+
await expect(controller.withSessionWriteLock(() => "yield-cleanup")).rejects.toBeInstanceOf(
144+
EmbeddedAttemptSessionTakeoverError,
145+
);
146+
expect(controller.hasSessionTakeover()).toBe(true);
147+
expect(acquireSessionWriteLock).toHaveBeenCalledTimes(2);
148+
expect(release).toHaveBeenCalledTimes(2);
149+
});
150+
131151
it("runs post-prompt transcript writes under a short reacquired lock", async () => {
132152
const events: string[] = [];
133153
const acquireSessionWriteLock = vi

src/agents/pi-embedded-runner/run/attempt.session-lock.ts

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -749,32 +749,31 @@ export async function createEmbeddedAttemptSessionLockController(params: {
749749

750750
const noopLock: SessionLock = { release: async () => {} };
751751

752+
async function releaseHeldLockWithFence(): Promise<void> {
753+
if (!heldLock) {
754+
return;
755+
}
756+
const lock = heldLock;
757+
heldLock = undefined;
758+
const fingerprint = await readSessionFileFingerprint(params.lockOptions.sessionFile);
759+
const ownedWrite = ownedSessionFileWrites.get(sessionFileFenceKey);
760+
const trustedGeneration = trustSessionFileState(sessionFileFenceKey, fingerprint);
761+
fenceFingerprint = fingerprint;
762+
fenceSnapshot = await readSessionFileFenceSnapshot(params.lockOptions.sessionFile);
763+
fenceGeneration =
764+
ownedWrite && sameSessionFileFingerprint(ownedWrite.fingerprint, fingerprint)
765+
? ownedWrite.generation
766+
: (trustedGeneration ?? fenceGeneration);
767+
fenceActive = true;
768+
await lock.release();
769+
}
770+
752771
return {
753772
async releaseForPrompt(): Promise<void> {
754-
if (!heldLock) {
755-
return;
756-
}
757-
const lock = heldLock;
758-
heldLock = undefined;
759-
const fingerprint = await readSessionFileFingerprint(params.lockOptions.sessionFile);
760-
const ownedWrite = ownedSessionFileWrites.get(sessionFileFenceKey);
761-
const trustedGeneration = trustSessionFileState(sessionFileFenceKey, fingerprint);
762-
fenceFingerprint = fingerprint;
763-
fenceSnapshot = await readSessionFileFenceSnapshot(params.lockOptions.sessionFile);
764-
fenceGeneration =
765-
ownedWrite && sameSessionFileFingerprint(ownedWrite.fingerprint, fingerprint)
766-
? ownedWrite.generation
767-
: (trustedGeneration ?? fenceGeneration);
768-
fenceActive = true;
769-
await lock.release();
773+
await releaseHeldLockWithFence();
770774
},
771775
async releaseHeldLockForAbort(): Promise<void> {
772-
if (!heldLock) {
773-
return;
774-
}
775-
const lock = heldLock;
776-
heldLock = undefined;
777-
await lock.release();
776+
await releaseHeldLockWithFence();
778777
},
779778
refreshAfterOwnedSessionWrite(): void {
780779
if (fenceActive && !takeoverDetected) {

0 commit comments

Comments
 (0)