Skip to content

Separately owned handles for read/write ends of futures/streams #475

Open
@alexcrichton

Description

@alexcrichton

I talked about this with some folks today but wanted to file an issue with our discussion and follow-ups. Today we realized that there is a previously-unknown issue with the semantics of "just a single handle" for both the read/write end of a future/stream within a single component, namely that if a polyglot scenario is considered where multiple languages are linked into the same component with shared-everything-linking then there's not necessarily a guarantee that the reader and writer are using the same canonical ABI. For example one might be using utf8 and one might be using utf16. With only having a single handle there's no easy way to arbitrate this across languages and solve this.

Personally I also have had concerns about having just a single handle for reading/writing as I feel like the resulting bindings/state machines that guests need to handle are quite complicated. One of the basic protections this is providing to the host, however, is that there's no need for a host to lift and lower to the same linear memory. That would make the side-effectful-order of lifts/lowers to become significant which is something we want to avoid specifying.

The basic idea for this issue is along the lines of:

  • Creating a future/stream returns two handles: the reader end and the write end. For now the write end can't leave components but the reader end can.
  • When a read/write are paired within the same component instance "somehow" the runtime returns this via a status code but doesn't actually do any lifting or lowering.

The hope is that the guest can easily handle this situation (somehow) and the host also doesn't have to do any lifts/lowers itself. In talking through this design space however we were unable to reach any firm conclusions about how exactly to slice/dice this. For example:

  1. One possibility is to for the host to wait for the read/write to be paired. When this happens (regardless of which came in second) the read side completes first with a status code indicating "a local read completed". The {stream,future}.read intrinsic would probably take a third argument of "local result area" where the pointer passed to {stream,future}.write is written passed in. This measn the stream/future is now in a state where no future reads can complete until this read is "acknowledged". This acknowledgement would happen via a new component builtin. The reader would itself perform the lift within linear memory so it's purely the guest's responsibility here. Upon finishing the lift the reader would acknowledge the local read (probably from this "local return area") and then that would queue up a completion notification for the writer. The downsides here are:
    • This just specifies that the reader completes first. That means if the writer came second it's blocked to go to the reader and the reader acknowledges. Shouldn't the writer also be able to do the same thing?
    • More intrinsics/component builtins.
    • This "auxiliary return area" is somewhat nontrivial
    • This needs to generate a trap if the read/write intrinsics have differing canonical ABI options. Removing this trap is probably going to require "lazy lifting/lowering" in the future and won't be trivial. In the meantime it's not easy to get around the trap.
  2. Another possibility is to trap the reads/writes and unconditionally (or fail them somehow) and require cooperation on behalf of the guest. It's a nice property today that if a T is sent along a future it requires no copies/lifts/lowers in the guest since it's known to be local, and ideally that could be preserved. This idea isn't fully fleshed out though.

Overall we reached the conclusion that to solve the original issue of polyglot components with heterogenous sets of options on lifts/lowers that we probably want a separate handle for each half of a future stream. How eactly to design this though is still uncertain.

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