diff --git a/packages/daemon/src/mutex.js b/packages/daemon/src/mutex.js new file mode 100644 index 0000000000..d5bc63ce5e --- /dev/null +++ b/packages/daemon/src/mutex.js @@ -0,0 +1,49 @@ +import { makePromiseKit } from '@endo/promise-kit'; + +/** + * @returns {{ put: (value: any) => void, get: () => Promise }} + */ +export const makeQueue = () => { + let { promise: tailPromise, resolve: tailResolve } = makePromiseKit(); + return { + put(value) { + const next = makePromiseKit(); + const promise = next.promise; + tailResolve({ value, promise }); + tailResolve = next.resolve; + }, + get() { + const promise = tailPromise.then(next => next.value); + tailPromise = tailPromise.then(next => next.promise); + return promise; + }, + }; +}; + +/** + * @returns {{ lock: () => Promise, unlock: () => void, enqueue: (asyncFn: () => Promise) => Promise }} + */ +export const makeMutex = () => { + const queue = makeQueue(); + const lock = () => { + return queue.get() + } + const unlock = () => { + queue.put() + } + unlock() + + return { + lock, + unlock, + // helper for correct usage + enqueue: async (asyncFn) => { + await lock() + try { + return await asyncFn() + } finally { + unlock() + } + }, + }; +} \ No newline at end of file diff --git a/packages/daemon/src/pet-store.js b/packages/daemon/src/pet-store.js index 8e2da048e8..c8a940bb42 100644 --- a/packages/daemon/src/pet-store.js +++ b/packages/daemon/src/pet-store.js @@ -3,6 +3,7 @@ import { Far } from '@endo/far'; import { makeChangeTopic } from './pubsub.js'; import { makeIteratorRef } from './reader-ref.js'; +import { makeMutex } from './mutex.js'; const { quote: q } = assert; @@ -27,6 +28,7 @@ export const makePetStoreMaker = (filePowers, locator) => { const formulaIdentifiers = new Map(); /** @type {import('./types.js').Topic} */ const changesTopic = makeChangeTopic(); + const writeLock = makeMutex(); /** @param {string} petName */ const read = async petName => { @@ -103,8 +105,11 @@ export const makePetStoreMaker = (filePowers, locator) => { const petNamePath = filePowers.joinPath(petNameDirectoryPath, petName); const petNameText = `${formulaIdentifier}\n`; - await filePowers.writeFileText(petNamePath, petNameText); changesTopic.publisher.next({ add: petName }); + + return writeLock.enqueue(async () => { + await filePowers.writeFileText(petNamePath, petNameText); + }); }; const list = () => harden([...petNames.keys()].sort()); @@ -136,7 +141,6 @@ export const makePetStoreMaker = (filePowers, locator) => { } const petNamePath = filePowers.joinPath(petNameDirectoryPath, petName); - await filePowers.removePath(petNamePath); petNames.delete(petName); const formulaPetNames = formulaIdentifiers.get(petName); if (formulaPetNames !== undefined) { @@ -145,6 +149,10 @@ export const makePetStoreMaker = (filePowers, locator) => { changesTopic.publisher.next({ remove: petName }); // TODO consider retaining a backlog of deleted names for recovery // TODO consider tracking historical pet names for formulas + + return writeLock.enqueue(async () => { + await filePowers.removePath(petNamePath); + }); }; /** @@ -178,7 +186,6 @@ export const makePetStoreMaker = (filePowers, locator) => { const fromPath = filePowers.joinPath(petNameDirectoryPath, fromName); const toPath = filePowers.joinPath(petNameDirectoryPath, toName); - await filePowers.renamePath(fromPath, toPath); petNames.set(toName, formulaIdentifier); petNames.delete(fromName); @@ -202,6 +209,10 @@ export const makePetStoreMaker = (filePowers, locator) => { changesTopic.publisher.next({ add: toName }); changesTopic.publisher.next({ remove: fromName }); // TODO consider retaining a backlog of overwritten names for recovery + + return writeLock.enqueue(async () => { + await filePowers.renamePath(fromPath, toPath); + }); }; /**