Files
rea/src/native/LldbReadbackFiles.ts
T
morluto a891bb80ae refactor(filesystem): preserve admitted file facts
Return the validated descriptor and its original metadata from regular-file
admission. Propagate that contract through owned reads, hashes, snapshots,
artifact inventory and provider file workflows instead of replacing initial
facts with another stat. Keep arbitrary output-handle operations separate.

Preserve final descriptor/path identity proofs, borrowed-source verification,
BigInt precision, cancellation provenance and failed-close ownership. Remove
the obsolete source and owned-ZIP baseline stages and update their consumers
and regressions. Tie read completion faults to actual reads instead of the
retired duplicate stat; instrument real handles without fake handle wrappers.

Validation: fresh runtime/catalog build; focused source, lifecycle, filesystem,
CLI/MCP and authored provider-fixture tests; full static checks; docs checks;
test discovery; duplication check. Platform-specific cases remain skipped on
macOS. No installed provider or device behavior was verified.
2026-10-25 04:09:11 +08:00

119 lines
3.7 KiB
TypeScript

import type { FileHandle } from "node:fs/promises";
import { err, ok, type Result } from "../domain/result.js";
import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
import {
ArtifactFileCleanupFailure,
withArtifactRegularFile,
} from "../artifacts/ArtifactFileOperation.js";
import type { AnalysisCleanupLease } from "../artifacts/AnalysisCleanupLease.js";
/** PID publication is decimal; allow the largest safe PID and a CRLF ending. */
const TARGET_PID_FILE_MAX_BYTES = String(Number.MAX_SAFE_INTEGER).length + 2;
/** One trace's readback descriptors, separate from the facts already read. */
export class LldbReadbackFiles {
readonly #resources = new ArtifactResourceScope();
readonly #targetPids = new Map<string, number>();
readonly #failed = new Map<
AnalysisCleanupLease,
{ readonly path: string; readonly cause: ArtifactFileCleanupFailure }
>();
get hasPending(): boolean {
return this.#resources.hasPending;
}
get cleanupResources(): readonly string[] {
return [
...new Set(
[...this.#failed]
.filter(([lease]) => !lease.released)
.map(([, failure]) => failure.path),
),
];
}
get cleanupCause(): unknown {
const failures = [...this.#failed]
.filter(([lease]) => !lease.released)
.map(([, failure]) => failure.cause);
return failures.length === 0
? undefined
: new AggregateError(
failures,
"LLDB readback descriptors remain open or unconfirmed",
);
}
/** Preserve a completed value even if final close transfers its owner here. */
async read<Value>(
path: string,
read: (handle: FileHandle) => Promise<Value>,
): Promise<Result<Value, unknown>> {
const blocked = [...this.#failed].find(
([lease, failure]) => !lease.released && failure.path === path,
);
if (blocked !== undefined) return err(blocked[1].cause);
let completed: { readonly value: Value } | undefined;
try {
return ok(
await withArtifactRegularFile(
path,
this.#resources,
undefined,
async ({ handle }) => {
const value = await read(handle);
completed = { value };
return value;
},
),
);
} catch (cause: unknown) {
if (cause instanceof ArtifactFileCleanupFailure)
this.#failed.set(cause.cleanupLease, { path, cause });
return completed === undefined ? err(cause) : ok(completed.value);
}
}
/** The producer publishes one target PID per runtime; retain its parsed fact. */
async readTargetPid(path: string): Promise<number | undefined> {
const known = this.#targetPids.get(path);
if (known !== undefined) return known;
const contents = await this.read(path, async (file) => {
const bytes = Buffer.alloc(TARGET_PID_FILE_MAX_BYTES + 1);
let length = 0;
while (length < bytes.length) {
const { bytesRead } = await file.read(
bytes,
length,
bytes.length - length,
length,
);
if (bytesRead === 0) break;
length += bytesRead;
}
if (length > TARGET_PID_FILE_MAX_BYTES) return undefined;
return bytes.subarray(0, length).toString("utf8").trim();
});
if (
!contents.ok ||
contents.value === undefined ||
!/^[1-9]\d*$/u.test(contents.value)
)
return undefined;
const pid = Number(contents.value);
if (!Number.isSafeInteger(pid)) return undefined;
this.#targetPids.set(path, pid);
return pid;
}
async retryCleanup(): Promise<void> {
try {
await this.#resources.retryCleanup();
} finally {
for (const lease of this.#failed.keys())
if (lease.released) this.#failed.delete(lease);
}
}
}