From 90fbd0eb60f197e3911f20124be3676f5e217b80 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 20 Jul 2020 08:28:07 -0700 Subject: [PATCH] Extract cancellation.electron This makes it possible to replace the cancellation logic for serverless --- .../src/extension.ts | 7 ++-- .../src/lazyClientHost.ts | 3 ++ .../src/test/server.test.ts | 7 ++-- .../src/tsServer/cancellation.electron.ts | 33 +++++++++++++++++++ .../src/tsServer/cancellation.ts | 23 +++++++++++++ .../src/tsServer/server.ts | 26 +-------------- .../src/tsServer/spawner.ts | 30 ++++++++++------- .../src/typeScriptServiceClientHost.ts | 5 ++- .../src/typescriptServiceClient.ts | 6 ++-- 9 files changed, 95 insertions(+), 45 deletions(-) create mode 100644 extensions/typescript-language-features/src/tsServer/cancellation.electron.ts create mode 100644 extensions/typescript-language-features/src/tsServer/cancellation.ts diff --git a/extensions/typescript-language-features/src/extension.ts b/extensions/typescript-language-features/src/extension.ts index f3e8127bbcc..31a3bc56747 100644 --- a/extensions/typescript-language-features/src/extension.ts +++ b/extensions/typescript-language-features/src/extension.ts @@ -5,14 +5,15 @@ import * as rimraf from 'rimraf'; import * as vscode from 'vscode'; -import { NodeLogDirectoryProvider } from './utils/logDirectoryProvider.electron'; import { Api, getExtensionApi } from './api'; import { registerCommands } from './commands/index'; import { LanguageConfigurationManager } from './features/languageConfiguration'; import * as task from './features/task'; import { createLazyClientHost, lazilyActivateClient } from './lazyClientHost'; +import { NodeRequestCanceller } from './tsServer/cancellation.electron'; import { CommandManager } from './utils/commandManager'; import * as electron from './utils/electron'; +import { NodeLogDirectoryProvider } from './utils/logDirectoryProvider.electron'; import { PluginManager } from './utils/plugins'; export function activate( @@ -29,7 +30,9 @@ export function activate( const logDirectoryProvider = new NodeLogDirectoryProvider(context); - const lazyClientHost = createLazyClientHost(context, pluginManager, commandManager, logDirectoryProvider, item => { + const lazyClientHost = createLazyClientHost(context, pluginManager, commandManager, logDirectoryProvider, { + create: (kind, tracer) => new NodeRequestCanceller(kind, tracer) + }, item => { onCompletionAccepted.fire(item); }); diff --git a/extensions/typescript-language-features/src/lazyClientHost.ts b/extensions/typescript-language-features/src/lazyClientHost.ts index ea2937caf2d..de29f958b3a 100644 --- a/extensions/typescript-language-features/src/lazyClientHost.ts +++ b/extensions/typescript-language-features/src/lazyClientHost.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import { OngoingRequestCancellerFactory } from './tsServer/cancellation'; import TypeScriptServiceClientHost from './typeScriptServiceClientHost'; import { flatten } from './utils/arrays'; import { CommandManager } from './utils/commandManager'; @@ -20,6 +21,7 @@ export function createLazyClientHost( pluginManager: PluginManager, commandManager: CommandManager, logDirectoryProvider: ILogDirectoryProvider, + cancellerFactory: OngoingRequestCancellerFactory, onCompletionAccepted: (item: vscode.CompletionItem) => void, ): Lazy { return lazy(() => { @@ -29,6 +31,7 @@ export function createLazyClientHost( pluginManager, commandManager, logDirectoryProvider, + cancellerFactory, onCompletionAccepted); context.subscriptions.push(clientHost); diff --git a/extensions/typescript-language-features/src/test/server.test.ts b/extensions/typescript-language-features/src/test/server.test.ts index d7189bdfb49..c225fc3c859 100644 --- a/extensions/typescript-language-features/src/test/server.test.ts +++ b/extensions/typescript-language-features/src/test/server.test.ts @@ -6,12 +6,13 @@ import * as assert from 'assert'; import 'mocha'; import * as stream from 'stream'; -import { PipeRequestCanceller, TsServerProcess, ProcessBasedTsServer } from '../tsServer/server'; +import type * as Proto from '../protocol'; +import { NodeRequestCanceller } from '../tsServer/cancellation.electron'; +import { ProcessBasedTsServer, TsServerProcess } from '../tsServer/server'; import { nulToken } from '../utils/cancellation'; import Logger from '../utils/logger'; import { TelemetryReporter } from '../utils/telemetry'; import Tracer from '../utils/tracer'; -import type * as Proto from '../protocol'; const NoopTelemetryReporter = new class implements TelemetryReporter { @@ -63,7 +64,7 @@ suite('Server', () => { test('should send requests with increasing sequence numbers', async () => { const process = new FakeServerProcess(); - const server = new ProcessBasedTsServer('semantic', process, undefined, new PipeRequestCanceller('semantic', undefined, tracer), undefined!, NoopTelemetryReporter, tracer); + const server = new ProcessBasedTsServer('semantic', process, undefined, new NodeRequestCanceller('semantic', tracer), undefined!, NoopTelemetryReporter, tracer); const onWrite1 = process.onWrite(); server.executeImpl('geterr', {}, { isAsync: false, token: nulToken, expectsResult: true }); diff --git a/extensions/typescript-language-features/src/tsServer/cancellation.electron.ts b/extensions/typescript-language-features/src/tsServer/cancellation.electron.ts new file mode 100644 index 00000000000..715729ba82f --- /dev/null +++ b/extensions/typescript-language-features/src/tsServer/cancellation.electron.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs'; +import { OngoingRequestCanceller } from './cancellation'; +import { getTempFile } from '../utils/electron'; +import Tracer from '../utils/tracer'; + +export class NodeRequestCanceller implements OngoingRequestCanceller { + public readonly cancellationPipeName: string; + + public constructor( + private readonly _serverId: string, + private readonly _tracer: Tracer, + ) { + this.cancellationPipeName = getTempFile('tscancellation'); + } + + public tryCancelOngoingRequest(seq: number): boolean { + if (!this.cancellationPipeName) { + return false; + } + this._tracer.logTrace(this._serverId, `TypeScript Server: trying to cancel ongoing request with sequence number ${seq}`); + try { + fs.writeFileSync(this.cancellationPipeName + seq, ''); + } catch { + // noop + } + return true; + } +} diff --git a/extensions/typescript-language-features/src/tsServer/cancellation.ts b/extensions/typescript-language-features/src/tsServer/cancellation.ts new file mode 100644 index 00000000000..031a0976bbb --- /dev/null +++ b/extensions/typescript-language-features/src/tsServer/cancellation.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import Tracer from '../utils/tracer'; + +export interface OngoingRequestCanceller { + readonly cancellationPipeName: string | undefined; + tryCancelOngoingRequest(seq: number): boolean; +} + +export interface OngoingRequestCancellerFactory { + create(serverId: string, tracer: Tracer): OngoingRequestCanceller; +} + +export const noopRequestCanceller = new class implements OngoingRequestCanceller { + public readonly cancellationPipeName = undefined; + + public tryCancelOngoingRequest(_seq: number): boolean { + return false; + } +}; diff --git a/extensions/typescript-language-features/src/tsServer/server.ts b/extensions/typescript-language-features/src/tsServer/server.ts index 55a14642a1f..d051a216b96 100644 --- a/extensions/typescript-language-features/src/tsServer/server.ts +++ b/extensions/typescript-language-features/src/tsServer/server.ts @@ -3,11 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as fs from 'fs'; import * as vscode from 'vscode'; import type * as Proto from '../protocol'; import { EventName } from '../protocol.const'; import { CallbackMap } from '../tsServer/callbackMap'; +import { OngoingRequestCanceller } from './cancellation'; import { RequestItem, RequestQueue, RequestQueueingType } from '../tsServer/requestQueue'; import { TypeScriptServerError } from '../tsServer/serverError'; import { ServerResponse, TypeScriptRequests } from '../typescriptService'; @@ -16,30 +16,6 @@ import { TelemetryReporter } from '../utils/telemetry'; import Tracer from '../utils/tracer'; import { TypeScriptVersion } from '../utils/versionProvider'; -export interface OngoingRequestCanceller { - tryCancelOngoingRequest(seq: number): boolean; -} - -export class PipeRequestCanceller implements OngoingRequestCanceller { - public constructor( - private readonly _serverId: string, - private readonly _cancellationPipeName: string | undefined, - private readonly _tracer: Tracer, - ) { } - - public tryCancelOngoingRequest(seq: number): boolean { - if (!this._cancellationPipeName) { - return false; - } - this._tracer.logTrace(this._serverId, `TypeScript Server: trying to cancel ongoing request with sequence number ${seq}`); - try { - fs.writeFileSync(this._cancellationPipeName + seq, ''); - } catch { - // noop - } - return true; - } -} export interface ITypeScriptServer { readonly onEvent: vscode.Event; diff --git a/extensions/typescript-language-features/src/tsServer/spawner.ts b/extensions/typescript-language-features/src/tsServer/spawner.ts index 4817ac6f447..b06eecd7542 100644 --- a/extensions/typescript-language-features/src/tsServer/spawner.ts +++ b/extensions/typescript-language-features/src/tsServer/spawner.ts @@ -5,6 +5,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; +import { OngoingRequestCancellerFactory } from '../tsServer/cancellation'; import { ClientCapabilities, ClientCapability } from '../typescriptService'; import API from '../utils/api'; import { SeparateSyntaxServerConfiguration, TsServerLogLevel, TypeScriptServiceConfiguration } from '../utils/configuration'; @@ -17,7 +18,7 @@ import { ChildServerProcess } from '../utils/serverProcess'; import { TelemetryReporter } from '../utils/telemetry'; import Tracer from '../utils/tracer'; import { TypeScriptVersion, TypeScriptVersionProvider } from '../utils/versionProvider'; -import { GetErrRoutingTsServer, ITypeScriptServer, PipeRequestCanceller, ProcessBasedTsServer, SyntaxRoutingTsServer, TsServerDelegate } from './server'; +import { GetErrRoutingTsServer, ITypeScriptServer, ProcessBasedTsServer, SyntaxRoutingTsServer, TsServerDelegate } from './server'; const enum ServerKind { Main = 'main', @@ -55,6 +56,7 @@ export class TypeScriptServerSpawner { capabilities: ClientCapabilities, configuration: TypeScriptServiceConfiguration, pluginManager: PluginManager, + cancellerFactory: OngoingRequestCancellerFactory, delegate: TsServerDelegate, ): ITypeScriptServer { let primaryServer: ITypeScriptServer; @@ -65,26 +67,26 @@ export class TypeScriptServerSpawner { { const enableDynamicRouting = serverType === CompositeServerType.DynamicSeparateSyntax; primaryServer = new SyntaxRoutingTsServer({ - syntax: this.spawnTsServer(ServerKind.Syntax, version, configuration, pluginManager), - semantic: this.spawnTsServer(ServerKind.Semantic, version, configuration, pluginManager) + syntax: this.spawnTsServer(ServerKind.Syntax, version, configuration, pluginManager, cancellerFactory), + semantic: this.spawnTsServer(ServerKind.Semantic, version, configuration, pluginManager, cancellerFactory), }, delegate, enableDynamicRouting); break; } case CompositeServerType.Single: { - primaryServer = this.spawnTsServer(ServerKind.Main, version, configuration, pluginManager); + primaryServer = this.spawnTsServer(ServerKind.Main, version, configuration, pluginManager, cancellerFactory); break; } case CompositeServerType.SyntaxOnly: { - primaryServer = this.spawnTsServer(ServerKind.Syntax, version, configuration, pluginManager); + primaryServer = this.spawnTsServer(ServerKind.Syntax, version, configuration, pluginManager, cancellerFactory); break; } } if (this.shouldUseSeparateDiagnosticsServer(configuration)) { return new GetErrRoutingTsServer({ - getErr: this.spawnTsServer(ServerKind.Diagnostics, version, configuration, pluginManager), + getErr: this.spawnTsServer(ServerKind.Diagnostics, version, configuration, pluginManager, cancellerFactory), primary: primaryServer, }, delegate); } @@ -126,10 +128,12 @@ export class TypeScriptServerSpawner { version: TypeScriptVersion, configuration: TypeScriptServiceConfiguration, pluginManager: PluginManager, + cancellerFactory: OngoingRequestCancellerFactory, ): ITypeScriptServer { const apiVersion = version.apiVersion || API.defaultVersion; - const { args, cancellationPipeName, tsServerLogFile } = this.getTsServerArgs(kind, configuration, version, apiVersion, pluginManager); + const canceller = cancellerFactory.create(kind, this._tracer); + const { args, tsServerLogFile } = this.getTsServerArgs(kind, configuration, version, apiVersion, pluginManager, canceller.cancellationPipeName); if (TypeScriptServerSpawner.isLoggingEnabled(configuration)) { if (tsServerLogFile) { @@ -147,7 +151,7 @@ export class TypeScriptServerSpawner { kind, new ChildServerProcess(childProcess), tsServerLogFile, - new PipeRequestCanceller(kind, cancellationPipeName, this._tracer), + canceller, version, this._telemetryReporter, this._tracer); @@ -171,7 +175,8 @@ export class TypeScriptServerSpawner { currentVersion: TypeScriptVersion, apiVersion: API, pluginManager: PluginManager, - ): { args: string[], cancellationPipeName: string, tsServerLogFile: string | undefined } { + cancellationPipeName: string | undefined, + ): { args: string[], tsServerLogFile: string | undefined } { const args: string[] = []; let tsServerLogFile: string | undefined; @@ -193,8 +198,9 @@ export class TypeScriptServerSpawner { args.push('--enableTelemetry'); } - const cancellationPipeName = electron.getTempFile('tscancellation'); - args.push('--cancellationPipeName', cancellationPipeName + '*'); + if (cancellationPipeName) { + args.push('--cancellationPipeName', cancellationPipeName + '*'); + } if (TypeScriptServerSpawner.isLoggingEnabled(configuration)) { const logDir = this._logDirectoryProvider.getNewLogDirectory(); @@ -238,7 +244,7 @@ export class TypeScriptServerSpawner { args.push('--validateDefaultNpmLocation'); } - return { args, cancellationPipeName, tsServerLogFile }; + return { args, tsServerLogFile }; } private static getDebugPort(kind: ServerKind): number | undefined { diff --git a/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts b/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts index 1f2b1704dcd..7784c63faa1 100644 --- a/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts +++ b/extensions/typescript-language-features/src/typeScriptServiceClientHost.ts @@ -9,18 +9,19 @@ * ------------------------------------------------------------------------------------------ */ import * as vscode from 'vscode'; -import { ILogDirectoryProvider } from './utils/logDirectoryProvider'; import { DiagnosticKind } from './features/diagnostics'; import FileConfigurationManager from './features/fileConfigurationManager'; import LanguageProvider from './languageProvider'; import * as Proto from './protocol'; import * as PConst from './protocol.const'; +import { OngoingRequestCancellerFactory } from './tsServer/cancellation'; import TypeScriptServiceClient from './typescriptServiceClient'; import { coalesce, flatten } from './utils/arrays'; import { CommandManager } from './utils/commandManager'; import { Disposable } from './utils/dispose'; import * as errorCodes from './utils/errorCodes'; import { DiagnosticLanguage, LanguageDescription } from './utils/languageDescription'; +import { ILogDirectoryProvider } from './utils/logDirectoryProvider'; import { PluginManager } from './utils/plugins'; import * as typeConverters from './utils/typeConverters'; import TypingsStatus, { AtaProgressReporter } from './utils/typingsStatus'; @@ -61,6 +62,7 @@ export default class TypeScriptServiceClientHost extends Disposable { pluginManager: PluginManager, private readonly commandManager: CommandManager, logDirectoryProvider: ILogDirectoryProvider, + cancellerFactory: OngoingRequestCancellerFactory, onCompletionAccepted: (item: vscode.CompletionItem) => void, ) { super(); @@ -70,6 +72,7 @@ export default class TypeScriptServiceClientHost extends Disposable { workspaceState, pluginManager, logDirectoryProvider, + cancellerFactory, allModeIds)); this.client.onDiagnosticsReceived(({ kind, resource, diagnostics }) => { diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 98d7fe8b97b..7d8cd7d8afa 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -7,7 +7,7 @@ import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; -import { onCaseInsenitiveFileSystem } from './utils/fileSystem'; +import { OngoingRequestCancellerFactory } from './tsServer/cancellation'; import BufferSyncSupport from './features/bufferSyncSupport'; import { DiagnosticKind, DiagnosticsManager } from './features/diagnostics'; import * as Proto from './protocol'; @@ -20,6 +20,7 @@ import API from './utils/api'; import { TsServerLogLevel, TypeScriptServiceConfiguration } from './utils/configuration'; import { Disposable } from './utils/dispose'; import * as fileSchemes from './utils/fileSchemes'; +import { onCaseInsenitiveFileSystem } from './utils/fileSystem'; import { ILogDirectoryProvider } from './utils/logDirectoryProvider'; import Logger from './utils/logger'; import { TypeScriptPluginPathsProvider } from './utils/pluginPathsProvider'; @@ -125,6 +126,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType private readonly workspaceState: vscode.Memento, public readonly pluginManager: PluginManager, private readonly logDirectoryProvider: ILogDirectoryProvider, + private readonly cancellerFactory: OngoingRequestCancellerFactory, allModeIds: readonly string[] ) { super(); @@ -348,7 +350,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType const apiVersion = version.apiVersion || API.defaultVersion; let mytoken = ++this.token; - const handle = this.typescriptServerSpawner.spawn(version, this.capabilities, this.configuration, this.pluginManager, { + const handle = this.typescriptServerSpawner.spawn(version, this.capabilities, this.configuration, this.pluginManager, this.cancellerFactory, { onFatalError: (command, err) => this.fatalError(command, err), }); this.serverState = new ServerState.Running(handle, apiVersion, undefined, true);