Files
bare-operating-system/packages/bare-os-booter/lib/bare-os-abort.js
T
2026-04-03 20:46:09 -04:00

97 lines
2.6 KiB
JavaScript

/**
* Optional {@link AbortSignal} and wall-clock timeouts for `ctx.execLine`, `ctx.readLine`,
* and VFS I/O. Domains may be absent in Bare; treat as optional.
*/
/** @typedef {{ signal?: AbortSignal | null | undefined, timeoutMs?: number | null | undefined }} BareOsAbortOpts */
/**
* @param {BareOsAbortOpts | null | undefined} opts
* @returns {AbortSignal | undefined}
*/
export function effectiveAbortSignal(opts) {
if (!opts || typeof opts !== 'object') return undefined
const s = opts.signal
if (s && typeof s.aborted === 'boolean') return s
return undefined
}
/**
* @param {BareOsAbortOpts | null | undefined} opts
* @returns {number | undefined} positive timeout in ms
*/
export function effectiveTimeoutMs(opts) {
if (!opts || typeof opts !== 'object') return undefined
const n = Number(opts.timeoutMs)
if (!Number.isFinite(n) || n <= 0) return undefined
return Math.min(Math.floor(n), 86_400_000)
}
/**
* Race `promise` with abort and/or timeout. On abort: reject with `DOMException` name AbortError if available.
* @template T
* @param {Promise<T>} promise
* @param {BareOsAbortOpts | null | undefined} opts
* @param {string} [label]
* @returns {Promise<T>}
*/
export function raceWithAbortAndTimeout(promise, opts, label = 'operation') {
const sig = effectiveAbortSignal(opts)
const ms = effectiveTimeoutMs(opts)
if (!sig && ms == null) return promise
return new Promise((resolve, reject) => {
let settled = false
const finish = (fn) => {
if (settled) return
settled = true
fn()
}
const onAbort = () => {
finish(() => {
try {
const DOMException = globalThis.DOMException
if (DOMException && typeof DOMException === 'function') {
reject(new DOMException(`${label} aborted`, 'AbortError'))
return
}
} catch {
/* ignore */
}
reject(new Error(`${label} aborted`))
})
}
let timer = null
if (ms != null) {
timer = setTimeout(() => {
finish(() => reject(new Error(`${label} timed out after ${ms}ms`)))
}, ms)
}
if (sig) {
if (sig.aborted) {
onAbort()
return
}
sig.addEventListener('abort', onAbort, { once: true })
}
Promise.resolve(promise).then(
(v) =>
finish(() => {
if (timer) clearTimeout(timer)
if (sig) sig.removeEventListener('abort', onAbort)
resolve(v)
}),
(e) =>
finish(() => {
if (timer) clearTimeout(timer)
if (sig) sig.removeEventListener('abort', onAbort)
reject(e)
})
)
})
}