Skip to content

Commit b2d9e4f

Browse files
committed
refactor(LSPClient): improve error handling and connection management
1 parent 68f2eff commit b2d9e4f

1 file changed

Lines changed: 38 additions & 24 deletions

File tree

source/mcp/lsp/LSPClient.ts

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,15 @@ export class LSPClient {
142142
this.markTransportClosed();
143143
});
144144

145-
// 子进程退出后,stdin 可能先于上层状态同步被销毁;这里要吞掉 error 事件,
146-
// 并把连接状态标记为关闭,避免后续继续写入触发未捕获异常导致 CLI 闪退。
147145
this.process.stdin?.on('error', () => {
148146
this.markTransportClosed();
149147
});
148+
this.process.stdout?.on('error', () => {
149+
this.markTransportClosed();
150+
});
151+
this.process.stderr?.on('error', () => {
152+
this.markTransportClosed();
153+
});
150154

151155
processManager.register(this.process);
152156

@@ -155,10 +159,20 @@ export class LSPClient {
155159
new StreamMessageWriter(this.process.stdin!),
156160
);
157161

158-
// Handle connection-level errors and closure
162+
// Handle connection-level errors and closure.
163+
// Suppress stream-destroyed errors that occur when the child process exits
164+
// while a write is still in-flight – these are expected during teardown.
159165
this.connection.onError(([error]) => {
160166
this.markTransportClosed();
161-
console.debug('LSP connection error:', error?.message || error);
167+
const msg = error?.message || '';
168+
if (
169+
msg.includes('stream was destroyed') ||
170+
msg.includes('ERR_STREAM_DESTROYED') ||
171+
msg.includes('write after end')
172+
) {
173+
return;
174+
}
175+
console.debug('LSP connection error:', msg || error);
162176
});
163177
this.connection.onClose(() => {
164178
this.markTransportClosed();
@@ -381,8 +395,8 @@ export class LSPClient {
381395
}
382396

383397
async gotoDefinition(uri: string, position: Position): Promise<Location[]> {
384-
if (!this.connection || !this.isInitialized) {
385-
throw new Error('LSP client not initialized');
398+
if (!this.canSendMessages()) {
399+
return [];
386400
}
387401

388402
if (this.config.language === 'csharp' && this.csharpSolutionLoadPromise) {
@@ -402,7 +416,7 @@ export class LSPClient {
402416
};
403417

404418
try {
405-
const result = await this.connection.sendRequest<
419+
const result = await this.connection!.sendRequest<
406420
Location | Location[] | null
407421
>('textDocument/definition', params);
408422

@@ -412,7 +426,7 @@ export class LSPClient {
412426

413427
return Array.isArray(result) ? result : [result];
414428
} catch (error) {
415-
console.debug('LSP gotoDefinition error:', error);
429+
this.markTransportClosed();
416430
return [];
417431
}
418432
}
@@ -422,8 +436,8 @@ export class LSPClient {
422436
position: Position,
423437
includeDeclaration = false,
424438
): Promise<Location[]> {
425-
if (!this.connection || !this.isInitialized) {
426-
throw new Error('LSP client not initialized');
439+
if (!this.canSendMessages()) {
440+
return [];
427441
}
428442

429443
if (!this.capabilities?.referencesProvider) {
@@ -437,21 +451,21 @@ export class LSPClient {
437451
};
438452

439453
try {
440-
const result = await this.connection.sendRequest<Location[] | null>(
454+
const result = await this.connection!.sendRequest<Location[] | null>(
441455
'textDocument/references',
442456
params,
443457
);
444458

445459
return result || [];
446460
} catch (error) {
447-
console.debug('LSP findReferences failed:', error);
461+
this.markTransportClosed();
448462
return [];
449463
}
450464
}
451465

452466
async hover(uri: string, position: Position): Promise<Hover | null> {
453-
if (!this.connection || !this.isInitialized) {
454-
throw new Error('LSP client not initialized');
467+
if (!this.canSendMessages()) {
468+
return null;
455469
}
456470

457471
if (!this.capabilities?.hoverProvider) {
@@ -464,21 +478,21 @@ export class LSPClient {
464478
};
465479

466480
try {
467-
const result = await this.connection.sendRequest<Hover | null>(
481+
const result = await this.connection!.sendRequest<Hover | null>(
468482
'textDocument/hover',
469483
params,
470484
);
471485

472486
return result;
473487
} catch (error) {
474-
console.debug('LSP hover failed:', error);
488+
this.markTransportClosed();
475489
return null;
476490
}
477491
}
478492

479493
async completion(uri: string, position: Position): Promise<CompletionItem[]> {
480-
if (!this.connection || !this.isInitialized) {
481-
throw new Error('LSP client not initialized');
494+
if (!this.canSendMessages()) {
495+
return [];
482496
}
483497

484498
if (!this.capabilities?.completionProvider) {
@@ -491,7 +505,7 @@ export class LSPClient {
491505
};
492506

493507
try {
494-
const result = await this.connection.sendRequest<
508+
const result = await this.connection!.sendRequest<
495509
CompletionItem[] | {items: CompletionItem[]} | null
496510
>('textDocument/completion', params);
497511

@@ -501,16 +515,16 @@ export class LSPClient {
501515

502516
return Array.isArray(result) ? result : result.items || [];
503517
} catch (error) {
504-
console.debug('LSP completion failed:', error);
518+
this.markTransportClosed();
505519
return [];
506520
}
507521
}
508522

509523
async documentSymbol(
510524
uri: string,
511525
): Promise<DocumentSymbol[] | SymbolInformation[]> {
512-
if (!this.connection || !this.isInitialized) {
513-
throw new Error('LSP client not initialized');
526+
if (!this.canSendMessages()) {
527+
return [];
514528
}
515529

516530
if (!this.capabilities?.documentSymbolProvider) {
@@ -522,13 +536,13 @@ export class LSPClient {
522536
};
523537

524538
try {
525-
const result = await this.connection.sendRequest<
539+
const result = await this.connection!.sendRequest<
526540
DocumentSymbol[] | SymbolInformation[] | null
527541
>('textDocument/documentSymbol', params);
528542

529543
return result || [];
530544
} catch (error) {
531-
console.debug('LSP documentSymbol failed:', error);
545+
this.markTransportClosed();
532546
return [];
533547
}
534548
}

0 commit comments

Comments
 (0)