@kenkaiiii/ggcoder 5.34.2 → 5.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-sidecar.js +11 -0
- package/dist/app-sidecar.js.map +1 -1
- package/dist/core/compaction/compactor.d.ts +50 -0
- package/dist/core/compaction/compactor.d.ts.map +1 -1
- package/dist/core/compaction/compactor.js +136 -35
- package/dist/core/compaction/compactor.js.map +1 -1
- package/dist/core/compaction/compactor.test.js +131 -1
- package/dist/core/compaction/compactor.test.js.map +1 -1
- package/dist/core/lsp/manager.d.ts +44 -9
- package/dist/core/lsp/manager.d.ts.map +1 -1
- package/dist/core/lsp/manager.js +64 -69
- package/dist/core/lsp/manager.js.map +1 -1
- package/dist/core/lsp/pool.d.ts +113 -0
- package/dist/core/lsp/pool.d.ts.map +1 -0
- package/dist/core/lsp/pool.js +255 -0
- package/dist/core/lsp/pool.js.map +1 -0
- package/dist/core/lsp/pool.test.d.ts +2 -0
- package/dist/core/lsp/pool.test.d.ts.map +1 -0
- package/dist/core/lsp/pool.test.js +286 -0
- package/dist/core/lsp/pool.test.js.map +1 -0
- package/dist/core/lsp/servers.d.ts.map +1 -1
- package/dist/core/lsp/servers.js +44 -3
- package/dist/core/lsp/servers.js.map +1 -1
- package/dist/core/lsp/servers.test.d.ts +2 -0
- package/dist/core/lsp/servers.test.d.ts.map +1 -0
- package/dist/core/lsp/servers.test.js +67 -0
- package/dist/core/lsp/servers.test.js.map +1 -0
- package/dist/core/lsp/windows.test.js +2 -14
- package/dist/core/lsp/windows.test.js.map +1 -1
- package/dist/core/mcp/client.d.ts +40 -0
- package/dist/core/mcp/client.d.ts.map +1 -1
- package/dist/core/mcp/client.js +119 -1
- package/dist/core/mcp/client.js.map +1 -1
- package/dist/core/mcp/index.d.ts +2 -0
- package/dist/core/mcp/index.d.ts.map +1 -1
- package/dist/core/mcp/index.js +1 -0
- package/dist/core/mcp/index.js.map +1 -1
- package/dist/core/mcp/session-elicitation-wiring.test.d.ts +2 -0
- package/dist/core/mcp/session-elicitation-wiring.test.d.ts.map +1 -0
- package/dist/core/mcp/session-elicitation-wiring.test.js +138 -0
- package/dist/core/mcp/session-elicitation-wiring.test.js.map +1 -0
- package/dist/core/mcp/shared-pool.d.ts +158 -0
- package/dist/core/mcp/shared-pool.d.ts.map +1 -0
- package/dist/core/mcp/shared-pool.js +235 -0
- package/dist/core/mcp/shared-pool.js.map +1 -0
- package/dist/core/mcp/shared-pool.test.d.ts +2 -0
- package/dist/core/mcp/shared-pool.test.d.ts.map +1 -0
- package/dist/core/mcp/shared-pool.test.js +425 -0
- package/dist/core/mcp/shared-pool.test.js.map +1 -0
- package/dist/core/mcp/types.d.ts +12 -0
- package/dist/core/mcp/types.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/core/lsp/manager.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { log } from "../logger.js";
|
|
3
|
-
import { LspClient } from "./client.js";
|
|
4
3
|
import { formatDiagnostics } from "./format.js";
|
|
4
|
+
import { lspClientPool } from "./pool.js";
|
|
5
5
|
import { LSP_SERVER_CATALOG, findProjectRoot, serverForFile, } from "./servers.js";
|
|
6
6
|
const DEFAULT_WARM_BUDGET_MS = 3000;
|
|
7
7
|
const DEFAULT_FIRST_BUDGET_MS = 8000;
|
|
@@ -15,11 +15,13 @@ const DEFAULT_FIRST_BUDGET_MS = 8000;
|
|
|
15
15
|
*/
|
|
16
16
|
const DEFAULT_SETTLE_MS = 1500;
|
|
17
17
|
const DEFAULT_SNAPSHOT_LIMIT = 100;
|
|
18
|
-
const INIT_TIMEOUT_MS = 10_000;
|
|
19
18
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* Per-session view over the process-wide language-server pool (see pool.ts).
|
|
20
|
+
*
|
|
21
|
+
* The manager owns only session-scoped state — per-file outcome snapshots and
|
|
22
|
+
* which keys have gone warm — while the servers themselves are shared by every
|
|
23
|
+
* session in the process and reclaimed when idle. That split is what stops two
|
|
24
|
+
* windows open on one repo from running two full tsserver stacks.
|
|
23
25
|
*/
|
|
24
26
|
export class LspManager {
|
|
25
27
|
cwd;
|
|
@@ -28,10 +30,18 @@ export class LspManager {
|
|
|
28
30
|
firstBudgetMs;
|
|
29
31
|
settleMs;
|
|
30
32
|
snapshotLimit;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
pool;
|
|
34
|
+
/**
|
|
35
|
+
* Keys that have completed a diagnostics pass, mapped to the pool generation
|
|
36
|
+
* that served it.
|
|
37
|
+
*
|
|
38
|
+
* Storing the generation rather than a bare flag is what keeps "warm" honest
|
|
39
|
+
* across idle reclamation: once the pool retires a server, the replacement is
|
|
40
|
+
* genuinely cold again, and treating it as warm would give it the 3s budget
|
|
41
|
+
* instead of 8s AND skip the cold-load settle guard — turning tsserver's
|
|
42
|
+
* premature empty publish into a reported "clean", which is a false all-clear.
|
|
43
|
+
*/
|
|
44
|
+
warmKeys = new Map();
|
|
35
45
|
latestOutcomes = new Map();
|
|
36
46
|
shutDown = false;
|
|
37
47
|
constructor(cwd, options) {
|
|
@@ -41,6 +51,7 @@ export class LspManager {
|
|
|
41
51
|
this.firstBudgetMs = options?.firstBudgetMs ?? DEFAULT_FIRST_BUDGET_MS;
|
|
42
52
|
this.settleMs = Math.max(0, options?.settleMs ?? DEFAULT_SETTLE_MS);
|
|
43
53
|
this.snapshotLimit = Math.max(1, options?.snapshotLimit ?? DEFAULT_SNAPSHOT_LIMIT);
|
|
54
|
+
this.pool = options?.pool ?? lspClientPool;
|
|
44
55
|
}
|
|
45
56
|
/**
|
|
46
57
|
* Compatibility surface used by edit/write tools. Diagnostics remain visible;
|
|
@@ -61,7 +72,7 @@ export class LspManager {
|
|
|
61
72
|
return this.record(this.outcome("unsupported", normalizedFilePath));
|
|
62
73
|
const root = findProjectRoot(normalizedFilePath, spec.rootMarkers, this.cwd);
|
|
63
74
|
const key = `${spec.id}\u0000${root}`;
|
|
64
|
-
const budgetMs = this.
|
|
75
|
+
const budgetMs = this.isWarm(key, spec, root) ? this.warmBudgetMs : this.firstBudgetMs;
|
|
65
76
|
const work = this.collect(key, spec, root, normalizedFilePath, content, budgetMs);
|
|
66
77
|
// Leave slow initialization/indexing alive to warm the next edit. Record
|
|
67
78
|
// its eventual evidence too, but report this call honestly as timed out.
|
|
@@ -82,22 +93,36 @@ export class LspManager {
|
|
|
82
93
|
getLatestOutcome(filePath) {
|
|
83
94
|
return this.latestOutcomes.get(path.resolve(this.cwd, filePath));
|
|
84
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Has this session already completed a pass against the server that is live
|
|
98
|
+
* NOW? False once the pool has rebuilt it, because the replacement is cold.
|
|
99
|
+
*/
|
|
100
|
+
isWarm(key, spec, root) {
|
|
101
|
+
const warmedGeneration = this.warmKeys.get(key);
|
|
102
|
+
return (warmedGeneration !== undefined && warmedGeneration === this.pool.generationFor(spec, root));
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Retained stderr of a language server this session is using, for failure
|
|
106
|
+
* reporting when a server accepts a document and then never answers.
|
|
107
|
+
*/
|
|
108
|
+
serverStderrTail() {
|
|
109
|
+
return this.pool.stderrTail(this);
|
|
110
|
+
}
|
|
85
111
|
/** Newest retained per-file evidence snapshots. */
|
|
86
112
|
getLatestOutcomes() {
|
|
87
113
|
return [...this.latestOutcomes.values()].reverse();
|
|
88
114
|
}
|
|
89
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* Release this session's claim on every server it used. Safe in process exit
|
|
117
|
+
* handlers.
|
|
118
|
+
*
|
|
119
|
+
* This drops REFERENCES, not processes: a server another session still holds
|
|
120
|
+
* keeps running, and one left with no holders is shut down immediately. The
|
|
121
|
+
* name is kept because every caller wires it into an exit path.
|
|
122
|
+
*/
|
|
90
123
|
shutdownAll() {
|
|
91
124
|
this.shutDown = true;
|
|
92
|
-
|
|
93
|
-
void pending
|
|
94
|
-
.then((resolution) => {
|
|
95
|
-
if (resolution.status === "ready")
|
|
96
|
-
resolution.client.shutdown();
|
|
97
|
-
})
|
|
98
|
-
.catch(() => { });
|
|
99
|
-
}
|
|
100
|
-
this.clients.clear();
|
|
125
|
+
this.pool.release(this);
|
|
101
126
|
this.warmKeys.clear();
|
|
102
127
|
}
|
|
103
128
|
outcome(kind, filePath) {
|
|
@@ -119,23 +144,37 @@ export class LspManager {
|
|
|
119
144
|
// here has to fit inside the same deadline or a good answer arrives after
|
|
120
145
|
// the caller has already given up and reported a timeout.
|
|
121
146
|
const deadline = Date.now() + budgetMs;
|
|
122
|
-
const resolution = await this.
|
|
147
|
+
const resolution = await this.pool.retain(spec, root, this);
|
|
123
148
|
if (resolution.status !== "ready")
|
|
124
149
|
return this.outcome(resolution.status, filePath);
|
|
125
150
|
const { client } = resolution;
|
|
126
151
|
if (!client.isAlive) {
|
|
127
|
-
this.
|
|
152
|
+
this.pool.markDead(spec, root);
|
|
128
153
|
log("WARN", "lsp", `${spec.id} server died`, { root });
|
|
129
154
|
return this.outcome("server_failed", filePath);
|
|
130
155
|
}
|
|
156
|
+
// Hold the entry open for the whole pass: a first-file load can take
|
|
157
|
+
// seconds, and the idle sweep must not reclaim a server that is mid-answer.
|
|
158
|
+
const endCall = this.pool.beginCall(spec, root);
|
|
159
|
+
try {
|
|
160
|
+
return await this.collectFrom(client, key, spec, root, filePath, content, budgetMs, deadline);
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
endCall();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** The diagnostics exchange itself, against an initialized live client. */
|
|
167
|
+
async collectFrom(client, key, spec, root, filePath, content, budgetMs, deadline) {
|
|
131
168
|
// Sampled BEFORE the collect: a cold client is the one that has to load the
|
|
132
169
|
// project, and therefore the only one that can answer prematurely.
|
|
133
|
-
const wasCold = !this.
|
|
170
|
+
const wasCold = !this.isWarm(key, spec, root);
|
|
134
171
|
const uri = client.syncDocument(filePath, content);
|
|
135
172
|
let diagnostics = await client.collectDiagnostics(uri, budgetMs);
|
|
136
|
-
|
|
173
|
+
// Record WHICH build of the server went warm, so a later reclamation of it
|
|
174
|
+
// is detectable rather than silently inherited as warm.
|
|
175
|
+
this.warmKeys.set(key, this.pool.generationFor(spec, root));
|
|
137
176
|
if (!client.isAlive) {
|
|
138
|
-
this.
|
|
177
|
+
this.pool.markDead(spec, root);
|
|
139
178
|
return this.outcome("server_failed", filePath);
|
|
140
179
|
}
|
|
141
180
|
if (diagnostics === null) {
|
|
@@ -176,50 +215,6 @@ export class LspManager {
|
|
|
176
215
|
}
|
|
177
216
|
return this.outcome(client.hasActiveProgress ? "low_confidence" : "clean", filePath);
|
|
178
217
|
}
|
|
179
|
-
ensureClient(key, spec, root) {
|
|
180
|
-
const existing = this.clients.get(key);
|
|
181
|
-
if (existing)
|
|
182
|
-
return existing;
|
|
183
|
-
const pending = (async () => {
|
|
184
|
-
const command = spec.resolveCommand(root);
|
|
185
|
-
if (!command) {
|
|
186
|
-
log("INFO", "lsp", `${spec.id} language server not available`, { root });
|
|
187
|
-
return { status: "unavailable" };
|
|
188
|
-
}
|
|
189
|
-
// `client` is declared outside the try so one that fails to initialize
|
|
190
|
-
// can still be killed. `new LspClient` SPAWNS the process, so discarding
|
|
191
|
-
// the reference on a throw leaked the server forever — one orphan per
|
|
192
|
-
// (server, root) every time initialize timed out, for the life of the
|
|
193
|
-
// session. Invisible on POSIX; on Windows the orphan keeps handles open
|
|
194
|
-
// in the project directory.
|
|
195
|
-
let client;
|
|
196
|
-
try {
|
|
197
|
-
const startedAt = Date.now();
|
|
198
|
-
client = new LspClient(spec, root, command);
|
|
199
|
-
await client.initialize(INIT_TIMEOUT_MS);
|
|
200
|
-
if (!client.isAlive)
|
|
201
|
-
return { status: "server_failed" };
|
|
202
|
-
log("INFO", "lsp", `${spec.id} server initialized`, {
|
|
203
|
-
root,
|
|
204
|
-
ms: String(Date.now() - startedAt),
|
|
205
|
-
});
|
|
206
|
-
return { status: "ready", client };
|
|
207
|
-
}
|
|
208
|
-
catch (error) {
|
|
209
|
-
log("WARN", "lsp", `${spec.id} server failed to start`, {
|
|
210
|
-
root,
|
|
211
|
-
error: error instanceof Error ? error.message : String(error),
|
|
212
|
-
// The server's own last words — usually the only explanation of why
|
|
213
|
-
// the handshake never completed.
|
|
214
|
-
stderr: client?.stderrTail() || "(none)",
|
|
215
|
-
});
|
|
216
|
-
client?.terminate();
|
|
217
|
-
return { status: "server_failed" };
|
|
218
|
-
}
|
|
219
|
-
})();
|
|
220
|
-
this.clients.set(key, pending);
|
|
221
|
-
return pending;
|
|
222
|
-
}
|
|
223
218
|
}
|
|
224
219
|
/** Race work against a hard budget while allowing it to settle in background. */
|
|
225
220
|
function withBudget(work, budgetMs, onTimeout) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../../src/core/lsp/manager.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../../src/core/lsp/manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { LspClient } from "./client.js";
|
|
2
|
+
import type { LspServerSpec } from "./servers.js";
|
|
3
|
+
/**
|
|
4
|
+
* Process-wide pool of language-server clients, shared by every `LspManager`
|
|
5
|
+
* in the daemon and reclaimed when idle.
|
|
6
|
+
*
|
|
7
|
+
* Two separate costs made LSP the heaviest thing in the app, and each needs a
|
|
8
|
+
* different mechanism:
|
|
9
|
+
*
|
|
10
|
+
* - **Duplication.** Every `AgentSession` built its own `LspManager`, so two
|
|
11
|
+
* windows open on one repo ran two complete tsserver stacks over identical
|
|
12
|
+
* files. Fixed by keying clients on (server, project root) for the whole
|
|
13
|
+
* process and REFERENCE COUNTING the managers holding them, so one session
|
|
14
|
+
* closing cannot pull a server out from under another.
|
|
15
|
+
* - **Pinning.** A manager's map had no expiry, so any root ever touched kept
|
|
16
|
+
* its server resident for the daemon's lifetime — measured at two roots with
|
|
17
|
+
* no window open at all, holding ~330 MB between them. Fixed by the idle
|
|
18
|
+
* sweep: a server unused for `idleTtlMs` is shut down even while sessions
|
|
19
|
+
* still hold it, because agent edits arrive in bursts and the next burst can
|
|
20
|
+
* afford a cold start (`firstBudgetMs` already assumes one).
|
|
21
|
+
*
|
|
22
|
+
* Holders are tracked as objects rather than counts so that `retain` is
|
|
23
|
+
* idempotent: a manager calls it on every diagnostics pass and stays exactly
|
|
24
|
+
* one reference, and an idle-evicted entry can be rebuilt without the old
|
|
25
|
+
* bookkeeping leaking into the new one.
|
|
26
|
+
*/
|
|
27
|
+
/** Outcome of asking for a client: ready, or why it cannot be used. */
|
|
28
|
+
export type PooledClient = {
|
|
29
|
+
status: "ready";
|
|
30
|
+
client: LspClient;
|
|
31
|
+
} | {
|
|
32
|
+
status: "unavailable" | "server_failed";
|
|
33
|
+
};
|
|
34
|
+
export interface LspClientPoolOptions {
|
|
35
|
+
idleTtlMs?: number;
|
|
36
|
+
sweepIntervalMs?: number;
|
|
37
|
+
}
|
|
38
|
+
export declare class LspClientPool {
|
|
39
|
+
private readonly entries;
|
|
40
|
+
/**
|
|
41
|
+
* Build counter per key, kept OUTSIDE `entries` so it survives eviction —
|
|
42
|
+
* that is the whole point: it tells a caller its warm server is gone.
|
|
43
|
+
*/
|
|
44
|
+
private readonly generations;
|
|
45
|
+
private readonly idleTtlMs;
|
|
46
|
+
private readonly sweepIntervalMs;
|
|
47
|
+
private sweepTimer?;
|
|
48
|
+
constructor(options?: LspClientPoolOptions);
|
|
49
|
+
private keyFor;
|
|
50
|
+
/**
|
|
51
|
+
* Get-or-spawn the client for (spec, root) and record `holder` as retaining
|
|
52
|
+
* it. Concurrent callers share one spawn: the entry and its pending promise
|
|
53
|
+
* are registered synchronously before the first `await`.
|
|
54
|
+
*/
|
|
55
|
+
retain(spec: LspServerSpec, root: string, holder: object): Promise<PooledClient>;
|
|
56
|
+
/**
|
|
57
|
+
* Identity of the server currently live for (spec, root), or 0 when none is.
|
|
58
|
+
*
|
|
59
|
+
* A caller records this alongside its own "I have warmed this server" state,
|
|
60
|
+
* and a later mismatch means the server it warmed is gone. Reporting the LIVE
|
|
61
|
+
* entry rather than the next build number is essential: callers ask before
|
|
62
|
+
* `retain`, when a reclaimed server has not been rebuilt yet, and a stale
|
|
63
|
+
* number there would look like a match and hand a cold server a warm budget.
|
|
64
|
+
*/
|
|
65
|
+
generationFor(spec: LspServerSpec, root: string): number;
|
|
66
|
+
/**
|
|
67
|
+
* Mark a diagnostics pass as in flight against (spec, root), returning the
|
|
68
|
+
* function that ends it. A first-file pass can take seconds; without this the
|
|
69
|
+
* sweep could shut the server down while it was still answering.
|
|
70
|
+
*/
|
|
71
|
+
beginCall(spec: LspServerSpec, root: string): () => void;
|
|
72
|
+
/**
|
|
73
|
+
* Record that this client died after starting, so later passes report
|
|
74
|
+
* `server_failed` instead of talking to a corpse.
|
|
75
|
+
*
|
|
76
|
+
* The failure is CACHED rather than cleared, preserving the pre-pool
|
|
77
|
+
* contract: a server that dies on a document is not respawned on every
|
|
78
|
+
* following write. The dead process is reaped either way.
|
|
79
|
+
*/
|
|
80
|
+
markDead(spec: LspServerSpec, root: string): void;
|
|
81
|
+
/**
|
|
82
|
+
* Drop `holder`'s claim on every entry it retained. Servers still held by
|
|
83
|
+
* another manager keep running; those left with no holders shut down now
|
|
84
|
+
* rather than waiting for the idle sweep, because a disposed session is
|
|
85
|
+
* proof the work is over.
|
|
86
|
+
*/
|
|
87
|
+
release(holder: object): void;
|
|
88
|
+
/** Shut every pooled server down regardless of holders (process exit). */
|
|
89
|
+
shutdownAll(): void;
|
|
90
|
+
/** Live pooled servers. Test/diagnostic use. */
|
|
91
|
+
get size(): number;
|
|
92
|
+
/** Reference count for one (spec, root), or 0 when nothing is pooled. */
|
|
93
|
+
refCount(spec: LspServerSpec, root: string): number;
|
|
94
|
+
/**
|
|
95
|
+
* Retained stderr of a server `holder` is using. Diagnostic probe for tests
|
|
96
|
+
* and failure reporting — a server's own last words are usually the only
|
|
97
|
+
* explanation of why it went quiet.
|
|
98
|
+
*/
|
|
99
|
+
stderrTail(holder: object): Promise<string>;
|
|
100
|
+
/** Force one idle sweep. Exposed so tests need not wait on the interval. */
|
|
101
|
+
sweepNow(now?: number): void;
|
|
102
|
+
private evict;
|
|
103
|
+
/**
|
|
104
|
+
* The sweep only runs while something is pooled, and is unref'd so it can
|
|
105
|
+
* never hold the CLI (or a test worker) open on its own.
|
|
106
|
+
*/
|
|
107
|
+
private startSweep;
|
|
108
|
+
private stopSweep;
|
|
109
|
+
private spawn;
|
|
110
|
+
}
|
|
111
|
+
/** The daemon-wide pool. One per process, which is exactly the sharing scope. */
|
|
112
|
+
export declare const lspClientPool: LspClientPool;
|
|
113
|
+
//# sourceMappingURL=pool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../../../src/core/lsp/pool.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,uEAAuE;AACvE,MAAM,MAAM,YAAY,GACpB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,SAAS,CAAA;CAAE,GACtC;IAAE,MAAM,EAAE,aAAa,GAAG,eAAe,CAAA;CAAE,CAAC;AAwBhD,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAoBD,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgC;IACxD;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,UAAU,CAAC,CAAiC;gBAExC,OAAO,CAAC,EAAE,oBAAoB;IAK1C,OAAO,CAAC,MAAM;IAId;;;;OAIG;IACG,MAAM,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IA6BtF;;;;;;;;OAQG;IACH,aAAa,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAIxD;;;;OAIG;IACH,SAAS,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,IAAI;IAaxD;;;;;;;OAOG;IACH,QAAQ,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAajD;;;;;OAKG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAQ7B,0EAA0E;IAC1E,WAAW,IAAI,IAAI;IAKnB,gDAAgD;IAChD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAInD;;;;OAIG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IASjD,4EAA4E;IAC5E,QAAQ,CAAC,GAAG,SAAa,GAAG,IAAI;IAShC,OAAO,CAAC,KAAK;IAYb;;;OAGG;IACH,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,SAAS;YAMH,KAAK;CAkCpB;AAED,iFAAiF;AACjF,eAAO,MAAM,aAAa,eAAsB,CAAC"}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { log } from "../logger.js";
|
|
2
|
+
import { LspClient } from "./client.js";
|
|
3
|
+
/**
|
|
4
|
+
* How long a server may sit unused before it is reclaimed. Long enough to cover
|
|
5
|
+
* a normal think-edit-verify rhythm, short enough that a project the user has
|
|
6
|
+
* moved on from stops costing hundreds of megabytes.
|
|
7
|
+
*/
|
|
8
|
+
const DEFAULT_IDLE_TTL_MS = 5 * 60 * 1000;
|
|
9
|
+
const DEFAULT_SWEEP_INTERVAL_MS = 30 * 1000;
|
|
10
|
+
const INIT_TIMEOUT_MS = 10_000;
|
|
11
|
+
/**
|
|
12
|
+
* Stable identity per catalog entry. Two sessions share the module-level
|
|
13
|
+
* catalog, so the same spec object means the same server; a test injecting its
|
|
14
|
+
* own spec gets its own pool entries instead of colliding with another test's
|
|
15
|
+
* fixture on the same id and root.
|
|
16
|
+
*/
|
|
17
|
+
const specIds = new WeakMap();
|
|
18
|
+
let nextSpecId = 0;
|
|
19
|
+
function specIdentity(spec) {
|
|
20
|
+
let id = specIds.get(spec);
|
|
21
|
+
if (!id) {
|
|
22
|
+
id = `${spec.id}#${nextSpecId++}`;
|
|
23
|
+
specIds.set(spec, id);
|
|
24
|
+
}
|
|
25
|
+
return id;
|
|
26
|
+
}
|
|
27
|
+
export class LspClientPool {
|
|
28
|
+
entries = new Map();
|
|
29
|
+
/**
|
|
30
|
+
* Build counter per key, kept OUTSIDE `entries` so it survives eviction —
|
|
31
|
+
* that is the whole point: it tells a caller its warm server is gone.
|
|
32
|
+
*/
|
|
33
|
+
generations = new Map();
|
|
34
|
+
idleTtlMs;
|
|
35
|
+
sweepIntervalMs;
|
|
36
|
+
sweepTimer;
|
|
37
|
+
constructor(options) {
|
|
38
|
+
this.idleTtlMs = Math.max(0, options?.idleTtlMs ?? DEFAULT_IDLE_TTL_MS);
|
|
39
|
+
this.sweepIntervalMs = Math.max(1, options?.sweepIntervalMs ?? DEFAULT_SWEEP_INTERVAL_MS);
|
|
40
|
+
}
|
|
41
|
+
keyFor(spec, root) {
|
|
42
|
+
return `${specIdentity(spec)}\u0000${root}`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get-or-spawn the client for (spec, root) and record `holder` as retaining
|
|
46
|
+
* it. Concurrent callers share one spawn: the entry and its pending promise
|
|
47
|
+
* are registered synchronously before the first `await`.
|
|
48
|
+
*/
|
|
49
|
+
async retain(spec, root, holder) {
|
|
50
|
+
const key = this.keyFor(spec, root);
|
|
51
|
+
let entry = this.entries.get(key);
|
|
52
|
+
if (!entry) {
|
|
53
|
+
entry = {
|
|
54
|
+
key,
|
|
55
|
+
pending: this.spawn(spec, root),
|
|
56
|
+
holders: new Set(),
|
|
57
|
+
activeCalls: 0,
|
|
58
|
+
lastUsedAt: Date.now(),
|
|
59
|
+
generation: (this.generations.get(key) ?? 0) + 1,
|
|
60
|
+
};
|
|
61
|
+
this.generations.set(key, entry.generation);
|
|
62
|
+
this.entries.set(key, entry);
|
|
63
|
+
this.startSweep();
|
|
64
|
+
}
|
|
65
|
+
entry.holders.add(holder);
|
|
66
|
+
const resolved = await entry.pending;
|
|
67
|
+
// Only a WORKING server counts as use. A failure stays cached so a broken
|
|
68
|
+
// toolchain is not respawned on every write — the long-standing contract —
|
|
69
|
+
// but its timestamp is left alone so the idle sweep can retire it like any
|
|
70
|
+
// other stale entry. Without that, a pooled failure would outlive every
|
|
71
|
+
// session in the daemon and disable diagnostics for that root permanently,
|
|
72
|
+
// where pre-pool a new session simply got a fresh manager and retried.
|
|
73
|
+
if (resolved.status === "ready")
|
|
74
|
+
entry.lastUsedAt = Date.now();
|
|
75
|
+
return resolved;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Identity of the server currently live for (spec, root), or 0 when none is.
|
|
79
|
+
*
|
|
80
|
+
* A caller records this alongside its own "I have warmed this server" state,
|
|
81
|
+
* and a later mismatch means the server it warmed is gone. Reporting the LIVE
|
|
82
|
+
* entry rather than the next build number is essential: callers ask before
|
|
83
|
+
* `retain`, when a reclaimed server has not been rebuilt yet, and a stale
|
|
84
|
+
* number there would look like a match and hand a cold server a warm budget.
|
|
85
|
+
*/
|
|
86
|
+
generationFor(spec, root) {
|
|
87
|
+
return this.entries.get(this.keyFor(spec, root))?.generation ?? 0;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Mark a diagnostics pass as in flight against (spec, root), returning the
|
|
91
|
+
* function that ends it. A first-file pass can take seconds; without this the
|
|
92
|
+
* sweep could shut the server down while it was still answering.
|
|
93
|
+
*/
|
|
94
|
+
beginCall(spec, root) {
|
|
95
|
+
const entry = this.entries.get(this.keyFor(spec, root));
|
|
96
|
+
if (!entry)
|
|
97
|
+
return () => { };
|
|
98
|
+
entry.activeCalls += 1;
|
|
99
|
+
let ended = false;
|
|
100
|
+
return () => {
|
|
101
|
+
if (ended)
|
|
102
|
+
return;
|
|
103
|
+
ended = true;
|
|
104
|
+
entry.activeCalls = Math.max(0, entry.activeCalls - 1);
|
|
105
|
+
entry.lastUsedAt = Date.now();
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Record that this client died after starting, so later passes report
|
|
110
|
+
* `server_failed` instead of talking to a corpse.
|
|
111
|
+
*
|
|
112
|
+
* The failure is CACHED rather than cleared, preserving the pre-pool
|
|
113
|
+
* contract: a server that dies on a document is not respawned on every
|
|
114
|
+
* following write. The dead process is reaped either way.
|
|
115
|
+
*/
|
|
116
|
+
markDead(spec, root) {
|
|
117
|
+
const key = this.keyFor(spec, root);
|
|
118
|
+
const entry = this.entries.get(key);
|
|
119
|
+
if (!entry)
|
|
120
|
+
return;
|
|
121
|
+
const dead = entry.pending;
|
|
122
|
+
entry.pending = Promise.resolve({ status: "server_failed" });
|
|
123
|
+
void dead
|
|
124
|
+
.then((resolved) => {
|
|
125
|
+
if (resolved.status === "ready")
|
|
126
|
+
resolved.client.terminate();
|
|
127
|
+
})
|
|
128
|
+
.catch(() => { });
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Drop `holder`'s claim on every entry it retained. Servers still held by
|
|
132
|
+
* another manager keep running; those left with no holders shut down now
|
|
133
|
+
* rather than waiting for the idle sweep, because a disposed session is
|
|
134
|
+
* proof the work is over.
|
|
135
|
+
*/
|
|
136
|
+
release(holder) {
|
|
137
|
+
for (const entry of [...this.entries.values()]) {
|
|
138
|
+
if (!entry.holders.delete(holder))
|
|
139
|
+
continue;
|
|
140
|
+
if (entry.holders.size === 0)
|
|
141
|
+
this.evict(entry, "last holder released");
|
|
142
|
+
}
|
|
143
|
+
if (this.entries.size === 0)
|
|
144
|
+
this.stopSweep();
|
|
145
|
+
}
|
|
146
|
+
/** Shut every pooled server down regardless of holders (process exit). */
|
|
147
|
+
shutdownAll() {
|
|
148
|
+
for (const entry of [...this.entries.values()])
|
|
149
|
+
this.evict(entry, "pool shutdown");
|
|
150
|
+
this.stopSweep();
|
|
151
|
+
}
|
|
152
|
+
/** Live pooled servers. Test/diagnostic use. */
|
|
153
|
+
get size() {
|
|
154
|
+
return this.entries.size;
|
|
155
|
+
}
|
|
156
|
+
/** Reference count for one (spec, root), or 0 when nothing is pooled. */
|
|
157
|
+
refCount(spec, root) {
|
|
158
|
+
return this.entries.get(this.keyFor(spec, root))?.holders.size ?? 0;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Retained stderr of a server `holder` is using. Diagnostic probe for tests
|
|
162
|
+
* and failure reporting — a server's own last words are usually the only
|
|
163
|
+
* explanation of why it went quiet.
|
|
164
|
+
*/
|
|
165
|
+
async stderrTail(holder) {
|
|
166
|
+
for (const entry of [...this.entries.values()]) {
|
|
167
|
+
if (!entry.holders.has(holder))
|
|
168
|
+
continue;
|
|
169
|
+
const resolved = await entry.pending.catch(() => undefined);
|
|
170
|
+
if (resolved?.status === "ready")
|
|
171
|
+
return resolved.client.stderrTail();
|
|
172
|
+
}
|
|
173
|
+
return "";
|
|
174
|
+
}
|
|
175
|
+
/** Force one idle sweep. Exposed so tests need not wait on the interval. */
|
|
176
|
+
sweepNow(now = Date.now()) {
|
|
177
|
+
for (const entry of [...this.entries.values()]) {
|
|
178
|
+
if (entry.activeCalls > 0)
|
|
179
|
+
continue;
|
|
180
|
+
if (now - entry.lastUsedAt < this.idleTtlMs)
|
|
181
|
+
continue;
|
|
182
|
+
this.evict(entry, `idle for ${Math.round((now - entry.lastUsedAt) / 1000)}s`);
|
|
183
|
+
}
|
|
184
|
+
if (this.entries.size === 0)
|
|
185
|
+
this.stopSweep();
|
|
186
|
+
}
|
|
187
|
+
evict(entry, reason) {
|
|
188
|
+
// Only remove the entry if it is still the live one for its key, so a
|
|
189
|
+
// rebuild that raced this eviction is not torn down by it.
|
|
190
|
+
if (this.entries.get(entry.key) === entry)
|
|
191
|
+
this.entries.delete(entry.key);
|
|
192
|
+
log("INFO", "lsp", "releasing pooled language server", { key: entry.key, reason });
|
|
193
|
+
void entry.pending
|
|
194
|
+
.then((resolved) => {
|
|
195
|
+
if (resolved.status === "ready")
|
|
196
|
+
resolved.client.shutdown();
|
|
197
|
+
})
|
|
198
|
+
.catch(() => { });
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* The sweep only runs while something is pooled, and is unref'd so it can
|
|
202
|
+
* never hold the CLI (or a test worker) open on its own.
|
|
203
|
+
*/
|
|
204
|
+
startSweep() {
|
|
205
|
+
if (this.sweepTimer || this.idleTtlMs === 0)
|
|
206
|
+
return;
|
|
207
|
+
this.sweepTimer = setInterval(() => this.sweepNow(), this.sweepIntervalMs);
|
|
208
|
+
this.sweepTimer.unref?.();
|
|
209
|
+
}
|
|
210
|
+
stopSweep() {
|
|
211
|
+
if (!this.sweepTimer)
|
|
212
|
+
return;
|
|
213
|
+
clearInterval(this.sweepTimer);
|
|
214
|
+
this.sweepTimer = undefined;
|
|
215
|
+
}
|
|
216
|
+
async spawn(spec, root) {
|
|
217
|
+
const command = spec.resolveCommand(root);
|
|
218
|
+
if (!command) {
|
|
219
|
+
log("INFO", "lsp", `${spec.id} language server not available`, { root });
|
|
220
|
+
return { status: "unavailable" };
|
|
221
|
+
}
|
|
222
|
+
// `client` is declared outside the try so one that fails to initialize can
|
|
223
|
+
// still be killed. `new LspClient` SPAWNS the process, so discarding the
|
|
224
|
+
// reference on a throw leaked the server forever — one orphan per
|
|
225
|
+
// (server, root) every time initialize timed out. Invisible on POSIX; on
|
|
226
|
+
// Windows the orphan keeps handles open in the project directory.
|
|
227
|
+
let client;
|
|
228
|
+
try {
|
|
229
|
+
const startedAt = Date.now();
|
|
230
|
+
client = new LspClient(spec, root, command);
|
|
231
|
+
await client.initialize(INIT_TIMEOUT_MS);
|
|
232
|
+
if (!client.isAlive)
|
|
233
|
+
return { status: "server_failed" };
|
|
234
|
+
log("INFO", "lsp", `${spec.id} server initialized`, {
|
|
235
|
+
root,
|
|
236
|
+
ms: String(Date.now() - startedAt),
|
|
237
|
+
});
|
|
238
|
+
return { status: "ready", client };
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
log("WARN", "lsp", `${spec.id} server failed to start`, {
|
|
242
|
+
root,
|
|
243
|
+
error: error instanceof Error ? error.message : String(error),
|
|
244
|
+
// The server's own last words — usually the only explanation of why the
|
|
245
|
+
// handshake never completed.
|
|
246
|
+
stderr: client?.stderrTail() || "(none)",
|
|
247
|
+
});
|
|
248
|
+
client?.terminate();
|
|
249
|
+
return { status: "server_failed" };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/** The daemon-wide pool. One per process, which is exactly the sharing scope. */
|
|
254
|
+
export const lspClientPool = new LspClientPool();
|
|
255
|
+
//# sourceMappingURL=pool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.js","sourceRoot":"","sources":["../../../src/core/lsp/pool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.test.d.ts","sourceRoot":"","sources":["../../../src/core/lsp/pool.test.ts"],"names":[],"mappings":""}
|