fix(lsp): cancel blocked document opens
This commit is contained in:
@@ -178,6 +178,40 @@ describe('LspInstance query and abort', () => {
|
||||
await instance.dispose()
|
||||
})
|
||||
|
||||
it('terminates when abort interrupts a backpressured didOpen write', async () => {
|
||||
// The fixture consumes initialized, then stops reading. A document larger than the stdio pipe
|
||||
// keeps didOpen's write callback pending until cancellation forces bounded process teardown.
|
||||
await writeFile(join(ws, 'a.ts'), 'x'.repeat(2_000_000))
|
||||
const marker = join(root, 'initialized.log')
|
||||
const instance = makeInstance({
|
||||
LSP_FAKE_INITIALIZED_MARKER: marker,
|
||||
LSP_FAKE_PAUSE_STDIN_AFTER_INITIALIZED: '1',
|
||||
}, {
|
||||
shutdownTimeoutMs: 100,
|
||||
killGraceMs: 100,
|
||||
})
|
||||
const controller = new AbortController()
|
||||
const pending = run(instance, 'goToDefinition', controller.signal)
|
||||
await waitForFile(marker)
|
||||
// Let the client enter the large didOpen write after the fixture has paused stdin.
|
||||
await new Promise<void>(resolve => setTimeout(resolve, 100))
|
||||
controller.abort(new Error('didOpen-abort'))
|
||||
await expect(pending).rejects.toThrow(/didOpen-abort/)
|
||||
expect(instance.dead).toBe(true)
|
||||
})
|
||||
|
||||
it('terminates when stdin fails during the didOpen write', async () => {
|
||||
// Closing stdin after initialized makes a large didOpen fail before `opened` can arm didClose;
|
||||
// the instance must still become dead so its provider can replace it.
|
||||
await writeFile(join(ws, 'a.ts'), 'x'.repeat(2_000_000))
|
||||
const instance = makeInstance({ LSP_FAKE_CLOSE_STDIN_AFTER_INITIALIZED: '1' }, {
|
||||
shutdownTimeoutMs: 100,
|
||||
killGraceMs: 100,
|
||||
})
|
||||
await expect(run(instance, 'goToDefinition')).rejects.toThrow()
|
||||
expect(instance.dead).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects when the server lacks the operation capability', async () => {
|
||||
const instance = makeInstance({ LSP_FAKE_CAPS: JSON.stringify({ definitionProvider: false }), LSP_FAKE_DEF: 'null' })
|
||||
await expect(run(instance, 'goToDefinition')).rejects.toThrow(/does not support goToDefinition/)
|
||||
@@ -287,3 +321,18 @@ function processAlive(pid: number): boolean {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/** Wait until a fixture marker exists, bounded so a broken handshake cannot hang the test. */
|
||||
async function waitForFile(path: string, timeoutMs = 3000): Promise<void> {
|
||||
const started = Date.now()
|
||||
for (;;) {
|
||||
try {
|
||||
await readFile(path)
|
||||
return
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
|
||||
}
|
||||
if (Date.now() - started > timeoutMs) throw new Error('waitForFile timed out')
|
||||
await new Promise<void>(resolve => setTimeout(resolve, 10))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user