diff --git a/packages/daemon/src/daemon-node-powers.js b/packages/daemon/src/daemon-node-powers.js index 683da6a79b..c425518aca 100644 --- a/packages/daemon/src/daemon-node-powers.js +++ b/packages/daemon/src/daemon-node-powers.js @@ -233,7 +233,7 @@ export const makeSocketPowers = ({ net }) => { ); /** @type {import('./types.js').SocketPowers['connectPort']} */ - const connectPort = ({ port, host, cancelled }) => + const connectPort = ({ port, host }) => new Promise((resolve, reject) => { const conn = net.connect(port, host, err => { if (err) { diff --git a/packages/daemon/src/daemon.js b/packages/daemon/src/daemon.js index 451940d136..710a5cdbb0 100644 --- a/packages/daemon/src/daemon.js +++ b/packages/daemon/src/daemon.js @@ -53,6 +53,10 @@ const makeInspector = (type, number, record) => list: () => Object.keys(record), }); +/** + * @param {import('./types.js').Context} context - The context to make far. + * @returns {import('./types.js').FarContext} The far context. + */ const makeFarContext = context => Far('Context', { cancel: context.cancel, @@ -65,7 +69,6 @@ const makeFarContext = context => * @param {import('./types.js').DaemonicPowers} powers * @param {Promise} webletPortP * @param {object} args - * @param {Promise} args.cancelled * @param {(error: Error) => void} args.cancel * @param {number} args.gracePeriodMs * @param {Promise} args.gracePeriodElapsed @@ -73,7 +76,7 @@ const makeFarContext = context => const makeEndoBootstrap = async ( powers, webletPortP, - { cancelled, cancel, gracePeriodMs, gracePeriodElapsed }, + { cancel, gracePeriodMs, gracePeriodElapsed }, ) => { const { crypto: cryptoPowers, @@ -862,7 +865,6 @@ export const makeDaemon = async (powers, daemonLabel, cancel, cancelled) => { ); const endoBootstrap = makeEndoBootstrap(powers, assignedWebletPortP, { - cancelled, cancel, gracePeriodMs, gracePeriodElapsed, diff --git a/packages/daemon/src/types.d.ts b/packages/daemon/src/types.d.ts index e6e1cdde0d..731effa9b7 100644 --- a/packages/daemon/src/types.d.ts +++ b/packages/daemon/src/types.d.ts @@ -169,6 +169,7 @@ export interface FarContext { cancel: (reason: string) => Promise; whenCancelled: () => Promise; whenDisposed: () => Promise; + addDisposalHook: Context['onCancel']; } export interface InternalExternal { diff --git a/packages/daemon/test/context-consumer.js b/packages/daemon/test/context-consumer.js new file mode 100644 index 0000000000..cb26df4041 --- /dev/null +++ b/packages/daemon/test/context-consumer.js @@ -0,0 +1,12 @@ +import { E, Far } from '@endo/far'; + +export const make = async (_powers, context) => { + return Far('Context consumer', { + async awaitCancellation() { + await E(context).whenCancelled(); + // await E(context).whenDisposed(); + // await new Promise(r => E(context).addDisposalHook(r)); + return 'cancelled'; + }, + }); +}; diff --git a/packages/daemon/test/test-endo.js b/packages/daemon/test/test-endo.js index 5b92e8d936..e0556ccbb2 100644 --- a/packages/daemon/test/test-endo.js +++ b/packages/daemon/test/test-endo.js @@ -566,11 +566,11 @@ test('guest facet receives a message for host', async t => { await stop(locator); }); -test('direct termination', async t => { +test('direct cancellation', async t => { const { promise: cancelled, reject: cancel } = makePromiseKit(); t.teardown(() => cancel(Error('teardown'))); - const locator = makeLocator('tmp', 'termination-direct'); + const locator = makeLocator('tmp', 'cancellation-direct'); await start(locator); t.teardown(() => stop(locator)); @@ -643,15 +643,13 @@ test('direct termination', async t => { ['counter'], ), ); - - t.pass(); }); -test('indirect termination', async t => { +test('indirect cancellation', async t => { const { promise: cancelled, reject: cancel } = makePromiseKit(); t.teardown(() => cancel(Error('teardown'))); - const locator = makeLocator('tmp', 'termination-indirect'); + const locator = makeLocator('tmp', 'cancellation-indirect'); await start(locator); t.teardown(() => stop(locator)); @@ -731,7 +729,7 @@ test('cancel because of requested capability', async t => { const { promise: cancelled, reject: cancel } = makePromiseKit(); t.teardown(() => cancel(Error('teardown'))); - const locator = makeLocator('tmp', 'termination-via-request'); + const locator = makeLocator('tmp', 'cancellation-via-request'); await start(locator); t.teardown(() => stop(locator)); @@ -815,6 +813,44 @@ test('cancel because of requested capability', async t => { ); }); +test('unconfined service can respond to cancellation', async t => { + const { promise: cancelled, reject: cancel } = makePromiseKit(); + t.teardown(() => cancel(Error('teardown'))); + + const locator = makeLocator('tmp', 'cancellation-unconfined-response'); + + await start(locator); + t.teardown(() => stop(locator)); + + const { getBootstrap } = await makeEndoClient( + 'client', + locator.sockPath, + cancelled, + ); + const bootstrap = getBootstrap(); + const host = E(bootstrap).host(); + await E(host).provideWorker('worker'); + + const capletPath = path.join(dirname, 'test', 'context-consumer.js'); + const capletLocation = url.pathToFileURL(capletPath).href; + await E(host).makeUnconfined( + 'worker', + capletLocation, + 'NONE', + 'context-consumer', + ); + + const result = E(host).evaluate( + 'worker', + 'E(caplet).awaitCancellation()', + ['caplet'], + ['context-consumer'], + ); + // await E(host).cancel('context-consumer'); + await E(host).cancel('worker'); + t.is(await result, 'cancelled'); +}); + test('make a host', async t => { const { promise: cancelled, reject: cancel } = makePromiseKit(); t.teardown(() => cancel(Error('teardown')));