Git Product home page Git Product logo

wrpc's Introduction

wRPC

Component-native transport-agnostic RPC protocol and framework based on WebAssembly Interface Types (WIT)

A Bytecode Alliance hosted project

build status Documentation Status

About

wRPC facilitates execution of arbitrary functionality defined in WIT over network or other means of communication.

Main use cases for wRPC are:

  • out-of-tree WebAssembly runtime plugins
  • distributed WebAssembly component communication

Even though wRPC is designed for Wasm components first and foremost, it is fully usable outside of WebAssembly context and can serve as a general-purpose RPC framework.

wRPC uses component model value definiton encoding on the wire.

wRPC supports both dynamic (based on e.g. runtime WebAssembly component type introspection) and static use cases.

For static use cases, wRPC provides WIT binding generators for:

  • Rust
  • Go

wRPC fully supports the unreleased native WIT stream and future data types along with all currently released WIT functionality.

Design

Transport

wRPC transport is the core abstraction on top of which all the other functionality is built.

A transport represents a multiplexed bidirectional communication channel, over which wRPC invocations are transmitted.

wRPC operates under assumption that transport communication channels can be "indexed" by a sequence of unsigned 32-bit integers, which represent a reflective structural path.

Invocation

As part of every wRPC invocation at least 2 independent, directional byte streams will be established by the chosen transport:

  • parameters (client -> server)
  • results (server -> client)

wRPC transport implementations MAY (and are encouraged to) provide two more directional communication channels:

  • client error (client -> server)
  • server error (server -> client)

Error channels are the only channels that are typed, in particular, values sent on these channels are strings.

If async values are being transmitted as parameters or results of an invocation, wRPC MAY send those values on an indexed path asynchronously.

Consider the invocation of WIT function foo from instance wrpc-example:doc/[email protected]:

package wrpc-example:doc@0.1.0;

interface example {
    record rec {
        a: stream<u8>,
        b: u32,
    }

    foo: func(v: rec) -> stream<u8>;
}
  1. Since foo parameter 0 is a record, which contains an async type (stream) as the first field, wRPC will communicate to the transport that apart from the "root" parameter channel, it may need to receive results at index path 0 (first return value).
  2. wRPC will encode the parameters as a single-element tuple in a non-blocking fashion. If full contents of rec.a are not available at the time of encoding, the stream will be encoded as option::none.
  3. (concurrently, if in 2. stream was not fully available) wRPC will transfer the contents of the stream<u8> on parameter byte stream at index 0->0 (first field of the record, which is the first parameter) as they become available.
  4. wRPC will attempt to decode stream<u8> from the "root" result byte stream.
  5. (if 4. decoded an option::none for the stream value) wRPC will attempt to decode stream<u8> from result byte stream at index 0

Note, that the handler of foo (server) MAY:

  • receive rec.b value before rec.a is sent or even available
  • send a result back to the invoker of foo (client) before it has received rec.a

Repository structure

This repository contains (for all supported languages):

  • core libraries and abstractions
  • binding generators
  • WebAssembly runtime integrations
  • wRPC transport implementations

wit-bindgen-wrpc aims to closely match UX of wit-bindgen and therefore includes a subtree merge of the project, which is occasionally merged into this tree.

  • wRPC binding generators among other tests, are tested using the wit-bindgen test suite
  • wit-bindgen documentation is reused where applicable

Contributing

GitHub repo Good Issues for newbies GitHub Help Wanted issues GitHub Help Wanted PRs GitHub repo Issues

👋 Welcome, new contributors!

Whether you're a seasoned developer or just getting started, your contributions are valuable to us. Don't hesitate to jump in, explore the project, and make an impact. To start contributing, please check out our Contribution Guidelines.

wrpc's People

Contributors

alexcrichton avatar rvolosatovs avatar peterhuene avatar sunfishcode avatar dependabot[bot] avatar mossaka avatar dicej avatar guybedford avatar fitzgen avatar jsturtevant avatar pchickey avatar rylev avatar liamolucko avatar github-actions[bot] avatar esoterra avatar jacobvm04 avatar yowl avatar tschneidereit avatar fibonacci1729 avatar kulakowski-wasm avatar radu-matei avatar elliottt avatar danbev avatar saona-raimundo avatar saulecabrera avatar willemneal avatar silesmo avatar lann avatar tsoutsman avatar thomastaylor312 avatar

Stargazers

Gio d'Amelio avatar Masashi Yoshimura avatar Nilson Nascimento avatar Conall Keane avatar Yoshiki Sakamoto avatar  avatar Brooks Townsend avatar Rikito Taniguchi avatar SOZEL avatar  avatar Elias Kauppi avatar Greg Bray avatar Sven avatar Glenn Lewis avatar Christoph Brewing avatar  avatar ウタカタキョウスイ avatar Ryo Hirayama avatar Zhu avatar Cristian Pallarés avatar Saza avatar kanarus avatar kazuya kawaguchi avatar Kotaro Suto avatar Taiga Mikami avatar tsuyoshi wada avatar skanehira avatar bokuweb avatar  avatar tanimon avatar sivchari avatar unvalley avatar Rintaro Itokawa avatar Yuji Sugiura avatar syumai avatar Shina avatar Yoshihiro Saito avatar Seiya Kokushi avatar temma avatar aobat avatar hadashiA avatar Okuto Oyama avatar yuta.suzuki avatar Yusuke Yanaka avatar mori yuta avatar genkey6 avatar V avatar karyo avatar TOMIKAWA Sotaro avatar Masahiro Nishida avatar Liam Woodleigh-Hardinge avatar Santo Cariotti avatar yumo avatar Shreeram avatar Tom Houlé avatar James Edmonds avatar kayh avatar Teo Stocco avatar Daniel Macovei avatar Jeancarlo Barrios avatar Lachlan Heywood avatar Yuchen Cheng avatar Sandalots avatar hydai avatar hzmi avatar 0x5457 avatar Kotaro Inoue avatar Akihiro Suda avatar Henry Gressmann avatar Ark avatar Eric Gregory avatar 趙子賢 avatar Cameron Taggart avatar Gardner Vickers avatar  avatar Benjamin Cane avatar Xyphuz avatar Calvin Prewitt avatar Ryuta Suzuki avatar Liam Randall avatar Nikita avatar Alexandre HEIM avatar acorn avatar Ahmed Tadde avatar Ben Caunt avatar Jeff Schilling avatar Chiiira avatar Dan Norris avatar Jeff Parsons avatar nelm avatar Daniel Bodnar avatar Nikolaus Schlemm avatar Arnold Schrijver avatar Ondřej Kupka avatar ocfox avatar

Watchers

Lucian avatar YAMAMOTO Yuji avatar Yordis Prieto avatar Shreeram avatar  avatar  avatar  avatar

wrpc's Issues

Dynamic Resource Support

Currently static methods and special types of resources are fully supported. It seems like most of the relevant typing and value representations for resources are there, I was wondering what the remaining tasks to get resource support working are?

If it would be helpful, I would be more than happy to contribute to this!

Simplify parameter type signatures

Currently there is some additional level of indirection added to trait parameter type signatures to work around latest Rust compiler reaching deadlock and/or recursion limit with recursive data structures. There should be a way to make this nicer post-MVP

Resource support in `wit-bindgen-wrpc`

Resources should be supported in wit-bindgen-wrpc. See #13 (comment) for some more context.

I think a simple first step of a prototype could look something like this:

  • Each resource is assigned a ULID, which is used in place of the wrpc_transport instance
  • All resources are stored indefinitely (until drop is called expliclitly) in a hashmap keyed by their ULIDs
  • wit-bindgen-wrpc generates functionality to allocate a new resource and fully handles resource lifetime, implementations just need to provide handler implementations, just like in upstream wit-bindgen

cc @jacobvm04

CI/CD

We should run tests, rustfmt, clippy etc on each PR and require that for merge

Refs #4

[go] Provide resource construction functionality

Currently, resources can only be constructed using a constructor in Go bindgen. This effectively makes implementing resources in interfaces not using a constructor (like wasi:keyvalue https://github.com/WebAssembly/wasi-keyvalue/blob/219ea3612a53f1bf5b2d137551b22d0268fd3c58/wit/store.wit#L54) impossible, since the implementations should be able to "conjure a resource out of thin air" to support these kind of use cases.

It seems that the only way to handle this would be exposing the resource construction and method serving functionality in the generated bindings (or perhaps in wrpc module itself?) not tied to the constructor

[go] Binding rename support

Go bindgen should support renaming of generated types, since we went for "idiomaticity" over clash-resistence in the design

[rs] Set async paths in `runtime-wasmtime`

We now have functionality to walk the type in wrpc_introspect and compute the async values, we should do the same for wasmtime APIs directly. input-stream and such should be treated as async

[rs] `pack` and `unpack` functionality

wRPC values will always require a multiplexed connection and appropriate APIs for transmission and receival (due to values potentially containing nested async values, like streams or futures) - there may be use cases, however, where it may be desired to directly encode/decode wRPC values to/from single byte buffer - for example, for persistent storage. For those use cases, I propose to introduce pack/unpack functionality, which works the following way:

  • All values can be packed - producing a single byte buffer with fully resolved/ready async values, if any
  • packed values can always be unpacked
  • Only fully resolved values can be packed (meaning that all incoming streams are fully received and finished and all futures are "ready" and their result is known). Packing operation may never complete if value being packed never resolves - therefore, packing operation MUST provide an API, which takes in a user-provided timeout.
  • Just like in "core wRPC":
    • resolved futures future<T> are encoded as option::some variant of option<T>
    • resolved streams stream<T> are encoded as chunk_count:<core:uN> (chunk:<vec<T>>)^chunk_length with or without the "end" 0-length chunk. The special case of a "ready" empty stream is encoded as a stream containing a single, empty chunk
    • Encoding format is otherwise defined in WebAssembly/component-model#336 Binary.md

cc @protochron

`wrpc-wasmtime-nats` binary

We should a wrpc-binary-nats binary, which takes a URL to a Wasm binary and runs it (for commands)/ serves it (for reactors) using NATS as the transport layer and Wasmtime as the runtime

Refs #4

[go] `wrpcnats` async parameter write indexing

Currently Index method is not implemented in wrpcnats.paramWriter, because as part of the root subject write it may lazily perform the handshake.
The implementation should be updated in such a way that the indexed parameter writers would not be able to write until the root writer performs the handshake. The transmission subject would then need to be communicated to the indexed writers.

Perhaps a possible way to handle this would be to use a RWMutex, which is locked on the root writer creation and unlocked once the handshake is complete. Indexed parameter writers Writes would then start with RLocking that shared mutex and block until the handshake is complete. We could also change init field to atomic.Bool here perhaps, which would let the indexed writers first check the atomic and only then attempt to RLock is they loaded false

https://github.com/wrpc/wrpc/blob/a75b04391524217058fb4c5284aee224ee8e4464/go/nats/client.go#L193

ci: implement fuzzing

This project could benefit from fuzzing, we should investigate that an enable it in CI

`wrpc-transport-ipc`

We should add an IPC "transport", probably using Unix sockets on Unix and named pipes on Windows

wRPC transport "upgrade"

Currently NATS transport uses "handshake" procedure to establish a two-way, point-to-point communication channel, it achieves this by setting up two NATS inboxes, one per peer. That is done, because there's no way to know if the peer is reachable by any other means than via the NATS broker. This works great for a single message exchange. However, in async scenarios or when payloads are large and do not fit in a single message, it would be great to have a way to negotiate a more efficient communication channel after the initial exchange. This basically means that we could conveniently do service discovery over NATS and then switch to a more efficient channel for actual data transfer.

Keeping the "0-rtt"-esque semantics of existing NATS transport, the way that a NATS -> QUIC upgrade could work is:

  1. Client sends the (potentially truncated) parameter payload to $prefix.foo.bar. If the client is reachable on a particular UDP endpoint, it could send that endpoint as the "reply" header. (the "reply" header should probably be an ordered list containing the NATS inbox as the "fallback")
  2. If the client's UDP endpoint is reachable by the server, it could reply directly on that endpoint. If not - it would just fall back to NATS. Similarly to the client, the server could now communicate the ordered list of endpoints that it would listen on.
  3. Client continues data transfer over the more efficient transport, if the endpoint is reachable and otherwise falls back to NATS. In case of QUIC, it could also simply use the connection established by the server if the client communicated a UDP endpoint to the server

This way we could eliminate the middleman (NATS) allowing for efficient data transfer without sacrificing the ease of service discovery that NATS gives us.

This mechanism does not seem to be specific to NATS, but seems to be beneficial for any transport, which relies on some kind of broker service in the middle

[rs] generate async I/O future in export handlers

Currently, async I/O future is generated for client bindings, when either parameter or result tuples contain at least one async type, we should do something similar for exports - probably generating impl Future<Output = anyhow::Result> as the last element in the tuple and failing the transmission if an error is returned (instead of a clean byte stream shutdown)

[rs] integrate `wrpc-transport-next`

The transport abstraction has been reworked in #81, we should:

  • migrate wit-bindgen-wrpc to the new transport API
  • replace wrpc-transport by wrpc-transport-next (the end result is that wrpc-transport-next is renamed to wrpc-transport)
  • deprecate wrpc-types crate

[go] Take parameter payload as argument in `Invoker.invoke`

Currently, invoke returns an io.Writer, and it is assumed that in case parameters are empty, the user must write an empty buffer (e.g. nil) to trigger the exchange. Instead of this hacky procedure, the invoke method should take in a []byte, which will be flushed fully before any of the bytes written on the returned io.Writer will be transported.
invoke then can eagerly send the payload before it returns

Resource design

Refs #93 #82

After giving it some thought, trying different things and gathering feedback, I think I finally figured out a reasonable resource design for wRPC.
First, a few key points:

  • It is generally expected that wRPC transports are namespaced (and possibly multi-tenant, where relationship of tenants to namespaces is 1:N)
  • Resources cannot escape the namespace and can only be used within the namespace they originated from.
  • Resource handles uniquely identify the origin of the resource and the resource itself in that origin, both of these are application-specific (and may include in itself e.g. the transport namespace)

With this, I think we can untangle the complicated OOP-centric resource design and accept that methods are really just functions, which take borrowed resource handles as first parameters, and they are handled exactly the same as all other functions. (except, perhaps, that for each exported resource, a wRPC peer must eagerly serve drop function, which is not explicitly defined in WIT)

In distributed, multi-namespace use cases, the application should be able to derive the namespace that resource originated from using the resource handle. Defining exactly how this would work is out-of-scope for wRPC, however wRPC may provide utility libraries for building these features e.g. distributed resource tables.

I think that "resources" on the protocol level should just be opaque byte blobs, list<u8>, which are entirely application specific

For example, let's consider Wasmtime binary using wRPC for providing host plugins.
Whenever a polyfilled (how about substituted?) resource is received or sent on the wire, it could be encoded as a following WIT record:

record handle {
   pid: u32,
   id: u32
}
  • pid uniquely identifies the Wasmtime process
  • id uniquely identifies the resource in the WASI context table, i.e. it's just the u32 index, since a single Wasmtime process can only run a single Wasm component

In scenarios where there may be multiple Wasmtime processes simultaneously exporting a particular resource, the pid could be used to figure out which exact Wasmtime process keeps the resource in memory and using some out-of-band, Wasmtime-specific mechanism acquire the resource state.

To enable this, in Rust, wrpc_transport crate would define a Resource struct roughly as:

struct Resource<T> { data: Vec<u8>, _ty: PhantomData<T> }
impl<T> AsRef<[u8]> for Resource<T> { .. }
impl<T> From<Vec<u8>> for Resource<T> { .. }
impl<T> From<Resource<T>> for Vec<u8> { .. }
impl<T> Resource<T> { pub fn new(data: Vec<u8>) -> Self { .. } }

I'm not sure what's best to do here about the drops, probably for now implementers will just have to manually call drop on owned handles, especially since it's potentially a fallible operation. We may want to opt for Borrow<T> and Own<T> records instead of a singular Resource<T>, similar to what's already done for Go https://github.com/wrpc/wrpc/blob/01a536b18c8522469a33cb72a2d5f702585a27ee/go/wrpc.go#L18-L43

For example, for https://github.com/WebAssembly/wasi-keyvalue/ generated Ruststore.open function signature could look like:

async fn(identifier: &str) -> Result<wrpc_transport::Own<store::Bucket>, store::Error>

@jacobvm04 what do you think?

[rs] rework `wrpc-transport` crate

wrpc-transport crate is a bit of a mess with leaky abstractions - it served well to reach an MVP with NATS transport, but it's time to rework it to better suit this projects needs.
Largely this should be based on wrpc Go package design
The biggest requirements here are:

  • handshake and accept logic should be moved into the individual transport implementations, wrpc-transport should just operate on plain AsyncRead and AsyncWrite handles with indexing
  • AsyncValue and AsyncTransmission should be removed (#1)
  • #2

[go] Create vanity for go.wrpc

Since we believe wRPC will be a foundational mutli-stakeholder project, there's a good chance it may be moved to another organization. We can keep from modifying our code if a transition occurs by providing a vanity url.

Create a vanity URL with github pages to go.wrpc.io.

The go work is currently in a subdir at github.com/wrpc/wrpc/go. If we use github pages, we need to test if we can host an index.html using modgen so that go.wrpc.io -> wrpc.github.io/wrpc/go

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.