Skip to content

Commit

Permalink
Refresh cache before writing contents to bundle (#9123)
Browse files Browse the repository at this point in the history
* Add Cache.refresh() method

* Refresh cache before reading contents of bundle

* Refresh cache before running WriteBundleRequest

..only if the packaging request didn't run in the main thread.

* Document Cache.refresh()

---------

Co-authored-by: David Alsh <12656294+alshdavid@users.noreply.github.com>
  • Loading branch information
lettertwo and alshdavid committed Jul 19, 2023
1 parent 2d2400d commit 7ff54c7
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 0 deletions.
4 changes: 4 additions & 0 deletions packages/core/cache/src/FSCache.js
Expand Up @@ -116,6 +116,10 @@ export class FSCache implements Cache {
logger.error(err, '@parcel/cache');
}
}

refresh(): void {
// NOOP
}
}

registerSerializableClass(`${packageJson.version}:FSCache`, FSCache);
4 changes: 4 additions & 0 deletions packages/core/cache/src/IDBCache.browser.js
Expand Up @@ -117,6 +117,10 @@ export class IDBCache implements Cache {
setLargeBlob(key: string, contents: Buffer | string): Promise<void> {
return this.setBlob(key, contents);
}

refresh(): void {
// NOOP
}
}

registerSerializableClass(`${packageJson.version}:IDBCache`, IDBCache);
8 changes: 8 additions & 0 deletions packages/core/cache/src/LMDBCache.js
Expand Up @@ -102,6 +102,14 @@ export class LMDBCache implements Cache {
async setLargeBlob(key: string, contents: Buffer | string): Promise<void> {
await this.fs.writeFile(path.join(this.dir, key), contents);
}

refresh(): void {
// Reset the read transaction for the store. This guarantees that
// the next read will see the latest changes to the store.
// Useful in scenarios where reads and writes are multi-threaded.
// See https://github.com/kriszyp/lmdb-js#resetreadtxn-void
this.store.resetReadTxn();
}
}

registerSerializableClass(`${packageJson.version}:LMDBCache`, LMDBCache);
6 changes: 6 additions & 0 deletions packages/core/cache/src/types.js
Expand Up @@ -14,4 +14,10 @@ export interface Cache {
getLargeBlob(key: string): Promise<Buffer>;
setLargeBlob(key: string, contents: Buffer | string): Promise<void>;
getBuffer(key: string): Promise<?Buffer>;
/**
* In a multi-threaded environment, where there are potentially multiple Cache
* instances writing to the cache, ensure that this instance has the latest view
* of the changes that may have been written to the cache in other threads.
*/
refresh(): void;
}
12 changes: 12 additions & 0 deletions packages/core/core/src/requests/WriteBundlesRequest.js
Expand Up @@ -101,6 +101,18 @@ async function run({input, api, farm, options}) {

let info = await api.runRequest(request);

if (!useMainThread) {
// Force a refresh of the cache to avoid a race condition
// between threaded reads and writes that can result in an LMDB cache miss:
// 1. The main thread has read some value from cache, necessitating a read transaction.
// 2. Concurrently, Thread A finishes a packaging request.
// 3. Subsequently, the main thread is tasked with this request, but fails because the read transaction is stale.
// This only occurs if the reading thread has a transaction that was created before the writing thread committed,
// and the transaction is still live when the reading thread attempts to get the written value.
// See https://github.com/parcel-bundler/parcel/issues/9121
options.cache.refresh();
}

bundleInfoMap[bundle.id] = info;
if (!info.hashReferences.length) {
hashRefToNameHash.set(
Expand Down

0 comments on commit 7ff54c7

Please sign in to comment.