From c3038c6ddd79cd781480c0b732f0de6b7f91742c Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Tue, 20 Aug 2024 17:14:12 -0600 Subject: [PATCH 1/3] fix(vow): export vat-compatible tools by default --- packages/vow/src/index.js | 9 ++++++++- packages/vow/src/tools.js | 6 +++--- packages/vow/vat.js | 16 +++++++++++----- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/vow/src/index.js b/packages/vow/src/index.js index 4b41d02b15a..5b14dd2afd2 100644 --- a/packages/vow/src/index.js +++ b/packages/vow/src/index.js @@ -1,8 +1,15 @@ // @ts-check -export * from './tools.js'; + +// We default to the vat-compatible version of this package, which is easy to +// reconfigure if not running under SwingSet. +export * from '../vat.js'; export { default as makeE } from './E.js'; export { VowShape, toPassableCap } from './vow-utils.js'; +/** + * @typedef {import('./tools.js').VowTools} VowTools + */ + // eslint-disable-next-line import/export export * from './types.js'; diff --git a/packages/vow/src/tools.js b/packages/vow/src/tools.js index ec224cbe078..ff35539726c 100644 --- a/packages/vow/src/tools.js +++ b/packages/vow/src/tools.js @@ -18,7 +18,7 @@ import { makeWhen } from './when.js'; * @param {object} [powers] * @param {IsRetryableReason} [powers.isRetryableReason] */ -export const prepareVowTools = (zone, powers = {}) => { +export const prepareBasicVowTools = (zone, powers = {}) => { const { isRetryableReason = /** @type {IsRetryableReason} */ (() => false) } = powers; const makeVowKit = prepareVowKit(zone); @@ -72,6 +72,6 @@ export const prepareVowTools = (zone, powers = {}) => { retriable, }); }; -harden(prepareVowTools); +harden(prepareBasicVowTools); -/** @typedef {ReturnType} VowTools */ +/** @typedef {ReturnType} VowTools */ diff --git a/packages/vow/vat.js b/packages/vow/vat.js index cc1170eaa7e..a486730b9c4 100644 --- a/packages/vow/vat.js +++ b/packages/vow/vat.js @@ -7,7 +7,9 @@ // @ts-check import { isUpgradeDisconnection } from '@agoric/internal/src/upgrade-api.js'; import { makeHeapZone } from '@agoric/base-zone/heap.js'; -import { makeE, prepareVowTools as rawPrepareVowTools } from './src/index.js'; + +import { prepareBasicVowTools } from './src/tools.js'; +import makeE from './src/E.js'; /** @type {import('./src/types.js').IsRetryableReason} */ const isRetryableReason = (reason, priorRetryValue) => { @@ -28,13 +30,17 @@ export const defaultPowers = harden({ /** * Produce SwingSet-compatible vowTools, with an arbitrary Zone type * - * @type {typeof rawPrepareVowTools} + * @type {typeof prepareBasicVowTools} */ export const prepareSwingsetVowTools = (zone, powers = {}) => - rawPrepareVowTools(zone, { ...defaultPowers, ...powers }); + prepareBasicVowTools(zone, { ...defaultPowers, ...powers }); +harden(prepareSwingsetVowTools); -/** @deprecated */ -export const prepareVowTools = prepareSwingsetVowTools; +/** + * Reexport as prepareVowTools, since that's the thing that people find easiest + * to reach. + */ +export { prepareSwingsetVowTools as prepareVowTools }; /** * `vowTools` that are not durable, but are useful in non-durable clients that From ea2d6a7d7a460afea81c4d61a729b7731684db71 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Wed, 21 Aug 2024 12:56:41 -0600 Subject: [PATCH 2/3] docs(vow): update for the current implementation --- packages/vow/README.md | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/packages/vow/README.md b/packages/vow/README.md index b34dc1db902..da4b8d34de3 100644 --- a/packages/vow/README.md +++ b/packages/vow/README.md @@ -26,12 +26,12 @@ Here they are: { } ``` -On Agoric, you can use `V` exported from `@agoric/vow/vat.js`, which -converts a chain of promises and vows to a promise for its final -fulfilment, by unwrapping any intermediate vows: +You can use `heapVowE` exported from `@agoric/vow`, which converts a chain of +promises and vows to a promise for its final fulfilment, by unwrapping any +intermediate vows: ```js -import { V as E } from '@agoric/vow/vat.js'; +import { heapVowE as E } from '@agoric/vow'; [...] const a = await E.when(w1); const b = await E(w2).something(...args); @@ -40,12 +40,13 @@ const b = await E(w2).something(...args); ## Vow Producer -On Agoric, use the following to create and resolve a vow: +Use the following to create and resolve a vow: ```js -// CAVEAT: `V` uses internal ephemeral promises, so while it is convenient, +// CAVEAT: `heapVow*` uses internal ephemeral promises, so while it is convenient, // it cannot be used by upgradable vats. See "Durability" below: -import { V as E, makeVowKit } from '@agoric/vow/vat.js'; +import { heapVowE, heapVowTools } from '@agoric/vow'; +const { makeVowKit } = heapVowTools; [...] const { resolver, vow } = makeVowKit(); // Send vow to a potentially different vat. @@ -56,15 +57,15 @@ resolver.resolve('now you know the answer'); ## Durability -The `@agoric/vow/vat.js` module allows vows to integrate Agoric's vat upgrade -mechanism. To create vow tools that deal with durable objects: +By default, the `@agoric/vow` module allows vows to integrate with Agoric's vat +upgrade mechanism. To create vow tools that deal with durable objects: ```js // NOTE: Cannot use `V` as it has non-durable internal state when unwrapping // vows. Instead, use the default vow-exposing `E` with the `watch` // operator. import { E } from '@endo/far'; -import { prepareVowTools } from '@agoric/vow/vat.js'; +import { prepareVowTools } from '@agoric/vow'; import { makeDurableZone } from '@agoric/zone'; // Only do the following once at the start of a new vat incarnation: @@ -94,20 +95,25 @@ final result: // that may not be side-effect free. let result = await specimenP; let vowInternals = getVowInternals(result); +let disconnectionState = undefined; // Loop until the result is no longer a vow. while (vowInternals) { try { - const shortened = await E(internals.vowV0).shorten(); + // WARNING: Do not use `shorten()` in your own code. This is an example + // for didactic purposes only. + const shortened = await E(vowInternals.vowV0).shorten(); const nextInternals = getVowInternals(shortened); // Atomically update the state. result = shortened; vowInternals = nextInternals; } catch (e) { - if (!isDisconnectionReason(e)) { + const nextDisconnectionState = isDisconnectionReason(e, disconnectionState); + if (!nextDisconnectionState) { // Not a disconnect, so abort. throw e; } - // It was a disconnect, so try again with the same state. + // It was a disconnect, so try again with the updated state. + disconnectionState = nextDisconnectionState; } } return result; From 63f029432624dd65646d73f46966233164b3961b Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Tue, 20 Aug 2024 17:14:42 -0600 Subject: [PATCH 3/3] test(vow): update tests to use basic tools --- packages/vow/test/asVow.test.js | 6 +++--- packages/vow/test/disconnect.test.js | 4 ++-- packages/vow/test/watch-utils.test.js | 30 +++++++++++++-------------- packages/vow/test/watch.test.js | 10 ++++----- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/vow/test/asVow.test.js b/packages/vow/test/asVow.test.js index 3109c74e027..35756da0dd3 100644 --- a/packages/vow/test/asVow.test.js +++ b/packages/vow/test/asVow.test.js @@ -4,11 +4,11 @@ import test from 'ava'; import { E } from '@endo/far'; import { makeHeapZone } from '@agoric/base-zone/heap.js'; -import { prepareVowTools } from '../src/tools.js'; +import { prepareBasicVowTools } from '../src/tools.js'; import { getVowPayload, isVow } from '../src/vow-utils.js'; test('asVow takes a function that throws/returns synchronously and returns a vow', async t => { - const { watch, when, asVow } = prepareVowTools(makeHeapZone()); + const { watch, when, asVow } = prepareBasicVowTools(makeHeapZone()); const fnThatThrows = () => { throw Error('fail'); @@ -36,7 +36,7 @@ test('asVow takes a function that throws/returns synchronously and returns a vow }); test('asVow does not resolve a vow to a vow', async t => { - const { watch, when, asVow } = prepareVowTools(makeHeapZone()); + const { watch, when, asVow } = prepareBasicVowTools(makeHeapZone()); const testVow = watch(Promise.resolve('payload')); const testVowAsVow = asVow(() => testVow); diff --git a/packages/vow/test/disconnect.test.js b/packages/vow/test/disconnect.test.js index 35713076214..5ea0e73e4c3 100644 --- a/packages/vow/test/disconnect.test.js +++ b/packages/vow/test/disconnect.test.js @@ -3,7 +3,7 @@ import test from 'ava'; import { makeHeapZone } from '@agoric/base-zone/heap.js'; import { makeTagged } from '@endo/pass-style'; -import { prepareVowTools } from '../src/tools.js'; +import { prepareBasicVowTools } from '../src/tools.js'; /** @import {Vow} from '../src/types.js' */ @@ -11,7 +11,7 @@ test('retry on disconnection', async t => { const zone = makeHeapZone(); const isRetryableReason = e => e && e.message === 'disconnected'; - const { watch, when } = prepareVowTools(zone, { + const { watch, when } = prepareBasicVowTools(zone, { isRetryableReason, }); const makeTestVowV0 = zone.exoClass( diff --git a/packages/vow/test/watch-utils.test.js b/packages/vow/test/watch-utils.test.js index e92809ac040..0a841e19327 100644 --- a/packages/vow/test/watch-utils.test.js +++ b/packages/vow/test/watch-utils.test.js @@ -4,11 +4,11 @@ import test from 'ava'; import { makeHeapZone } from '@agoric/base-zone/heap.js'; import { E, getInterfaceOf } from '@endo/far'; -import { prepareVowTools } from '../src/tools.js'; +import { prepareBasicVowTools } from '../src/tools.js'; test('allVows waits for a single vow to complete', async t => { const zone = makeHeapZone(); - const { watch, when, allVows } = prepareVowTools(zone); + const { watch, when, allVows } = prepareBasicVowTools(zone); const testPromiseP = Promise.resolve('promise'); const vowA = watch(testPromiseP); @@ -20,7 +20,7 @@ test('allVows waits for a single vow to complete', async t => { test('allVows waits for an array of vows to complete', async t => { const zone = makeHeapZone(); - const { watch, when, allVows } = prepareVowTools(zone); + const { watch, when, allVows } = prepareBasicVowTools(zone); const testPromiseAP = Promise.resolve('promiseA'); const testPromiseBP = Promise.resolve('promiseB'); @@ -36,7 +36,7 @@ test('allVows waits for an array of vows to complete', async t => { test('allVows returns vows in order', async t => { const zone = makeHeapZone(); - const { watch, when, allVows, makeVowKit } = prepareVowTools(zone); + const { watch, when, allVows, makeVowKit } = prepareBasicVowTools(zone); const kit = makeVowKit(); const testPromiseAP = Promise.resolve('promiseA'); @@ -55,7 +55,7 @@ test('allVows returns vows in order', async t => { test('allVows rejects upon first rejection', async t => { const zone = makeHeapZone(); - const { watch, when, allVows } = prepareVowTools(zone); + const { watch, when, allVows } = prepareBasicVowTools(zone); const testPromiseAP = Promise.resolve('promiseA'); const testPromiseBP = Promise.reject(Error('rejectedA')); @@ -75,7 +75,7 @@ test('allVows rejects upon first rejection', async t => { test('allVows can accept vows awaiting other vows', async t => { const zone = makeHeapZone(); - const { watch, when, allVows } = prepareVowTools(zone); + const { watch, when, allVows } = prepareBasicVowTools(zone); const testPromiseAP = Promise.resolve('promiseA'); const testPromiseBP = Promise.resolve('promiseB'); @@ -93,7 +93,7 @@ test('allVows can accept vows awaiting other vows', async t => { test('allVows - works with just promises', async t => { const zone = makeHeapZone(); - const { when, allVows } = prepareVowTools(zone); + const { when, allVows } = prepareBasicVowTools(zone); const result = await when( allVows([Promise.resolve('promiseA'), Promise.resolve('promiseB')]), @@ -104,7 +104,7 @@ test('allVows - works with just promises', async t => { test('allVows - watch promises mixed with vows', async t => { const zone = makeHeapZone(); - const { watch, when, allVows } = prepareVowTools(zone); + const { watch, when, allVows } = prepareBasicVowTools(zone); const testPromiseP = Promise.resolve('vow'); const vowA = watch(testPromiseP); @@ -116,7 +116,7 @@ test('allVows - watch promises mixed with vows', async t => { test('allVows can accept passable data (PureData)', async t => { const zone = makeHeapZone(); - const { watch, when, allVows } = prepareVowTools(zone); + const { watch, when, allVows } = prepareBasicVowTools(zone); const testPromiseP = Promise.resolve('vow'); const vowA = watch(testPromiseP); @@ -135,7 +135,7 @@ const prepareAccount = zone => test('allVows supports Promise pipelining', async t => { const zone = makeHeapZone(); - const { watch, when, allVows } = prepareVowTools(zone); + const { watch, when, allVows } = prepareBasicVowTools(zone); // makeAccount returns a Promise const prepareLocalChain = makeAccount => { @@ -170,7 +170,7 @@ test('allVows supports Promise pipelining', async t => { test('allVows does NOT support Vow pipelining', async t => { const zone = makeHeapZone(); - const { watch, when, allVows } = prepareVowTools(zone); + const { watch, when, allVows } = prepareBasicVowTools(zone); // makeAccount returns a Vow const prepareLocalChainVowish = makeAccount => { @@ -202,7 +202,7 @@ test('allVows does NOT support Vow pipelining', async t => { test('asPromise converts a vow to a promise', async t => { const zone = makeHeapZone(); - const { watch, asPromise } = prepareVowTools(zone); + const { watch, asPromise } = prepareBasicVowTools(zone); const testPromiseP = Promise.resolve('test value'); const vow = watch(testPromiseP); @@ -213,7 +213,7 @@ test('asPromise converts a vow to a promise', async t => { test('asPromise handles vow rejection', async t => { const zone = makeHeapZone(); - const { watch, asPromise } = prepareVowTools(zone); + const { watch, asPromise } = prepareBasicVowTools(zone); const testPromiseP = Promise.reject(new Error('test error')); const vow = watch(testPromiseP); @@ -223,7 +223,7 @@ test('asPromise handles vow rejection', async t => { test('asPromise accepts and resolves promises', async t => { const zone = makeHeapZone(); - const { asPromise } = prepareVowTools(zone); + const { asPromise } = prepareBasicVowTools(zone); const p = Promise.resolve('a promise'); const result = await asPromise(p); @@ -232,7 +232,7 @@ test('asPromise accepts and resolves promises', async t => { test('asPromise handles watcher arguments', async t => { const zone = makeHeapZone(); - const { watch, asPromise } = prepareVowTools(zone); + const { watch, asPromise } = prepareBasicVowTools(zone); const testPromiseP = Promise.resolve('watcher test'); const vow = watch(testPromiseP); diff --git a/packages/vow/test/watch.test.js b/packages/vow/test/watch.test.js index 851b43ae9f4..61ccec95a9b 100644 --- a/packages/vow/test/watch.test.js +++ b/packages/vow/test/watch.test.js @@ -3,7 +3,7 @@ import test from 'ava'; import { makeHeapZone } from '@agoric/base-zone/heap.js'; -import { prepareVowTools } from '../src/tools.js'; +import { prepareBasicVowTools } from '../src/tools.js'; /** * @import {ExecutionContext} from 'ava' @@ -59,7 +59,7 @@ const prepareArityCheckWatcher = (zone, t) => { */ test('ack watcher - shim', async t => { const zone = makeHeapZone(); - const { watch, when, makeVowKit } = prepareVowTools(zone); + const { watch, when, makeVowKit } = prepareBasicVowTools(zone); const makeAckWatcher = prepareAckWatcher(zone, t); const packet = harden({ portId: 'port-1', channelId: 'channel-1' }); @@ -112,7 +112,7 @@ test('ack watcher - shim', async t => { */ test('watcher args arity - shim', async t => { const zone = makeHeapZone(); - const { watch, when, makeVowKit } = prepareVowTools(zone); + const { watch, when, makeVowKit } = prepareBasicVowTools(zone); const makeArityCheckWatcher = prepareArityCheckWatcher(zone, t); const testCases = /** @type {const} */ ({ @@ -173,7 +173,7 @@ test('watcher args arity - shim', async t => { test('vow self resolution', async t => { const zone = makeHeapZone(); - const { watch, when, makeVowKit } = prepareVowTools(zone); + const { watch, when, makeVowKit } = prepareBasicVowTools(zone); // A direct self vow resolution const { vow: vow1, resolver: resolver1 } = makeVowKit(); @@ -226,7 +226,7 @@ test('vow self resolution', async t => { test('disconnection of non-vow informs watcher', async t => { const zone = makeHeapZone(); - const { watch, when } = prepareVowTools(zone, { + const { watch, when } = prepareBasicVowTools(zone, { isRetryableReason: reason => reason === 'disconnected', });