






















@@ -26,13 +26,19 @@ function makeRuntime(
2626tools: Array<{ toolName: string; description: string }>,
2727serverName = "bundleProbe",
2828): SessionMcpRuntime {
29+const createdAt = Date.now();
30+let lastUsedAt = createdAt;
2931return {
3032sessionId: "session-colliding-tools",
3133workspaceDir: "/tmp",
3234configFingerprint: "fingerprint",
33-createdAt: 0,
34-lastUsedAt: 0,
35-markUsed: () => {},
35+ createdAt,
36+get lastUsedAt() {
37+return lastUsedAt;
38+},
39+markUsed: () => {
40+lastUsedAt = Date.now();
41+},
3642getCatalog: async () => ({
3743version: 1,
3844generatedAt: 0,
@@ -135,6 +141,27 @@ describe("session MCP runtime", () => {
135141]);
136142});
137143144+it("holds a runtime lease until the materialized tool runtime is disposed", async () => {
145+let activeLeases = 0;
146+const runtime = {
147+ ...makeRuntime([{ toolName: "bundle_probe", description: "Bundle MCP probe" }]),
148+acquireLease: () => {
149+activeLeases += 1;
150+return () => {
151+activeLeases -= 1;
152+};
153+},
154+};
155+156+const materialized = await materializeBundleMcpToolsForRun({ runtime });
157+expect(activeLeases).toBe(1);
158+159+await materialized.dispose();
160+await materialized.dispose();
161+162+expect(activeLeases).toBe(0);
163+});
164+138165it("reuses repeated materialization and recreates after explicit disposal", async () => {
139166const created: SessionMcpRuntime[] = [];
140167const disposed: string[] = [];
@@ -361,4 +388,94 @@ describe("session MCP runtime", () => {
361388retireSessionMcpRuntimeForSessionKey({ sessionKey: "agent:test:missing", reason: "test" }),
362389).resolves.toBe(false);
363390});
391+392+it("evicts idle runtimes after the configured TTL but skips active leases", async () => {
393+let now = 1_000;
394+const disposed: string[] = [];
395+const createRuntime: RuntimeFactory = (params) => {
396+let lastUsedAt = now;
397+let activeLeases = 0;
398+return {
399+ ...makeRuntime([{ toolName: "bundle_probe", description: "Bundle MCP probe" }]),
400+sessionId: params.sessionId,
401+sessionKey: params.sessionKey,
402+workspaceDir: params.workspaceDir,
403+configFingerprint: params.configFingerprint ?? "fingerprint",
404+get lastUsedAt() {
405+return lastUsedAt;
406+},
407+get activeLeases() {
408+return activeLeases;
409+},
410+markUsed: () => {
411+lastUsedAt = now;
412+},
413+acquireLease: () => {
414+activeLeases += 1;
415+return () => {
416+activeLeases -= 1;
417+lastUsedAt = now;
418+};
419+},
420+dispose: async () => {
421+disposed.push(params.sessionId);
422+},
423+};
424+};
425+const manager = __testing.createSessionMcpRuntimeManager({
426+ createRuntime,
427+now: () => now,
428+enableIdleSweepTimer: false,
429+});
430+431+const runtime = await manager.getOrCreate({
432+sessionId: "session-idle",
433+sessionKey: "agent:test:session-idle",
434+workspaceDir: "/workspace",
435+cfg: { mcp: { servers: {}, sessionIdleTtlMs: 50 } },
436+});
437+const releaseLease = runtime.acquireLease?.();
438+439+now += 60;
440+await expect(manager.sweepIdleRuntimes()).resolves.toBe(0);
441+expect(manager.listSessionIds()).toEqual(["session-idle"]);
442+443+releaseLease?.();
444+now += 60;
445+await expect(manager.sweepIdleRuntimes()).resolves.toBe(1);
446+447+expect(disposed).toEqual(["session-idle"]);
448+expect(manager.listSessionIds()).toEqual([]);
449+expect(manager.resolveSessionId("agent:test:session-idle")).toBeUndefined();
450+});
451+452+it("keeps idle runtime eviction disabled when the TTL is zero", async () => {
453+let now = 1_000;
454+const disposed: string[] = [];
455+const manager = __testing.createSessionMcpRuntimeManager({
456+createRuntime: (params) => ({
457+ ...makeRuntime([{ toolName: "bundle_probe", description: "Bundle MCP probe" }]),
458+sessionId: params.sessionId,
459+sessionKey: params.sessionKey,
460+workspaceDir: params.workspaceDir,
461+configFingerprint: params.configFingerprint ?? "fingerprint",
462+dispose: async () => {
463+disposed.push(params.sessionId);
464+},
465+}),
466+now: () => now,
467+enableIdleSweepTimer: false,
468+});
469+470+await manager.getOrCreate({
471+sessionId: "session-no-ttl",
472+workspaceDir: "/workspace",
473+cfg: { mcp: { servers: {}, sessionIdleTtlMs: 0 } },
474+});
475+476+now += 60_000_000;
477+await expect(manager.sweepIdleRuntimes()).resolves.toBe(0);
478+expect(manager.listSessionIds()).toEqual(["session-no-ttl"]);
479+expect(disposed).toEqual([]);
480+});
364481});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。