Skip to content

Commit

Permalink
fix(daemon): pet-store uses mutex to ensure serial file operations
Browse files Browse the repository at this point in the history
  • Loading branch information
kumavis committed Dec 1, 2023
1 parent 6050df8 commit e87b856
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 3 deletions.
49 changes: 49 additions & 0 deletions packages/daemon/src/mutex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { makePromiseKit } from '@endo/promise-kit';

/**
* @returns {{ put: (value: any) => void, get: () => Promise<any> }}
*/
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<void>, unlock: () => void, enqueue: (asyncFn: () => Promise<any>) => Promise<any> }}
*/
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()
}
},
};
}
17 changes: 14 additions & 3 deletions packages/daemon/src/pet-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -27,6 +28,7 @@ export const makePetStoreMaker = (filePowers, locator) => {
const formulaIdentifiers = new Map();
/** @type {import('./types.js').Topic<unknown>} */
const changesTopic = makeChangeTopic();
const writeLock = makeMutex();

/** @param {string} petName */
const read = async petName => {
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
});
};

/**
Expand Down Expand Up @@ -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);

Expand All @@ -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);
});
};

/**
Expand Down

0 comments on commit e87b856

Please sign in to comment.