Skip to content

Commit

Permalink
fix(endo): Synchronize host cancellation with formula graph
Browse files Browse the repository at this point in the history
Synchronizes the host's `cancel()` with the formula graph by awaiting
the formula graph mutex in a new daemon method, `cancelValue()`. Modifies
the mutex implementation to permit calling `enqueue()` without
specifying function to call.
  • Loading branch information
rekmarks committed Feb 22, 2024
1 parent 5f90673 commit 8a52453
Show file tree
Hide file tree
Showing 5 changed files with 24 additions and 17 deletions.
10 changes: 9 additions & 1 deletion packages/daemon/src/daemon.js
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,14 @@ const makeDaemonCore = async (
return controller;
};

/** @type {import('./types.js').CancelValue} */
const cancelValue = async (formulaIdentifier, reason) => {
await formulaGraphMutex.enqueue();
const controller = provideControllerForFormulaIdentifier(formulaIdentifier);
console.log('Cancelled:');
return controller.context.cancel(reason);
};

/** @type {import('./types.js').ProvideValueForFormulaIdentifier} */
const provideValueForFormulaIdentifier = formulaIdentifier => {
const controller = /** @type {import('./types.js').Controller<>} */ (
Expand Down Expand Up @@ -1087,8 +1095,8 @@ const makeDaemonCore = async (
const makeMailbox = makeMailboxMaker({
getFormulaIdentifierForRef,
provideValueForFormulaIdentifier,
provideControllerForFormulaIdentifier,
provideControllerForFormulaIdentifierAndResolveHandle,
cancelValue,
});

const makeIdentifiedGuestController = makeGuestMaker({
Expand Down
10 changes: 3 additions & 7 deletions packages/daemon/src/mail.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ const { quote: q } = assert;
/**
* @param {object} args
* @param {import('./types.js').ProvideValueForFormulaIdentifier} args.provideValueForFormulaIdentifier
* @param {import('./types.js').ProvideControllerForFormulaIdentifier} args.provideControllerForFormulaIdentifier
* @param {import('./types.js').GetFormulaIdentifierForRef} args.getFormulaIdentifierForRef
* @param {import('./types.js').ProvideControllerForFormulaIdentifierAndResolveHandle} args.provideControllerForFormulaIdentifierAndResolveHandle
* @param {import('./types.js').CancelValue} args.cancelValue
*/
export const makeMailboxMaker = ({
getFormulaIdentifierForRef,
provideValueForFormulaIdentifier,
provideControllerForFormulaIdentifier,
provideControllerForFormulaIdentifierAndResolveHandle,
cancelValue,
}) => {
/**
* @param {object} args
Expand Down Expand Up @@ -81,11 +81,7 @@ export const makeMailboxMaker = ({
if (formulaIdentifier === undefined) {
throw new TypeError(`Unknown pet name: ${q(petName)}`);
}
// Behold, recursion:
const controller =
provideControllerForFormulaIdentifier(formulaIdentifier);
console.log('Cancelled:');
return controller.context.cancel(reason);
return cancelValue(formulaIdentifier, reason);
};

/** @type {import('./types.js').Mail['list']} */
Expand Down
5 changes: 2 additions & 3 deletions packages/daemon/src/mutex.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { makeQueue } from '@endo/stream';

/**
* @returns {{ lock: () => Promise<void>, unlock: () => void, enqueue: (asyncFn: () => Promise<any>) => Promise<any> }}
* @returns {import('./types.js').Mutex}
*/
export const makeMutex = () => {
/** @type {import('@endo/stream').AsyncQueue<void>} */
Expand All @@ -17,8 +17,7 @@ export const makeMutex = () => {
return {
lock,
unlock,
// helper for correct usage
enqueue: async asyncFn => {
enqueue: async (asyncFn = /** @type {any} */ (async () => {})) => {
await lock();
try {
return await asyncFn();
Expand Down
10 changes: 10 additions & 0 deletions packages/daemon/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,10 @@ export type ProvideControllerForFormulaIdentifier = (
export type ProvideControllerForFormulaIdentifierAndResolveHandle = (
formulaIdentifier: string,
) => Promise<Controller>;
export type CancelValue = (
formulaIdentifier: string,
reason: Error,
) => Promise<void>;

/**
* A handle is used to create a pointer to a formula without exposing it directly.
Expand Down Expand Up @@ -632,3 +636,9 @@ export type DaemonicPowers = {
persistence: DaemonicPersistencePowers;
control: DaemonicControlPowers;
};

type Mutex = {
lock: () => Promise<void>;
unlock: () => void;
enqueue: <T>(asyncFn?: () => Promise<T>) => Promise<T>;
};
6 changes: 0 additions & 6 deletions packages/daemon/test/test-endo.js
Original file line number Diff line number Diff line change
Expand Up @@ -847,9 +847,6 @@ test('unconfined service can respond to cancellation', async t => {
['caplet'],
['context-consumer'],
);
// TODO:cancel This should not be necessary.
// eslint-disable-next-line no-undef
await new Promise(resolve => setTimeout(resolve, 100));
await E(host).cancel('context-consumer');
t.is(await result, 'cancelled');
});
Expand Down Expand Up @@ -883,9 +880,6 @@ test('confined service can respond to cancellation', async t => {
['caplet'],
['context-consumer'],
);
// TODO:cancel This should not be necessary.
// eslint-disable-next-line no-undef
await new Promise(resolve => setTimeout(resolve, 100));
await E(host).cancel('context-consumer');
t.is(await result, 'cancelled');
});
Expand Down

0 comments on commit 8a52453

Please sign in to comment.