Extract cancellation.electron

This makes it possible to replace the cancellation logic for serverless
This commit is contained in:
Matt Bierner 2020-07-20 08:28:07 -07:00
parent 3b9db3df27
commit 90fbd0eb60
9 changed files with 95 additions and 45 deletions

View file

@ -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);
});

View file

@ -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<TypeScriptServiceClientHost> {
return lazy(() => {
@ -29,6 +31,7 @@ export function createLazyClientHost(
pluginManager,
commandManager,
logDirectoryProvider,
cancellerFactory,
onCompletionAccepted);
context.subscriptions.push(clientHost);

View file

@ -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 });

View file

@ -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;
}
}

View file

@ -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;
}
};

View file

@ -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<Proto.Event>;

View file

@ -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 {

View file

@ -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 }) => {

View file

@ -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);