Files
bare-operating-system/packages/bare-os-booter/lib/host/bare-os-lifecycle-manager.js
T
2026-08-18 18:11:28 -04:00

126 lines
4.4 KiB
JavaScript

/**
* Host exit and ordered teardown of swarm/Hyperdrive/Corestore (Pear-safe ordering).
* @module bare-os-lifecycle-manager
*/
/**
* On Pear/Bare, `process.exit` is often missing or ineffective; use `Bare.exit`
* (see holepunch `pear-prerelease`, `pear-terminal`, `bare-process`).
* @param {number} code
*/
export function exitHostProcess(code) {
const Bare = globalThis.Bare
if (Bare && typeof Bare.exit === 'function') {
/* Defer so Hyperdrive/swarm/native stdio teardown is not on the same stack as exit
* (avoids malloc "pointer being freed was not allocated" under Pear on macOS). */
const run = () => Bare.exit(code)
if (typeof setImmediate === 'function') setImmediate(run)
else Promise.resolve().then(run)
return
}
const p = globalThis.process
if (p && typeof p.exit === 'function') p.exit(code)
}
/**
* Stop replication before closing drives — closing Hyperdrive while Protomux streams
* are still live can corrupt native heaps under Pear.
*
* Boot budget / bare-stdlib telemetry is recorded by the booter before initd (**`BARE_OS_BOOT_BARE_STDLIB_RESOLUTION_MS`**, **`bootReadyStateRef.subsystems.bareStdlib`**) and by the guest in **`/run/bare-os/boot-perf.json`** (**schema 5**); **`metrics_live.json`** **`bootBudgetTelemetry`** (**schema 2**) mirrors the same env keys for dashboards.
*
* @param {{
* hdmsController?: { deactivate(): Promise<unknown> },
* personalDrive?: { close(): Promise<unknown> },
* drive?: { close(): Promise<unknown> }
* }} disk
* @param {{ destroy(): Promise<unknown> }} swarm
* @param {{ close(): Promise<unknown> }} store
*/
export async function teardownBareOsBootResources(disk, swarm, store) {
try {
if (disk.hdmsController) await disk.hdmsController.deactivate()
} catch (_) {}
try {
await swarm.destroy()
} catch (_) {}
try {
if (disk.personalDrive) await disk.personalDrive.close()
} catch (_) {}
try {
if (disk.drive) await disk.drive.close()
} catch (_) {}
try {
await store.close()
} catch (_) {}
}
/**
* @returns {{ state: string, canTransitionTo: (next: string) => boolean, transition: (next: string) => boolean, snapshot: () => { state: string, atMs: number, transitions: Array<{ from: string, to: string, atMs: number }> } }}
*/
export function createBareOsLifecycleStateMachine() {
/** @type {'opening'|'ready'|'suspending'|'resuming'|'closing'|'closed'} */
let state = 'opening'
/** @type {Array<{ from: string, to: string, atMs: number }>} */
const transitions = []
const ok = (from, to) =>
(from === 'opening' && (to === 'ready' || to === 'closing')) ||
(from === 'ready' && (to === 'suspending' || to === 'closing')) ||
(from === 'suspending' && (to === 'resuming' || to === 'closing')) ||
(from === 'resuming' && (to === 'ready' || to === 'closing')) ||
(from === 'closing' && to === 'closed')
return {
get state() {
return state
},
canTransitionTo(next) {
return ok(state, String(next || ''))
},
transition(next) {
const n = String(next || '')
if (!ok(state, n)) return false
transitions.push({ from: state, to: n, atMs: Date.now() })
state = /** @type {typeof state} */ (n)
return true
},
snapshot() {
return { state, atMs: Date.now(), transitions: [...transitions] }
}
}
}
/**
* @param {Array<{ name: string, run: () => Promise<unknown> }>} hooks
* @returns {Promise<{ schema: 1, ok: boolean, rows: Array<{ name: string, ok: boolean, error?: string, atMs: number }> }>}
*/
export async function runTransactionalLifecycleHooks(hooks) {
/** @type {Array<{ name: string, ok: boolean, error?: string, atMs: number }>} */
const rows = []
let ok = true
for (const h of Array.isArray(hooks) ? hooks : []) {
const name = String(h && h.name ? h.name : 'hook')
try {
await Promise.resolve(h.run())
rows.push({ name, ok: true, atMs: Date.now() })
} catch (e) {
ok = false
rows.push({
name,
ok: false,
error: e && e.message ? e.message : String(e),
atMs: Date.now()
})
}
}
return { schema: 1, ok, rows }
}
/**
* Best-effort settle delay before teardown.
* @param {{ timeoutMs?: number }} [opts]
*/
export async function quiesceReplicationBeforeTeardown(opts = {}) {
const t = Math.max(0, Math.min(10000, Number(opts.timeoutMs) || 200))
if (t <= 0) return
await new Promise((resolve) => setTimeout(resolve, t))
}