fix(lsp): make transport recovery ownership-safe
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
* Generic stdio language-server backend for `ctx.lsp`. One plugin instance configures a named table
|
||||
* of server commands and registers one isolated provider for each entry. Every provider lazily
|
||||
* single-flights one server process per canonical workspace realpath, serves transient-open queries
|
||||
* through it, and replaces a transport that dies between a pool liveness check and the next
|
||||
* read-only query. Providers read sources through Node APIs in the host namespace (not `ctx.fs`)
|
||||
* through it, and replaces a selected transport that fails before or during the next read-only
|
||||
* query. Providers read sources through Node APIs in the host namespace (not `ctx.fs`)
|
||||
* and trust their configured servers — no sandbox confinement.
|
||||
*
|
||||
* Namespace plugin (named exports, no default export). Lifecycle is effect-scoped: disposal
|
||||
@@ -224,16 +224,20 @@ class LocalLspProvider implements LspProvider {
|
||||
try {
|
||||
return await instance.query(request, source, signal)
|
||||
} catch (error) {
|
||||
// A child can die after the pre-query liveness check but before or during the next write.
|
||||
// Queries are read-only, so replace a newly failed transport once and retry transparently.
|
||||
if (!instance.dead) throw error
|
||||
// A selected child can have died while idle or fail during the next write. Queries are
|
||||
// read-only, so replace that transport once and retry transparently.
|
||||
if (!instance.isTransportFailure(error)) throw error
|
||||
await instance.dispose()
|
||||
this.evictIfCurrent(workspace, instance)
|
||||
this.assertActive(signal)
|
||||
instance = this.instanceFor(workspace)
|
||||
return await instance.query(request, source, signal)
|
||||
} finally {
|
||||
// Drop a crashed slot only when it still owns this instance; a replacement must survive.
|
||||
if (instance.dead) this.evictIfCurrent(workspace, instance)
|
||||
// Reach quiescence before dropping a dead slot; a replacement must survive this ownership check.
|
||||
if (instance.dead) {
|
||||
await instance.dispose()
|
||||
this.evictIfCurrent(workspace, instance)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user