242 lines
8.4 KiB
JavaScript
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
|
|
}
|
|
}
|