Skip to content

Commit 49d92d8

Browse files
committed
defer callback and remove handler object
1 parent 5c2fea4 commit 49d92d8

2 files changed

Lines changed: 101 additions & 98 deletions

File tree

src/server/server.ts

Lines changed: 88 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ namespace ts.server {
272272
readonly typingSafeListLocation: string,
273273
readonly typesMapLocation: string,
274274
private readonly npmLocation: string | undefined,
275-
private eventSender: EventSender) {
275+
private event: Event) {
276276
}
277277

278278
isKnownTypesPackageName(name: string): boolean {
@@ -304,8 +304,8 @@ namespace ts.server {
304304
if (this.installerPidReported) {
305305
return;
306306
}
307-
if (this.eventSender && this.installer) {
308-
this.eventSender.event({ pid: this.installer.pid }, "typingsInstallerPid");
307+
if (this.event && this.installer) {
308+
this.event({ pid: this.installer.pid }, "typingsInstallerPid");
309309
this.installerPidReported = true;
310310
}
311311
}
@@ -416,32 +416,32 @@ namespace ts.server {
416416
}
417417
case EventInitializationFailed:
418418
{
419-
if (!this.eventSender) {
419+
if (!this.event) {
420420
break;
421421
}
422422
const body: protocol.TypesInstallerInitializationFailedEventBody = {
423423
message: response.message
424424
};
425425
const eventName: protocol.TypesInstallerInitializationFailedEventName = "typesInstallerInitializationFailed";
426-
this.eventSender.event(body, eventName);
426+
this.event(body, eventName);
427427
break;
428428
}
429429
case EventBeginInstallTypes:
430430
{
431-
if (!this.eventSender) {
431+
if (!this.event) {
432432
break;
433433
}
434434
const body: protocol.BeginInstallTypesEventBody = {
435435
eventId: response.eventId,
436436
packages: response.packagesToInstall,
437437
};
438438
const eventName: protocol.BeginInstallTypesEventName = "beginInstallTypes";
439-
this.eventSender.event(body, eventName);
439+
this.event(body, eventName);
440440
break;
441441
}
442442
case EventEndInstallTypes:
443443
{
444-
if (!this.eventSender) {
444+
if (!this.event) {
445445
break;
446446
}
447447
if (this.telemetryEnabled) {
@@ -454,7 +454,7 @@ namespace ts.server {
454454
}
455455
};
456456
const eventName: protocol.TelemetryEventName = "telemetry";
457-
this.eventSender.event(body, eventName);
457+
this.event(body, eventName);
458458
}
459459

460460
const body: protocol.EndInstallTypesEventBody = {
@@ -463,7 +463,7 @@ namespace ts.server {
463463
success: response.installSuccess,
464464
};
465465
const eventName: protocol.EndInstallTypesEventName = "endInstallTypes";
466-
this.eventSender.event(body, eventName);
466+
this.event(body, eventName);
467467
break;
468468
}
469469
case ActionInvalidate:
@@ -495,8 +495,8 @@ namespace ts.server {
495495

496496
this.projectService.updateTypingsForProject(response);
497497

498-
if (this.eventSender) {
499-
this.eventSender.event(response, "setTypings");
498+
if (this.event) {
499+
this.event(response, "setTypings");
500500
}
501501

502502
break;
@@ -515,72 +515,49 @@ namespace ts.server {
515515
}
516516
}
517517

518-
export class DefaultEventSender implements EventSender {
519-
constructor(protected host: ServerHost,
520-
protected byteLength: (buf: string, encoding?: string) => number,
521-
protected logger: Logger,
522-
protected canUseEvents: boolean) { }
523-
524-
public event = <T>(body: T, eventName: string) => {
525-
const ev: protocol.Event = {
526-
seq: 0,
527-
type: "event",
528-
event: eventName,
529-
body
530-
};
531-
defaultSend(this.host, this.byteLength, this.logger, this.canUseEvents, ev);
532-
}
533-
}
518+
// export class DefaultEventSender implements EventSender {
519+
// constructor(protected host: ServerHost,
520+
// protected byteLength: (buf: string, encoding?: string) => number,
521+
// protected logger: Logger,
522+
// protected canUseEvents: boolean) { }
523+
524+
// public event = <T>(body: T, eventName: string) => {
525+
// const ev: protocol.Event = {
526+
// seq: 0,
527+
// type: "event",
528+
// event: eventName,
529+
// body
530+
// };
531+
// defaultSend(this.host, this.byteLength, this.logger, this.canUseEvents, ev);
532+
// }
533+
// }
534534

535-
class SocketEventSender implements EventSender {
535+
class IOSession extends Session {
536+
private eventPort: number;
536537
private eventSocket: NodeSocket | undefined;
537538
private socketEventQueue: { body: any, eventName: string }[] | undefined;
539+
private constructed: boolean | undefined;
538540

539-
constructor(private host: ServerHost,
540-
private byteLength: (buf: string, encoding?: string) => number,
541-
private logger: Logger,
542-
private eventPort: number) {
543-
544-
const s = net.connect({ port: this.eventPort }, () => {
545-
this.eventSocket = s;
546-
if (this.socketEventQueue) {
547-
// flush queue.
548-
for (const event of this.socketEventQueue) {
549-
this.writeToEventSocket(event.body, event.eventName);
550-
}
551-
this.socketEventQueue = undefined;
552-
}
553-
});
554-
}
555-
556-
public event = <T>(body: T, eventName: string) => {
557-
if (!this.eventSocket) {
558-
if (this.logger.hasLevel(LogLevel.verbose)) {
559-
this.logger.info(`eventPort: event "${eventName}" queued, but socket not yet initialized`);
560-
}
561-
(this.socketEventQueue || (this.socketEventQueue = [])).push({ body, eventName });
562-
return;
563-
}
564-
else {
565-
Debug.assert(this.socketEventQueue === undefined);
566-
this.writeToEventSocket(body, eventName);
567-
}
568-
};
569-
570-
private writeToEventSocket(body: any, eventName: string): void {
571-
this.eventSocket.write(formatMessage({ seq: 0, type: "event", event: eventName, body }, this.logger, this.byteLength, this.host.newLine), "utf8");
572-
}
573-
}
574-
575-
class IOSession extends Session {
576541
constructor(options: IoSessionOptions) {
577542
const { host, eventPort, globalTypingsCacheLocation, typingSafeListLocation, typesMapLocation, npmLocation, canUseEvents } = options;
578543

579-
const eventSender = eventPort && canUseEvents ? new SocketEventSender(host, Buffer.byteLength, logger, eventPort) : new DefaultEventSender(host, Buffer.byteLength, logger, canUseEvents);
544+
const event: Event | undefined = canUseEvents ?
545+
(body: {}, eventName: string) => {
546+
if (this.constructed) {
547+
this.event(body, eventName);
548+
}
549+
else {
550+
// It is unsafe to dereference `this` before initialization completes,
551+
// so we defer until the next tick.
552+
//
553+
// Construction should finish before the next tick fires, so we do not need to do this recursively.
554+
setImmediate(() => this.event(body, eventName));
555+
}
556+
} : undefined;
580557

581558
const typingsInstaller = disableAutomaticTypingAcquisition
582559
? undefined
583-
: new NodeTypingsInstaller(telemetryEnabled, logger, host, globalTypingsCacheLocation, typingSafeListLocation, typesMapLocation, npmLocation, eventSender);
560+
: new NodeTypingsInstaller(telemetryEnabled, logger, host, globalTypingsCacheLocation, typingSafeListLocation, typesMapLocation, npmLocation, event);
584561

585562
super({
586563
host,
@@ -592,10 +569,51 @@ namespace ts.server {
592569
hrtime: process.hrtime,
593570
logger,
594571
canUseEvents,
595-
eventSender,
596572
globalPlugins: options.globalPlugins,
597573
pluginProbeLocations: options.pluginProbeLocations,
598-
allowLocalPluginLoads: options.allowLocalPluginLoads });
574+
allowLocalPluginLoads: options.allowLocalPluginLoads
575+
});
576+
577+
this.eventPort = eventPort;
578+
if (this.canUseEvents && this.eventPort) {
579+
const s = net.connect({ port: this.eventPort }, () => {
580+
this.eventSocket = s;
581+
if (this.socketEventQueue) {
582+
// flush queue.
583+
for (const event of this.socketEventQueue) {
584+
this.writeToEventSocket(event.body, event.eventName);
585+
}
586+
this.socketEventQueue = undefined;
587+
}
588+
});
589+
}
590+
591+
this.constructed = true;
592+
}
593+
594+
event<T>(body: T, eventName: string): void {
595+
Debug.assert(this.constructed, "Should only call `IOSession.prototype.event` on an initialized IOSession");
596+
597+
if (this.canUseEvents && this.eventPort) {
598+
if (!this.eventSocket) {
599+
if (this.logger.hasLevel(LogLevel.verbose)) {
600+
this.logger.info(`eventPort: event "${eventName}" queued, but socket not yet initialized`);
601+
}
602+
(this.socketEventQueue || (this.socketEventQueue = [])).push({ body, eventName });
603+
return;
604+
}
605+
else {
606+
Debug.assert(this.socketEventQueue === undefined);
607+
this.writeToEventSocket(body, eventName);
608+
}
609+
}
610+
else {
611+
super.event(body, eventName);
612+
}
613+
}
614+
615+
private writeToEventSocket(body: any, eventName: string): void {
616+
this.eventSocket.write(formatMessage({ seq: 0, type: "event", event: eventName, body }, this.logger, this.byteLength, this.host.newLine), "utf8");
599617
}
600618

601619
exit() {

src/server/session.ts

Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -239,25 +239,12 @@ namespace ts.server {
239239
}
240240
}
241241

242+
export type Event = <T>(body: T, eventName: string) => void;
243+
242244
export interface EventSender {
243-
event: <T>(body: T, eventName: string) => void;
245+
event: Event;
244246
}
245247

246-
/** @internal */
247-
export function defaultSend(
248-
host: ServerHost,
249-
byteLength: (buf: string, encoding?: string) => number,
250-
logger: Logger,
251-
canUseEvents: boolean,
252-
msg: protocol.Message) {
253-
if (msg.type === "event" && !canUseEvents) {
254-
if (logger.hasLevel(LogLevel.verbose)) {
255-
logger.info(`Session does not support events: ignored event: ${JSON.stringify(msg)}`);
256-
}
257-
return;
258-
}
259-
host.write(formatMessage(msg, logger, byteLength, host.newLine));
260-
}
261248
export interface SessionOptions {
262249
host: ServerHost;
263250
cancellationToken: ServerCancellationToken;
@@ -271,10 +258,6 @@ namespace ts.server {
271258
* If falsy, all events are suppressed.
272259
*/
273260
canUseEvents: boolean;
274-
/**
275-
* An optional callback overriding the default behavior for sending messages.
276-
*/
277-
eventSender?: EventSender;
278261
eventHandler?: ProjectServiceEventHandler;
279262
throttleWaitMilliseconds?: number;
280263

@@ -291,14 +274,14 @@ namespace ts.server {
291274
private currentRequestId: number;
292275
private errorCheck: MultistepOperation;
293276

294-
private host: ServerHost;
277+
protected host: ServerHost;
295278
private readonly cancellationToken: ServerCancellationToken;
296279
protected readonly typingsInstaller: ITypingsInstaller;
297-
private byteLength: (buf: string, encoding?: string) => number;
280+
protected byteLength: (buf: string, encoding?: string) => number;
298281
private hrtime: (start?: number[]) => number[];
299282
protected logger: Logger;
300283

301-
private canUseEvents: boolean;
284+
protected canUseEvents: boolean;
302285
private eventHandler: ProjectServiceEventHandler;
303286

304287
constructor(opts: SessionOptions) {
@@ -312,10 +295,6 @@ namespace ts.server {
312295

313296
const { throttleWaitMilliseconds } = opts;
314297

315-
if (opts.eventSender) {
316-
this.event = opts.eventSender.event;
317-
}
318-
319298
this.eventHandler = this.canUseEvents
320299
? opts.eventHandler || (event => this.defaultEventHandler(event))
321300
: undefined;
@@ -411,7 +390,13 @@ namespace ts.server {
411390
}
412391

413392
public send(msg: protocol.Message) {
414-
defaultSend(this.host, this.byteLength, this.logger, this.canUseEvents, msg);
393+
if (msg.type === "event" && !this.canUseEvents) {
394+
if (this.logger.hasLevel(LogLevel.verbose)) {
395+
this.logger.info(`Session does not support events: ignored event: ${JSON.stringify(msg)}`);
396+
}
397+
return;
398+
}
399+
this.host.write(formatMessage(msg, this.logger, this.byteLength, this.host.newLine));
415400
}
416401

417402
public event<T>(body: T, eventName: string): void {

0 commit comments

Comments
 (0)