send all events through common stream

This commit is contained in:
Arthur Ozga 2017-11-08 18:28:50 -08:00
parent 594ac0163c
commit 23da1cf822
4 changed files with 99 additions and 73 deletions

View File

@ -16,6 +16,7 @@ namespace ts.server {
export const ProjectInfoTelemetryEvent = "projectInfo";
// tslint:enable variable-name
// TODO: make these inherit from protocol.Event?
export interface ProjectsUpdatedInBackgroundEvent {
eventName: typeof ProjectsUpdatedInBackgroundEvent;
data: { openFiles: string[]; };
@ -320,6 +321,7 @@ namespace ts.server {
pluginProbeLocations?: ReadonlyArray<string>;
allowLocalPluginLoads?: boolean;
typesMapLocation?: string;
eventSender?: EventSender;
}
type WatchFile = (host: ServerHost, file: string, cb: FileWatcherCallback, watchType: WatchType, project?: Project) => FileWatcher;
@ -436,7 +438,7 @@ namespace ts.server {
this.loadTypesMap();
}
this.typingsInstaller.attach(this);
this.typingsInstaller.attach(this, opts.eventSender);
this.typingsCache = new TypingsCache(this.typingsInstaller);

View File

@ -1,4 +1,3 @@
/// <reference types="node" />
/// <reference path="shared.ts" />
/// <reference path="session.ts" />
@ -7,7 +6,7 @@ namespace ts.server {
host: ServerHost;
cancellationToken: ServerCancellationToken;
canUseEvents: boolean;
installerEventPort: number;
eventPort: number;
useSingleInferredProject: boolean;
useInferredProjectPerProjectRoot: boolean;
disableAutomaticTypingAcquisition: boolean;
@ -22,10 +21,6 @@ namespace ts.server {
allowLocalPluginLoads: boolean;
}
const net: {
connect(options: { port: number }, onConnect?: () => void): NodeSocket
} = require("net");
const childProcess: {
fork(modulePath: string, args: string[], options?: { execArgv: string[], env?: MapLike<string> }): NodeChildProcess;
execFileSync(file: string, args: string[], options: { stdio: "ignore", env: MapLike<string> }): string | Buffer;
@ -83,10 +78,6 @@ namespace ts.server {
pid: number;
}
interface NodeSocket {
write(data: string, encoding: string): boolean;
}
interface ReadLineOptions {
input: NodeJS.ReadableStream;
output?: NodeJS.WritableStream;
@ -244,9 +235,8 @@ namespace ts.server {
class NodeTypingsInstaller implements ITypingsInstaller {
private installer: NodeChildProcess;
private installerPidReported = false;
private socket: NodeSocket;
private projectService: ProjectService;
private eventSender: EventSender;
private eventSender: EventSender | undefined;
private activeRequestCount = 0;
private requestQueue: QueuedOperation[] = [];
private requestMap = createMap<QueuedOperation>(); // Maps operation ID to newest requestQueue entry with that ID
@ -267,18 +257,10 @@ namespace ts.server {
private readonly telemetryEnabled: boolean,
private readonly logger: server.Logger,
private readonly host: ServerHost,
eventPort: number,
readonly globalTypingsCacheLocation: string,
readonly typingSafeListLocation: string,
readonly typesMapLocation: string,
private readonly npmLocation: string | undefined,
private newLine: string) {
if (eventPort) {
const s = net.connect({ port: eventPort }, () => {
this.socket = s;
this.reportInstallerProcessId();
});
}
private readonly npmLocation: string | undefined) {
}
isKnownTypesPackageName(name: string): boolean {
@ -310,26 +292,23 @@ namespace ts.server {
if (this.installerPidReported) {
return;
}
if (this.socket && this.installer) {
this.sendEvent(0, "typingsInstallerPid", { pid: this.installer.pid });
if (this.installer && this.eventSender) {
this.eventSender.event({ pid: this.installer.pid }, "typingsInstallerPid");
this.installerPidReported = true;
}
}
private sendEvent(seq: number, event: string, body: any): void {
this.socket.write(formatMessage({ seq, type: "event", event, body }, this.logger, Buffer.byteLength, this.newLine), "utf8");
}
setTelemetrySender(telemetrySender: EventSender) {
this.eventSender = telemetrySender;
}
attach(projectService: ProjectService) {
attach(projectService: ProjectService, eventSender?: EventSender) {
this.projectService = projectService;
if (this.logger.hasLevel(LogLevel.requestTime)) {
this.logger.info("Binding...");
}
if (eventSender) {
this.eventSender = eventSender;
}
const args: string[] = [Arguments.GlobalCacheLocation, this.globalTypingsCacheLocation];
if (this.telemetryEnabled) {
args.push(Arguments.EnableTelemetry);
@ -353,10 +332,10 @@ namespace ts.server {
if (match) {
// if port is specified - use port + 1
// otherwise pick a default port depending on if 'debug' or 'inspect' and use its value + 1
const currentPort = match[2] !== undefined
? +match[2]
: match[1].charAt(0) === "d" ? 5858 : 9229;
execArgv.push(`--${match[1]}=${currentPort + 1}`);
// const currentPort = match[2] !== undefined
// ? +match[2]
// : match[1].charAt(0) === "d" ? 5858 : 9229;
// execArgv.push(`--${match[1]}=${currentPort + 1}`);
break;
}
}
@ -508,8 +487,8 @@ namespace ts.server {
this.projectService.updateTypingsForProject(response);
if (this.socket) {
this.sendEvent(0, "setTypings", response);
if (this.eventSender) {
this.eventSender.event(response, "setTypings");
}
break;
@ -530,10 +509,10 @@ namespace ts.server {
class IOSession extends Session {
constructor(options: IoSessionOptions) {
const { host, installerEventPort, globalTypingsCacheLocation, typingSafeListLocation, typesMapLocation, npmLocation, canUseEvents } = options;
const { host, eventPort, globalTypingsCacheLocation, typingSafeListLocation, typesMapLocation, npmLocation, canUseEvents } = options;
const typingsInstaller = disableAutomaticTypingAcquisition
? undefined
: new NodeTypingsInstaller(telemetryEnabled, logger, host, installerEventPort, globalTypingsCacheLocation, typingSafeListLocation, typesMapLocation, npmLocation, host.newLine);
: new NodeTypingsInstaller(telemetryEnabled, logger, host, globalTypingsCacheLocation, typingSafeListLocation, typesMapLocation, npmLocation);
super({
host,
@ -545,13 +524,10 @@ namespace ts.server {
hrtime: process.hrtime,
logger,
canUseEvents,
eventPort,
globalPlugins: options.globalPlugins,
pluginProbeLocations: options.pluginProbeLocations,
allowLocalPluginLoads: options.allowLocalPluginLoads });
if (telemetryEnabled && typingsInstaller) {
typingsInstaller.setTelemetrySender(this);
}
}
exit() {
@ -936,8 +912,8 @@ namespace ts.server {
const options: IoSessionOptions = {
host: sys,
cancellationToken,
installerEventPort: eventPort,
canUseEvents: eventPort === undefined,
eventPort,
canUseEvents: true,
useSingleInferredProject,
useInferredProjectPerProjectRoot,
disableAutomaticTypingAcquisition,

View File

@ -1,9 +1,19 @@
/// <reference types="node" />
/// <reference path="..\compiler\commandLineParser.ts" />
/// <reference path="..\services\services.ts" />
/// <reference path="protocol.ts" />
/// <reference path="editorServices.ts" />
namespace ts.server {
interface NodeSocket {
write(data: string, encoding: string): boolean;
}
const net: {
connect(options: { port: number }, onConnect?: () => void): NodeSocket
} = require("net");
interface StackTraceError extends Error {
stack?: string;
}
@ -253,6 +263,10 @@ namespace ts.server {
hrtime: (start?: number[]) => number[];
logger: Logger;
canUseEvents: boolean;
/**
* If defined, the Session will send events through `eventPort` instead of stdout.
*/
eventPort?: number;
eventHandler?: ProjectServiceEventHandler;
throttleWaitMilliseconds?: number;
@ -269,15 +283,19 @@ namespace ts.server {
private currentRequestId: number;
private errorCheck: MultistepOperation;
private eventHandler: ProjectServiceEventHandler;
private host: ServerHost;
private readonly cancellationToken: ServerCancellationToken;
protected readonly typingsInstaller: ITypingsInstaller;
private byteLength: (buf: string, encoding?: string) => number;
private hrtime: (start?: number[]) => number[];
protected logger: Logger;
private canUseEvents: boolean;
private eventPort: number | undefined;
private eventSocket: NodeSocket;
private eventHandler: ProjectServiceEventHandler;
public readonly event: EventSender["event"];
private socketEventQueue: { info: any, eventName: string}[] | undefined;
constructor(opts: SessionOptions) {
this.host = opts.host;
@ -286,14 +304,49 @@ namespace ts.server {
this.byteLength = opts.byteLength;
this.hrtime = opts.hrtime;
this.logger = opts.logger;
this.eventPort = opts.eventPort;
this.canUseEvents = opts.canUseEvents;
const { throttleWaitMilliseconds } = opts;
if (!this.canUseEvents) {
this.event = noop;
}
else if (this.eventPort) {
const s = net.connect({ port: this.eventPort }, () => {
this.eventSocket = s;
this.clearSocketEventQueue();
});
this.event = function <T>(info: T, eventName: string) {
if (!this.eventSocket) {
if (this.logger.hasLevel(LogLevel.verbose)) {
this.logger.info(`eventPort: event queued, but socket not yet initialized`);
}
(this.socketEventQueue || (this.socketEventQueue = [])).push({ info, eventName });
return;
}
else {
Debug.assert(this.socketEventQueue === undefined);
this.writeToEventSocket(info, eventName);
}
};
}
else {
this.event = function <T>(info: T, eventName: string) {
const ev: protocol.Event = {
seq: 0,
type: "event",
event: eventName,
body: info
};
this.send(ev);
};
}
this.eventHandler = this.canUseEvents
? opts.eventHandler || (event => this.defaultEventHandler(event))
: undefined;
const multistepOperationHost: MultistepOperationHost = {
executeWithRequestId: (requestId, action) => this.executeWithRequestId(requestId, action),
getCurrentRequestId: () => this.currentRequestId,
@ -314,20 +367,26 @@ namespace ts.server {
eventHandler: this.eventHandler,
globalPlugins: opts.globalPlugins,
pluginProbeLocations: opts.pluginProbeLocations,
allowLocalPluginLoads: opts.allowLocalPluginLoads
allowLocalPluginLoads: opts.allowLocalPluginLoads,
eventSender: this
};
this.projectService = new ProjectService(settings);
this.gcTimer = new GcTimer(this.host, /*delay*/ 7000, this.logger);
}
private clearSocketEventQueue() {
for (const event of this.socketEventQueue) {
this.writeToEventSocket(event.info, event.eventName);
}
this.socketEventQueue = undefined;
}
private writeToEventSocket(info: any, eventName: string): void {
this.eventSocket.write(formatMessage({ seq: 0, type: "event", event: eventName, body: info }, this.logger, Buffer.byteLength, this.host.newLine), "utf8");
}
private sendRequestCompletedEvent(requestId: number): void {
const event: protocol.RequestCompletedEvent = {
seq: 0,
type: "event",
event: "requestCompleted",
body: { request_seq: requestId }
};
this.send(event);
this.event<protocol.RequestCompletedEventBody>({ request_seq: requestId }, "requestCompleted");
}
private defaultEventHandler(event: ProjectServiceEvent) {
@ -392,26 +451,15 @@ namespace ts.server {
}
public send(msg: protocol.Message) {
if (msg.type === "event" && !this.canUseEvents) {
if (this.logger.hasLevel(LogLevel.verbose)) {
this.logger.info(`Session does not support events: ignored event: ${JSON.stringify(msg)}`);
}
return;
if (msg.type === "event") {
Debug.assert(this.canUseEvents);
Debug.assert(!this.eventPort);
}
this.host.write(formatMessage(msg, this.logger, this.byteLength, this.host.newLine));
}
public event<T>(info: T, eventName: string) {
const ev: protocol.Event = {
seq: 0,
type: "event",
event: eventName,
body: info
};
this.send(ev);
}
// For backwards-compatibility only.
/** @deprecated */
public output(info: any, cmdName: string, reqSeq?: number, errorMsg?: string): void {
this.doOutput(info, cmdName, reqSeq, /*success*/ !errorMsg, errorMsg);
}

View File

@ -10,7 +10,7 @@ namespace ts.server {
isKnownTypesPackageName(name: string): boolean;
installPackage(options: InstallPackageOptionsWithProjectRootPath): Promise<ApplyCodeActionCommandResult>;
enqueueInstallTypingsRequest(p: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>): void;
attach(projectService: ProjectService): void;
attach(projectService: ProjectService, eventSender?: EventSender): void;
onProjectClosed(p: Project): void;
readonly globalTypingsCacheLocation: string;
}