You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
A server that dies during a write is never restarted: the writer's stream error has no count, and closed() is skipped while stopping #1919
Versions: vscode-languageclient 10.1.2, vscode-jsonrpc 9.0.3; the same code is on main at e4ddcca.
What happens. With the default error handler, when a server process dies while the client is writing to it, the client stops for good instead of restarting the server. The output shows:
[Error] Client X: connection to server is erroring.
write EPIPE
Shutting down server.
[Error] Connection to server got closed. Server will not be restarted.
A crash that happens while the client is still writing, for example during the textDocument/didOpen notifications after a start, takes this path. A crash that the client notices first through the read side's end restarts as expected.
Cause. Three pieces combine:
WriteableStreamMessageWriter forwards the stream's own error event with no count (jsonrpc/src/common/messageWriter.ts, this.writable.onError((error: any) => this.fireError(error))). The failed write() also reports the error, with a count (handleError).
DefaultErrorHandler.error() continues only if count && count <= 3, so the count-less report means Shutdown.
handleConnectionError then calls stop(). When the connection closes, handleConnectionClosed sees ClientState.Stopping and skips errorHandler.closed(), which is what would have restarted the server, within the default limit of 5 crashes in 3 minutes.
Reproduction (vscode-jsonrpc). This child closes its stdin but keeps running, so writes fail with EPIPE before any end on the read side:
constcp=require('node:child_process');constrpc=require('vscode-jsonrpc/node');constchild=cp.spawn('sh',['-c','exec 0<&-; sleep 3'],{stdio: ['pipe','pipe','inherit']});constconn=rpc.createMessageConnection(newrpc.StreamMessageReader(child.stdout),newrpc.StreamMessageWriter(child.stdin));conn.onError(([e,m,c])=>console.log('onError',e.code,m?.method,c));conn.onClose(()=>console.log('onClose'));conn.listen();setTimeout(()=>{for(leti=0;i<4;i++)conn.sendNotification('test/n',{ i }).catch(()=>{});},300);setTimeout(()=>child.kill(),1500);
The first report has no count, so a LanguageClient with the default handler shuts down at it, and the close that follows doesn't restart the server. The same happens when the server process exits instead of closing its stdin, whenever the client writes before it reads the end of the server's output.
Possible fixes, any of which would restore the restart:
don't forward the stream's error event when a pending write's callback reports the same error;
give the forwarded error a count;
in handleConnectionClosed, consult closed() when the stop came from the error handler's Shutdown, not from the application.
Workaround. We pass an errorHandler whose error() returns Continue for EPIPE, ECONNRESET, and ERR_STREAM_DESTROYED, the codes after which Node destroys the pipe and the connection closes, and delegates everything else, and closed(), to createDefaultErrorHandler() (SaltedTan/virgil-language-server#216).
Activity