Skip to content

Commit 23da1cf

Browse files
committed
send all events through common stream
1 parent 594ac01 commit 23da1cf

4 files changed

Lines changed: 99 additions & 73 deletions

File tree

src/server/editorServices.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ namespace ts.server {
1616
export const ProjectInfoTelemetryEvent = "projectInfo";
1717
// tslint:enable variable-name
1818

19+
// TODO: make these inherit from protocol.Event?
1920
export interface ProjectsUpdatedInBackgroundEvent {
2021
eventName: typeof ProjectsUpdatedInBackgroundEvent;
2122
data: { openFiles: string[]; };
@@ -320,6 +321,7 @@ namespace ts.server {
320321
pluginProbeLocations?: ReadonlyArray<string>;
321322
allowLocalPluginLoads?: boolean;
322323
typesMapLocation?: string;
324+
eventSender?: EventSender;
323325
}
324326

325327
type WatchFile = (host: ServerHost, file: string, cb: FileWatcherCallback, watchType: WatchType, project?: Project) => FileWatcher;
@@ -436,7 +438,7 @@ namespace ts.server {
436438
this.loadTypesMap();
437439
}
438440

439-
this.typingsInstaller.attach(this);
441+
this.typingsInstaller.attach(this, opts.eventSender);
440442

441443
this.typingsCache = new TypingsCache(this.typingsInstaller);
442444

src/server/server.ts

Lines changed: 21 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/// <reference types="node" />
21
/// <reference path="shared.ts" />
32
/// <reference path="session.ts" />
43

@@ -7,7 +6,7 @@ namespace ts.server {
76
host: ServerHost;
87
cancellationToken: ServerCancellationToken;
98
canUseEvents: boolean;
10-
installerEventPort: number;
9+
eventPort: number;
1110
useSingleInferredProject: boolean;
1211
useInferredProjectPerProjectRoot: boolean;
1312
disableAutomaticTypingAcquisition: boolean;
@@ -22,10 +21,6 @@ namespace ts.server {
2221
allowLocalPluginLoads: boolean;
2322
}
2423

25-
const net: {
26-
connect(options: { port: number }, onConnect?: () => void): NodeSocket
27-
} = require("net");
28-
2924
const childProcess: {
3025
fork(modulePath: string, args: string[], options?: { execArgv: string[], env?: MapLike<string> }): NodeChildProcess;
3126
execFileSync(file: string, args: string[], options: { stdio: "ignore", env: MapLike<string> }): string | Buffer;
@@ -83,10 +78,6 @@ namespace ts.server {
8378
pid: number;
8479
}
8580

86-
interface NodeSocket {
87-
write(data: string, encoding: string): boolean;
88-
}
89-
9081
interface ReadLineOptions {
9182
input: NodeJS.ReadableStream;
9283
output?: NodeJS.WritableStream;
@@ -244,9 +235,8 @@ namespace ts.server {
244235
class NodeTypingsInstaller implements ITypingsInstaller {
245236
private installer: NodeChildProcess;
246237
private installerPidReported = false;
247-
private socket: NodeSocket;
248238
private projectService: ProjectService;
249-
private eventSender: EventSender;
239+
private eventSender: EventSender | undefined;
250240
private activeRequestCount = 0;
251241
private requestQueue: QueuedOperation[] = [];
252242
private requestMap = createMap<QueuedOperation>(); // Maps operation ID to newest requestQueue entry with that ID
@@ -267,18 +257,10 @@ namespace ts.server {
267257
private readonly telemetryEnabled: boolean,
268258
private readonly logger: server.Logger,
269259
private readonly host: ServerHost,
270-
eventPort: number,
271260
readonly globalTypingsCacheLocation: string,
272261
readonly typingSafeListLocation: string,
273262
readonly typesMapLocation: string,
274-
private readonly npmLocation: string | undefined,
275-
private newLine: string) {
276-
if (eventPort) {
277-
const s = net.connect({ port: eventPort }, () => {
278-
this.socket = s;
279-
this.reportInstallerProcessId();
280-
});
281-
}
263+
private readonly npmLocation: string | undefined) {
282264
}
283265

284266
isKnownTypesPackageName(name: string): boolean {
@@ -310,26 +292,23 @@ namespace ts.server {
310292
if (this.installerPidReported) {
311293
return;
312294
}
313-
if (this.socket && this.installer) {
314-
this.sendEvent(0, "typingsInstallerPid", { pid: this.installer.pid });
295+
if (this.installer && this.eventSender) {
296+
this.eventSender.event({ pid: this.installer.pid }, "typingsInstallerPid");
315297
this.installerPidReported = true;
316298
}
317299
}
318300

319-
private sendEvent(seq: number, event: string, body: any): void {
320-
this.socket.write(formatMessage({ seq, type: "event", event, body }, this.logger, Buffer.byteLength, this.newLine), "utf8");
321-
}
322301

323-
setTelemetrySender(telemetrySender: EventSender) {
324-
this.eventSender = telemetrySender;
325-
}
326-
327-
attach(projectService: ProjectService) {
302+
attach(projectService: ProjectService, eventSender?: EventSender) {
328303
this.projectService = projectService;
329304
if (this.logger.hasLevel(LogLevel.requestTime)) {
330305
this.logger.info("Binding...");
331306
}
332307

308+
if (eventSender) {
309+
this.eventSender = eventSender;
310+
}
311+
333312
const args: string[] = [Arguments.GlobalCacheLocation, this.globalTypingsCacheLocation];
334313
if (this.telemetryEnabled) {
335314
args.push(Arguments.EnableTelemetry);
@@ -353,10 +332,10 @@ namespace ts.server {
353332
if (match) {
354333
// if port is specified - use port + 1
355334
// otherwise pick a default port depending on if 'debug' or 'inspect' and use its value + 1
356-
const currentPort = match[2] !== undefined
357-
? +match[2]
358-
: match[1].charAt(0) === "d" ? 5858 : 9229;
359-
execArgv.push(`--${match[1]}=${currentPort + 1}`);
335+
// const currentPort = match[2] !== undefined
336+
// ? +match[2]
337+
// : match[1].charAt(0) === "d" ? 5858 : 9229;
338+
// execArgv.push(`--${match[1]}=${currentPort + 1}`);
360339
break;
361340
}
362341
}
@@ -508,8 +487,8 @@ namespace ts.server {
508487

509488
this.projectService.updateTypingsForProject(response);
510489

511-
if (this.socket) {
512-
this.sendEvent(0, "setTypings", response);
490+
if (this.eventSender) {
491+
this.eventSender.event(response, "setTypings");
513492
}
514493

515494
break;
@@ -530,10 +509,10 @@ namespace ts.server {
530509

531510
class IOSession extends Session {
532511
constructor(options: IoSessionOptions) {
533-
const { host, installerEventPort, globalTypingsCacheLocation, typingSafeListLocation, typesMapLocation, npmLocation, canUseEvents } = options;
512+
const { host, eventPort, globalTypingsCacheLocation, typingSafeListLocation, typesMapLocation, npmLocation, canUseEvents } = options;
534513
const typingsInstaller = disableAutomaticTypingAcquisition
535514
? undefined
536-
: new NodeTypingsInstaller(telemetryEnabled, logger, host, installerEventPort, globalTypingsCacheLocation, typingSafeListLocation, typesMapLocation, npmLocation, host.newLine);
515+
: new NodeTypingsInstaller(telemetryEnabled, logger, host, globalTypingsCacheLocation, typingSafeListLocation, typesMapLocation, npmLocation);
537516

538517
super({
539518
host,
@@ -545,13 +524,10 @@ namespace ts.server {
545524
hrtime: process.hrtime,
546525
logger,
547526
canUseEvents,
527+
eventPort,
548528
globalPlugins: options.globalPlugins,
549529
pluginProbeLocations: options.pluginProbeLocations,
550530
allowLocalPluginLoads: options.allowLocalPluginLoads });
551-
552-
if (telemetryEnabled && typingsInstaller) {
553-
typingsInstaller.setTelemetrySender(this);
554-
}
555531
}
556532

557533
exit() {
@@ -936,8 +912,8 @@ namespace ts.server {
936912
const options: IoSessionOptions = {
937913
host: sys,
938914
cancellationToken,
939-
installerEventPort: eventPort,
940-
canUseEvents: eventPort === undefined,
915+
eventPort,
916+
canUseEvents: true,
941917
useSingleInferredProject,
942918
useInferredProjectPerProjectRoot,
943919
disableAutomaticTypingAcquisition,

src/server/session.ts

Lines changed: 74 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
1+
/// <reference types="node" />
12
/// <reference path="..\compiler\commandLineParser.ts" />
23
/// <reference path="..\services\services.ts" />
34
/// <reference path="protocol.ts" />
45
/// <reference path="editorServices.ts" />
56

67
namespace ts.server {
8+
9+
interface NodeSocket {
10+
write(data: string, encoding: string): boolean;
11+
}
12+
13+
const net: {
14+
connect(options: { port: number }, onConnect?: () => void): NodeSocket
15+
} = require("net");
16+
717
interface StackTraceError extends Error {
818
stack?: string;
919
}
@@ -253,6 +263,10 @@ namespace ts.server {
253263
hrtime: (start?: number[]) => number[];
254264
logger: Logger;
255265
canUseEvents: boolean;
266+
/**
267+
* If defined, the Session will send events through `eventPort` instead of stdout.
268+
*/
269+
eventPort?: number;
256270
eventHandler?: ProjectServiceEventHandler;
257271
throttleWaitMilliseconds?: number;
258272

@@ -269,15 +283,19 @@ namespace ts.server {
269283
private currentRequestId: number;
270284
private errorCheck: MultistepOperation;
271285

272-
private eventHandler: ProjectServiceEventHandler;
273-
274286
private host: ServerHost;
275287
private readonly cancellationToken: ServerCancellationToken;
276288
protected readonly typingsInstaller: ITypingsInstaller;
277289
private byteLength: (buf: string, encoding?: string) => number;
278290
private hrtime: (start?: number[]) => number[];
279291
protected logger: Logger;
292+
280293
private canUseEvents: boolean;
294+
private eventPort: number | undefined;
295+
private eventSocket: NodeSocket;
296+
private eventHandler: ProjectServiceEventHandler;
297+
public readonly event: EventSender["event"];
298+
private socketEventQueue: { info: any, eventName: string}[] | undefined;
281299

282300
constructor(opts: SessionOptions) {
283301
this.host = opts.host;
@@ -286,14 +304,49 @@ namespace ts.server {
286304
this.byteLength = opts.byteLength;
287305
this.hrtime = opts.hrtime;
288306
this.logger = opts.logger;
307+
this.eventPort = opts.eventPort;
289308
this.canUseEvents = opts.canUseEvents;
290309

291310
const { throttleWaitMilliseconds } = opts;
292311

312+
if (!this.canUseEvents) {
313+
this.event = noop;
314+
}
315+
else if (this.eventPort) {
316+
const s = net.connect({ port: this.eventPort }, () => {
317+
this.eventSocket = s;
318+
this.clearSocketEventQueue();
319+
});
320+
321+
this.event = function <T>(info: T, eventName: string) {
322+
if (!this.eventSocket) {
323+
if (this.logger.hasLevel(LogLevel.verbose)) {
324+
this.logger.info(`eventPort: event queued, but socket not yet initialized`);
325+
}
326+
(this.socketEventQueue || (this.socketEventQueue = [])).push({ info, eventName });
327+
return;
328+
}
329+
else {
330+
Debug.assert(this.socketEventQueue === undefined);
331+
this.writeToEventSocket(info, eventName);
332+
}
333+
};
334+
}
335+
else {
336+
this.event = function <T>(info: T, eventName: string) {
337+
const ev: protocol.Event = {
338+
seq: 0,
339+
type: "event",
340+
event: eventName,
341+
body: info
342+
};
343+
this.send(ev);
344+
};
345+
}
346+
293347
this.eventHandler = this.canUseEvents
294348
? opts.eventHandler || (event => this.defaultEventHandler(event))
295349
: undefined;
296-
297350
const multistepOperationHost: MultistepOperationHost = {
298351
executeWithRequestId: (requestId, action) => this.executeWithRequestId(requestId, action),
299352
getCurrentRequestId: () => this.currentRequestId,
@@ -314,20 +367,26 @@ namespace ts.server {
314367
eventHandler: this.eventHandler,
315368
globalPlugins: opts.globalPlugins,
316369
pluginProbeLocations: opts.pluginProbeLocations,
317-
allowLocalPluginLoads: opts.allowLocalPluginLoads
370+
allowLocalPluginLoads: opts.allowLocalPluginLoads,
371+
eventSender: this
318372
};
319373
this.projectService = new ProjectService(settings);
320374
this.gcTimer = new GcTimer(this.host, /*delay*/ 7000, this.logger);
321375
}
322376

377+
private clearSocketEventQueue() {
378+
for (const event of this.socketEventQueue) {
379+
this.writeToEventSocket(event.info, event.eventName);
380+
}
381+
this.socketEventQueue = undefined;
382+
}
383+
384+
private writeToEventSocket(info: any, eventName: string): void {
385+
this.eventSocket.write(formatMessage({ seq: 0, type: "event", event: eventName, body: info }, this.logger, Buffer.byteLength, this.host.newLine), "utf8");
386+
}
387+
323388
private sendRequestCompletedEvent(requestId: number): void {
324-
const event: protocol.RequestCompletedEvent = {
325-
seq: 0,
326-
type: "event",
327-
event: "requestCompleted",
328-
body: { request_seq: requestId }
329-
};
330-
this.send(event);
389+
this.event<protocol.RequestCompletedEventBody>({ request_seq: requestId }, "requestCompleted");
331390
}
332391

333392
private defaultEventHandler(event: ProjectServiceEvent) {
@@ -392,26 +451,15 @@ namespace ts.server {
392451
}
393452

394453
public send(msg: protocol.Message) {
395-
if (msg.type === "event" && !this.canUseEvents) {
396-
if (this.logger.hasLevel(LogLevel.verbose)) {
397-
this.logger.info(`Session does not support events: ignored event: ${JSON.stringify(msg)}`);
398-
}
399-
return;
454+
if (msg.type === "event") {
455+
Debug.assert(this.canUseEvents);
456+
Debug.assert(!this.eventPort);
400457
}
401458
this.host.write(formatMessage(msg, this.logger, this.byteLength, this.host.newLine));
402459
}
403460

404-
public event<T>(info: T, eventName: string) {
405-
const ev: protocol.Event = {
406-
seq: 0,
407-
type: "event",
408-
event: eventName,
409-
body: info
410-
};
411-
this.send(ev);
412-
}
413-
414461
// For backwards-compatibility only.
462+
/** @deprecated */
415463
public output(info: any, cmdName: string, reqSeq?: number, errorMsg?: string): void {
416464
this.doOutput(info, cmdName, reqSeq, /*success*/ !errorMsg, errorMsg);
417465
}

src/server/typingsCache.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace ts.server {
1010
isKnownTypesPackageName(name: string): boolean;
1111
installPackage(options: InstallPackageOptionsWithProjectRootPath): Promise<ApplyCodeActionCommandResult>;
1212
enqueueInstallTypingsRequest(p: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>): void;
13-
attach(projectService: ProjectService): void;
13+
attach(projectService: ProjectService, eventSender?: EventSender): void;
1414
onProjectClosed(p: Project): void;
1515
readonly globalTypingsCacheLocation: string;
1616
}

0 commit comments

Comments
 (0)