Skip to content

Commit a58953d

Browse files
committed
Address code-quality PR review feedback
1 parent a30bc49 commit a58953d

File tree

4 files changed

+70
-2
lines changed

4 files changed

+70
-2
lines changed

server/dist/codeql-development-mcp-server.js

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/dist/codeql-development-mcp-server.js.map

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/src/lib/server-manager.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,11 @@ export class CodeQLServerManager {
289289
try {
290290
return await work;
291291
} finally {
292+
// Ensure `work` has settled before removing the pendingStarts entry so
293+
// that concurrent callers waiting on the same promise see a consistent
294+
// map state. The try/catch avoids re-throwing a rejection that the
295+
// outer `try` block already handles.
296+
try { await work; } catch { /* already handled */ }
292297
if (this.pendingStarts.get(type) === work) {
293298
this.pendingStarts.delete(type);
294299
}

server/test/src/lib/server-manager.test.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,65 @@ describe('CodeQLServerManager', () => {
179179
expect(server1).toBe(server2);
180180
expect(manager.isRunning('language')).toBe(true);
181181
});
182+
183+
it('should allow a retry after a failed start', async () => {
184+
const { spawn } = await import('child_process');
185+
const manager = new CodeQLServerManager({ sessionId: 'ls-fail-retry' });
186+
const config = { searchPath: '/ql' };
187+
188+
// First call: spawn throws so the start rejects
189+
(spawn as ReturnType<typeof vi.fn>).mockImplementationOnce(() => {
190+
throw new Error('mock spawn failure');
191+
});
192+
193+
await expect(manager.getLanguageServer(config)).rejects.toThrow('mock spawn failure');
194+
expect(manager.isRunning('language')).toBe(false);
195+
196+
// Second call: spawn works again — should succeed, not hang on a
197+
// stale pendingStarts entry.
198+
const server = await manager.getLanguageServer(config);
199+
expect(server).toBeDefined();
200+
expect(server.isRunning()).toBe(true);
201+
});
202+
203+
it('should not leak pendingStarts when concurrent calls race and the first rejects', async () => {
204+
const { spawn } = await import('child_process');
205+
const manager = new CodeQLServerManager({ sessionId: 'ls-race-reject' });
206+
const config = { searchPath: '/ql' };
207+
208+
let callCount = 0;
209+
const originalImpl = (spawn as ReturnType<typeof vi.fn>).getMockImplementation();
210+
(spawn as ReturnType<typeof vi.fn>).mockImplementation((...args: unknown[]) => {
211+
callCount++;
212+
if (callCount === 1) {
213+
// First spawn fails — simulates a transient error
214+
throw new Error('transient spawn failure');
215+
}
216+
// Subsequent spawns succeed using the original mock implementation
217+
if (originalImpl) return originalImpl(...args);
218+
// Fallback: should not happen, but satisfy the type checker
219+
throw new Error('no original implementation');
220+
});
221+
222+
// Fire two concurrent requests — first will reject, second should
223+
// still succeed because pendingStarts is cleaned up properly.
224+
const results = await Promise.allSettled([
225+
manager.getLanguageServer(config),
226+
manager.getLanguageServer(config),
227+
]);
228+
229+
// At least one call should have succeeded
230+
const fulfilled = results.filter(r => r.status === 'fulfilled');
231+
expect(fulfilled.length).toBeGreaterThanOrEqual(1);
232+
233+
// The manager should have a running language server afterwards
234+
expect(manager.isRunning('language')).toBe(true);
235+
236+
// Restore original mock
237+
if (originalImpl) {
238+
(spawn as ReturnType<typeof vi.fn>).mockImplementation(originalImpl);
239+
}
240+
});
182241
});
183242

184243
describe('getQueryServer', () => {

0 commit comments

Comments
 (0)