Skip to content

Commit 1f28624

Browse files
committed
test(streamableHttp): add re-entrancy guard tests for close()
Demonstrates the fix for the stack overflow: verifies that calling close() recursively from within onclose does not recurse, and that concurrent close() calls only clean up streams once.
1 parent ff06d69 commit 1f28624

File tree

1 file changed

+35
-0
lines changed

1 file changed

+35
-0
lines changed

packages/server/test/server/streamableHttp.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -956,4 +956,39 @@ describe('Zod v4', () => {
956956
expect(error?.message).toContain('Unsupported protocol version');
957957
});
958958
});
959+
960+
describe('close() re-entrancy guard', () => {
961+
it('should not recurse when onclose triggers a second close()', async () => {
962+
const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: randomUUID });
963+
964+
let closeCallCount = 0;
965+
transport.onclose = () => {
966+
closeCallCount++;
967+
// Simulate the Protocol layer calling close() again from within onclose —
968+
// the re-entrancy guard should prevent infinite recursion / stack overflow.
969+
void transport.close();
970+
};
971+
972+
// Should resolve without throwing RangeError: Maximum call stack size exceeded
973+
await expect(transport.close()).resolves.toBeUndefined();
974+
expect(closeCallCount).toBe(1);
975+
});
976+
977+
it('should clean up all streams exactly once even when close() is called concurrently', async () => {
978+
const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: randomUUID });
979+
980+
const cleanupCalls: string[] = [];
981+
982+
// Inject a fake stream entry to verify cleanup runs exactly once
983+
// @ts-expect-error accessing private map for test purposes
984+
transport._streamMapping.set('stream-1', {
985+
cleanup: () => { cleanupCalls.push('stream-1'); }
986+
});
987+
988+
// Fire two concurrent close() calls — only the first should proceed
989+
await Promise.all([transport.close(), transport.close()]);
990+
991+
expect(cleanupCalls).toEqual(['stream-1']);
992+
});
993+
});
959994
});

0 commit comments

Comments
 (0)