@agoric/async-flow 0.1.1-calypso-dev-84eb287.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -0
- package/LICENSE +201 -0
- package/README.md +40 -0
- package/docs/async-flow-states.key +0 -0
- package/docs/async-flow-states.md +15 -0
- package/docs/async-flow-states.png +0 -0
- package/index.d.ts +3 -0
- package/index.d.ts.map +1 -0
- package/index.js +2 -0
- package/package.json +66 -0
- package/src/async-flow.d.ts +94 -0
- package/src/async-flow.d.ts.map +1 -0
- package/src/async-flow.js +520 -0
- package/src/bijection.d.ts +31 -0
- package/src/bijection.d.ts.map +1 -0
- package/src/bijection.js +207 -0
- package/src/convert.d.ts +6 -0
- package/src/convert.d.ts.map +1 -0
- package/src/convert.js +133 -0
- package/src/endowments.d.ts +16 -0
- package/src/endowments.d.ts.map +1 -0
- package/src/endowments.js +292 -0
- package/src/ephemera.d.ts +3 -0
- package/src/ephemera.d.ts.map +1 -0
- package/src/ephemera.js +39 -0
- package/src/equate.d.ts +2 -0
- package/src/equate.d.ts.map +1 -0
- package/src/equate.js +123 -0
- package/src/log-store.d.ts +27 -0
- package/src/log-store.d.ts.map +1 -0
- package/src/log-store.js +169 -0
- package/src/replay-membrane.d.ts +40 -0
- package/src/replay-membrane.d.ts.map +1 -0
- package/src/replay-membrane.js +752 -0
- package/src/type-guards.d.ts +4 -0
- package/src/type-guards.d.ts.map +1 -0
- package/src/type-guards.js +68 -0
- package/src/types.d.ts +67 -0
- package/src/types.d.ts.map +1 -0
- package/src/types.js +196 -0
- package/test/async-flow-crank.test.js +102 -0
- package/test/async-flow-early-completion.test.js +203 -0
- package/test/async-flow-no-this.js +65 -0
- package/test/async-flow.test.js +383 -0
- package/test/bad-host.test.js +210 -0
- package/test/bijection.test.js +124 -0
- package/test/convert.test.js +132 -0
- package/test/endowments.test.js +157 -0
- package/test/equate.test.js +120 -0
- package/test/log-store.test.js +120 -0
- package/test/prepare-test-env-ava.js +28 -0
- package/test/replay-membrane-eventual.test.js +217 -0
- package/test/replay-membrane-settlement.test.js +173 -0
- package/test/replay-membrane-zombie.test.js +187 -0
- package/test/replay-membrane.test.js +297 -0
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +13 -0
- package/typedoc.json +8 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// eslint-disable-next-line import/order
|
|
2
|
+
import { test, getBaggage, annihilate } from './prepare-test-env-ava.js';
|
|
3
|
+
|
|
4
|
+
import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js';
|
|
5
|
+
import { prepareVowTools } from '@agoric/vow';
|
|
6
|
+
import { makeHeapZone } from '@agoric/zone/heap.js';
|
|
7
|
+
import { makeVirtualZone } from '@agoric/zone/virtual.js';
|
|
8
|
+
import { makeDurableZone } from '@agoric/zone/durable.js';
|
|
9
|
+
|
|
10
|
+
import { prepareAsyncFlowTools } from '../src/async-flow.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @import {Zone} from '@agoric/base-zone';
|
|
14
|
+
* @import {Vow, VowTools} from '@agoric/vow'
|
|
15
|
+
* @import {AsyncFlow} from '../src/async-flow.js'
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const { apply } = Reflect;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {any} t
|
|
22
|
+
* @param {Zone} zone
|
|
23
|
+
*/
|
|
24
|
+
const testPlay = async (t, zone) => {
|
|
25
|
+
const vowTools = prepareVowTools(zone);
|
|
26
|
+
const { asyncFlow } = prepareAsyncFlowTools(zone, {
|
|
27
|
+
vowTools,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const { guestFunc } = {
|
|
31
|
+
async guestFunc() {
|
|
32
|
+
t.is(this, undefined);
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const wrapperFunc = asyncFlow(zone, 'guestFunc', guestFunc);
|
|
37
|
+
|
|
38
|
+
// Demonstrates that even if something is passed as `this` to the wrapperFunc,
|
|
39
|
+
// the guestFunc will run with `this` bound to `undefined`.
|
|
40
|
+
apply(wrapperFunc, 'bogus', []);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
test.serial('test heap no guest this', async t => {
|
|
44
|
+
annihilate();
|
|
45
|
+
const zone = makeHeapZone('heapRoot');
|
|
46
|
+
await testPlay(t, zone);
|
|
47
|
+
|
|
48
|
+
await eventLoopIteration();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test.serial('test virtual no guest this', async t => {
|
|
52
|
+
annihilate();
|
|
53
|
+
const zone = makeVirtualZone('virtualRoot');
|
|
54
|
+
await testPlay(t, zone);
|
|
55
|
+
|
|
56
|
+
await eventLoopIteration();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test.serial('test durable no guest this', async t => {
|
|
60
|
+
annihilate();
|
|
61
|
+
const zone = makeDurableZone(getBaggage(), 'durableRoot');
|
|
62
|
+
await testPlay(t, zone);
|
|
63
|
+
|
|
64
|
+
await eventLoopIteration();
|
|
65
|
+
});
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
// eslint-disable-next-line import/order
|
|
2
|
+
import {
|
|
3
|
+
test,
|
|
4
|
+
getBaggage,
|
|
5
|
+
annihilate,
|
|
6
|
+
nextLife,
|
|
7
|
+
} from './prepare-test-env-ava.js';
|
|
8
|
+
|
|
9
|
+
import { Fail } from '@endo/errors';
|
|
10
|
+
import { passStyleOf } from '@endo/pass-style';
|
|
11
|
+
import { makeCopyMap } from '@endo/patterns';
|
|
12
|
+
import { makePromiseKit } from '@endo/promise-kit';
|
|
13
|
+
import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js';
|
|
14
|
+
import { isVow } from '@agoric/vow/src/vow-utils.js';
|
|
15
|
+
import { prepareVowTools } from '@agoric/vow';
|
|
16
|
+
import { makeHeapZone } from '@agoric/zone/heap.js';
|
|
17
|
+
import { makeVirtualZone } from '@agoric/zone/virtual.js';
|
|
18
|
+
import { makeDurableZone } from '@agoric/zone/durable.js';
|
|
19
|
+
|
|
20
|
+
import { prepareAsyncFlowTools } from '../src/async-flow.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @import {AsyncFlow} from '../src/async-flow.js'
|
|
24
|
+
* @import {Vow, VowTools} from '@agoric/vow'
|
|
25
|
+
* @import {PromiseKit} from '@endo/promise-kit'
|
|
26
|
+
* @import {Zone} from '@agoric/base-zone'
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {Zone} zone
|
|
31
|
+
* @param {number} [k]
|
|
32
|
+
*/
|
|
33
|
+
const prepareOrchestra = (zone, k = 1) =>
|
|
34
|
+
zone.exoClass(
|
|
35
|
+
'Orchestra',
|
|
36
|
+
undefined,
|
|
37
|
+
(factor, vow, resolver) => ({ factor, vow, resolver }),
|
|
38
|
+
{
|
|
39
|
+
scale(n) {
|
|
40
|
+
const { state } = this;
|
|
41
|
+
return k * state.factor * n;
|
|
42
|
+
},
|
|
43
|
+
vow() {
|
|
44
|
+
const { state } = this;
|
|
45
|
+
return state.vow;
|
|
46
|
+
},
|
|
47
|
+
resolve(x) {
|
|
48
|
+
const { state } = this;
|
|
49
|
+
state.resolver.resolve(x);
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
/** @typedef {ReturnType<ReturnType<prepareOrchestra>>} Orchestra */
|
|
55
|
+
|
|
56
|
+
const firstLogLen = 7;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {any} t
|
|
60
|
+
* @param {Zone} zone
|
|
61
|
+
*/
|
|
62
|
+
const testFirstPlay = async (t, zone) => {
|
|
63
|
+
t.log('firstPlay started');
|
|
64
|
+
const vowTools = prepareVowTools(zone);
|
|
65
|
+
const { asyncFlow, adminAsyncFlow } = prepareAsyncFlowTools(zone, {
|
|
66
|
+
vowTools,
|
|
67
|
+
});
|
|
68
|
+
const makeOrchestra = prepareOrchestra(zone);
|
|
69
|
+
const { makeVowKit } = vowTools;
|
|
70
|
+
|
|
71
|
+
const { vow: v1, resolver: r1 } = zone.makeOnce('v1', () => makeVowKit());
|
|
72
|
+
const { vow: v2, resolver: r2 } = zone.makeOnce('v2', () => makeVowKit());
|
|
73
|
+
const { vow: v3, resolver: _r3 } = zone.makeOnce('v3', () => makeVowKit());
|
|
74
|
+
const hOrch7 = zone.makeOnce('hOrch7', () => makeOrchestra(7, v2, r2));
|
|
75
|
+
|
|
76
|
+
// purposely violate rule that guestMethod is closed.
|
|
77
|
+
const { promise: promiseStep, resolve: resolveStep } = makePromiseKit();
|
|
78
|
+
|
|
79
|
+
const { guestMethod } = {
|
|
80
|
+
async guestMethod(gOrch7, g1, _p3) {
|
|
81
|
+
t.log(' firstPlay about to await g1');
|
|
82
|
+
t.is(await g1, 'x');
|
|
83
|
+
const p2 = gOrch7.vow();
|
|
84
|
+
const prod = gOrch7.scale(3);
|
|
85
|
+
t.is(prod, 21);
|
|
86
|
+
|
|
87
|
+
let gErr;
|
|
88
|
+
try {
|
|
89
|
+
gOrch7.scale(9n);
|
|
90
|
+
} catch (e) {
|
|
91
|
+
gErr = e;
|
|
92
|
+
}
|
|
93
|
+
t.is(gErr.name, 'TypeError');
|
|
94
|
+
|
|
95
|
+
resolveStep(true);
|
|
96
|
+
t.log(' firstPlay to hang awaiting p2');
|
|
97
|
+
// awaiting a promise that won't be resolved until next incarnation
|
|
98
|
+
await p2;
|
|
99
|
+
t.fail('must not reach here in first incarnation');
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const wrapperFunc = asyncFlow(zone, 'AsyncFlow1', guestMethod);
|
|
104
|
+
|
|
105
|
+
const outcomeV = zone.makeOnce('outcomeV', () => wrapperFunc(hOrch7, v1, v3));
|
|
106
|
+
|
|
107
|
+
t.true(isVow(outcomeV));
|
|
108
|
+
r1.resolve('x');
|
|
109
|
+
|
|
110
|
+
const flow = zone.makeOnce('flow', () =>
|
|
111
|
+
adminAsyncFlow.getFlowForOutcomeVow(outcomeV),
|
|
112
|
+
);
|
|
113
|
+
t.is(passStyleOf(flow), 'remotable');
|
|
114
|
+
|
|
115
|
+
await promiseStep;
|
|
116
|
+
|
|
117
|
+
const logDump = flow.dump();
|
|
118
|
+
t.is(logDump.length, firstLogLen);
|
|
119
|
+
t.deepEqual(logDump, [
|
|
120
|
+
['doFulfill', v1, 'x'],
|
|
121
|
+
['checkCall', hOrch7, 'vow', [], 1],
|
|
122
|
+
['doReturn', 1, v2],
|
|
123
|
+
['checkCall', hOrch7, 'scale', [3], 3],
|
|
124
|
+
['doReturn', 3, 21],
|
|
125
|
+
['checkCall', hOrch7, 'scale', [9n], 5],
|
|
126
|
+
[
|
|
127
|
+
'doThrow',
|
|
128
|
+
5,
|
|
129
|
+
TypeError('Cannot mix BigInt and other types, use explicit conversions'),
|
|
130
|
+
],
|
|
131
|
+
]);
|
|
132
|
+
t.log('firstPlay done');
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @param {any} t
|
|
137
|
+
* @param {Zone} zone
|
|
138
|
+
*/
|
|
139
|
+
const testBadReplay = async (t, zone) => {
|
|
140
|
+
t.log('badReplay started');
|
|
141
|
+
const vowTools = prepareVowTools(zone);
|
|
142
|
+
const { asyncFlow, adminAsyncFlow } = prepareAsyncFlowTools(zone, {
|
|
143
|
+
vowTools,
|
|
144
|
+
});
|
|
145
|
+
prepareOrchestra(zone);
|
|
146
|
+
const { when } = vowTools;
|
|
147
|
+
const hOrch7 = /** @type {Orchestra} */ (
|
|
148
|
+
zone.makeOnce('hOrch7', () => Fail`need hOrch7`)
|
|
149
|
+
);
|
|
150
|
+
// purposely violate rule that guestMethod is closed.
|
|
151
|
+
const { promise: promiseStep, resolve: resolveStep } = makePromiseKit();
|
|
152
|
+
|
|
153
|
+
const { guestMethod } = {
|
|
154
|
+
async guestMethod(gOrch7, g1, _p3) {
|
|
155
|
+
t.log(' badReplay about to await g1');
|
|
156
|
+
await g1;
|
|
157
|
+
gOrch7.vow();
|
|
158
|
+
resolveStep(true);
|
|
159
|
+
// This is a replay error
|
|
160
|
+
gOrch7.scale(4);
|
|
161
|
+
t.fail('badReplay must not reach here');
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// `asyncFlow` can be used simply to re-prepare the guest function
|
|
166
|
+
// by ignoring the returned wrapper function. If the wrapper function is
|
|
167
|
+
// invoked, that would be a *new* activation with a new outcome and
|
|
168
|
+
// flow, and would have nothing to do with the existing one.
|
|
169
|
+
asyncFlow(zone, 'AsyncFlow1', guestMethod);
|
|
170
|
+
|
|
171
|
+
const outcomeV = /** @type {Vow} */ (
|
|
172
|
+
zone.makeOnce('outcomeV', () => Fail`need outcomeV`)
|
|
173
|
+
);
|
|
174
|
+
const flow = /** @type {AsyncFlow} */ (
|
|
175
|
+
zone.makeOnce('flow', () => Fail`need flow`)
|
|
176
|
+
);
|
|
177
|
+
const flow1 = adminAsyncFlow.getFlowForOutcomeVow(outcomeV);
|
|
178
|
+
t.is(flow, flow1);
|
|
179
|
+
t.is(passStyleOf(flow), 'remotable');
|
|
180
|
+
|
|
181
|
+
// This unblocks `await p2;` but only after the replay failure is fixed in
|
|
182
|
+
// the next incarnation.
|
|
183
|
+
hOrch7.resolve('y');
|
|
184
|
+
await promiseStep;
|
|
185
|
+
t.is(await when(hOrch7.vow()), 'y');
|
|
186
|
+
|
|
187
|
+
const logDump = flow.dump();
|
|
188
|
+
t.is(logDump.length, firstLogLen);
|
|
189
|
+
|
|
190
|
+
const replayProblem = flow.getOptFatalProblem();
|
|
191
|
+
t.true(replayProblem instanceof Error);
|
|
192
|
+
|
|
193
|
+
t.deepEqual(
|
|
194
|
+
adminAsyncFlow.getFailures(),
|
|
195
|
+
makeCopyMap([[flow, replayProblem]]),
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
t.log(' badReplay failures', flow.getOptFatalProblem());
|
|
199
|
+
t.log('badReplay done');
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* @param {any} t
|
|
204
|
+
* @param {Zone} zone
|
|
205
|
+
*/
|
|
206
|
+
const testGoodReplay = async (t, zone) => {
|
|
207
|
+
t.log('goodReplay started');
|
|
208
|
+
const vowTools = prepareVowTools(zone);
|
|
209
|
+
const { asyncFlow, adminAsyncFlow } = prepareAsyncFlowTools(zone, {
|
|
210
|
+
vowTools,
|
|
211
|
+
});
|
|
212
|
+
prepareOrchestra(zone, 2); // Note change in new behavior
|
|
213
|
+
const { when } = vowTools;
|
|
214
|
+
const hOrch7 = /** @type {Orchestra} */ (
|
|
215
|
+
zone.makeOnce('hOrch7', () => Fail`need hOrch7`)
|
|
216
|
+
);
|
|
217
|
+
// purposely violate rule that guestMethod is closed.
|
|
218
|
+
const { promise: promiseStep, resolve: resolveStep } = makePromiseKit();
|
|
219
|
+
|
|
220
|
+
const { guestMethod } = {
|
|
221
|
+
async guestMethod(gOrch7, g1, p3) {
|
|
222
|
+
t.log(' goodReplay about to await g1');
|
|
223
|
+
await g1;
|
|
224
|
+
const p2 = gOrch7.vow();
|
|
225
|
+
const prod = gOrch7.scale(3);
|
|
226
|
+
t.is(prod, 21);
|
|
227
|
+
|
|
228
|
+
let gErr;
|
|
229
|
+
try {
|
|
230
|
+
gOrch7.scale(9n);
|
|
231
|
+
} catch (e) {
|
|
232
|
+
gErr = e;
|
|
233
|
+
}
|
|
234
|
+
t.is(gErr.name, 'TypeError');
|
|
235
|
+
|
|
236
|
+
resolveStep(true);
|
|
237
|
+
t.log(' goodReplay about to await p2');
|
|
238
|
+
// awaiting a promise that won't be resolved until this incarnation
|
|
239
|
+
await p2;
|
|
240
|
+
t.log(' goodReplay woke up!');
|
|
241
|
+
const prod2 = gOrch7.scale(3);
|
|
242
|
+
// same question. different answer
|
|
243
|
+
t.is(prod2, 42);
|
|
244
|
+
t.log('about to await p3');
|
|
245
|
+
await p3;
|
|
246
|
+
t.log('p3 settled');
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// `asyncFlow` can be used simply to re-prepare the guest function
|
|
251
|
+
// by ignoring the returned wrapper function. If the wrapper function is
|
|
252
|
+
// invoked, that would be a *new* activation with a new outcome and
|
|
253
|
+
// flow, and would have nothing to do with the existing one.
|
|
254
|
+
asyncFlow(zone, 'AsyncFlow1', guestMethod);
|
|
255
|
+
|
|
256
|
+
const outcomeV = /** @type {Vow} */ (
|
|
257
|
+
zone.makeOnce('outcomeV', () => Fail`need outcomeV`)
|
|
258
|
+
);
|
|
259
|
+
const flow = /** @type {AsyncFlow} */ (
|
|
260
|
+
zone.makeOnce('flow', () => Fail`need flow`)
|
|
261
|
+
);
|
|
262
|
+
const flow1 = adminAsyncFlow.getFlowForOutcomeVow(outcomeV);
|
|
263
|
+
t.is(flow, flow1);
|
|
264
|
+
t.is(passStyleOf(flow), 'remotable');
|
|
265
|
+
|
|
266
|
+
const v2 = hOrch7.vow();
|
|
267
|
+
t.is(await when(v2), 'y');
|
|
268
|
+
await eventLoopIteration();
|
|
269
|
+
|
|
270
|
+
await promiseStep;
|
|
271
|
+
|
|
272
|
+
const { vow: v1 } = zone.makeOnce('v1', () => Fail`need v1`);
|
|
273
|
+
const { resolver: r3 } = zone.makeOnce('v3', () => Fail`need v3`);
|
|
274
|
+
|
|
275
|
+
const logDump = flow.dump();
|
|
276
|
+
t.is(logDump.length, firstLogLen + 3);
|
|
277
|
+
t.deepEqual(logDump, [
|
|
278
|
+
['doFulfill', v1, 'x'],
|
|
279
|
+
['checkCall', hOrch7, 'vow', [], 1],
|
|
280
|
+
['doReturn', 1, v2],
|
|
281
|
+
['checkCall', hOrch7, 'scale', [3], 3],
|
|
282
|
+
['doReturn', 3, 21],
|
|
283
|
+
['checkCall', hOrch7, 'scale', [9n], 5],
|
|
284
|
+
[
|
|
285
|
+
'doThrow',
|
|
286
|
+
5,
|
|
287
|
+
TypeError('Cannot mix BigInt and other types, use explicit conversions'),
|
|
288
|
+
],
|
|
289
|
+
// new stuff
|
|
290
|
+
['doFulfill', v2, 'y'],
|
|
291
|
+
['checkCall', hOrch7, 'scale', [3], firstLogLen + 1],
|
|
292
|
+
// same question. different answer
|
|
293
|
+
['doReturn', firstLogLen + 1, 42],
|
|
294
|
+
]);
|
|
295
|
+
|
|
296
|
+
// @ts-expect-error TS doesn't know it is a resolver
|
|
297
|
+
r3.resolve('done');
|
|
298
|
+
await eventLoopIteration();
|
|
299
|
+
|
|
300
|
+
t.is(await when(outcomeV), undefined);
|
|
301
|
+
t.deepEqual(flow.dump(), []);
|
|
302
|
+
|
|
303
|
+
t.log('goodReplay done', await promiseStep);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* @param {any} t
|
|
308
|
+
* @param {Zone} zone
|
|
309
|
+
*/
|
|
310
|
+
const testAfterPlay = async (t, zone) => {
|
|
311
|
+
t.log('testAfterPlay started');
|
|
312
|
+
const vowTools = prepareVowTools(zone);
|
|
313
|
+
const { asyncFlow, adminAsyncFlow } = prepareAsyncFlowTools(zone, {
|
|
314
|
+
vowTools,
|
|
315
|
+
});
|
|
316
|
+
prepareOrchestra(zone);
|
|
317
|
+
|
|
318
|
+
const { guestMethod } = {
|
|
319
|
+
async guestMethod(_gOrch7, _gP, _p3) {
|
|
320
|
+
t.fail('Must not replay this');
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
// `asyncFlow` can be used simply to re-prepare the guest function
|
|
325
|
+
// by ignoring the returned wrapper function. If the wrapper function is
|
|
326
|
+
// invoked, that would be a *new* activation with a new outcome and
|
|
327
|
+
// flow, and would have nothing to do with the existing one.
|
|
328
|
+
asyncFlow(zone, 'AsyncFlow1', guestMethod);
|
|
329
|
+
|
|
330
|
+
const outcomeV = /** @type {Vow} */ (
|
|
331
|
+
zone.makeOnce('outcomeV', () => Fail`need outcomeV`)
|
|
332
|
+
);
|
|
333
|
+
const flow = /** @type {AsyncFlow} */ (
|
|
334
|
+
zone.makeOnce('flow', () => Fail`need flow`)
|
|
335
|
+
);
|
|
336
|
+
t.is(passStyleOf(flow), 'remotable');
|
|
337
|
+
|
|
338
|
+
t.throws(() => adminAsyncFlow.getFlowForOutcomeVow(outcomeV), {
|
|
339
|
+
message:
|
|
340
|
+
'key "[Alleged: VowInternalsKit vowV0]" not found in collection "flowForOutcomeVow"',
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Even this doesn't wake it up.
|
|
344
|
+
flow.wake();
|
|
345
|
+
await eventLoopIteration();
|
|
346
|
+
|
|
347
|
+
t.log('testAfterDoneReplay done');
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
test.serial('test heap async-flow', async t => {
|
|
351
|
+
const zone = makeHeapZone('heapRoot');
|
|
352
|
+
return testFirstPlay(t, zone);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test.serial('test virtual async-flow', async t => {
|
|
356
|
+
annihilate();
|
|
357
|
+
const zone = makeVirtualZone('virtualRoot');
|
|
358
|
+
return testFirstPlay(t, zone);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
test.serial('test durable async-flow', async t => {
|
|
362
|
+
annihilate();
|
|
363
|
+
const zone1 = makeDurableZone(getBaggage(), 'durableRoot');
|
|
364
|
+
await testFirstPlay(t, zone1);
|
|
365
|
+
|
|
366
|
+
await eventLoopIteration();
|
|
367
|
+
|
|
368
|
+
nextLife();
|
|
369
|
+
const zone2 = makeDurableZone(getBaggage(), 'durableRoot');
|
|
370
|
+
await testBadReplay(t, zone2);
|
|
371
|
+
|
|
372
|
+
await eventLoopIteration();
|
|
373
|
+
|
|
374
|
+
nextLife();
|
|
375
|
+
const zone3 = makeDurableZone(getBaggage(), 'durableRoot');
|
|
376
|
+
await testGoodReplay(t, zone3);
|
|
377
|
+
|
|
378
|
+
await eventLoopIteration();
|
|
379
|
+
|
|
380
|
+
nextLife();
|
|
381
|
+
const zone4 = makeDurableZone(getBaggage(), 'durableRoot');
|
|
382
|
+
return testAfterPlay(t, zone4);
|
|
383
|
+
});
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// eslint-disable-next-line import/order
|
|
2
|
+
import {
|
|
3
|
+
test,
|
|
4
|
+
getBaggage,
|
|
5
|
+
annihilate,
|
|
6
|
+
nextLife,
|
|
7
|
+
} from './prepare-test-env-ava.js';
|
|
8
|
+
|
|
9
|
+
import { Fail } from '@endo/errors';
|
|
10
|
+
import { M } from '@endo/patterns';
|
|
11
|
+
import { makePromiseKit } from '@endo/promise-kit';
|
|
12
|
+
import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js';
|
|
13
|
+
import { prepareVowTools } from '@agoric/vow';
|
|
14
|
+
import { makeHeapZone } from '@agoric/zone/heap.js';
|
|
15
|
+
import { makeVirtualZone } from '@agoric/zone/virtual.js';
|
|
16
|
+
import { makeDurableZone } from '@agoric/zone/durable.js';
|
|
17
|
+
|
|
18
|
+
import { prepareAsyncFlowTools } from '../src/async-flow.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @import {PromiseKit} from '@endo/promise-kit'
|
|
22
|
+
* @import {Zone} from '@agoric/base-zone'
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const nonPassableFunc = () => 'non-passable-function';
|
|
26
|
+
harden(nonPassableFunc);
|
|
27
|
+
const guestCreatedPromise = harden(Promise.resolve('guest-created'));
|
|
28
|
+
let badResult;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {Zone} zone
|
|
32
|
+
*/
|
|
33
|
+
const prepareBadHost = zone =>
|
|
34
|
+
zone.exoClass(
|
|
35
|
+
'BadHost',
|
|
36
|
+
M.interface('BadHost', {}, { defaultGuards: 'raw' }),
|
|
37
|
+
() => ({}),
|
|
38
|
+
{
|
|
39
|
+
badMethod(_badArg = undefined) {
|
|
40
|
+
return badResult;
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
/** @typedef {ReturnType<ReturnType<prepareBadHost>>} BadHost */
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {any} t
|
|
49
|
+
* @param {Zone} zone
|
|
50
|
+
*/
|
|
51
|
+
const testBadHostFirstPlay = async (t, zone) => {
|
|
52
|
+
t.log('badHost firstPlay started');
|
|
53
|
+
const vowTools = prepareVowTools(zone);
|
|
54
|
+
const { asyncFlow, adminAsyncFlow } = prepareAsyncFlowTools(zone, {
|
|
55
|
+
vowTools,
|
|
56
|
+
});
|
|
57
|
+
const makeBadHost = prepareBadHost(zone);
|
|
58
|
+
const { makeVowKit } = vowTools;
|
|
59
|
+
|
|
60
|
+
const { vow: v1, resolver: _r1 } = zone.makeOnce('v1', () => makeVowKit());
|
|
61
|
+
// purposely violate rule that guestMethod is closed.
|
|
62
|
+
const { promise: promiseStep, resolve: resolveStep } = makePromiseKit();
|
|
63
|
+
|
|
64
|
+
const { guestMethod } = {
|
|
65
|
+
async guestMethod(badGuest, _p1) {
|
|
66
|
+
// nothing bad yet baseline
|
|
67
|
+
t.is(badGuest.badMethod(), undefined);
|
|
68
|
+
|
|
69
|
+
t.throws(() => badGuest.badMethod(guestCreatedPromise), {
|
|
70
|
+
message:
|
|
71
|
+
'In a Failed state: see getFailures() or getOptFatalProblem() for more information',
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
resolveStep(true);
|
|
75
|
+
t.log(' badHost firstPlay about to return "bogus"');
|
|
76
|
+
// Must not settle outcomeVow
|
|
77
|
+
return 'bogus';
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const wrapperFunc = asyncFlow(zone, 'AsyncFlow1', guestMethod);
|
|
82
|
+
|
|
83
|
+
const badHost = zone.makeOnce('badHost', () => makeBadHost());
|
|
84
|
+
|
|
85
|
+
const outcomeV = zone.makeOnce('outcomeV', () => wrapperFunc(badHost, v1));
|
|
86
|
+
|
|
87
|
+
const flow = adminAsyncFlow.getFlowForOutcomeVow(outcomeV);
|
|
88
|
+
await promiseStep;
|
|
89
|
+
|
|
90
|
+
const fatalProblem = flow.getOptFatalProblem();
|
|
91
|
+
t.throws(
|
|
92
|
+
() => {
|
|
93
|
+
throw fatalProblem;
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
message: '[3]: [0]: cannot yet send guest promises "[Promise]"',
|
|
97
|
+
},
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
t.deepEqual(flow.dump(), [
|
|
101
|
+
['checkCall', badHost, 'badMethod', [], 0],
|
|
102
|
+
['doReturn', 0, undefined],
|
|
103
|
+
// Notice that the bad call was not recorded in the log
|
|
104
|
+
]);
|
|
105
|
+
t.log('badHost firstPlay done');
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @param {any} t
|
|
110
|
+
* @param {Zone} zone
|
|
111
|
+
*/
|
|
112
|
+
const testBadHostReplay1 = async (t, zone) => {
|
|
113
|
+
t.log('badHost replay1 started');
|
|
114
|
+
const vowTools = prepareVowTools(zone);
|
|
115
|
+
const { asyncFlow, adminAsyncFlow } = prepareAsyncFlowTools(zone, {
|
|
116
|
+
vowTools,
|
|
117
|
+
});
|
|
118
|
+
prepareBadHost(zone);
|
|
119
|
+
|
|
120
|
+
// const { vow: v1, resolver: r1 } = zone.makeOnce('v1', () => Fail`need v1`);
|
|
121
|
+
// purposely violate rule that guestMethod is closed.
|
|
122
|
+
const { promise: promiseStep, resolve: resolveStep } = makePromiseKit();
|
|
123
|
+
|
|
124
|
+
const { guestMethod } = {
|
|
125
|
+
async guestMethod(badGuest, p1) {
|
|
126
|
+
// nothing bad yet baseline
|
|
127
|
+
t.is(badGuest.badMethod(), undefined);
|
|
128
|
+
|
|
129
|
+
// purposely violate rule that guestMethod is closed.
|
|
130
|
+
badResult = nonPassableFunc;
|
|
131
|
+
|
|
132
|
+
let gErr;
|
|
133
|
+
try {
|
|
134
|
+
badGuest.badMethod();
|
|
135
|
+
} catch (err) {
|
|
136
|
+
gErr = err;
|
|
137
|
+
}
|
|
138
|
+
t.throws(
|
|
139
|
+
() => {
|
|
140
|
+
throw gErr;
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
message:
|
|
144
|
+
'Remotables must be explicitly declared: "[Function nonPassableFunc]"',
|
|
145
|
+
},
|
|
146
|
+
);
|
|
147
|
+
t.log(' badHost replay1 guest error caused by host error', gErr);
|
|
148
|
+
|
|
149
|
+
// show that flow is not Failed by host error
|
|
150
|
+
badResult = 'fine';
|
|
151
|
+
t.is(badGuest.badMethod(), 'fine');
|
|
152
|
+
|
|
153
|
+
resolveStep(true);
|
|
154
|
+
t.log(' badHost replay1 to hang awaiting p1');
|
|
155
|
+
// awaiting a promise that won't be resolved until next incarnation
|
|
156
|
+
await p1;
|
|
157
|
+
t.fail('must not reach here in replay 1');
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
asyncFlow(zone, 'AsyncFlow1', guestMethod);
|
|
162
|
+
|
|
163
|
+
const badHost = zone.makeOnce('badHost', () => Fail`need badHost`);
|
|
164
|
+
|
|
165
|
+
const outcomeV = zone.makeOnce('outcomeV', () => Fail`need outcomeV`);
|
|
166
|
+
|
|
167
|
+
const flow = adminAsyncFlow.getFlowForOutcomeVow(outcomeV);
|
|
168
|
+
await promiseStep;
|
|
169
|
+
|
|
170
|
+
const logDump = flow.dump();
|
|
171
|
+
|
|
172
|
+
t.deepEqual(logDump, [
|
|
173
|
+
['checkCall', badHost, 'badMethod', [], 0],
|
|
174
|
+
['doReturn', 0, undefined],
|
|
175
|
+
['checkCall', badHost, 'badMethod', [], 2],
|
|
176
|
+
[
|
|
177
|
+
'doThrow',
|
|
178
|
+
2,
|
|
179
|
+
Error(
|
|
180
|
+
'Remotables must be explicitly declared: "[Function nonPassableFunc]"',
|
|
181
|
+
),
|
|
182
|
+
],
|
|
183
|
+
['checkCall', badHost, 'badMethod', [], 4],
|
|
184
|
+
['doReturn', 4, 'fine'],
|
|
185
|
+
]);
|
|
186
|
+
t.log('badHost replay1 done');
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
test.serial('test heap async-flow bad host', async t => {
|
|
190
|
+
const zone = makeHeapZone('heapRoot');
|
|
191
|
+
return testBadHostFirstPlay(t, zone);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test.serial('test virtual async-flow bad host', async t => {
|
|
195
|
+
annihilate();
|
|
196
|
+
const zone = makeVirtualZone('virtualRoot');
|
|
197
|
+
return testBadHostFirstPlay(t, zone);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test.serial('test durable async-flow bad host', async t => {
|
|
201
|
+
annihilate();
|
|
202
|
+
const zone1 = makeDurableZone(getBaggage(), 'durableRoot');
|
|
203
|
+
await testBadHostFirstPlay(t, zone1);
|
|
204
|
+
|
|
205
|
+
await eventLoopIteration();
|
|
206
|
+
|
|
207
|
+
nextLife();
|
|
208
|
+
const zone3 = makeDurableZone(getBaggage(), 'durableRoot');
|
|
209
|
+
return testBadHostReplay1(t, zone3);
|
|
210
|
+
});
|