Files
bare-operating-system/packages/bare-os-booter/lib/vfs-warm-cache.js
T
2026-08-18 17:44:24 -04:00

242 lines
8.4 KiB
JavaScript

/**
* /bin and /lib/bare warm read-cache maps + eviction helpers.
*/
import { bareOsKernelMetricInc } from './bare-os-kernel-metrics.js'
const BIN_READ_CACHE_MAX = 64
/**
* @param {{
* env: Record<string, string | undefined>,
* warmReadCacheStatsRef?: { current: Record<string, unknown> | null } | null
* }} deps
*/
export function createVfsWarmReadCache(deps) {
const { env, warmReadCacheStatsRef = null } = deps
/**
* Warm read-cache invalidation (decision matrix — operator + maintainer reference):
* | Trigger | Mechanism | Notes |
* | --- | --- | --- |
* | Full flush | `bareOsClearWarmReadCaches` | Clears `/bin` + `/lib/bare` maps; syscall proc cache reset in booter. |
* | Replication core growth | `BARE_OS_VFS_WARM_CACHE_INVALIDATE_ON_REPLICATION` + swarm hints | Prefix eviction via `bareOsInvalidateWarmReadCachesForReplicationPrefixes`. |
* | Batch `ctx.bareOsVfsBatchWrite` | `bareOsVfsBatchWrite` | Clears on `bin/` / `lib/bare/` puts. |
* | Manifest-only bundle rows | `bareOsEvictLibBareBundlesFromManifest` + `ctx.bareOsInvalidateWarmReadCachesFromBareManifestJson` | Targeted `/lib/bare/bundles/<ctxKey>.js` + manifest path. |
* | Parse errors on manifest JSON | `bareOsEvictLibBareBundlesFromManifest` | Falls back to **full** clear. |
*/
const binCacheEnabled =
env.BARE_OS_VFS_BIN_CACHE === '1' || env.BARE_OS_VFS_BIN_CACHE === 'true'
const binCacheBlake2b =
binCacheEnabled &&
(env.BARE_OS_VFS_BIN_CACHE_BLAKE2B === '1' ||
env.BARE_OS_VFS_BIN_CACHE_BLAKE2B === 'true')
/** @type {Map<string, Uint8Array> | null} */
const binReadCache = binCacheEnabled && !binCacheBlake2b ? new Map() : null
/** @type {Map<string, Uint8Array> | null} digest hex → bytes */
const binDigestCache = binCacheBlake2b ? new Map() : null
/** @type {Map<string, string> | null} logical path → digest */
const binPathToBlakeDigest = binCacheBlake2b ? new Map() : null
/** @type {Map<string, number> | null} digest refcount */
const binDigestRefcount = binCacheBlake2b ? new Map() : null
/** @type {string[] | null} */
const binBlake2bLruPaths = binCacheBlake2b ? [] : null
const libBareWarmCache =
binCacheEnabled &&
(env.BARE_OS_VFS_LIB_BARE_CACHE === '1' ||
env.BARE_OS_VFS_LIB_BARE_CACHE === 'true')
const warmReadCacheStats =
binReadCache || binDigestCache
? {
schema: 2,
hits: 0,
misses: 0,
binHits: 0,
libBareHits: 0,
libBareCacheEnabled: !!libBareWarmCache,
/** Last selective invalidation driven by replication core-length hints (metrics_live). */
replicationPrefixEviction: /** @type {{ atMs: number, pathsEvicted: number, prefixes: string[] } | null} */ (
null
)
}
: null
if (warmReadCacheStatsRef && warmReadCacheStats) {
warmReadCacheStatsRef.current = warmReadCacheStats
}
/** @param {string} abs */
function bumpWarmReadCacheHit(absFollowed) {
if (!warmReadCacheStats) return
warmReadCacheStats.hits++
if (absFollowed.startsWith('/lib/bare/')) warmReadCacheStats.libBareHits++
else warmReadCacheStats.binHits++
}
/** @param {string} abs */
function isWarmReadCachePath(abs) {
if (abs.startsWith('/bin/')) return true
return !!(libBareWarmCache && abs.startsWith('/lib/bare/'))
}
function touchBinBlake2bLru(p) {
if (!binBlake2bLruPaths || !binPathToBlakeDigest) return
const i = binBlake2bLruPaths.indexOf(p)
if (i >= 0) binBlake2bLruPaths.splice(i, 1)
binBlake2bLruPaths.push(p)
while (binBlake2bLruPaths.length > BIN_READ_CACHE_MAX) {
const victim = binBlake2bLruPaths.shift()
if (!victim) continue
const hex = binPathToBlakeDigest.get(victim)
if (!hex) continue
binPathToBlakeDigest.delete(victim)
const n = (binDigestRefcount.get(hex) || 1) - 1
if (n <= 0) {
binDigestRefcount.delete(hex)
binDigestCache.delete(hex)
} else {
binDigestRefcount.set(hex, n)
}
}
}
/** Drop `/bin` and `/lib/bare` warm read caches (replication / OTA safety). */
function bareOsClearWarmReadCaches() {
if (binReadCache) binReadCache.clear()
if (binDigestCache) binDigestCache.clear()
if (binPathToBlakeDigest) binPathToBlakeDigest.clear()
if (binDigestRefcount) binDigestRefcount.clear()
if (binBlake2bLruPaths) binBlake2bLruPaths.length = 0
if (warmReadCacheStats) {
warmReadCacheStats.hits = 0
warmReadCacheStats.misses = 0
warmReadCacheStats.binHits = 0
warmReadCacheStats.libBareHits = 0
warmReadCacheStats.replicationPrefixEviction = null
}
bareOsKernelMetricInc('vfs.warm_read_cache_clear')
}
/**
* Evict warm-cache rows whose logical paths start with one of the prefixes (replication / OTA).
* @param {string[]} prefixes
* @returns {number} paths evicted
*/
function bareOsEvictWarmReadPrefixes(prefixes) {
if (!binReadCache && !binDigestCache) return 0
const ps = (Array.isArray(prefixes) ? prefixes : [])
.map((p) => String(p || '').replace(/\\/g, '/'))
.filter((p) => p.startsWith('/'))
if (!ps.length) return 0
/** @type {string[]} */
const victims = []
const collect = (k) => {
if (!isWarmReadCachePath(k)) return
if (!ps.some((p) => k.startsWith(p))) return
victims.push(k)
}
if (binReadCache) {
for (const k of binReadCache.keys()) collect(k)
}
if (binPathToBlakeDigest) {
for (const k of binPathToBlakeDigest.keys()) collect(k)
}
for (const k of victims) bareOsEvictSingleWarmReadPath(k)
const n = victims.length
if (n > 0 && warmReadCacheStats) {
warmReadCacheStats.replicationPrefixEviction = {
atMs: Date.now(),
pathsEvicted: n,
prefixes: ps.slice(0, 16)
}
bareOsKernelMetricInc('vfs.warm_read_cache_invalidate_prefix')
}
return n
}
/** @param {string} absFollowed */
function bareOsEvictSingleWarmReadPath(absFollowed) {
if (!isWarmReadCachePath(absFollowed)) return
if (binReadCache) binReadCache.delete(absFollowed)
if (binDigestCache && binPathToBlakeDigest && binDigestRefcount) {
const oldHex = binPathToBlakeDigest.get(absFollowed)
if (oldHex) {
binPathToBlakeDigest.delete(absFollowed)
const n0 = (binDigestRefcount.get(oldHex) || 1) - 1
if (n0 <= 0) {
binDigestRefcount.delete(oldHex)
binDigestCache.delete(oldHex)
} else {
binDigestRefcount.set(oldHex, n0)
}
}
}
if (binBlake2bLruPaths) {
const i = binBlake2bLruPaths.indexOf(absFollowed)
if (i >= 0) binBlake2bLruPaths.splice(i, 1)
}
}
/**
* Invalidate warm-cache entries for `bare-module-manifest.json` and bundled
* `/lib/bare/bundles/<ctxKey>.js` rows (parse errors fall back to full clear).
* @param {Uint8Array | ArrayBuffer} buf
*/
function bareOsEvictLibBareBundlesFromManifest(buf) {
const u8 =
buf instanceof Uint8Array ? buf : new Uint8Array(/** @type {ArrayBuffer} */ (buf))
let text
try {
text = new TextDecoder().decode(u8)
} catch {
bareOsClearWarmReadCaches()
return
}
/** @type {unknown} */
let o
try {
o = JSON.parse(text)
} catch {
bareOsClearWarmReadCaches()
return
}
if (
!o ||
typeof o !== 'object' ||
!Array.isArray(/** @type {{ entries?: unknown }} */ (o).entries)
) {
bareOsClearWarmReadCaches()
return
}
bareOsEvictSingleWarmReadPath('/lib/bare/bare-module-manifest.json')
for (const e of /** @type {{ entries: unknown[] }} */ (o).entries) {
if (!e || typeof e !== 'object') continue
const ent = /** @type {{ bundle?: boolean, ctxKey?: string }} */ (e)
if (ent.bundle !== true) continue
const ck = String(ent.ctxKey || '').trim()
if (!ck) continue
bareOsEvictSingleWarmReadPath(`/lib/bare/bundles/${ck}.js`)
}
bareOsKernelMetricInc('vfs.warm_read_cache_evict_manifest')
}
return {
BIN_READ_CACHE_MAX,
binCacheEnabled,
binCacheBlake2b,
binReadCache,
binDigestCache,
binPathToBlakeDigest,
binDigestRefcount,
binBlake2bLruPaths,
libBareWarmCache,
warmReadCacheStats,
bumpWarmReadCacheHit,
isWarmReadCachePath,
touchBinBlake2bLru,
bareOsClearWarmReadCaches,
bareOsEvictWarmReadPrefixes,
bareOsEvictSingleWarmReadPath,
bareOsEvictLibBareBundlesFromManifest
}
}