Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(asyncFlow): E support #9322

Draft
wants to merge 1 commit into
base: markm-asyncFlow-no-E
Choose a base branch
from
Draft

Conversation

erights
Copy link
Member

@erights erights commented May 6, 2024

Staged on #9443
closes: #9299
refs: #9097 #9336

Description

To support guest uses of E to do eventual sends,

  • we make guest remotable wrappers of host remotables into remote presences. This avoids any need to distinguish between local host remotables vs host remote presences.
  • we make guest promise wrappers of host vows into pipelinable promises.

Not yet implemented

  • Both have a handler that will implement applyMethod to translate an eventual send on the guest wrapper into a corresponding eventual send to the host target, logging it as a checkSend, by analogy with a checkCall. Like a checkCall that immediately returns a promise, it will be followed immediately by a corresponding doReturn of the corresponding vow. Unlike checkCall/doReturn, there is no possible callback interleaving between a checkSend and its doReturn. Later, when the target vow has settled, that will cause a corresponding doFulfill or doReject. Unlike checkCall, the target of a checkSend can be either a remotable or a promise.

Security Considerations

just more surface area and more tricky things that might be wrong.

Scaling Considerations

should not be significantly different than the existing support for checkCall.

Documentation Considerations

one less restriction that needs to be documented. Once this PR works, it should "just work" in the sense of merely removing a surprise.

Testing Considerations

  • write tests

Upgrade Considerations

Prior to this PR, a guest use of E(guestWrapper).foo() would, in a later turn, do a guest-side guestWrapper.foo() causing a checkCall/doReturn to get logged. This is not incorrect on its own terms. But we need not to commit to any such durable logs prior to this PR. With this PR, that same expression will immediately cause checkSend to get logged with an immediate doReturn of a promise that later settles with a doFulfill or doReject. With this change in the log, we expect we have durable logs we're willing to commit to.

Note that we have that even before we implement the applyMethod handler. In its absence, this PR should cause any such guest use of E to be an error. First, we must test that. Second, we need to decide how the asyncFlow mechanism should treat such errors.

  • Are they in model -- where the not-yet-implemented error gets thrown back at the guest function?
  • Or are they failures suspending the guest in a Failed state, so a later upgrade of the asyncFunc implementation would allow such failed guests to "resume" without requiring any change to the guest?

@erights erights changed the base branch from master to markm-async-flow May 6, 2024 05:58
@erights erights self-assigned this May 6, 2024
@erights erights changed the title feat(asyncFlow): E support feat(asyncFlow): E support May 6, 2024
Copy link

cloudflare-pages bot commented May 6, 2024

Deploying agoric-sdk with  Cloudflare Pages  Cloudflare Pages

Latest commit: e495bc2
Status: ✅  Deploy successful!
Preview URL: https://b44c5d71.agoric-sdk.pages.dev
Branch Preview URL: https://markm-asyncflow-e.agoric-sdk.pages.dev

View logs

@erights erights requested review from mhofman and michaelfig May 6, 2024 06:23
@erights erights added the asyncFlow related to membrane-based replay and upgrade of async functions label May 6, 2024
@erights erights force-pushed the markm-asyncFlow-E branch 2 times, most recently from e8e56d9 to 7575a6c Compare May 8, 2024 01:25
@erights erights force-pushed the markm-async-flow branch 2 times, most recently from 596304e to 4cb7ed6 Compare May 8, 2024 23:24
@erights erights changed the base branch from markm-async-flow to markm-zonify-vowTools May 8, 2024 23:29
Base automatically changed from markm-zonify-vowTools to markm-async-flow May 14, 2024 18:32
Base automatically changed from markm-async-flow to master May 19, 2024 22:08
@erights erights force-pushed the markm-asyncFlow-E branch 2 times, most recently from e9420ab to 98c12fb Compare May 24, 2024 03:29
Copy link
Member

@mhofman mhofman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass, but I'm confused about the handling of the result vow. I understand we have the logic about interpreter nesting, but I think it would be simpler to include the result vow in the checkSend log entry, and have a synthetic doReturn with no result (and ignore the outcome completely on replay because it either succeeds or panics, a throw outcome should not happen for a checkSend)

const hostPromise = optVerb
? E(hostTarget)[optVerb](...hostArgs)
: E(hostTarget)(...hostArgs);
resolver.resolve(hostPromise); // TODO does this always work?
Copy link
Member

@mhofman mhofman May 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah thinking about this, if hostTarget is a local object for which call returns a promise, we will fail during an upgrade. Unlike the checkCall case, we're not in a position to do any enforcment and fail unconditionally, unless we can somehow sniff the type of the hostTarget (if it has an eventual handler or not), and bypass the E call if it doesn't. If the hostTarget object does not return a promise, then the hostPromise will be fulfilled in the same crank, so it's safe.

: E(hostTarget)(...hostArgs);
resolver.resolve(hostPromise); // TODO does this always work?
} catch (hostProblem) {
throw Fail`internal: eventual send synchrously failed ${hostProblem}`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since I didn't understand this right originally, let's add a comment: Since a well behaved eventual-send should not synchronously fail (it doesn't synchronously interact with the target), a synchronous throw does not represent a failure we should commit to, but instead is a panic.

Comment on lines +261 to +267
// TODO FIX BUG this is not quite right. When guestResultP is returned
// as the resolution of guestResultP, it create a visious cycle error.
const hostResultKit = makeVowKit();
bijection.init(guestReturnedP, hostResultKit.vow);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clarify how this cycle could actually happen?

Comment on lines +263 to +267
const hostResultKit = makeVowKit();
bijection.init(guestReturnedP, hostResultKit.vow);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if we're replaying we need to use the vow that was previously created for the send, no?

I also don't see how we're rewiring the guestReturnedP to the watched vow on replay.

Comment on lines +250 to +255
} catch (problem) {
throw panic(problem);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't the caller of performSend already panic on exceptions?

Comment on lines 352 to 358
const promise = new HandledPromise((res, rej, _resPres) => {
resolve = res;
reject = rej;
}, guestHandler);
Copy link
Member

@mhofman mhofman May 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, makePromiseKit has some logic to sever references from the resolvers to the promise once resolved, but I think in this case all resolver users are well behaved and drop resolvers after usage, so we're good.

Comment on lines +308 to +313
case 'throw': {
throw outcome.problem;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this can ever happen, can it?

Comment on lines +231 to +235
? E(hostTarget)[optVerb](...hostArgs)
: E(hostTarget)(...hostArgs);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case of host vows, we should be using the V helper instead of E

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After talking with @michaelfig we clarified that V is heap only, and does not queue sends durably. We need to structure the membrane handling of sends to vows such that they are only sent after the vow has settled. We can rely on the fact that the membrane has replayed (no need to durably store the pending sends, instead use the guest promise).

@erights erights force-pushed the markm-asyncFlow-E branch 2 times, most recently from 9fc7e06 to 81c8c76 Compare June 2, 2024 20:21
@erights erights changed the base branch from master to markm-asyncFlow-no-E June 2, 2024 20:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
asyncFlow related to membrane-based replay and upgrade of async functions
Projects
None yet
Development

Successfully merging this pull request may close these issues.

asyncFlow should directly support E via promise handlers
2 participants