-
Notifications
You must be signed in to change notification settings - Fork 23
Expand file tree
/
Copy pathadapter-disposal.ts
More file actions
64 lines (61 loc) · 2.6 KB
/
Copy pathadapter-disposal.ts
File metadata and controls
64 lines (61 loc) · 2.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
* The one way to dispose an adapter you are done with.
*
* Two call sites own an adapter's registry slot and have to give it back:
* `AdapterLease.release()` (the setup window) and `ProxyManager.cleanup()`
* (after the transfer). Both ran their own dispose-with-warn block, and the
* two blocks disagreed in the way that matters: the lease awaited inside a
* `try`, while `ProxyManager` fire-and-forgot with `dispose().catch(...)` — a
* `dispose()` that throws *synchronously* produces no promise, so that
* `.catch` never runs and the throw escaped into `cleanup()` mid-teardown.
*
* Awaiting inside the guard covers both failure modes with one warn shape.
* Neither site has anywhere to report a failure to give a slot back — the
* lease is a `finally` guarding the error the caller is about to report, and
* cleanup runs on the exit path — so this is deliberately total: a rejection,
* a synchronous throw, an adapter that turns out not to have `dispose` at all,
* and a logger that throws while reporting one of those all end here.
*
* `context` is the per-site prefix, and it carries the whole difference
* between the two messages, which are pinned by their respective tests.
*/
import type { IDebugAdapter, ILogger } from '@debugmcp/shared';
import { getErrorMessage } from '../errors/debug-errors.js';
export type AdapterDisposalReporter = (error: unknown) => void;
/**
* Dispose an adapter and report either a synchronous throw or an asynchronous
* rejection without ever allowing disposal or reporting to escape.
*/
export async function disposeAdapterSafely(
adapter: IDebugAdapter,
reporter: AdapterDisposalReporter
): Promise<void> {
try {
await adapter.dispose();
} catch (disposeError: unknown) {
try {
reporter(disposeError);
} catch {
// Disposal is a terminal cleanup path; a broken reporter cannot revive it.
}
}
}
/**
* Dispose `adapter`, reporting any failure as `"<context>: <message>"` and
* never throwing.
*
* The duck-typed `typeof adapter.dispose === 'function'` guard both sites used
* to carry is gone: `IDebugAdapter.dispose()` is required, and the guard only
* ever protected hand-rolled test doubles that omitted it. A double that still
* does is now reported as a failed disposal rather than silently skipped,
* which is the honest answer — the registry slot was not returned.
*/
export async function disposeAdapterQuietly(
adapter: IDebugAdapter,
logger: ILogger,
context: string
): Promise<void> {
await disposeAdapterSafely(adapter, (disposeError) => {
logger.warn(`${context}: ${getErrorMessage(disposeError)}`);
});
}