diff --git a/packages/metro-file-map/src/cache/__tests__/DiskCacheManager-test.js b/packages/metro-file-map/src/cache/__tests__/DiskCacheManager-test.js index e00d96feb7..def4f67cbc 100644 --- a/packages/metro-file-map/src/cache/__tests__/DiskCacheManager-test.js +++ b/packages/metro-file-map/src/cache/__tests__/DiskCacheManager-test.js @@ -215,6 +215,34 @@ describe('cacheManager', () => { ); }); + test('serialises the snapshot before anything can change it', async () => { + const cacheManager = new DiskCacheManager({buildParameters}, defaultConfig); + // The file map's own state, which it changes once control returns to the + // event loop. + const fileSystemData = new Map(); + const snapshot: CacheData = { + clocks: new Map(), + fileSystemData, + plugins: new Map(), + }; + const written = serialize(snapshot); + const getSnapshot = () => { + queueMicrotask(() => { + fileSystemData.set('later.js', [0, 0, 0, null, 0]); + }); + return snapshot; + }; + await cacheManager.write(getSnapshot, { + changedSinceCacheRead: true, + eventSource: {onChange: () => () => {}}, + onWriteError: () => {}, + }); + expect(mockWriteFile).toHaveBeenCalledWith( + cacheManager.getCacheFilePath(), + written, + ); + }); + test('creates the cache directory if it does not exist', async () => { const cacheManager = new DiskCacheManager({buildParameters}, defaultConfig); const getSnapshot = jest.fn( diff --git a/packages/metro-file-map/src/flow-types.js b/packages/metro-file-map/src/flow-types.js index 988326af0d..3ff7057e1a 100644 --- a/packages/metro-file-map/src/flow-types.js +++ b/packages/metro-file-map/src/flow-types.js @@ -51,7 +51,9 @@ export interface CacheManager { * crawler - i.e. internal state reflects the current file system state. * * getSnapshot may be retained and called at any time before end(), such as - * in response to eventSource 'change' events. + * in response to eventSource 'change' events. What it returns is the file + * map's own state, not a copy, and changes once control returns to the + * event loop. Serialize it before then, or copy it to keep it. */ write( getSnapshot: () => CacheData, @@ -392,6 +394,7 @@ export interface FileSystem { removedFiles: Set, filesToStat: Map, }; + // The file system's own data, not a copy (see `CacheManager.write`). getSerializableSnapshot(): CacheData['fileSystemData']; getSha1(file: Path): ?string; getOrComputeSha1(file: Path): Promise; diff --git a/packages/metro-file-map/src/lib/TreeFS.js b/packages/metro-file-map/src/lib/TreeFS.js index a2b1ea7ec2..29327240a3 100644 --- a/packages/metro-file-map/src/lib/TreeFS.js +++ b/packages/metro-file-map/src/lib/TreeFS.js @@ -207,7 +207,7 @@ export default class TreeFS implements MutableFileSystem { } getSerializableSnapshot(): CacheData['fileSystemData'] { - return this.#cloneTree(this.#rootNode); + return this.#rootNode; } static fromDeserializedSnapshot(args: DeserializedSnapshotInput): TreeFS { @@ -1490,16 +1490,4 @@ export default class TreeFS implements MutableFileSystem { } return result.node; } - - #cloneTree(root: DirectoryNode): DirectoryNode { - const clone: DirectoryNode = new Map(); - for (const [name, node] of root) { - if (isDirectory(node)) { - clone.set(name, this.#cloneTree(node)); - } else { - clone.set(name, [...node]); - } - } - return clone; - } }