Skip to content

Commit 869d09d

Browse files
committed
perf: Support request cancellation
When multiple requests of the same type are sent in quick succession, the LSP client will automatically issue `$/cancelRequest` notification to cancel the earlier requests. For example, when the mouse is moving around, multiple `hover` requests could be fired, but only the last one is meaningful to users. In this case, we should handle the cancelled requests and not do any work. This will significantly speed up our server's ability to process incoming requests / notifications.
1 parent 3cc6d6d commit 869d09d

File tree

2 files changed

+37
-1
lines changed

2 files changed

+37
-1
lines changed

integration/lsp/ivy_spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,24 @@ describe('Angular Ivy language server', () => {
9292
.toBe(`Property 'doesnotexist' does not exist on type 'FooComponent'.`);
9393
});
9494

95+
it('should support request cancellation', async () => {
96+
openTextDocument(client, APP_COMPONENT);
97+
const languageServiceEnabled = await waitForNgcc(client);
98+
expect(languageServiceEnabled).toBeTrue();
99+
// Send a request and immediately cancel it
100+
const promise = client.sendRequest(lsp.HoverRequest.type, {
101+
textDocument: {
102+
uri: `file://${APP_COMPONENT}`,
103+
},
104+
position: {line: 4, character: 25},
105+
});
106+
// Request above is tagged with ID = 1
107+
client.sendNotification('$/cancelRequest', {id: 1});
108+
await expectAsync(promise).toBeRejectedWith(jasmine.objectContaining({
109+
code: lsp.LSPErrorCodes.RequestCancelled,
110+
}));
111+
});
112+
95113
it('does not break after opening `.d.ts` file from external template', async () => {
96114
client.sendNotification(lsp.DidOpenTextDocumentNotification.type, {
97115
textDocument: {

server/src/session.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,25 @@ export class Session {
6767
this.ivy = options.ivy;
6868
this.logToConsole = options.logToConsole;
6969
// Create a connection for the server. The connection uses Node's IPC as a transport.
70-
this.connection = lsp.createConnection();
70+
this.connection = lsp.createConnection({
71+
// cancelUndispatched is a "middleware" to handle all cancellation requests.
72+
// LSP spec requires every request to send a response back, even if it is
73+
// cancelled. See
74+
// https://microsoft.github.io/language-server-protocol/specifications/specification-current/#cancelRequest
75+
cancelUndispatched(message: lsp.Message): lsp.ResponseMessage |
76+
undefined {
77+
return {
78+
jsonrpc: message.jsonrpc,
79+
// This ID is just a placeholder to satisfy the ResponseMessage type.
80+
// `vscode-jsonrpc` will replace the ID with the ID of the message to
81+
// be cancelled. See
82+
// https://github.com/microsoft/vscode-languageserver-node/blob/193f06bf602ee1120afda8f0bac33c5161cab18e/jsonrpc/src/common/connection.ts#L619
83+
id: -1,
84+
error: new lsp.ResponseError(lsp.LSPErrorCodes.RequestCancelled, 'Request cancelled'),
85+
};
86+
}
87+
});
88+
7189
this.addProtocolHandlers(this.connection);
7290
this.projectService = this.createProjectService(options);
7391
}

0 commit comments

Comments
 (0)