Skip to content

Commit 7c91349

Browse files
Added clean shutdown + tests
1 parent 3f178b2 commit 7c91349

File tree

3 files changed

+74
-0
lines changed

3 files changed

+74
-0
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Server } from "../server/index.js";
2+
import { StdioServerTransport } from "../server/stdio.js";
3+
4+
describe("Process cleanup", () => {
5+
jest.setTimeout(5000); // 5 second timeout
6+
7+
it("should exit cleanly after closing transport", async () => {
8+
const server = new Server(
9+
{
10+
name: "test-server",
11+
version: "1.0.0",
12+
},
13+
{
14+
capabilities: {},
15+
}
16+
);
17+
18+
const transport = new StdioServerTransport();
19+
await server.connect(transport);
20+
21+
// Close the transport
22+
await transport.close();
23+
24+
// If we reach here without hanging, the test passes
25+
// The test runner will fail if the process hangs
26+
expect(true).toBe(true);
27+
});
28+
});

src/server/stdio.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,41 @@ test("should read multiple messages", async () => {
100100
await finished;
101101
expect(readMessages).toEqual(messages);
102102
});
103+
104+
test("should properly clean up resources when closed", async () => {
105+
// Create mock streams that track their destroyed state
106+
const mockStdin = new Readable({
107+
read() {}, // No-op implementation
108+
destroy() {
109+
this.destroyed = true;
110+
return this;
111+
}
112+
});
113+
const mockStdout = new Writable({
114+
write(chunk, encoding, callback) {
115+
callback();
116+
},
117+
destroy() {
118+
this.destroyed = true;
119+
return this;
120+
}
121+
});
122+
123+
const transport = new StdioServerTransport(mockStdin, mockStdout);
124+
await transport.start();
125+
126+
// Send a message to potentially create 'drain' listeners
127+
await transport.send({ jsonrpc: "2.0", method: "test", id: 1 });
128+
129+
// Close the transport
130+
await transport.close();
131+
132+
// Check that all listeners were removed
133+
expect(mockStdin.listenerCount('data')).toBe(0);
134+
expect(mockStdin.listenerCount('error')).toBe(0);
135+
expect(mockStdout.listenerCount('drain')).toBe(0);
136+
137+
// Check that streams were properly ended
138+
expect(mockStdin.destroyed).toBe(true);
139+
expect(mockStdout.destroyed).toBe(true);
140+
});

src/server/stdio.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,16 @@ export class StdioServerTransport implements Transport {
6262
}
6363

6464
async close(): Promise<void> {
65+
// Remove all event listeners
6566
this._stdin.off("data", this._ondata);
6667
this._stdin.off("error", this._onerror);
68+
this._stdout.removeAllListeners('drain');
69+
70+
// Destroy both streams
71+
this._stdin.destroy();
72+
this._stdout.destroy();
73+
74+
// Clear the buffer and notify closure
6775
this._readBuffer.clear();
6876
this.onclose?.();
6977
}

0 commit comments

Comments
 (0)