Skip to content

Change client resolution semantics to match what's needed for 3PH #494

@zenhack

Description

@zenhack

Background from rpc.capnp:

# *The Tribble 4-way Race Condition*
#
# Any implementation of promise resolution and embargos must be aware of what we call the
# "Tribble 4-way race condition", after Dean Tribble, who explained the problem in a lively
# Friam meeting.
#
# Embargos are designed to work in the case where a two-hop path is being shortened to one hop.
# But sometimes there are more hops. Imagine that Alice has a reference to a remote promise P1
# that eventually resolves to _another_ remote promise P2 (in a third vat), which _at the same
# time_ happens to resolve to Bob (in a fourth vat). In this case, we're shortening from a 3-hop
# path (with four parties) to a 1-hop path (Alice -> Bob).
#
# Extending the embargo/disembargo protocol to be able to shorted multiple hops at once seems
# difficult. Instead, we make a rule that prevents this case from coming up:
#
# One a promise P has been resolved to a remote object reference R, then all further messages
# received addressed to P will be forwarded strictly to R. Even if it turns out later that R is
# itself a promise, and has resolved to some other object Q, messages sent to P will still be
# forwarded to R, not directly to Q (R will of course further forward the messages to Q).
#
# This rule does not cause a significant performance burden because once P has resolved to R, it
# is expected that people sending messages to P will shortly start sending them to R instead and
# drop P. P is at end-of-life anyway, so it doesn't matter if it ignores chances to further
# optimize its path.

Right now, when a capability is resolved, we overwrite the internal clientHook. This has a couple implications:

  1. The fact that the client was a promise at some point in the past is forgotten.
  2. We eventually end up shortening any potentially multi-hop paths that arise.

(2) sounds like a good thing initially, but per the docs above this will likely be problematic in a level 3 implementation. Also, (1) is probably not ok either, since if we are to avoid multi-hop shortening, sendCap() needs to continue to encode the capability as the original promise.

My current thinking is that we need to refactor the internals of Client so that on resolution, the initial clientHook is not dropped, and after resolution we always invoke the first resolution, rather than doing what resolveHook() does and walk as deep into the chain as it can. I think this will work for basic correctness, but I see two downsides:

  • I can envision a scenario where:

    1. Alice is a promise in vat A resolves to bob in vat B, which is in turn a promise which resolves to carol in vat C. If the app is calling methods on alice, it seems like calls would needlessly continue to route through bob. I suppose we could change the way Client.Resolve works so that it will actually give you the final client, but it's a shame this can't be transparent to the app. But maybe this is just a limitation of the protocol that we have to live with.
    2. If the caller doesn't explicitly drop the promise, we could build up chains of promises routing calls around the network uselessly. If it were done automatically by library this would not be a concern, but I don't love requiring the app to deal with it.

I want to think a bit more to see if there's a way we can keep this transparent; I have some things ideas investigate that may not pan out.

@lthibault interested in your thoughts.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions