Skip to content

Commit c5f82b6

Browse files
committed
fix: address comments
1 parent da99f65 commit c5f82b6

File tree

3 files changed

+143
-41
lines changed

3 files changed

+143
-41
lines changed

src/common/sessionStore.ts

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,7 @@
11
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
22
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
33
import logger, { LogId, McpLogger } from "./logger.js";
4-
5-
class TimeoutManager {
6-
private timeoutId?: NodeJS.Timeout;
7-
public onerror?: (error: unknown) => void;
8-
9-
constructor(
10-
private readonly callback: () => Promise<void> | void,
11-
private readonly timeoutMS: number
12-
) {
13-
if (timeoutMS <= 0) {
14-
throw new Error("timeoutMS must be greater than 0");
15-
}
16-
this.reset();
17-
}
18-
19-
clear() {
20-
if (this.timeoutId) {
21-
clearTimeout(this.timeoutId);
22-
this.timeoutId = undefined;
23-
}
24-
}
25-
26-
private async runCallback() {
27-
if (this.callback) {
28-
try {
29-
await this.callback();
30-
} catch (error: unknown) {
31-
this.onerror?.(error);
32-
}
33-
}
34-
}
35-
36-
reset() {
37-
this.clear();
38-
this.timeoutId = setTimeout(() => {
39-
void this.runCallback().finally(() => {
40-
this.timeoutId = undefined;
41-
});
42-
}, this.timeoutMS);
43-
}
44-
}
4+
import { TimeoutManager } from "./timeoutManager.js";
455

466
export class SessionStore {
477
private sessions: {

src/common/timeoutManager.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* A class that manages timeouts for a callback function.
3+
* It is used to ensure that a callback function is called after a certain amount of time.
4+
* If the callback function is not called after the timeout, it will be called with an error.
5+
*/
6+
export class TimeoutManager {
7+
private timeoutId?: NodeJS.Timeout;
8+
9+
/**
10+
* A callback function that is called when the timeout is reached.
11+
*/
12+
public onerror?: (error: unknown) => void;
13+
14+
/**
15+
* Creates a new TimeoutManager.
16+
* @param callback - A callback function that is called when the timeout is reached.
17+
* @param timeoutMS - The timeout in milliseconds.
18+
*/
19+
constructor(
20+
private readonly callback: () => Promise<void> | void,
21+
private readonly timeoutMS: number
22+
) {
23+
if (timeoutMS <= 0) {
24+
throw new Error("timeoutMS must be greater than 0");
25+
}
26+
this.reset();
27+
}
28+
29+
/**
30+
* Clears the timeout.
31+
*/
32+
clear() {
33+
if (this.timeoutId) {
34+
clearTimeout(this.timeoutId);
35+
this.timeoutId = undefined;
36+
}
37+
}
38+
39+
/**
40+
* Runs the callback function.
41+
*/
42+
private async runCallback() {
43+
if (this.callback) {
44+
try {
45+
await this.callback();
46+
} catch (error: unknown) {
47+
this.onerror?.(error);
48+
}
49+
}
50+
}
51+
52+
/**
53+
* Resets the timeout.
54+
*/
55+
reset() {
56+
this.clear();
57+
this.timeoutId = setTimeout(() => {
58+
void this.runCallback().finally(() => {
59+
this.timeoutId = undefined;
60+
});
61+
}, this.timeoutMS);
62+
}
63+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
2+
import { TimeoutManager } from "../../../src/common/timeoutManager.js";
3+
4+
describe("TimeoutManager", () => {
5+
beforeAll(() => {
6+
vi.useFakeTimers();
7+
});
8+
9+
afterAll(() => {
10+
vi.useRealTimers();
11+
});
12+
13+
it("calls the timeout callback", () => {
14+
const callback = vi.fn();
15+
16+
new TimeoutManager(callback, 1000);
17+
18+
vi.advanceTimersByTime(1000);
19+
expect(callback).toHaveBeenCalled();
20+
});
21+
22+
it("does not call the timeout callback if the timeout is cleared", () => {
23+
const callback = vi.fn();
24+
25+
const timeoutManager = new TimeoutManager(callback, 1000);
26+
27+
vi.advanceTimersByTime(500);
28+
timeoutManager.clear();
29+
vi.advanceTimersByTime(500);
30+
31+
expect(callback).not.toHaveBeenCalled();
32+
});
33+
34+
it("does not call the timeout callback if the timeout is reset", () => {
35+
const callback = vi.fn();
36+
37+
const timeoutManager = new TimeoutManager(callback, 1000);
38+
39+
vi.advanceTimersByTime(500);
40+
timeoutManager.reset();
41+
vi.advanceTimersByTime(500);
42+
expect(callback).not.toHaveBeenCalled();
43+
});
44+
45+
it("calls the onerror callback", () => {
46+
const onerrorCallback = vi.fn();
47+
48+
const tm = new TimeoutManager(() => {
49+
throw new Error("test");
50+
}, 1000);
51+
tm.onerror = onerrorCallback;
52+
53+
vi.advanceTimersByTime(1000);
54+
expect(onerrorCallback).toHaveBeenCalled();
55+
});
56+
57+
describe("if timeout is reset", () => {
58+
it("does not call the timeout callback within the timeout period", () => {
59+
const callback = vi.fn();
60+
61+
const timeoutManager = new TimeoutManager(callback, 1000);
62+
63+
vi.advanceTimersByTime(500);
64+
timeoutManager.reset();
65+
vi.advanceTimersByTime(500);
66+
expect(callback).not.toHaveBeenCalled();
67+
});
68+
it("calls the timeout callback after the timeout period", () => {
69+
const callback = vi.fn();
70+
71+
const timeoutManager = new TimeoutManager(callback, 1000);
72+
73+
vi.advanceTimersByTime(500);
74+
timeoutManager.reset();
75+
vi.advanceTimersByTime(1000);
76+
expect(callback).toHaveBeenCalled();
77+
});
78+
});
79+
});

0 commit comments

Comments
 (0)