Skip to content

Commit

Permalink
refactor(daemon): Tidy controller system
Browse files Browse the repository at this point in the history
  • Loading branch information
kriskowal committed Apr 11, 2024
1 parent 87cb9da commit 2c066d1
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 61 deletions.
83 changes: 35 additions & 48 deletions packages/daemon/src/daemon.js
Original file line number Diff line number Diff line change
Expand Up @@ -382,8 +382,7 @@ const makeDaemonCore = async (
};

/**
* Creates a controller for a `lookup` formula. The external facet is the
* resolved value of the lookup.
* Creates a controller for a `lookup` formula.
*
* @param {string} hubId
* @param {string[]} path
Expand Down Expand Up @@ -688,24 +687,25 @@ const makeDaemonCore = async (
* @param {string} id
* @param {import('./types.js').Context} context
*/
const makeControllerForId = async (id, context) => {
const evaluateFormulaForId = async (id, context) => {
const { number: formulaNumber, node: formulaNode } = parseId(id);
const isRemote = formulaNode !== ownNodeIdentifier;
if (isRemote) {
// eslint-disable-next-line no-use-before-define
const peerIdentifier = await getPeerIdForNodeIdentifier(formulaNode);
// Behold, forward reference:
// eslint-disable-next-line no-use-before-define
return provideRemoteValue(peerIdentifier, id);
const peerId = await getPeerIdForNodeIdentifier(formulaNode);
const peer = /** @type {Promise<import('./types.js').EndoGateway>} */ (
// Behold, forward reference:
// eslint-disable-next-line no-use-before-define
provide(peerId)
);
return E(peer).provide(id);
}

const formula = await getFormulaForId(id);
console.log(`Reincarnating ${formula.type} ${id}`);
assertValidFormulaType(formula.type);
// TODO further validation

const value = evaluateFormula(id, formulaNumber, formula, context);
return { external: value };
return evaluateFormula(id, formulaNumber, formula, context);
};

/** @type {import('./types.js').DaemonCore['formulate']} */
Expand All @@ -720,39 +720,42 @@ const makeDaemonCore = async (

// Memoize for lookup.
console.log(`Making ${formula.type} ${id}`);
const { promise: partial, resolve: resolvePartial } =
/** @type {import('@endo/promise-kit').PromiseKit<import('./types.js').InternalExternal<>>} */ (
const { promise, resolve } =
/** @type {import('@endo/promise-kit').PromiseKit<unknown>} */ (
makePromiseKit()
);

// Behold, recursion:
// eslint-disable-next-line no-use-before-define
const context = makeContext(id);
partial.catch(context.cancel);
promise.catch(context.cancel);
const controller = harden({
context,
external: E.get(partial).external.then(value => {
if (typeof value === 'object' && value !== null) {
idForRef.add(value, id);
}
return value;
}),
value: promise,
});
controllerForId.set(id, controller);

// The controller _must_ be constructed in the synchronous prelude of this function.
const value = evaluateFormula(id, formulaNumber, formula, context);
const controllerValue = { external: value };

// Ensure that failure to flush the formula to storage
// causes a rejection for both the controller and the value.
const written = persistencePowers.writeFormula(formulaNumber, formula);
resolvePartial(written.then(() => /** @type {any} */ (controllerValue)));
// The controller _must_ be constructed in the synchronous prelude of this function.
const valuePromise = evaluateFormula(
id,
formulaNumber,
formula,
context,
).then(value => {
if (typeof value === 'object' && value !== null) {
idForRef.add(value, id);
}
return value;
});
resolve(written.then(() => valuePromise));
await written;

return harden({
id,
value: controller.external,
value: controller.value,
});
};

Expand All @@ -763,22 +766,23 @@ const makeDaemonCore = async (
return controller;
}

const { promise: partial, resolve } =
/** @type {import('@endo/promise-kit').PromiseKit<import('./types.js').InternalExternal<>>} */ (
const { promise, resolve } =
/** @type {import('@endo/promise-kit').PromiseKit<unknown>} */ (
makePromiseKit()
);

// Behold, recursion:
// eslint-disable-next-line no-use-before-define
const context = makeContext(id);
partial.catch(context.cancel);
promise.catch(context.cancel);
controller = harden({
context,
external: E.get(partial).external,
value: promise,
});
controllerForId.set(id, controller);

resolve(makeControllerForId(id, context));
// The controller must be in place before we evaluate the formula.
resolve(evaluateFormulaForId(id, context));

return controller;
};
Expand Down Expand Up @@ -817,7 +821,7 @@ const makeDaemonCore = async (
const controller = /** @type {import('./types.js').Controller<>} */ (
provideController(id)
);
return controller.external.then(value => {
return controller.value.then(value => {
// Release the value to the public only after ensuring
// we can reverse-lookup its nonce.
if (typeof value === 'object' && value !== null) {
Expand Down Expand Up @@ -1469,23 +1473,6 @@ const makeDaemonCore = async (
throw new Error('Cannot connect to peer: no supported addresses');
};

/**
* This is used to provide a value for a formula identifier that is known to
* originate from the specified peer.
* @param {string} peerId
* @param {string} remoteValueId
* @returns {Promise<import('./types.js').ControllerPartial<unknown, undefined>>}
*/
const provideRemoteValue = async (peerId, remoteValueId) => {
const peer = /** @type {import('./types.js').EndoPeer} */ (
await provide(peerId)
);
const remoteValueP = peer.provide(remoteValueId);
const external = remoteValueP;
const internal = Promise.resolve(undefined);
return harden({ internal, external });
};

const makeContext = makeContextMaker({
controllerForId,
provideController,
Expand Down
17 changes: 4 additions & 13 deletions packages/daemon/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,16 +342,8 @@ export interface FarContext {
addDisposalHook: Context['onCancel'];
}

export interface InternalExternal<External = unknown> {
external: External;
}

export interface ControllerPartial<External = unknown> {
external: Promise<External>;
}

export interface Controller<External = unknown>
extends ControllerPartial<External> {
export interface Controller<Value = unknown> {
value: Promise<Value>;
context: Context;
}

Expand Down Expand Up @@ -469,8 +461,6 @@ export type MakeHostOrGuestOptions = {
export interface EndoPeer {
provide: (id: string) => Promise<unknown>;
}
export type EndoPeerControllerPartial = ControllerPartial<EndoPeer>;
export type EndoPeerController = Controller<EndoPeer>;

export interface EndoGateway {
provide: (id: string) => Promise<unknown>;
Expand Down Expand Up @@ -651,11 +641,12 @@ export type DaemonicPersistencePowers = {
export interface DaemonWorkerFacet {}

export interface WorkerDaemonFacet {
terminate(): void;
terminate(): Promise<void>;
evaluate(
source: string,
names: Array<string>,
values: Array<unknown>,
id: string,
cancelled: Promise<never>,
): Promise<unknown>;
makeBundle(bundle: ERef<EndoReadable>, powers: ERef<unknown>);
Expand Down

0 comments on commit 2c066d1

Please sign in to comment.