From c59b5bcb397ee1c2a9f3df954fa1b0a33c5ae3ec Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Wed, 6 Mar 2024 19:24:34 -0800 Subject: [PATCH 1/7] refactor(daemon): Extract incarnation hooks to module --- packages/daemon/src/async-hooks.js | 16 ++++++++++++++ packages/daemon/src/daemon.js | 2 +- packages/daemon/src/host.js | 16 ++++++-------- packages/daemon/src/types.d.ts | 27 +++++++++++++++++------- packages/daemon/test/test-async-hooks.js | 20 ++++++++++++++++++ 5 files changed, 63 insertions(+), 18 deletions(-) create mode 100644 packages/daemon/src/async-hooks.js create mode 100644 packages/daemon/test/test-async-hooks.js diff --git a/packages/daemon/src/async-hooks.js b/packages/daemon/src/async-hooks.js new file mode 100644 index 0000000000..8bf2dd0207 --- /dev/null +++ b/packages/daemon/src/async-hooks.js @@ -0,0 +1,16 @@ +/** + * @returns {import('./types.js').AsyncHooks} + */ +export const makeAsyncHooks = () => { + /** @type {import('./types.js').AsyncHook[]} */ + const hooks = []; + + return { + add: hook => { + hooks.push(hook); + }, + execute: async identifiers => { + await Promise.all(hooks.map(hook => hook(identifiers))); + }, + }; +}; diff --git a/packages/daemon/src/daemon.js b/packages/daemon/src/daemon.js index f469d8542b..38b8b41625 100644 --- a/packages/daemon/src/daemon.js +++ b/packages/daemon/src/daemon.js @@ -1073,7 +1073,7 @@ const makeDaemonCore = async ( evalFormulaIdentifier: ownFormulaIdentifier, }); - await Promise.all(hooks.map(hook => hook(identifiers))); + await hooks.execute(identifiers); return identifiers; }); diff --git a/packages/daemon/src/host.js b/packages/daemon/src/host.js index 5c11d93440..a1b32375f3 100644 --- a/packages/daemon/src/host.js +++ b/packages/daemon/src/host.js @@ -4,6 +4,7 @@ import { E, Far } from '@endo/far'; import { makeIteratorRef } from './reader-ref.js'; import { assertPetName, petNamePathFrom } from './pet-name.js'; import { makePetSitter } from './pet-sitter.js'; +import { makeAsyncHooks } from './async-hooks.js'; const { quote: q } = assert; @@ -246,8 +247,9 @@ export const makeHostMaker = ({ }; /** + * @typedef {import('./types.js').AsyncHooks} EvalAsyncHooks * @param {string | 'MAIN' | 'NEW'} workerName - * @param {(hook: import('./types.js').EvalFormulaHook) => void} addHook + * @param {EvalAsyncHooks['add']} addHook * @returns {string | undefined} */ const provideWorkerFormulaIdentifierSync = (workerName, addHook) => { @@ -308,16 +310,12 @@ export const makeHostMaker = ({ throw new Error('Evaluator requires one pet name for each code name'); } - /** @type {import('./types.js').EvalFormulaHook[]} */ - const hooks = []; - /** @type {(hook: import('./types.js').EvalFormulaHook) => void} */ - const addHook = hook => { - hooks.push(hook); - }; + /** @type {import('./types.js').AsyncHooks} */ + const hooks = makeAsyncHooks(); const workerFormulaIdentifier = provideWorkerFormulaIdentifierSync( workerName, - addHook, + hooks.add, ); /** @type {(string | string[])[]} */ @@ -341,7 +339,7 @@ export const makeHostMaker = ({ ); if (resultName !== undefined) { - addHook(identifiers => + hooks.add(identifiers => petStore.write(resultName, identifiers.evalFormulaIdentifier), ); } diff --git a/packages/daemon/src/types.d.ts b/packages/daemon/src/types.d.ts index feeb13595b..8f9f6ab38c 100644 --- a/packages/daemon/src/types.d.ts +++ b/packages/daemon/src/types.d.ts @@ -105,13 +105,11 @@ type EvalFormula = { // TODO formula slots }; -export type EvalFormulaHook = ( - identifiers: Readonly<{ - endowmentFormulaIdentifiers: string[]; - evalFormulaIdentifier: string; - workerFormulaIdentifier: string; - }>, -) => Promise; +export type EvalFormulaHooks = { + endowmentFormulaIdentifiers: string[]; + evalFormulaIdentifier: string; + workerFormulaIdentifier: string; +}; type ReadableBlobFormula = { type: 'readable-blob'; @@ -706,6 +704,19 @@ export type DaemonicPowers = { }; type IncarnateResult = Promise<{ formulaIdentifier: string; value: T }>; + +export type AsyncHook> = ( + formulaIdentifiers: Readonly, +) => Promise; + +/** + * A collection of async hooks that can be executed in parallel. + */ +export type AsyncHooks> = { + add(value: AsyncHook): void; + execute(identifiers: Readonly): Promise; +}; + export interface DaemonCore { nodeIdentifier: string; provideValueForFormulaIdentifier: ( @@ -754,7 +765,7 @@ export interface DaemonCore { source: string, codeNames: string[], endowmentFormulaIdsOrPaths: (string | string[])[], - hooks: EvalFormulaHook[], + hooks: AsyncHooks, specifiedWorkerFormulaIdentifier?: string, ) => IncarnateResult; incarnateUnconfined: ( diff --git a/packages/daemon/test/test-async-hooks.js b/packages/daemon/test/test-async-hooks.js new file mode 100644 index 0000000000..b94007a90c --- /dev/null +++ b/packages/daemon/test/test-async-hooks.js @@ -0,0 +1,20 @@ +import test from 'ava'; +import { makeAsyncHooks } from '../src/async-hooks.js'; + +test('execute', async t => { + const hooks = makeAsyncHooks(); + const results = []; + hooks.add(async () => { + results.push(1); + }); + hooks.add(async () => { + results.push(2); + }); + hooks.add(async () => { + results.push(3); + }); + + await hooks.execute(); + + t.deepEqual(results.sort(), [1, 2, 3]); +}); From d166967503c014a56d1413ea70c901c0f414251d Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Thu, 7 Mar 2024 15:05:04 -0800 Subject: [PATCH 2/7] refactor(daemon): Synchronize incarnateGuest Synchronizes the daemon's incarnateGuest(). Replaces incarnateHandle() and incarnatePetStore() with their "incarnateNumbered" equivalents. --- packages/daemon/src/daemon.js | 106 ++++++++++++++++++++++----------- packages/daemon/src/host.js | 26 ++++---- packages/daemon/src/types.d.ts | 10 +--- 3 files changed, 82 insertions(+), 60 deletions(-) diff --git a/packages/daemon/src/daemon.js b/packages/daemon/src/daemon.js index 38b8b41625..7739feb2ff 100644 --- a/packages/daemon/src/daemon.js +++ b/packages/daemon/src/daemon.js @@ -78,6 +78,11 @@ const getDerivedId = (path, rootNonce, digester) => { return nonce; }; +/** + * @template T + * @typedef {import('./types.js').IncarnateResult} IncarnateResult + */ + /** * @param {import('./types.js').DaemonicPowers} powers * @param {Promise} webletPortP @@ -896,10 +901,15 @@ const makeDaemonCore = async ( }; /** - * @type {import('./types.js').DaemonCore['incarnateHandle']} + * Incarnates a `handle` formula and synchronously adds it to the formula graph. + * The returned promise is resolved after the formula is persisted. + * + * @typedef {import('./types.js').ExternalHandle} ExternalHandle + * @param {string} formulaNumber - The formula number of the handle to incarnate. + * @param {string} targetFormulaIdentifier - The formula identifier of the handle's target. + * @returns {IncarnateResult} The incarnated handle. */ - const incarnateHandle = async targetFormulaIdentifier => { - const formulaNumber = await randomHex512(); + const incarnateNumberedHandle = (formulaNumber, targetFormulaIdentifier) => { /** @type {import('./types.js').HandleFormula} */ const formula = { type: 'handle', @@ -911,10 +921,14 @@ const makeDaemonCore = async ( }; /** - * @type {import('./types.js').DaemonCore['incarnatePetStore']} + * Incarnates a `pet-store` formula and synchronously adds it to the formula graph. + * The returned promise is resolved after the formula is persisted. + * + * @typedef {import('./types.js').PetStore} PetStore + * @param {string} formulaNumber - The formula number of the pet store to incarnate. + * @returns {IncarnateResult} The incarnated pet store. */ - const incarnatePetStore = async specifiedFormulaNumber => { - const formulaNumber = specifiedFormulaNumber ?? (await randomHex512()); + const incarnateNumberedPetStore = async formulaNumber => { /** @type {import('./types.js').PetStoreFormula} */ const formula = { type: 'pet-store', @@ -929,7 +943,7 @@ const makeDaemonCore = async ( */ const incarnateDirectory = async () => { const { formulaIdentifier: petStoreFormulaIdentifier } = - await incarnatePetStore(); + await incarnateNumberedPetStore(await randomHex512()); const formulaNumber = await randomHex512(); /** @type {import('./types.js').DirectoryFormula} */ const formula = { @@ -941,25 +955,12 @@ const makeDaemonCore = async ( ); }; - /** - * @type {import('./types.js').DaemonCore['incarnateWorker']} - */ - const incarnateWorker = async () => { - const formulaNumber = await randomHex512(); - /** @type {import('./types.js').WorkerFormula} */ - const formula = { - type: 'worker', - }; - return /** @type {import('./types').IncarnateResult} */ ( - provideValueForNumberedFormula(formula.type, formulaNumber, formula) - ); - }; - /** * Incarnates a `worker` formula and synchronously adds it to the formula graph. * The returned promise is resolved after the formula is persisted. + * * @param {string} formulaNumber - The worker formula number. - * @returns {Promise<{ formulaIdentifier: string, value: import('./types').EndoWorker }>} + * @returns {ReturnType} */ const incarnateNumberedWorker = formulaNumber => { /** @type {import('./types.js').WorkerFormula} */ @@ -972,6 +973,14 @@ const makeDaemonCore = async ( ); }; + /** + * @type {import('./types.js').DaemonCore['incarnateWorker']} + */ + const incarnateWorker = async () => { + const formulaNumber = await formulaGraphMutex.enqueue(randomHex512); + return incarnateNumberedWorker(formulaNumber); + }; + /** @type {import('./types.js').DaemonCore['incarnateHost']} */ const incarnateHost = async ( endoFormulaIdentifier, @@ -983,10 +992,10 @@ const makeDaemonCore = async ( let workerFormulaIdentifier = specifiedWorkerFormulaIdentifier; if (workerFormulaIdentifier === undefined) { ({ formulaIdentifier: workerFormulaIdentifier } = - await incarnateWorker()); + await incarnateNumberedWorker(await randomHex512())); } const { formulaIdentifier: storeFormulaIdentifier } = - await incarnatePetStore(); + await incarnateNumberedPetStore(await randomHex512()); const { formulaIdentifier: inspectorFormulaIdentifier } = // eslint-disable-next-line no-use-before-define await incarnatePetInspector(storeFormulaIdentifier); @@ -1006,12 +1015,40 @@ const makeDaemonCore = async ( }; /** @type {import('./types.js').DaemonCore['incarnateGuest']} */ - const incarnateGuest = async hostHandleFormulaIdentifier => { - const formulaNumber = await randomHex512(); - const { formulaIdentifier: storeFormulaIdentifier } = - await incarnatePetStore(); - const { formulaIdentifier: workerFormulaIdentifier } = - await incarnateWorker(); + const incarnateGuest = async hostFormulaIdentifier => { + const { + guestFormulaNumber, + hostHandleFormulaIdentifier, + storeFormulaIdentifier, + workerFormulaIdentifier, + } = await formulaGraphMutex.enqueue(async () => { + const [ + ownFormulaNumber, + hostHandle, + storeIncarnation, + workerIncarnation, + ] = await Promise.all([ + randomHex512(), + randomHex512(), + randomHex512(), + randomHex512(), + ]).then(([own, handle, store, worker]) => + Promise.all([ + own, + incarnateNumberedHandle(handle, hostFormulaIdentifier), + incarnateNumberedPetStore(store), + incarnateNumberedWorker(worker), + ]), + ); + + return harden({ + guestFormulaNumber: ownFormulaNumber, + hostHandleFormulaIdentifier: hostHandle.formulaIdentifier, + storeFormulaIdentifier: storeIncarnation.formulaIdentifier, + workerFormulaIdentifier: workerIncarnation.formulaIdentifier, + }); + }); + /** @type {import('./types.js').GuestFormula} */ const formula = { type: 'guest', @@ -1020,7 +1057,7 @@ const makeDaemonCore = async ( worker: workerFormulaIdentifier, }; return /** @type {import('./types').IncarnateResult} */ ( - provideValueForNumberedFormula(formula.type, formulaNumber, formula) + provideValueForNumberedFormula(formula.type, guestFormulaNumber, formula) ); }; @@ -1265,13 +1302,13 @@ const makeDaemonCore = async ( }); const { formulaIdentifier: defaultHostWorkerFormulaIdentifier } = - await incarnateWorker(); + await incarnateNumberedWorker(await randomHex512()); const { formulaIdentifier: networksDirectoryFormulaIdentifier } = await incarnateNetworksDirectory(); const { formulaIdentifier: leastAuthorityFormulaIdentifier } = await incarnateLeastAuthority(); const { formulaIdentifier: newPeersFormulaIdentifier } = - await incarnatePetStore(peersFormulaNumber); + await incarnateNumberedPetStore(peersFormulaNumber); if (newPeersFormulaIdentifier !== peersFormulaIdentifier) { throw new Error( `Peers PetStore formula identifier did not match expected value.`, @@ -1444,7 +1481,6 @@ const makeDaemonCore = async ( incarnateUnconfined, incarnateBundle, incarnateWebBundle, - incarnateHandle, storeReaderRef, makeMailbox, makeDirectoryNode, @@ -1597,8 +1633,6 @@ const makeDaemonCore = async ( incarnateLeastAuthority, incarnateNetworksDirectory, incarnateLoopbackNetwork, - incarnateHandle, - incarnatePetStore, incarnateDirectory, incarnateWorker, incarnateHost, diff --git a/packages/daemon/src/host.js b/packages/daemon/src/host.js index a1b32375f3..ecdda24efe 100644 --- a/packages/daemon/src/host.js +++ b/packages/daemon/src/host.js @@ -8,6 +8,11 @@ import { makeAsyncHooks } from './async-hooks.js'; const { quote: q } = assert; +/** + * @template {Record} T + * @typedef {import('./types.js').AsyncHooks} AsyncHooks + */ + /** * @param {object} args * @param {import('./types.js').DaemonCore['provideValueForFormulaIdentifier']} args.provideValueForFormulaIdentifier @@ -20,7 +25,6 @@ const { quote: q } = assert; * @param {import('./types.js').DaemonCore['incarnateUnconfined']} args.incarnateUnconfined * @param {import('./types.js').DaemonCore['incarnateBundle']} args.incarnateBundle * @param {import('./types.js').DaemonCore['incarnateWebBundle']} args.incarnateWebBundle - * @param {import('./types.js').DaemonCore['incarnateHandle']} args.incarnateHandle * @param {import('./types.js').DaemonCore['storeReaderRef']} args.storeReaderRef * @param {import('./types.js').DaemonCore['getAllNetworkAddresses']} args.getAllNetworkAddresses * @param {import('./types.js').MakeMailbox} args.makeMailbox @@ -38,7 +42,6 @@ export const makeHostMaker = ({ incarnateUnconfined, incarnateBundle, incarnateWebBundle, - incarnateHandle, storeReaderRef, getAllNetworkAddresses, makeMailbox, @@ -98,13 +101,6 @@ export const makeHostMaker = ({ return endoBootstrap; }; - /** - * @returns {Promise<{ formulaIdentifier: string, value: import('./types').ExternalHandle }>} - */ - const makeNewHandleForSelf = () => { - return incarnateHandle(hostFormulaIdentifier); - }; - /** * @param {import('./types.js').Controller} newController * @param {Record} introducedNames @@ -140,12 +136,11 @@ export const makeHostMaker = ({ } if (formulaIdentifier === undefined) { - const { formulaIdentifier: hostHandleFormulaIdentifier } = - await makeNewHandleForSelf(); const { value, formulaIdentifier: guestFormulaIdentifier } = // Behold, recursion: // eslint-disable-next-line no-use-before-define - await incarnateGuest(hostHandleFormulaIdentifier); + await incarnateGuest(hostFormulaIdentifier); + // TODO: move to hook if (petName !== undefined) { assertPetName(petName); await petStore.write(petName, guestFormulaIdentifier); @@ -168,6 +163,8 @@ export const makeHostMaker = ({ provideControllerForFormulaIdentifier(formulaIdentifier) ); if (introducedNames !== undefined) { + // TODO: move to hook + // can use provideValueForFormulaIdentifier on the guest formula introduceNamesToNewHostOrGuest(newGuestController, introducedNames); } return { @@ -247,9 +244,8 @@ export const makeHostMaker = ({ }; /** - * @typedef {import('./types.js').AsyncHooks} EvalAsyncHooks * @param {string | 'MAIN' | 'NEW'} workerName - * @param {EvalAsyncHooks['add']} addHook + * @param {AsyncHooks<{ workerFormulaIdentifier: string }>['add']} addHook * @returns {string | undefined} */ const provideWorkerFormulaIdentifierSync = (workerName, addHook) => { @@ -310,7 +306,7 @@ export const makeHostMaker = ({ throw new Error('Evaluator requires one pet name for each code name'); } - /** @type {import('./types.js').AsyncHooks} */ + /** @type {AsyncHooks} */ const hooks = makeAsyncHooks(); const workerFormulaIdentifier = provideWorkerFormulaIdentifierSync( diff --git a/packages/daemon/src/types.d.ts b/packages/daemon/src/types.d.ts index 8f9f6ab38c..3d5c7306b4 100644 --- a/packages/daemon/src/types.d.ts +++ b/packages/daemon/src/types.d.ts @@ -741,9 +741,6 @@ export interface DaemonCore { specifiedFormulaNumber: string, ) => IncarnateResult; incarnateWorker: () => IncarnateResult; - incarnatePetStore: ( - specifiedFormulaNumber?: string, - ) => IncarnateResult; incarnateDirectory: () => IncarnateResult; incarnatePetInspector: ( petStoreFormulaIdentifier: string, @@ -754,9 +751,7 @@ export interface DaemonCore { leastAuthorityFormulaIdentifier: string, specifiedWorkerFormulaIdentifier?: string | undefined, ) => IncarnateResult; - incarnateGuest: ( - hostHandleFormulaIdenfitier: string, - ) => IncarnateResult; + incarnateGuest: (hostFormulaIdentifier: string) => IncarnateResult; incarnateReadableBlob: ( contentSha512: string, ) => IncarnateResult; @@ -786,9 +781,6 @@ export interface DaemonCore { powersFormulaIdentifier: string, bundleFormulaIdentifier: string, ) => IncarnateResult; - incarnateHandle: ( - targetFormulaIdentifier: string, - ) => IncarnateResult; incarnatePeer: ( networksFormulaIdentifier: string, addresses: Array, From 002f459e186666202dd9ab32eef87980d66c63e8 Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Thu, 7 Mar 2024 15:44:55 -0800 Subject: [PATCH 3/7] refactor(daemon): Encapsulate controller provision within introduceNamesToParty --- packages/daemon/src/host.js | 31 +++++++++++++++++-------------- packages/daemon/src/types.d.ts | 4 ++-- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/packages/daemon/src/host.js b/packages/daemon/src/host.js index ecdda24efe..877fb98bb0 100644 --- a/packages/daemon/src/host.js +++ b/packages/daemon/src/host.js @@ -102,15 +102,18 @@ export const makeHostMaker = ({ }; /** - * @param {import('./types.js').Controller} newController - * @param {Record} introducedNames + * @param {string} formulaIdentifier - The guest or host formula identifier. + * @param {Record} introducedNames - The names to introduce. * @returns {Promise} */ - const introduceNamesToNewHostOrGuest = async ( - newController, + const introduceNamesToParty = async ( + formulaIdentifier, introducedNames, ) => { - const { petStore: newPetStore } = await newController.internal; + /** @type {import('./types.js').Controller} */ + const controller = + provideControllerForFormulaIdentifier(formulaIdentifier); + const { petStore: newPetStore } = await controller.internal; await Promise.all( Object.entries(introducedNames).map(async ([parentName, childName]) => { const introducedFormulaIdentifier = @@ -158,15 +161,14 @@ export const makeHostMaker = ({ ); } - const newGuestController = - /** @type {import('./types.js').Controller<>} */ ( - provideControllerForFormulaIdentifier(formulaIdentifier) - ); if (introducedNames !== undefined) { // TODO: move to hook - // can use provideValueForFormulaIdentifier on the guest formula - introduceNamesToNewHostOrGuest(newGuestController, introducedNames); + introduceNamesToParty(formulaIdentifier, introducedNames); } + const newGuestController = + /** @type {import('./types.js').Controller} */ ( + provideControllerForFormulaIdentifier(formulaIdentifier) + ); return { formulaIdentifier, value: /** @type {Promise} */ ( @@ -466,13 +468,14 @@ export const makeHostMaker = ({ )}`, ); } + + if (introducedNames !== undefined) { + introduceNamesToParty(formulaIdentifier, introducedNames); + } const newHostController = /** @type {import('./types.js').Controller<>} */ ( provideControllerForFormulaIdentifier(formulaIdentifier) ); - if (introducedNames !== undefined) { - introduceNamesToNewHostOrGuest(newHostController, introducedNames); - } return { formulaIdentifier, value: /** @type {Promise} */ ( diff --git a/packages/daemon/src/types.d.ts b/packages/daemon/src/types.d.ts index 3d5c7306b4..8bc40efc59 100644 --- a/packages/daemon/src/types.d.ts +++ b/packages/daemon/src/types.d.ts @@ -537,14 +537,14 @@ export interface EndoHost extends EndoDirectory { addPeerInfo(peerInfo: PeerInfo): Promise; } -export interface InternalEndoHost { +export interface InternalEndoParty { receive: Mail['receive']; respond: Mail['respond']; petStore: PetStore; } export interface EndoHostController - extends Controller, InternalEndoHost> {} + extends Controller, InternalEndoParty> {} export type EndoInspector = { lookup: (petName: Record) => Promise; From 8b85f3014aa617b8991fb72e2b5d3725ebba31a0 Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Thu, 7 Mar 2024 17:19:08 -0800 Subject: [PATCH 4/7] refactor(daemon): Synchronize host makeGuest() --- packages/daemon/src/daemon.js | 10 ++++- packages/daemon/src/host.js | 74 ++++++++++++++++---------------- packages/daemon/src/pet-store.js | 2 + packages/daemon/src/types.d.ts | 13 ++++-- 4 files changed, 57 insertions(+), 42 deletions(-) diff --git a/packages/daemon/src/daemon.js b/packages/daemon/src/daemon.js index 7739feb2ff..350156f674 100644 --- a/packages/daemon/src/daemon.js +++ b/packages/daemon/src/daemon.js @@ -1015,7 +1015,7 @@ const makeDaemonCore = async ( }; /** @type {import('./types.js').DaemonCore['incarnateGuest']} */ - const incarnateGuest = async hostFormulaIdentifier => { + const incarnateGuest = async (hostFormulaIdentifier, hooks) => { const { guestFormulaNumber, hostHandleFormulaIdentifier, @@ -1041,6 +1041,14 @@ const makeDaemonCore = async ( ]), ); + await hooks.execute({ + guestFormulaIdentifier: serializeFormulaIdentifier({ + type: 'guest', + number: ownFormulaNumber, + node: ownNodeIdentifier, + }), + }); + return harden({ guestFormulaNumber: ownFormulaNumber, hostHandleFormulaIdentifier: hostHandle.formulaIdentifier, diff --git a/packages/daemon/src/host.js b/packages/daemon/src/host.js index 877fb98bb0..c72e2a18e8 100644 --- a/packages/daemon/src/host.js +++ b/packages/daemon/src/host.js @@ -132,48 +132,48 @@ export const makeHostMaker = ({ * @returns {Promise<{formulaIdentifier: string, value: Promise}>} */ const makeGuest = async (petName, { introducedNames = {} } = {}) => { - /** @type {string | undefined} */ - let formulaIdentifier; if (petName !== undefined) { - formulaIdentifier = petStore.identifyLocal(petName); - } + const formulaIdentifier = petStore.identifyLocal(petName); + if (formulaIdentifier !== undefined) { + if (formulaIdentifier.startsWith('guest:')) { + throw new Error( + `Existing pet name does not designate a guest powers capability: ${q( + petName, + )}`, + ); + } - if (formulaIdentifier === undefined) { - const { value, formulaIdentifier: guestFormulaIdentifier } = - // Behold, recursion: - // eslint-disable-next-line no-use-before-define - await incarnateGuest(hostFormulaIdentifier); - // TODO: move to hook - if (petName !== undefined) { - assertPetName(petName); - await petStore.write(petName, guestFormulaIdentifier); + // TODO: Should this be awaited? + introduceNamesToParty(formulaIdentifier, introducedNames); + const guestController = + provideControllerForFormulaIdentifier(formulaIdentifier); + return { + formulaIdentifier, + value: /** @type {Promise} */ ( + guestController.external + ), + }; } + } - return { - value: Promise.resolve(value), - formulaIdentifier: guestFormulaIdentifier, - }; - } else if (!formulaIdentifier.startsWith('guest:')) { - throw new Error( - `Existing pet name does not designate a guest powers capability: ${q( - petName, - )}`, + /** @type {AsyncHooks} */ + const hooks = makeAsyncHooks(); + if (petName !== undefined) { + hooks.add(identifiers => + petStore.write(petName, identifiers.guestFormulaIdentifier), ); } - if (introducedNames !== undefined) { - // TODO: move to hook - introduceNamesToParty(formulaIdentifier, introducedNames); - } - const newGuestController = - /** @type {import('./types.js').Controller} */ ( - provideControllerForFormulaIdentifier(formulaIdentifier) - ); + const { value, formulaIdentifier } = + // Behold, recursion: + // eslint-disable-next-line no-use-before-define + await incarnateGuest(hostFormulaIdentifier, hooks); + // TODO: Should this be awaited? + introduceNamesToParty(formulaIdentifier, introducedNames); + return { + value: Promise.resolve(value), formulaIdentifier, - value: /** @type {Promise} */ ( - newGuestController.external - ), }; }; @@ -280,7 +280,7 @@ export const makeHostMaker = ({ )); if (guestFormulaIdentifier === undefined) { throw new Error( - `panic: provideGuest must return an guest with a corresponding formula identifier`, + `panic: makeGuest must return a guest with a corresponding formula identifier`, ); } } @@ -308,7 +308,7 @@ export const makeHostMaker = ({ throw new Error('Evaluator requires one pet name for each code name'); } - /** @type {AsyncHooks} */ + /** @type {AsyncHooks} */ const hooks = makeAsyncHooks(); const workerFormulaIdentifier = provideWorkerFormulaIdentifierSync( @@ -469,9 +469,7 @@ export const makeHostMaker = ({ ); } - if (introducedNames !== undefined) { - introduceNamesToParty(formulaIdentifier, introducedNames); - } + introduceNamesToParty(formulaIdentifier, introducedNames); const newHostController = /** @type {import('./types.js').Controller<>} */ ( provideControllerForFormulaIdentifier(formulaIdentifier) diff --git a/packages/daemon/src/pet-store.js b/packages/daemon/src/pet-store.js index 8bfcf31fef..113b648f81 100644 --- a/packages/daemon/src/pet-store.js +++ b/packages/daemon/src/pet-store.js @@ -85,12 +85,14 @@ export const makePetStoreMaker = (filePowers, locator) => { assertValidName(petName); assertValidFormulaIdentifier(formulaIdentifier, petName); + // TODO: Return early if the formula identifier is the same. if (petNames.has(petName)) { // Perform cleanup on the overwritten pet name. const formulaPetNames = formulaIdentifiers.get(petName); if (formulaPetNames !== undefined) { formulaPetNames.delete(petName); } + // TODO: Should this only happen if something is actually deleted? changesTopic.publisher.next({ remove: petName }); } diff --git a/packages/daemon/src/types.d.ts b/packages/daemon/src/types.d.ts index 8bc40efc59..80abb9d660 100644 --- a/packages/daemon/src/types.d.ts +++ b/packages/daemon/src/types.d.ts @@ -92,6 +92,10 @@ type GuestFormula = { worker: string; }; +export type GuestHookParams = { + guestFormulaIdentifier: string; +}; + type LeastAuthorityFormula = { type: 'least-authority'; }; @@ -105,7 +109,7 @@ type EvalFormula = { // TODO formula slots }; -export type EvalFormulaHooks = { +export type EvalHookParams = { endowmentFormulaIdentifiers: string[]; evalFormulaIdentifier: string; workerFormulaIdentifier: string; @@ -751,7 +755,10 @@ export interface DaemonCore { leastAuthorityFormulaIdentifier: string, specifiedWorkerFormulaIdentifier?: string | undefined, ) => IncarnateResult; - incarnateGuest: (hostFormulaIdentifier: string) => IncarnateResult; + incarnateGuest: ( + hostFormulaIdentifier: string, + hooks: AsyncHooks, + ) => IncarnateResult; incarnateReadableBlob: ( contentSha512: string, ) => IncarnateResult; @@ -760,7 +767,7 @@ export interface DaemonCore { source: string, codeNames: string[], endowmentFormulaIdsOrPaths: (string | string[])[], - hooks: AsyncHooks, + hooks: AsyncHooks, specifiedWorkerFormulaIdentifier?: string, ) => IncarnateResult; incarnateUnconfined: ( From c73fcf77fa9eab1b0664569b59ef55499afb9265 Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Thu, 7 Mar 2024 19:44:12 -0800 Subject: [PATCH 5/7] refactor(daemon): Rename asyncHooks to deferredTasks --- packages/daemon/src/async-hooks.js | 16 ----------- packages/daemon/src/daemon.js | 8 +++--- packages/daemon/src/deferred-tasks.js | 16 +++++++++++ packages/daemon/src/host.js | 28 +++++++++---------- packages/daemon/src/types.d.ts | 17 +++++------ ...-async-hooks.js => test-deferred-tasks.js} | 12 ++++---- 6 files changed, 49 insertions(+), 48 deletions(-) delete mode 100644 packages/daemon/src/async-hooks.js create mode 100644 packages/daemon/src/deferred-tasks.js rename packages/daemon/test/{test-async-hooks.js => test-deferred-tasks.js} (50%) diff --git a/packages/daemon/src/async-hooks.js b/packages/daemon/src/async-hooks.js deleted file mode 100644 index 8bf2dd0207..0000000000 --- a/packages/daemon/src/async-hooks.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @returns {import('./types.js').AsyncHooks} - */ -export const makeAsyncHooks = () => { - /** @type {import('./types.js').AsyncHook[]} */ - const hooks = []; - - return { - add: hook => { - hooks.push(hook); - }, - execute: async identifiers => { - await Promise.all(hooks.map(hook => hook(identifiers))); - }, - }; -}; diff --git a/packages/daemon/src/daemon.js b/packages/daemon/src/daemon.js index 350156f674..b292cab3d8 100644 --- a/packages/daemon/src/daemon.js +++ b/packages/daemon/src/daemon.js @@ -1015,7 +1015,7 @@ const makeDaemonCore = async ( }; /** @type {import('./types.js').DaemonCore['incarnateGuest']} */ - const incarnateGuest = async (hostFormulaIdentifier, hooks) => { + const incarnateGuest = async (hostFormulaIdentifier, deferredTasks) => { const { guestFormulaNumber, hostHandleFormulaIdentifier, @@ -1041,7 +1041,7 @@ const makeDaemonCore = async ( ]), ); - await hooks.execute({ + await deferredTasks.execute({ guestFormulaIdentifier: serializeFormulaIdentifier({ type: 'guest', number: ownFormulaNumber, @@ -1075,7 +1075,7 @@ const makeDaemonCore = async ( source, codeNames, endowmentFormulaIdsOrPaths, - hooks, + deferredTasks, specifiedWorkerFormulaIdentifier, ) => { const { @@ -1118,7 +1118,7 @@ const makeDaemonCore = async ( evalFormulaIdentifier: ownFormulaIdentifier, }); - await hooks.execute(identifiers); + await deferredTasks.execute(identifiers); return identifiers; }); diff --git a/packages/daemon/src/deferred-tasks.js b/packages/daemon/src/deferred-tasks.js new file mode 100644 index 0000000000..a815dcbd9e --- /dev/null +++ b/packages/daemon/src/deferred-tasks.js @@ -0,0 +1,16 @@ +/** + * @returns {import('./types.js').DeferredTasks} + */ +export const makeDeferredTasks = () => { + /** @type {import('./types.js').DeferredTask[]} */ + const tasks = []; + + return { + execute: async param => { + await Promise.all(tasks.map(task => task(param))); + }, + push: task => { + tasks.push(task); + }, + }; +}; diff --git a/packages/daemon/src/host.js b/packages/daemon/src/host.js index c72e2a18e8..707ce7db3d 100644 --- a/packages/daemon/src/host.js +++ b/packages/daemon/src/host.js @@ -4,13 +4,13 @@ import { E, Far } from '@endo/far'; import { makeIteratorRef } from './reader-ref.js'; import { assertPetName, petNamePathFrom } from './pet-name.js'; import { makePetSitter } from './pet-sitter.js'; -import { makeAsyncHooks } from './async-hooks.js'; +import { makeDeferredTasks } from './deferred-tasks.js'; const { quote: q } = assert; /** * @template {Record} T - * @typedef {import('./types.js').AsyncHooks} AsyncHooks + * @typedef {import('./types.js').DeferredTasks} DeferredTasks */ /** @@ -156,10 +156,10 @@ export const makeHostMaker = ({ } } - /** @type {AsyncHooks} */ - const hooks = makeAsyncHooks(); + /** @type {DeferredTasks} */ + const tasks = makeDeferredTasks(); if (petName !== undefined) { - hooks.add(identifiers => + tasks.push(identifiers => petStore.write(petName, identifiers.guestFormulaIdentifier), ); } @@ -167,7 +167,7 @@ export const makeHostMaker = ({ const { value, formulaIdentifier } = // Behold, recursion: // eslint-disable-next-line no-use-before-define - await incarnateGuest(hostFormulaIdentifier, hooks); + await incarnateGuest(hostFormulaIdentifier, tasks); // TODO: Should this be awaited? introduceNamesToParty(formulaIdentifier, introducedNames); @@ -247,10 +247,10 @@ export const makeHostMaker = ({ /** * @param {string | 'MAIN' | 'NEW'} workerName - * @param {AsyncHooks<{ workerFormulaIdentifier: string }>['add']} addHook + * @param {DeferredTasks<{ workerFormulaIdentifier: string }>['push']} deferTask * @returns {string | undefined} */ - const provideWorkerFormulaIdentifierSync = (workerName, addHook) => { + const provideWorkerFormulaIdentifierSync = (workerName, deferTask) => { if (workerName === 'MAIN') { return mainWorkerFormulaIdentifier; } else if (workerName === 'NEW') { @@ -260,7 +260,7 @@ export const makeHostMaker = ({ assertPetName(workerName); const workerFormulaIdentifier = petStore.identifyLocal(workerName); if (workerFormulaIdentifier === undefined) { - addHook(identifiers => + deferTask(identifiers => petStore.write(workerName, identifiers.workerFormulaIdentifier), ); return undefined; @@ -308,12 +308,12 @@ export const makeHostMaker = ({ throw new Error('Evaluator requires one pet name for each code name'); } - /** @type {AsyncHooks} */ - const hooks = makeAsyncHooks(); + /** @type {DeferredTasks} */ + const tasks = makeDeferredTasks(); const workerFormulaIdentifier = provideWorkerFormulaIdentifierSync( workerName, - hooks.add, + tasks.push, ); /** @type {(string | string[])[]} */ @@ -337,7 +337,7 @@ export const makeHostMaker = ({ ); if (resultName !== undefined) { - hooks.add(identifiers => + tasks.push(identifiers => petStore.write(resultName, identifiers.evalFormulaIdentifier), ); } @@ -347,7 +347,7 @@ export const makeHostMaker = ({ source, codeNames, endowmentFormulaIdsOrPaths, - hooks, + tasks, workerFormulaIdentifier, ); return value; diff --git a/packages/daemon/src/types.d.ts b/packages/daemon/src/types.d.ts index 80abb9d660..d27a3edb6a 100644 --- a/packages/daemon/src/types.d.ts +++ b/packages/daemon/src/types.d.ts @@ -92,7 +92,7 @@ type GuestFormula = { worker: string; }; -export type GuestHookParams = { +export type GuestDeferredTaskParams = { guestFormulaIdentifier: string; }; @@ -109,7 +109,7 @@ type EvalFormula = { // TODO formula slots }; -export type EvalHookParams = { +export type EvalDeferredTaskParams = { endowmentFormulaIdentifiers: string[]; evalFormulaIdentifier: string; workerFormulaIdentifier: string; @@ -709,16 +709,17 @@ export type DaemonicPowers = { type IncarnateResult = Promise<{ formulaIdentifier: string; value: T }>; -export type AsyncHook> = ( +export type DeferredTask> = ( formulaIdentifiers: Readonly, ) => Promise; /** - * A collection of async hooks that can be executed in parallel. + * A collection of deferred tasks (i.e. async functions) that can be executed in + * parallel. */ -export type AsyncHooks> = { - add(value: AsyncHook): void; +export type DeferredTasks> = { execute(identifiers: Readonly): Promise; + push(value: DeferredTask): void; }; export interface DaemonCore { @@ -757,7 +758,7 @@ export interface DaemonCore { ) => IncarnateResult; incarnateGuest: ( hostFormulaIdentifier: string, - hooks: AsyncHooks, + deferredTasks: DeferredTasks, ) => IncarnateResult; incarnateReadableBlob: ( contentSha512: string, @@ -767,7 +768,7 @@ export interface DaemonCore { source: string, codeNames: string[], endowmentFormulaIdsOrPaths: (string | string[])[], - hooks: AsyncHooks, + deferredTasks: DeferredTasks, specifiedWorkerFormulaIdentifier?: string, ) => IncarnateResult; incarnateUnconfined: ( diff --git a/packages/daemon/test/test-async-hooks.js b/packages/daemon/test/test-deferred-tasks.js similarity index 50% rename from packages/daemon/test/test-async-hooks.js rename to packages/daemon/test/test-deferred-tasks.js index b94007a90c..9c6a09a5dc 100644 --- a/packages/daemon/test/test-async-hooks.js +++ b/packages/daemon/test/test-deferred-tasks.js @@ -1,20 +1,20 @@ import test from 'ava'; -import { makeAsyncHooks } from '../src/async-hooks.js'; +import { makeDeferredTasks } from '../src/deferred-tasks.js'; test('execute', async t => { - const hooks = makeAsyncHooks(); + const tasks = makeDeferredTasks(); const results = []; - hooks.add(async () => { + tasks.push(async () => { results.push(1); }); - hooks.add(async () => { + tasks.push(async () => { results.push(2); }); - hooks.add(async () => { + tasks.push(async () => { results.push(3); }); - await hooks.execute(); + await tasks.execute(); t.deepEqual(results.sort(), [1, 2, 3]); }); From 8f44e6e7202078fda56fa629b3f8e0838cc5c89a Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Thu, 7 Mar 2024 22:46:38 -0800 Subject: [PATCH 6/7] refactor(daemon): Remove typedefs --- packages/daemon/src/daemon.js | 11 ++--------- packages/daemon/src/host.js | 11 +++-------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/packages/daemon/src/daemon.js b/packages/daemon/src/daemon.js index b292cab3d8..fca23e9abf 100644 --- a/packages/daemon/src/daemon.js +++ b/packages/daemon/src/daemon.js @@ -78,11 +78,6 @@ const getDerivedId = (path, rootNonce, digester) => { return nonce; }; -/** - * @template T - * @typedef {import('./types.js').IncarnateResult} IncarnateResult - */ - /** * @param {import('./types.js').DaemonicPowers} powers * @param {Promise} webletPortP @@ -904,10 +899,9 @@ const makeDaemonCore = async ( * Incarnates a `handle` formula and synchronously adds it to the formula graph. * The returned promise is resolved after the formula is persisted. * - * @typedef {import('./types.js').ExternalHandle} ExternalHandle * @param {string} formulaNumber - The formula number of the handle to incarnate. * @param {string} targetFormulaIdentifier - The formula identifier of the handle's target. - * @returns {IncarnateResult} The incarnated handle. + * @returns {import('./types.js').IncarnateResult} The incarnated handle. */ const incarnateNumberedHandle = (formulaNumber, targetFormulaIdentifier) => { /** @type {import('./types.js').HandleFormula} */ @@ -924,9 +918,8 @@ const makeDaemonCore = async ( * Incarnates a `pet-store` formula and synchronously adds it to the formula graph. * The returned promise is resolved after the formula is persisted. * - * @typedef {import('./types.js').PetStore} PetStore * @param {string} formulaNumber - The formula number of the pet store to incarnate. - * @returns {IncarnateResult} The incarnated pet store. + * @returns {import('./types.js').IncarnateResult} The incarnated pet store. */ const incarnateNumberedPetStore = async formulaNumber => { /** @type {import('./types.js').PetStoreFormula} */ diff --git a/packages/daemon/src/host.js b/packages/daemon/src/host.js index 707ce7db3d..93f8f3a90e 100644 --- a/packages/daemon/src/host.js +++ b/packages/daemon/src/host.js @@ -8,11 +8,6 @@ import { makeDeferredTasks } from './deferred-tasks.js'; const { quote: q } = assert; -/** - * @template {Record} T - * @typedef {import('./types.js').DeferredTasks} DeferredTasks - */ - /** * @param {object} args * @param {import('./types.js').DaemonCore['provideValueForFormulaIdentifier']} args.provideValueForFormulaIdentifier @@ -156,7 +151,7 @@ export const makeHostMaker = ({ } } - /** @type {DeferredTasks} */ + /** @type {import('./types.js').DeferredTasks} */ const tasks = makeDeferredTasks(); if (petName !== undefined) { tasks.push(identifiers => @@ -247,7 +242,7 @@ export const makeHostMaker = ({ /** * @param {string | 'MAIN' | 'NEW'} workerName - * @param {DeferredTasks<{ workerFormulaIdentifier: string }>['push']} deferTask + * @param {import('./types.js').DeferredTasks<{ workerFormulaIdentifier: string }>['push']} deferTask * @returns {string | undefined} */ const provideWorkerFormulaIdentifierSync = (workerName, deferTask) => { @@ -308,7 +303,7 @@ export const makeHostMaker = ({ throw new Error('Evaluator requires one pet name for each code name'); } - /** @type {DeferredTasks} */ + /** @type {import('./types.js').DeferredTasks} */ const tasks = makeDeferredTasks(); const workerFormulaIdentifier = provideWorkerFormulaIdentifierSync( From f546680118172a4b1599c1b4d37c28e5eb85d9d6 Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Thu, 7 Mar 2024 22:47:18 -0800 Subject: [PATCH 7/7] refactor(daemon): Simplify incarnateGuest parallelism --- packages/daemon/src/daemon.js | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/packages/daemon/src/daemon.js b/packages/daemon/src/daemon.js index fca23e9abf..cd5eeb1672 100644 --- a/packages/daemon/src/daemon.js +++ b/packages/daemon/src/daemon.js @@ -1015,35 +1015,28 @@ const makeDaemonCore = async ( storeFormulaIdentifier, workerFormulaIdentifier, } = await formulaGraphMutex.enqueue(async () => { - const [ - ownFormulaNumber, - hostHandle, - storeIncarnation, - workerIncarnation, - ] = await Promise.all([ - randomHex512(), - randomHex512(), - randomHex512(), - randomHex512(), - ]).then(([own, handle, store, worker]) => - Promise.all([ - own, - incarnateNumberedHandle(handle, hostFormulaIdentifier), - incarnateNumberedPetStore(store), - incarnateNumberedWorker(worker), - ]), + const formulaNumber = await randomHex512(); + const hostHandle = await incarnateNumberedHandle( + await randomHex512(), + hostFormulaIdentifier, + ); + const storeIncarnation = await incarnateNumberedPetStore( + await randomHex512(), + ); + const workerIncarnation = await incarnateNumberedWorker( + await randomHex512(), ); await deferredTasks.execute({ guestFormulaIdentifier: serializeFormulaIdentifier({ type: 'guest', - number: ownFormulaNumber, + number: formulaNumber, node: ownNodeIdentifier, }), }); return harden({ - guestFormulaNumber: ownFormulaNumber, + guestFormulaNumber: formulaNumber, hostHandleFormulaIdentifier: hostHandle.formulaIdentifier, storeFormulaIdentifier: storeIncarnation.formulaIdentifier, workerFormulaIdentifier: workerIncarnation.formulaIdentifier,