@kenkaiiii/ggcoder 5.34.3 → 5.35.1
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/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/dist/core/persistent-shell.d.ts.map +1 -1
- package/dist/core/persistent-shell.js +4 -1
- package/dist/core/persistent-shell.js.map +1 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +2 -1
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/web-fetch.d.ts.map +1 -1
- package/dist/tools/web-fetch.js +4 -1
- package/dist/tools/web-fetch.js.map +1 -1
- package/package.json +4 -4
|
@@ -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":""}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { LspManager } from "./manager.js";
|
|
7
|
+
import { LspClientPool } from "./pool.js";
|
|
8
|
+
import { removeWhenReleased } from "./test-support.js";
|
|
9
|
+
const FIXTURE = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../tools/__fixtures__/fake-lsp-server.mjs");
|
|
10
|
+
function fakeSpec(serverArgs = []) {
|
|
11
|
+
return {
|
|
12
|
+
id: "fake",
|
|
13
|
+
extensions: [".fake"],
|
|
14
|
+
rootMarkers: ["fake-root.json"],
|
|
15
|
+
languageIdFor: () => "fake",
|
|
16
|
+
resolveCommand: () => ({ command: process.execPath, args: [FIXTURE, ...serverArgs] }),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** True while a pid is a live process. */
|
|
20
|
+
function alive(pid) {
|
|
21
|
+
try {
|
|
22
|
+
process.kill(pid, 0);
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function waitForFile(file, timeoutMs = 5000) {
|
|
30
|
+
const deadline = Date.now() + timeoutMs;
|
|
31
|
+
for (;;) {
|
|
32
|
+
const body = await fs.readFile(file, "utf8").catch(() => undefined);
|
|
33
|
+
if (body)
|
|
34
|
+
return body;
|
|
35
|
+
if (Date.now() > deadline)
|
|
36
|
+
throw new Error(`timed out waiting for ${file}`);
|
|
37
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function waitUntilDead(pid, timeoutMs = 5000) {
|
|
41
|
+
const deadline = Date.now() + timeoutMs;
|
|
42
|
+
while (Date.now() < deadline) {
|
|
43
|
+
if (!alive(pid))
|
|
44
|
+
return true;
|
|
45
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
describe("LspClientPool", () => {
|
|
50
|
+
let tmpDir;
|
|
51
|
+
let pool;
|
|
52
|
+
const managers = [];
|
|
53
|
+
beforeEach(async () => {
|
|
54
|
+
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "lsp-pool-test-"));
|
|
55
|
+
await fs.writeFile(path.join(tmpDir, "fake-root.json"), "{}");
|
|
56
|
+
// Long TTL by default so only the tests that mean to exercise expiry do.
|
|
57
|
+
pool = new LspClientPool({ idleTtlMs: 60_000 });
|
|
58
|
+
});
|
|
59
|
+
afterEach(async () => {
|
|
60
|
+
for (const manager of managers.splice(0))
|
|
61
|
+
manager.shutdownAll();
|
|
62
|
+
pool.shutdownAll();
|
|
63
|
+
await removeWhenReleased(tmpDir);
|
|
64
|
+
});
|
|
65
|
+
/**
|
|
66
|
+
* Two managers sharing one catalog spec is exactly the production shape: the
|
|
67
|
+
* server catalog is module-level, so every session sees the same spec object.
|
|
68
|
+
*/
|
|
69
|
+
function twoSessions(spec) {
|
|
70
|
+
const build = () => {
|
|
71
|
+
const manager = new LspManager(tmpDir, {
|
|
72
|
+
catalog: [spec],
|
|
73
|
+
pool,
|
|
74
|
+
warmBudgetMs: 5000,
|
|
75
|
+
firstBudgetMs: 5000,
|
|
76
|
+
});
|
|
77
|
+
managers.push(manager);
|
|
78
|
+
return manager;
|
|
79
|
+
};
|
|
80
|
+
return [build(), build()];
|
|
81
|
+
}
|
|
82
|
+
it("spawns ONE server for two sessions on the same project root", async () => {
|
|
83
|
+
const pidFile = path.join(tmpDir, "server.pid");
|
|
84
|
+
const spec = fakeSpec([`--pid-file=${pidFile}`]);
|
|
85
|
+
const [a, b] = twoSessions(spec);
|
|
86
|
+
const first = await a.diagnosticsAfterWrite(path.join(tmpDir, "a.fake"), "has ERROR here\n");
|
|
87
|
+
const second = await b.diagnosticsAfterWrite(path.join(tmpDir, "b.fake"), "has ERROR here\n");
|
|
88
|
+
// Both sessions get real diagnostics from the one shared server.
|
|
89
|
+
expect(first).toContain("Diagnostics in a.fake");
|
|
90
|
+
expect(second).toContain("Diagnostics in b.fake");
|
|
91
|
+
expect(pool.size).toBe(1);
|
|
92
|
+
expect(pool.refCount(spec, tmpDir)).toBe(2);
|
|
93
|
+
});
|
|
94
|
+
it("keeps the server alive when one session disposes, and kills it when the last does", async () => {
|
|
95
|
+
const pidFile = path.join(tmpDir, "server.pid");
|
|
96
|
+
const spec = fakeSpec([`--pid-file=${pidFile}`]);
|
|
97
|
+
const [a, b] = twoSessions(spec);
|
|
98
|
+
await a.diagnosticsAfterWrite(path.join(tmpDir, "a.fake"), "ok\n");
|
|
99
|
+
await b.diagnosticsAfterWrite(path.join(tmpDir, "b.fake"), "ok\n");
|
|
100
|
+
const pid = Number(await waitForFile(pidFile));
|
|
101
|
+
expect(alive(pid)).toBe(true);
|
|
102
|
+
a.shutdownAll();
|
|
103
|
+
expect(pool.refCount(spec, tmpDir)).toBe(1);
|
|
104
|
+
// The surviving session's server must still be running and usable.
|
|
105
|
+
expect(alive(pid)).toBe(true);
|
|
106
|
+
const afterFirstDispose = await b.diagnosticsAfterWrite(path.join(tmpDir, "c.fake"), "has ERROR here\n");
|
|
107
|
+
expect(afterFirstDispose).toContain("Diagnostics in c.fake");
|
|
108
|
+
b.shutdownAll();
|
|
109
|
+
expect(pool.size).toBe(0);
|
|
110
|
+
expect(await waitUntilDead(pid)).toBe(true);
|
|
111
|
+
}, 20_000);
|
|
112
|
+
it("gives different project roots their own servers", async () => {
|
|
113
|
+
const otherRoot = path.join(tmpDir, "nested");
|
|
114
|
+
await fs.mkdir(otherRoot, { recursive: true });
|
|
115
|
+
await fs.writeFile(path.join(otherRoot, "fake-root.json"), "{}");
|
|
116
|
+
const spec = fakeSpec();
|
|
117
|
+
const [a] = twoSessions(spec);
|
|
118
|
+
await a.diagnosticsAfterWrite(path.join(tmpDir, "a.fake"), "ok\n");
|
|
119
|
+
await a.diagnosticsAfterWrite(path.join(otherRoot, "b.fake"), "ok\n");
|
|
120
|
+
expect(pool.size).toBe(2);
|
|
121
|
+
});
|
|
122
|
+
/** The pinning bug: a root touched once used to stay resident forever. */
|
|
123
|
+
it("reclaims a server that has gone idle even while a session still holds it", async () => {
|
|
124
|
+
const pidFile = path.join(tmpDir, "server.pid");
|
|
125
|
+
// Expiry is driven by passing a future `now` to sweepNow rather than by
|
|
126
|
+
// sleeping past a short TTL: a background sweep racing the assertions made
|
|
127
|
+
// this flaky under parallel test load.
|
|
128
|
+
const idleTtlMs = 60_000;
|
|
129
|
+
const idlePool = new LspClientPool({ idleTtlMs, sweepIntervalMs: 10_000 });
|
|
130
|
+
const spec = fakeSpec([`--pid-file=${pidFile}`]);
|
|
131
|
+
const manager = new LspManager(tmpDir, { catalog: [spec], pool: idlePool });
|
|
132
|
+
managers.push(manager);
|
|
133
|
+
await manager.diagnosticsAfterWrite(path.join(tmpDir, "a.fake"), "ok\n");
|
|
134
|
+
const pid = Number(await waitForFile(pidFile));
|
|
135
|
+
expect(idlePool.size).toBe(1);
|
|
136
|
+
idlePool.sweepNow(Date.now() + idleTtlMs + 1);
|
|
137
|
+
expect(idlePool.size).toBe(0);
|
|
138
|
+
expect(await waitUntilDead(pid)).toBe(true);
|
|
139
|
+
// ...and the session still works afterwards: the next edit rebuilds it.
|
|
140
|
+
const revived = await manager.diagnosticsAfterWrite(path.join(tmpDir, "b.fake"), "has ERROR here\n");
|
|
141
|
+
expect(revived).toContain("Diagnostics in b.fake");
|
|
142
|
+
idlePool.shutdownAll();
|
|
143
|
+
}, 20_000);
|
|
144
|
+
/**
|
|
145
|
+
* A cached failure must not outlive the daemon's patience. Not retrying on
|
|
146
|
+
* every write is deliberate, but pooling made the failure process-wide, so
|
|
147
|
+
* without expiry one broken spawn would disable that root for every present
|
|
148
|
+
* AND future session — where pre-pool a new session simply got a fresh
|
|
149
|
+
* manager and tried again.
|
|
150
|
+
*/
|
|
151
|
+
it("lets the idle sweep clear a cached failure so a later session can retry", async () => {
|
|
152
|
+
let spawns = 0;
|
|
153
|
+
// Real elapsed time rather than a synthetic future `now`: the bug is that
|
|
154
|
+
// continued use refreshes a failed entry's timestamp, which only shows up
|
|
155
|
+
// when the writes are actually spread across the TTL.
|
|
156
|
+
const idleTtlMs = 600;
|
|
157
|
+
const failPool = new LspClientPool({ idleTtlMs, sweepIntervalMs: 10_000 });
|
|
158
|
+
const spec = {
|
|
159
|
+
...fakeSpec(),
|
|
160
|
+
resolveCommand: () => {
|
|
161
|
+
spawns++;
|
|
162
|
+
return { command: process.execPath, args: [FIXTURE, "--init-error"] };
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
const manager = new LspManager(tmpDir, {
|
|
166
|
+
catalog: [spec],
|
|
167
|
+
pool: failPool,
|
|
168
|
+
firstBudgetMs: 3000,
|
|
169
|
+
warmBudgetMs: 3000,
|
|
170
|
+
});
|
|
171
|
+
managers.push(manager);
|
|
172
|
+
const file = path.join(tmpDir, "a.fake");
|
|
173
|
+
const start = Date.now();
|
|
174
|
+
expect((await manager.diagnosticsAfterWriteDetailed(file, "x\n")).kind).toBe("server_failed");
|
|
175
|
+
// A second write PAST the halfway mark. This is the discriminating step: if
|
|
176
|
+
// a failed retain refreshed `lastUsedAt`, continued writing would keep the
|
|
177
|
+
// entry looking fresh forever and the sweep below would spare it.
|
|
178
|
+
await new Promise((resolve) => setTimeout(resolve, idleTtlMs * 0.7));
|
|
179
|
+
expect((await manager.diagnosticsAfterWriteDetailed(file, "y\n")).kind).toBe("server_failed");
|
|
180
|
+
// Still cached: no respawn on the next write while the entry lives.
|
|
181
|
+
expect(spawns).toBe(1);
|
|
182
|
+
// Now past the TTL as measured from the FAILURE, not from the last write.
|
|
183
|
+
await new Promise((resolve) => setTimeout(resolve, idleTtlMs * 0.5));
|
|
184
|
+
failPool.sweepNow();
|
|
185
|
+
expect(Date.now() - start).toBeLessThan(idleTtlMs * 2);
|
|
186
|
+
expect(failPool.size).toBe(0);
|
|
187
|
+
await manager.diagnosticsAfterWriteDetailed(file, "z\n");
|
|
188
|
+
expect(spawns).toBe(2);
|
|
189
|
+
failPool.shutdownAll();
|
|
190
|
+
}, 20_000);
|
|
191
|
+
/**
|
|
192
|
+
* After reclamation the replacement server is genuinely cold. Treating it as
|
|
193
|
+
* warm would hand it the short budget and skip the cold-load settle guard,
|
|
194
|
+
* which turns tsserver's premature empty publish into a reported "clean" — a
|
|
195
|
+
* false all-clear, the worst failure mode for a diagnostics tool.
|
|
196
|
+
*/
|
|
197
|
+
it("treats a rebuilt server as cold again rather than inheriting warm state", async () => {
|
|
198
|
+
const idleTtlMs = 60_000;
|
|
199
|
+
const idlePool = new LspClientPool({ idleTtlMs, sweepIntervalMs: 10_000 });
|
|
200
|
+
// The server needs longer than the WARM budget but less than the FIRST
|
|
201
|
+
// budget, so which budget the manager picks is directly observable: a cold
|
|
202
|
+
// pass returns diagnostics, a wrongly-warm one times out.
|
|
203
|
+
const spec = fakeSpec(["--delay-ms=700"]);
|
|
204
|
+
const manager = new LspManager(tmpDir, {
|
|
205
|
+
catalog: [spec],
|
|
206
|
+
pool: idlePool,
|
|
207
|
+
firstBudgetMs: 5000,
|
|
208
|
+
warmBudgetMs: 250,
|
|
209
|
+
settleMs: 0,
|
|
210
|
+
});
|
|
211
|
+
managers.push(manager);
|
|
212
|
+
const first = await manager.diagnosticsAfterWriteDetailed(path.join(tmpDir, "a.fake"), "has ERROR here\n");
|
|
213
|
+
expect(first.kind).toBe("diagnostics");
|
|
214
|
+
expect(idlePool.generationFor(spec, tmpDir)).toBe(1);
|
|
215
|
+
idlePool.sweepNow(Date.now() + idleTtlMs + 1);
|
|
216
|
+
expect(idlePool.size).toBe(0);
|
|
217
|
+
// The replacement is a new generation, so the warm mark is stale and this
|
|
218
|
+
// pass must get the first-file budget again.
|
|
219
|
+
const revived = await manager.diagnosticsAfterWriteDetailed(path.join(tmpDir, "b.fake"), "has ERROR here\n");
|
|
220
|
+
expect(idlePool.generationFor(spec, tmpDir)).toBe(2);
|
|
221
|
+
expect(revived.kind).toBe("diagnostics");
|
|
222
|
+
idlePool.shutdownAll();
|
|
223
|
+
}, 20_000);
|
|
224
|
+
it("does not reclaim a server while a diagnostics pass is still in flight", async () => {
|
|
225
|
+
const idlePool = new LspClientPool({ idleTtlMs: 0, sweepIntervalMs: 10_000 });
|
|
226
|
+
// Publishes only after 400ms, so the sweep below lands mid-pass.
|
|
227
|
+
const spec = fakeSpec(["--delay-ms=400"]);
|
|
228
|
+
const manager = new LspManager(tmpDir, {
|
|
229
|
+
catalog: [spec],
|
|
230
|
+
pool: idlePool,
|
|
231
|
+
firstBudgetMs: 5000,
|
|
232
|
+
warmBudgetMs: 5000,
|
|
233
|
+
});
|
|
234
|
+
managers.push(manager);
|
|
235
|
+
const pending = manager.diagnosticsAfterWrite(path.join(tmpDir, "slow.fake"), "has ERROR here\n");
|
|
236
|
+
// Let the client spawn and the pass register before sweeping.
|
|
237
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
238
|
+
idlePool.sweepNow();
|
|
239
|
+
expect(idlePool.size).toBe(1);
|
|
240
|
+
// The in-flight pass still gets its answer rather than a dead server.
|
|
241
|
+
expect(await pending).toContain("Diagnostics in slow.fake");
|
|
242
|
+
idlePool.shutdownAll();
|
|
243
|
+
}, 20_000);
|
|
244
|
+
it("releasing a holder twice does not disturb another session's server", async () => {
|
|
245
|
+
const spec = fakeSpec();
|
|
246
|
+
const [a, b] = twoSessions(spec);
|
|
247
|
+
await a.diagnosticsAfterWrite(path.join(tmpDir, "a.fake"), "ok\n");
|
|
248
|
+
await b.diagnosticsAfterWrite(path.join(tmpDir, "b.fake"), "ok\n");
|
|
249
|
+
a.shutdownAll();
|
|
250
|
+
a.shutdownAll();
|
|
251
|
+
expect(pool.refCount(spec, tmpDir)).toBe(1);
|
|
252
|
+
expect(await b.diagnosticsAfterWrite(path.join(tmpDir, "c.fake"), "has ERROR here\n")).toContain("Diagnostics in c.fake");
|
|
253
|
+
}, 20_000);
|
|
254
|
+
/**
|
|
255
|
+
* Caching a start failure is the pre-existing contract (see manager.test.ts:
|
|
256
|
+
* "marks a server broken after spawn failure and never retries"). Pooling must
|
|
257
|
+
* not turn one broken toolchain into a fresh spawn on every write.
|
|
258
|
+
*/
|
|
259
|
+
it("caches a start failure across BOTH sessions instead of respawning per write", async () => {
|
|
260
|
+
let spawns = 0;
|
|
261
|
+
const spec = {
|
|
262
|
+
...fakeSpec(["--init-error"]),
|
|
263
|
+
resolveCommand: () => {
|
|
264
|
+
spawns++;
|
|
265
|
+
return { command: process.execPath, args: [FIXTURE, "--init-error"] };
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
const [a, b] = twoSessions(spec);
|
|
269
|
+
for (const manager of [a, b, a, b]) {
|
|
270
|
+
const outcome = await manager.diagnosticsAfterWriteDetailed(path.join(tmpDir, "a.fake"), "has ERROR here\n");
|
|
271
|
+
expect(outcome.kind).toBe("server_failed");
|
|
272
|
+
}
|
|
273
|
+
expect(spawns).toBe(1);
|
|
274
|
+
}, 20_000);
|
|
275
|
+
it("shuts every pooled server down on pool shutdown regardless of holders", async () => {
|
|
276
|
+
const pidFile = path.join(tmpDir, "server.pid");
|
|
277
|
+
const spec = fakeSpec([`--pid-file=${pidFile}`]);
|
|
278
|
+
const [a] = twoSessions(spec);
|
|
279
|
+
await a.diagnosticsAfterWrite(path.join(tmpDir, "a.fake"), "ok\n");
|
|
280
|
+
const pid = Number(await waitForFile(pidFile));
|
|
281
|
+
pool.shutdownAll();
|
|
282
|
+
expect(pool.size).toBe(0);
|
|
283
|
+
expect(await waitUntilDead(pid)).toBe(true);
|
|
284
|
+
}, 20_000);
|
|
285
|
+
});
|
|
286
|
+
//# sourceMappingURL=pool.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.test.js","sourceRoot":"","sources":["../../../src/core/lsp/pool.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"servers.d.ts","sourceRoot":"","sources":["../../../src/core/lsp/servers.ts"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AAEH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,+EAA+E;IAC/E,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,gEAAgE;IAChE,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,2EAA2E;IAC3E,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,4CAA4C;IAC5C,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IACzC,8EAA8E;IAC9E,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAAC;CAC7D;AAiCD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqB/E;
|
|
1
|
+
{"version":3,"file":"servers.d.ts","sourceRoot":"","sources":["../../../src/core/lsp/servers.ts"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AAEH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,+EAA+E;IAC/E,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,gEAAgE;IAChE,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,2EAA2E;IAC3E,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,4CAA4C;IAC5C,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IACzC,8EAA8E;IAC9E,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAAC;CAC7D;AAiCD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqB/E;AAsHD,eAAO,MAAM,kBAAkB,EAAE,SAAS,aAAa,EAqFtD,CAAC;AAEF,gFAAgF;AAChF,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,SAAS,aAAa,EAAuB,GACrD,aAAa,GAAG,IAAI,CAItB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,EAAE,MAAM,GACd,MAAM,CAqBR"}
|
package/dist/core/lsp/servers.js
CHANGED
|
@@ -134,6 +134,38 @@ const TS_LANGUAGE_IDS = {
|
|
|
134
134
|
".cjs": "javascript",
|
|
135
135
|
".jsx": "javascriptreact",
|
|
136
136
|
};
|
|
137
|
+
/**
|
|
138
|
+
* tsserver tuning applied to every TypeScript/JavaScript project root.
|
|
139
|
+
*
|
|
140
|
+
* Shape verified against typescript-language-server 5.3.0's `initialize`
|
|
141
|
+
* handler: `disableAutomaticTypingAcquisition` and `maxTsServerMemory` are
|
|
142
|
+
* read from the TOP level of initializationOptions, while `useSyntaxServer`
|
|
143
|
+
* is read from the nested `tsserver` object.
|
|
144
|
+
*
|
|
145
|
+
* Untuned, one root costs FOUR processes: the language-server bridge, the
|
|
146
|
+
* semantic tsserver, a second `--serverMode partialSemantic` tsserver, and
|
|
147
|
+
* `typingsInstaller`. Both extras exist for an interactive editor, not for us:
|
|
148
|
+
*
|
|
149
|
+
* - The syntax server keeps completions and highlighting responsive while the
|
|
150
|
+
* semantic server loads a project. We only ever call
|
|
151
|
+
* `diagnosticsAfterWrite`, so it is a whole second tsserver holding a second
|
|
152
|
+
* copy of the program for features we never request. `"never"` collapses the
|
|
153
|
+
* pair into a single server (CompositeServerType.Single).
|
|
154
|
+
* - Automatic typing acquisition downloads `@types` packages over the network
|
|
155
|
+
* for untyped JS. This catalog never installs anything at runtime (see the
|
|
156
|
+
* module header), so it only costs the `typingsInstaller` process.
|
|
157
|
+
*
|
|
158
|
+
* Net effect: four processes per root become two.
|
|
159
|
+
*/
|
|
160
|
+
const TS_INITIALIZATION_OPTIONS = {
|
|
161
|
+
disableAutomaticTypingAcquisition: true,
|
|
162
|
+
/**
|
|
163
|
+
* Emitted as `--max-old-space-size`. A blast-radius guard rather than a
|
|
164
|
+
* saving — measured roots sit well under this — matching the VS Code default.
|
|
165
|
+
*/
|
|
166
|
+
maxTsServerMemory: 3072,
|
|
167
|
+
tsserver: { useSyntaxServer: "never" },
|
|
168
|
+
};
|
|
137
169
|
export const LSP_SERVER_CATALOG = [
|
|
138
170
|
{
|
|
139
171
|
id: "typescript",
|
|
@@ -153,12 +185,21 @@ export const LSP_SERVER_CATALOG = [
|
|
|
153
185
|
return null;
|
|
154
186
|
// Prefer the project's own typescript (correct version semantics); fall
|
|
155
187
|
// back to ggcoder's bundled copy so bare projects still get diagnostics.
|
|
156
|
-
|
|
157
|
-
|
|
188
|
+
// Tuning applies to BOTH paths: a project with its own TypeScript is the
|
|
189
|
+
// common case, so skipping it there would leave the flags nearly dead.
|
|
190
|
+
if (projectTsserverPath(projectRoot)) {
|
|
191
|
+
return { ...command, initializationOptions: TS_INITIALIZATION_OPTIONS };
|
|
192
|
+
}
|
|
158
193
|
const tsserver = bundledTsserverPath();
|
|
159
194
|
if (!tsserver)
|
|
160
195
|
return null;
|
|
161
|
-
return {
|
|
196
|
+
return {
|
|
197
|
+
...command,
|
|
198
|
+
initializationOptions: {
|
|
199
|
+
...TS_INITIALIZATION_OPTIONS,
|
|
200
|
+
tsserver: { ...TS_INITIALIZATION_OPTIONS.tsserver, path: tsserver },
|
|
201
|
+
},
|
|
202
|
+
};
|
|
162
203
|
},
|
|
163
204
|
},
|
|
164
205
|
{
|