




















@@ -3882,6 +3882,97 @@ describe("embedded attempt session lock lifecycle", () => {
38823882expect(releases).toEqual(["release", "release", "release"]);
38833883});
388438843885+it("releases a retained post-prompt lock after takeover so the next inbound can acquire it", async () => {
3886+const sessionFile = await createTempSessionFile();
3887+const options = { ...lockOptions, sessionFile, timeoutMs: 250 };
3888+const controller = await createEmbeddedAttemptSessionLockController({
3889+ acquireSessionWriteLock,
3890+lockOptions: options,
3891+});
3892+3893+await controller.releaseForPrompt();
3894+await controller.reacquireAfterPrompt();
3895+await fs.appendFile(sessionFile, '{"type":"message","id":"external"}\n', "utf8");
3896+3897+await expect(
3898+controller.withSessionWriteLock(async () => {
3899+await fs.appendFile(sessionFile, '{"type":"message","id":"late"}\n', "utf8");
3900+}),
3901+).rejects.toBeInstanceOf(EmbeddedAttemptSessionTakeoverError);
3902+expect(controller.hasSessionTakeover()).toBe(true);
3903+3904+const nextController = await createEmbeddedAttemptSessionLockController({
3905+ acquireSessionWriteLock,
3906+lockOptions: options,
3907+});
3908+await nextController.withSessionWriteLock(async () => {
3909+await fs.appendFile(sessionFile, '{"type":"message","id":"next-inbound"}\n', "utf8");
3910+});
3911+await nextController.dispose();
3912+await controller.dispose();
3913+3914+const transcript = await fs.readFile(sessionFile, "utf8");
3915+expect(transcript).toContain('"id":"next-inbound"');
3916+expect(transcript).not.toContain('"id":"late"');
3917+});
3918+3919+it("waits for active retained writers before releasing a takeover lock", async () => {
3920+const sessionFile = await createTempSessionFile();
3921+const events: string[] = [];
3922+let releaseActiveWrite!: () => void;
3923+const activeWriteStarted = new Promise<void>((resolve) => {
3924+releaseActiveWrite = () => {
3925+events.push("active-finish");
3926+resolve();
3927+};
3928+});
3929+const releasePrep = vi.fn(async () => events.push("prep-release"));
3930+const releaseRetained = vi.fn(async () => events.push("retained-release"));
3931+const acquireSessionWriteLockLocal = vi
3932+.fn()
3933+.mockResolvedValueOnce({ release: releasePrep })
3934+.mockResolvedValueOnce({ release: releaseRetained });
3935+const controller = await createEmbeddedAttemptSessionLockController({
3936+acquireSessionWriteLock: acquireSessionWriteLockLocal,
3937+lockOptions: { ...lockOptions, sessionFile },
3938+});
3939+3940+await controller.releaseForPrompt();
3941+await controller.reacquireAfterPrompt();
3942+const activeWrite = controller.withSessionWriteLock(async () => {
3943+events.push("active-start");
3944+await activeWriteStarted;
3945+});
3946+await new Promise<void>((resolve) => {
3947+setImmediate(resolve);
3948+});
3949+await fs.appendFile(sessionFile, '{"type":"message","id":"external"}\n', "utf8");
3950+3951+let takeoverSettled = false;
3952+const takeoverWrite = controller
3953+.withSessionWriteLock(async () => {
3954+events.push("late-write");
3955+})
3956+.catch((error: unknown) => error)
3957+.finally(() => {
3958+takeoverSettled = true;
3959+});
3960+await new Promise<void>((resolve) => {
3961+setImmediate(resolve);
3962+});
3963+3964+expect(takeoverSettled).toBe(false);
3965+expect(releaseRetained).not.toHaveBeenCalled();
3966+3967+releaseActiveWrite();
3968+await expect(activeWrite).resolves.toBeUndefined();
3969+await expect(takeoverWrite).resolves.toBeInstanceOf(EmbeddedAttemptSessionTakeoverError);
3970+3971+expect(releaseRetained).toHaveBeenCalledTimes(1);
3972+expect(events).toEqual(["prep-release", "active-start", "active-finish", "retained-release"]);
3973+expect(acquireSessionWriteLockLocal).toHaveBeenCalledTimes(2);
3974+});
3975+38853976it("returns a no-op cleanup lock after prompt lock reacquisition times out", async () => {
38863977const releases: string[] = [];
38873978const acquireSessionWriteLockResult = vi
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。