From f687a074d8fbd4622eaeca4d4b758ad1f8dc525d Mon Sep 17 00:00:00 2001 From: can1357 Date: Wed, 5 Aug 2026 02:27:08 +0200 Subject: [PATCH] fix(natives): refresh active cache directories (cherry picked from commit c62702b89d1f29bc48067d64575c739c65456bc8) --- packages/natives/native/loader-state.d.ts | 2 ++ packages/natives/native/loader-state.js | 18 ++++++++++++++++-- packages/natives/test/windows-staging.test.ts | 3 +++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/natives/native/loader-state.d.ts b/packages/natives/native/loader-state.d.ts index 63a042cd7..e61d2da21 100644 --- a/packages/natives/native/loader-state.d.ts +++ b/packages/natives/native/loader-state.d.ts @@ -88,6 +88,8 @@ export interface CleanupStaleNativeVersionsInput { export function cleanupStaleNativeVersions(input: CleanupStaleNativeVersionsInput): string[]; +export function prepareNativeVersionDir(versionedDir: string): void; + export interface ExtractEmbeddedAddonArchiveInput { archivePath: string; files: EmbeddedAddonFile[]; diff --git a/packages/natives/native/loader-state.js b/packages/natives/native/loader-state.js index db06d5fd6..385aed862 100644 --- a/packages/natives/native/loader-state.js +++ b/packages/natives/native/loader-state.js @@ -202,6 +202,20 @@ function isOlderReleaseVersion(candidate, current) { // stale. const NATIVE_CACHE_CLEANUP_GRACE_MS = 10 * 60_000; +/** + * Create a version cache directory and refresh its activity timestamp before + * extraction or staging begins. Recursive mkdir does not update the mtime of + * an existing directory, so the explicit touch is what protects interrupted + * or partially populated caches from concurrent cleanup. + * + * @param {string} versionedDir + */ +export function prepareNativeVersionDir(versionedDir) { + fs.mkdirSync(versionedDir, { recursive: true }); + const now = new Date(); + fs.utimesSync(versionedDir, now, now); +} + /** * Remove version-pinned native cache directories older than the loaded package. * Best-effort by design: permission errors and concurrent processes must not @@ -522,7 +536,7 @@ function maybeExtractEmbeddedAddon(ctx, errors) { startupMarker("native:extractEmbeddedAddon:start"); try { - fs.mkdirSync(ctx.versionedDir, { recursive: true }); + prepareNativeVersionDir(ctx.versionedDir); } catch (err) { const message = err instanceof Error ? err.message : String(err); errors.push(`embedded addon dir: ${message}`); @@ -589,7 +603,7 @@ function maybeStageNodeModulesAddon(ctx, errors) { if (!fs.existsSync(sourcePath)) continue; try { - fs.mkdirSync(ctx.versionedDir, { recursive: true }); + prepareNativeVersionDir(ctx.versionedDir); } catch (err) { const message = err instanceof Error ? err.message : String(err); errors.push(`staged addon dir: ${message}`); diff --git a/packages/natives/test/windows-staging.test.ts b/packages/natives/test/windows-staging.test.ts index 144f8ef6a..a951ba5b7 100644 --- a/packages/natives/test/windows-staging.test.ts +++ b/packages/natives/test/windows-staging.test.ts @@ -27,6 +27,7 @@ import { cleanupStaleNativeVersions, getAddonFilenames, initLoaderContext, + prepareNativeVersionDir, resolveLoaderCandidates, shouldStageNodeModulesAddon, } from "../native/loader-state.js"; @@ -205,6 +206,8 @@ describe("windows native addon staging", () => { await fs.mkdir(path.join(nativesDir, "not-a-version")); await Bun.write(path.join(nativesDir, "README.txt"), "not a version directory"); await fs.utimes(path.join(nativesDir, staleVersion), new Date(0), new Date(0)); + await fs.utimes(path.join(nativesDir, freshVersion), new Date(0), new Date(0)); + prepareNativeVersionDir(path.join(nativesDir, freshVersion)); const removed = cleanupStaleNativeVersions({ nativesDir, currentVersion: packageJson.version });