Merge remote-tracking branch 'origin/feat/directory-picker' into feat/workspace-directory-browser
This commit is contained in:
@@ -95,6 +95,49 @@ export function boundedInsert(window: ListingCandidate[], candidate: ListingCand
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Await `operation`, but reject with the signal's reason the moment it
|
||||
* aborts. Node's filesystem reads are not retractable, so the operation
|
||||
* itself keeps running against a handle the caller then closes — its late
|
||||
* settlement is swallowed here so an abandoned read cannot surface as an
|
||||
* unhandled rejection.
|
||||
* @param operation - the in-flight filesystem step.
|
||||
* @param signal - caller lifetime; absent means plain awaiting.
|
||||
* @returns the operation's value.
|
||||
*/
|
||||
export function raceAbort<T>(operation: Promise<T>, signal: AbortSignal | undefined): Promise<T> {
|
||||
if (signal === undefined) return operation
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const onAbort = (): void => {
|
||||
operation.catch(() => {
|
||||
// Abandoned read: its handle is being closed by the aborting caller,
|
||||
// and the abort reason already carried the outcome.
|
||||
})
|
||||
reject(asError(signal.reason))
|
||||
}
|
||||
if (signal.aborted) {
|
||||
onAbort()
|
||||
return
|
||||
}
|
||||
signal.addEventListener('abort', onAbort, { once: true })
|
||||
operation.then(
|
||||
(value) => {
|
||||
signal.removeEventListener('abort', onAbort)
|
||||
resolve(value)
|
||||
},
|
||||
(reason: unknown) => {
|
||||
signal.removeEventListener('abort', onAbort)
|
||||
reject(asError(reason))
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/** The thrown value as an Error (wire/abort reasons may be anything). */
|
||||
function asError(reason: unknown): Error {
|
||||
return reason instanceof Error ? reason : new Error(String(reason))
|
||||
}
|
||||
|
||||
/** Message text of an unknown thrown value. */
|
||||
function messageOf(error: unknown): string {
|
||||
/* v8 ignore next -- node:fs rejects with Error instances; the String arm only satisfies the unknown narrowing. */
|
||||
@@ -180,16 +223,35 @@ export default class BrowseDirectoryPicker extends DirectoryPicker {
|
||||
const window: ListingCandidate[] = []
|
||||
let evicted = false
|
||||
try {
|
||||
const level = await opendir(target)
|
||||
for await (const dirent of level) {
|
||||
// A disconnected/timed-out caller stops the scan here; throwing out
|
||||
// of the loop closes the directory handle via the iterator's return.
|
||||
signal?.throwIfAborted()
|
||||
// Only rows a browser could enter contend for the window; dirent
|
||||
// says "directory" outright, a symlink needs the later stat probe.
|
||||
if (!dirent.isDirectory() && !dirent.isSymbolicLink()) continue
|
||||
const candidate = { name: dirent.name, isDirectory: dirent.isDirectory(), isSymbolicLink: dirent.isSymbolicLink() }
|
||||
if (boundedInsert(window, candidate, keep)) evicted = true
|
||||
// Every filesystem await races the caller's signal: a stalled
|
||||
// opendir/read on a network filesystem must not keep a departed
|
||||
// caller's scan alive, and an already-aborted request rejects even
|
||||
// when the level is empty.
|
||||
const opening = opendir(target)
|
||||
const level = await raceAbort(opening, signal).catch((error: unknown) => {
|
||||
// The abandoned open can still mint a handle after the abort won;
|
||||
// close it so a departed caller cannot leak a descriptor. (A lost
|
||||
// race against opendir's own rejection has nothing to close.)
|
||||
void opening.then(async (dir) => { await dir.close() }, () => {
|
||||
// Already rejected: raceAbort surfaced or swallowed it.
|
||||
})
|
||||
throw error
|
||||
})
|
||||
try {
|
||||
for (;;) {
|
||||
const dirent = await raceAbort(level.read(), signal)
|
||||
if (dirent === null) break
|
||||
// Only rows a browser could enter contend for the window; dirent
|
||||
// says "directory" outright, a symlink needs the later stat probe.
|
||||
if (!dirent.isDirectory() && !dirent.isSymbolicLink()) continue
|
||||
const candidate = { name: dirent.name, isDirectory: dirent.isDirectory(), isSymbolicLink: dirent.isSymbolicLink() }
|
||||
if (boundedInsert(window, candidate, keep)) evicted = true
|
||||
}
|
||||
} finally {
|
||||
// Manual read() never auto-closes; close on every exit, the aborted
|
||||
// one included (its abandoned read settles against the closed handle
|
||||
// and raceAbort already swallowed that settlement).
|
||||
await level.close()
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
// An abort is the caller's own reason, not an unreadable directory.
|
||||
|
||||
Reference in New Issue
Block a user