Skip to content

Commit 457aa1e

Browse files
committed
fix(agent): trim retained-lock comments
Signed-off-by: sallyom <[email protected]>
1 parent cc37eda commit 457aa1e

2 files changed

Lines changed: 8 additions & 81 deletions

File tree

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

Lines changed: 1 addition & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3993,11 +3993,6 @@ describe("embedded attempt session lock lifecycle", () => {
39933993
});
39943994

39953995
it("releaseHeldLockWithFence sets deferred flag when bailed out during active scope; re-attempted after scope deactivation (#95915)", async () => {
3996-
// When releaseHeldLockForAbort is called from inside an active write
3997-
// scope, releaseHeldLockWithFence bails out (waitForRetainedLockIdle
3998-
// returns false) and sets releaseHeldLockDeferred. After the write scope
3999-
// completes, runWithPhysicalWriteLockScope must re-attempt the release
4000-
// so the held lock does not leak until the watchdog fires.
40013996
const events: string[] = [];
40023997
const releasePrep = vi.fn(async () => events.push("prep-release"));
40033998
const releaseRetained = vi.fn(async () => events.push("retained-release"));
@@ -4011,36 +4006,20 @@ describe("embedded attempt session lock lifecycle", () => {
40114006
lockOptions,
40124007
});
40134008

4014-
// Construction acquired a lock (releasePrep). Release and reacquire
4015-
// to set up the held lock (releaseRetained) that write scopes share.
40164009
await controller.releaseForPrompt();
40174010
await controller.reacquireAfterPrompt();
40184011

4019-
// Call releaseHeldLockForAbort from inside the active write scope.
4020-
// releaseHeldLockWithFence detects the active scope, sets
4021-
// releaseHeldLockDeferred, and returns without releasing.
40224012
await controller.withSessionWriteLock(async () => {
40234013
events.push("write-start");
40244014
await controller.releaseHeldLockForAbort();
40254015
events.push("write-end");
40264016
});
40274017

4028-
// After the write scope completes, runWithPhysicalWriteLockScope
4029-
// sees releaseHeldLockDeferred and re-attempts the release. The held
4030-
// lock (releaseRetained) should now be released.
4031-
expect(events).toEqual([
4032-
"prep-release", // releaseForPrompt released the initial lock
4033-
"write-start",
4034-
"write-end",
4035-
"retained-release", // deferred releaseHeldLockWithFence at scope completion
4036-
]);
4018+
expect(events).toEqual(["prep-release", "write-start", "write-end", "retained-release"]);
40374019
expect(acquireSessionWriteLockLocal).toHaveBeenCalledTimes(2);
40384020
});
40394021

40404022
it("controls the held lock lifecycle across deferred abort release, reacquisition, and prompt release", async () => {
4041-
// Integration test: deferred abort release → reacquire → prompt release.
4042-
// Verifies that after a deferred releaseHeldLockWithFence, the lock
4043-
// state is clean and subsequent lock operations work correctly.
40444023
const events: string[] = [];
40454024
const acquireSessionWriteLockLocal = vi
40464025
.fn()
@@ -4053,35 +4032,24 @@ describe("embedded attempt session lock lifecycle", () => {
40534032
lockOptions,
40544033
});
40554034

4056-
// Release initial and reacquire to set up held lock.
40574035
await controller.releaseForPrompt();
40584036
await controller.reacquireAfterPrompt();
40594037

4060-
// Abort from inside active scope → deferred release.
40614038
await controller.withSessionWriteLock(async () => {
40624039
events.push("write");
40634040
await controller.releaseHeldLockForAbort();
40644041
});
40654042

40664043
expect(events).toEqual(["init-release", "write", "held-release"]);
40674044

4068-
// After deferred release, reacquire a fresh held lock.
40694045
await controller.reacquireAfterPrompt();
4070-
// Prompt release should release the fresh lock.
40714046
await controller.releaseForPrompt();
40724047

40734048
expect(events).toEqual(["init-release", "write", "held-release", "reacquire-release"]);
40744049
expect(acquireSessionWriteLockLocal).toHaveBeenCalledTimes(3);
40754050
});
40764051

40774052
it("takeHeldLockAfterRetainedIdle does not self-deadlock when called from inside active write scope (#95915)", async () => {
4078-
// When acquireForCleanup is called from within an active retained write
4079-
// scope, takeHeldLockAfterRetainedIdle must NOT await
4080-
// retainedLockIdleWaiters — the waiter resolves only when the
4081-
// retained use count reaches zero, which cannot happen while the
4082-
// current active scope is still alive. Instead it returns undefined
4083-
// (scope active, can't take lock). acquireCleanupLock falls through to
4084-
// acquireLock which fails with EEXIST → returns undefined → noopLock.
40854053
const events: string[] = [];
40864054
const acquireSessionWriteLockLocal = vi
40874055
.fn()
@@ -4100,35 +4068,20 @@ describe("embedded attempt session lock lifecycle", () => {
41004068
lockOptions,
41014069
});
41024070

4103-
// Release initial lock and reacquire to set up held lock.
41044071
await controller.releaseForPrompt();
41054072
await controller.reacquireAfterPrompt();
41064073

4107-
// Call acquireForCleanup from inside the active write scope.
4108-
// This must not deadlock. The fallback acquireLock rejects with
4109-
// SessionWriteLockTimeoutError (same-pid lock-timeout branch),
4110-
// acquireCleanupLock catches it via isSessionWriteLockAcquireError
4111-
// and returns undefined → noopLock. The scope then detects
4112-
// takeoverDetected and throws EmbeddedAttemptSessionTakeoverError.
41134074
const takeoverError = await controller
41144075
.withSessionWriteLock(async () => {
41154076
events.push("write-start");
4116-
// Calling acquireForCleanup inside the active scope exercises
4117-
// takeHeldLockAfterRetainedIdle's false branch. It must return
4118-
// undefined (not await retainedLockIdleWaiters).
41194077
const cleanupLock = await controller.acquireForCleanup();
4120-
// NoopLock — release is a safe no-op.
41214078
await cleanupLock.release();
41224079
events.push("cleanup-inside-done");
41234080
})
41244081
.catch((error: unknown) => error);
41254082

41264083
expect(takeoverError).toBeInstanceOf(EmbeddedAttemptSessionTakeoverError);
41274084

4128-
// After the scope throws with takeover, the held lock is still set
4129-
// (takeHeldLockAfterRetainedIdle inside the scope returned undefined
4130-
// and the deferred release path was not triggered). A real cleanup
4131-
// call outside the scope can take the held lock.
41324085
const cleanupLock = await controller.acquireForCleanup();
41334086
await cleanupLock.release();
41344087

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

Lines changed: 7 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,10 +1171,8 @@ export async function createEmbeddedAttemptSessionLockController(params: {
11711171
let fenceGeneration = 0;
11721172
let fenceActive = false;
11731173
let takeoverDetected = false;
1174-
// Tracks whether releaseHeldLockWithFence() bailed out because the active
1175-
// write-lock scope is still alive. When set, runWithPhysicalWriteLockScope
1176-
// re-attempts the release after the scope is deactivated so the held lock
1177-
// does not leak until the watchdog fires.
1174+
// Set when an active retained write prevents immediate held-lock release.
1175+
// The scope completion path retries release after the retained use unwinds.
11781176
let releaseHeldLockDeferred = false;
11791177
let retainedLockUseCount = 0;
11801178
const retainedLockIdleWaiters = new Set<() => void>();
@@ -1608,10 +1606,6 @@ export async function createEmbeddedAttemptSessionLockController(params: {
16081606
const drainOwner = await beginHeldLockDrain();
16091607
try {
16101608
if (!(await waitForRetainedLockIdle())) {
1611-
// A retained session write is in progress and the caller is inside
1612-
// the active write-lock scope. Defer the release: when the write
1613-
// scope completes, runWithPhysicalWriteLockScope re-attempts
1614-
// releaseHeldLockWithFence after scope deactivation.
16151609
releaseHeldLockDeferred = true;
16161610
return;
16171611
}
@@ -1649,16 +1643,8 @@ export async function createEmbeddedAttemptSessionLockController(params: {
16491643
const drainOwner = await beginHeldLockDrain();
16501644
try {
16511645
if (!(await waitForRetainedLockIdle())) {
1652-
// A retained session write is in progress and the caller is inside the
1653-
// active write-lock scope. Do NOT await retainedLockIdleWaiters here —
1654-
// that waiter resolves when the retained use count reaches zero, which
1655-
// cannot happen until the current active scope unwinds. Awaiting from
1656-
// inside that same scope would self-deadlock.
1657-
//
1658-
// Return undefined so acquireCleanupLock() falls through to
1659-
// acquireLock(). The held lock will be released by the scope-completion
1660-
// path in runWithPhysicalWriteLockScope, which re-attempts
1661-
// releaseHeldLockWithFence() when releaseHeldLockDeferred is set.
1646+
// Do not wait for retained idle from inside the active scope; that
1647+
// scope must unwind before the retained-use waiter can resolve.
16621648
return undefined;
16631649
}
16641650
if (!heldLock) {
@@ -1680,11 +1666,7 @@ export async function createEmbeddedAttemptSessionLockController(params: {
16801666
const drainOwner = await beginHeldLockDrain();
16811667
try {
16821668
if (!(await waitForRetainedLockIdle())) {
1683-
// A retained session write is in progress and the caller is inside the
1684-
// active write-lock scope. Do NOT await retainedLockIdleWaiters here —
1685-
// same self-deadlock risk described in takeHeldLockAfterRetainedIdle.
1686-
// The held lock will be released by the scope-completion path in
1687-
// runWithPhysicalWriteLockScope when releaseHeldLockDeferred is set.
1669+
// Same active-scope self-deadlock guard as takeHeldLockAfterRetainedIdle.
16881670
return;
16891671
}
16901672
if (!heldLock) {
@@ -1746,16 +1728,8 @@ export async function createEmbeddedAttemptSessionLockController(params: {
17461728
}
17471729
}
17481730
await releaseHeldLockAfterTakeover();
1749-
// If releaseHeldLockWithFence() bailed out earlier because the active
1750-
// write-lock scope was still alive, re-attempt the release now that the
1751-
// scope has been deactivated and the retained-use count has been released.
1752-
// At this point waitForRetainedLockIdle() will not self-wait because the
1753-
// current scope is no longer the active one.
1754-
//
1755-
// This fixes a lock-leak scenario where abort fires during a retained
1756-
// session write: releaseHeldLockWithFence bails out (scope active), leaving
1757-
// heldLock set. Without this re-attempt the held lock would only be released
1758-
// by the maxHoldMs watchdog (issue #95915).
1731+
// Retained use has been released and the active scope is no longer live,
1732+
// so a prior active-scope release bailout can drain the held file lock now.
17591733
if (releaseHeldLockDeferred) {
17601734
releaseHeldLockDeferred = false;
17611735
await releaseHeldLockWithFence();

0 commit comments

Comments
 (0)