Skip to content

Commit

Permalink
fixup! fix and test bad pass error behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
erights committed May 8, 2024
1 parent a113d2a commit e2b58fd
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 7 deletions.
6 changes: 4 additions & 2 deletions packages/async-flow/src/async-flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,9 @@ export const prepareAsyncFlowTools = (outerZone, outerOptions = {}) => {
// async IFFE ensures guestResultP is a fresh promise
guestAsyncFunc(...guestArgs))();

bijection.init(guestResultP, outcomeKit.vow);
if (flow.getFlowState() !== 'Failed') {
bijection.init(guestResultP, outcomeKit.vow);
}
// log is driven at first by guestAyncFunc interaction through the
// membrane with the host activationArgs. At the end of its first
// turn, it returns a promise for its eventual guest result.
Expand Down Expand Up @@ -347,7 +349,7 @@ export const prepareAsyncFlowTools = (outerZone, outerOptions = {}) => {
// console logging and
// resource exhaustion, including infinite loops
const err = makeError(
X`In a replay failure: see getFailures() for more information`,
X`In a Failed state: see getFailures() for more information`,
);
annotateError(err, X`due to ${fatalProblem}`);
throw err;
Expand Down
2 changes: 2 additions & 0 deletions packages/async-flow/src/replay-membrane.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ export const makeReplayMembrane = (
hostResult = optVerb
? hostTarget[optVerb](...hostArgs)
: hostTarget(...hostArgs);
// Try converting here just to route the error correctly
hostToGuest(hostResult, `converting ${optVerb || 'host'} result`);
} catch (hostProblem) {
return logDo(nestDispatch, harden(['doThrow', callIndex, hostProblem]));
}
Expand Down
6 changes: 1 addition & 5 deletions packages/async-flow/test/test-async-flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,8 +347,6 @@ await test.serial('test virtual async-flow', async t => {

await test.serial('test durable async-flow', async t => {
annihilate();

nextLife();
const zone1 = makeDurableZone(getBaggage(), 'durableRoot');
const vowTools1 = prepareWatchableVowTools(zone1);
await testFirstPlay(t, zone1, vowTools1);
Expand All @@ -372,7 +370,5 @@ await test.serial('test durable async-flow', async t => {
nextLife();
const zone4 = makeDurableZone(getBaggage(), 'durableRoot');
const vowTools4 = prepareWatchableVowTools(zone4);
await testAfterPlay(t, zone4, vowTools4);

await eventLoopIteration();
return testAfterPlay(t, zone4, vowTools4);
});
208 changes: 208 additions & 0 deletions packages/async-flow/test/test-bad-host.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// eslint-disable-next-line import/order
import {
test,
getBaggage,
annihilate,
nextLife,
} from './prepare-test-env-ava.js';

import { Fail } from '@endo/errors';
import { M } from '@endo/patterns';
import { makePromiseKit } from '@endo/promise-kit';
import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js';
import { prepareVowTools } from '@agoric/vow';
import { prepareVowTools as prepareWatchableVowTools } from '@agoric/vat-data/vow.js';
import { makeHeapZone } from '@agoric/zone/heap.js';
import { makeVirtualZone } from '@agoric/zone/virtual.js';
import { makeDurableZone } from '@agoric/zone/durable.js';

import { prepareAsyncFlowTools } from '../src/async-flow.js';

const nonPassableFunc = () => 'non-passable-function';
harden(nonPassableFunc);
const guestCreatedPromise = harden(Promise.resolve('guest-created'));
let badResult;

/**
* @param {Zone} zone
*/
const prepareBadHost = zone =>
zone.exoClass(
'BadHost',
M.interface('BadHost', {}, { defaultGuards: 'raw' }),
() => ({}),
{
badMethod(_badArg = undefined) {
return badResult;
},
},
);

/** @typedef {ReturnType<ReturnType<prepareBadHost>>} BadHost */

// TODO https://github.com/Agoric/agoric-sdk/issues/9231

/**
* @param {any} t
* @param {Zone} zone
* @param {VowTools} vowTools
*/
const testBadHostFirstPlay = async (t, zone, vowTools) => {
t.log('badHost firstPlay started');
const { asyncFlow, adminAsyncFlow } = prepareAsyncFlowTools(zone, {
vowTools,
});
const makeBadHost = prepareBadHost(zone);
const { makeVowKit } = vowTools;

const { vow: v1, resolver: _r1 } = zone.makeOnce('v1', () => makeVowKit());
// purposely violate rule that guestMethod is closed.
const { promise: promiseStep, resolve: resolveStep } = makePromiseKit();

const { guestMethod } = {
async guestMethod(badGuest, _p1) {
// nothing bad yet baseline
t.is(badGuest.badMethod(), undefined);

t.throws(() => badGuest.badMethod(guestCreatedPromise), {
message: 'In a Failed state: see getFailures() for more information',
});

resolveStep(true);
t.log(' badHost firstPlay about to return "bogus"');
// Must not settle outcomeVow
return 'bogus';
},
};

const wrapperFunc = asyncFlow(zone, 'AsyncFlow1', guestMethod);

const badHost = zone.makeOnce('badHost', () => makeBadHost());

const outcomeV = zone.makeOnce('outcomeV', () => wrapperFunc(badHost, v1));

const flow = adminAsyncFlow.getFlowForOutcomeVow(outcomeV);
await promiseStep;

const fatalProblem = flow.getOptFatalProblem();
t.throws(
() => {
throw fatalProblem;
},
{
message: '[3]: [0]: cannot yet send guest promises "[Promise]"',
},
);

t.deepEqual(flow.dump(), [
['checkCall', badHost, 'badMethod', [], 0],
['doReturn', 0, undefined],
// Notice that the bad call was not recorded in the log
]);
t.log('badHost firstPlay done');
return promiseStep;
};

/**
* @param {any} t
* @param {Zone} zone
* @param {VowTools} vowTools
*/
const testBadHostReplay1 = async (t, zone, vowTools) => {
t.log('badHost replay1 started');
const { asyncFlow, adminAsyncFlow } = prepareAsyncFlowTools(zone, {
vowTools,
});
prepareBadHost(zone);

// const { vow: v1, resolver: r1 } = zone.makeOnce('v1', () => Fail`need v1`);
// purposely violate rule that guestMethod is closed.
const { promise: promiseStep, resolve: resolveStep } = makePromiseKit();

const { guestMethod } = {
async guestMethod(badGuest, p1) {
// nothing bad yet baseline
t.is(badGuest.badMethod(), undefined);

// purposely violate rule that guestMethod is closed.
badResult = nonPassableFunc;

let gErr;
try {
badGuest.badMethod();
} catch (err) {
gErr = err;
}
t.throws(
() => {
throw gErr;
},
{
message:
'converting badMethod result: "[Symbol(passStyle)]" property expected: "[Function <anon>]"',
},
);
t.log(' badHost replay1 guest error caused by host error', gErr);

resolveStep(true);
t.log(' badHost replay1 to hang awaiting p2');
// awaiting a promise that won't be resolved until next incarnation
await p1;
t.fail('must not reach here in replay 1');
},
};

asyncFlow(zone, 'AsyncFlow1', guestMethod);

const badHost = zone.makeOnce('badHost', () => Fail`need badHost`);

const outcomeV = zone.makeOnce('outcomeV', () => Fail`need outcomeV`);

// TODO I shouldn't need to do this.
await adminAsyncFlow.wakeAll();

const flow = adminAsyncFlow.getFlowForOutcomeVow(outcomeV);
await promiseStep;

t.deepEqual(flow.dump(), [
['checkCall', badHost, 'badMethod', [], 0],
['doReturn', 0, undefined],
['checkCall', badHost, 'badMethod', [], 2],
[
'doThrow',
2,
Error(
'converting badMethod result: "[Symbol(passStyle)]" property expected: "[Function <anon>]"',
),
],
]);
t.log('badHost replay1 done');
return promiseStep;
};

await test.serial('test heap async-flow bad host', async t => {
const zone = makeHeapZone('heapRoot');
const vowTools = prepareVowTools(zone);
return testBadHostFirstPlay(t, zone, vowTools);
});

await test.serial('test virtual async-flow bad host', async t => {
annihilate();
const zone = makeVirtualZone('virtualRoot');
const vowTools = prepareVowTools(zone);
return testBadHostFirstPlay(t, zone, vowTools);
});

await test.serial('test durable async-flow bad host', async t => {
annihilate();
const zone1 = makeDurableZone(getBaggage(), 'durableRoot');
const vowTools1 = prepareWatchableVowTools(zone1);
await testBadHostFirstPlay(t, zone1, vowTools1);

await eventLoopIteration();

nextLife();
const zone3 = makeDurableZone(getBaggage(), 'durableRoot');
const vowTools3 = prepareWatchableVowTools(zone3);
return testBadHostReplay1(t, zone3, vowTools3);
});

0 comments on commit e2b58fd

Please sign in to comment.