Skip to content
This repository has been archived by the owner on Oct 13, 2023. It is now read-only.

Commit

Permalink
feat(wasi): sync wit definition with wasmtime v13 (#177)
Browse files Browse the repository at this point in the history
This will update the WIT definitions related to WASI to match wasmtime v13.


* feat(wasi): sync wit definition with wasmtime v13

* chore: update definition

* feat(wasi): update http client code

* chore: use wasmtime v13 commit hash

* chore: bump wasi-preview2-prototype to v0.0.3
  • Loading branch information
eduardomourar authored Sep 20, 2023
1 parent 1af2a12 commit ecfc05a
Show file tree
Hide file tree
Showing 12 changed files with 296 additions and 180 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion wasi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
# name = "wasi"
name = "wasi-preview2-prototype"
version = "0.0.2"
version = "0.0.3"
# version = "0.12.0+wasi-snapshot-preview2"
description = "Experimental WASI Preview2 API bindings for Rust"
edition.workspace = true
Expand Down
74 changes: 54 additions & 20 deletions wasi/src/http_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ use crate::snapshots::preview_2::wasi::http::{outgoing_handler, types as http_ty
use crate::snapshots::preview_2::wasi::io::streams;
use crate::snapshots::preview_2::wasi::poll::poll;

struct DropPollable {
pollable: poll::Pollable,
}

impl Drop for DropPollable {
fn drop(&mut self) {
poll::drop_pollable(self.pollable);
}
}

pub struct DefaultClient {
options: Option<outgoing_handler::RequestOptions>,
}
Expand Down Expand Up @@ -68,16 +78,36 @@ impl TryFrom<http::Request<Bytes>> for Request {
.map_err(|_| anyhow!("outgoing request write failed"))?;

if body.is_empty() {
streams::write(request_body, &[]).map_err(|_| anyhow!("writing request body"))?;
} else {
let output_stream_pollable = streams::subscribe_to_output_stream(request_body);
let mut body_cursor = 0;
while body_cursor < body.len() {
let (written, _) = streams::write(request_body, &body[body_cursor..])
.map_err(|_| anyhow!("writing request body"))?;
body_cursor += written as usize;
let sub = DropPollable {
pollable: streams::subscribe_to_output_stream(request_body),
};
let mut buf = body.as_ref();
while !buf.is_empty() {
poll::poll_oneoff(&[sub.pollable]);

let permit = match streams::check_write(request_body) {
Ok(n) => usize::try_from(n)?,
Err(_) => anyhow::bail!("output stream error"),
};

let len = buf.len().min(permit);
let (chunk, rest) = buf.split_at(len);
buf = rest;

if streams::write(request_body, chunk).is_err() {
anyhow::bail!("output stream error")
}
}

if streams::flush(request_body).is_err() {
anyhow::bail!("output stream error")
}

poll::poll_oneoff(&[sub.pollable]);

if streams::check_write(request_body).is_err() {
anyhow::bail!("output stream error")
}
poll::drop_pollable(output_stream_pollable);
}

Ok(Request::new(request, request_body))
Expand Down Expand Up @@ -146,21 +176,25 @@ impl TryFrom<Response> for http::Response<Bytes> {

let body_stream = http_types::incoming_response_consume(incoming_response)
.map_err(|_| anyhow!("consuming incoming response"))?;
let input_stream_pollable = streams::subscribe_to_input_stream(body_stream);

let mut body = BytesMut::new();
let mut eof = streams::StreamStatus::Open;
while eof != streams::StreamStatus::Ended {
let (body_chunk, stream_status) = streams::read(body_stream, u64::MAX)
.map_err(|e| anyhow!("reading response body: {e:?}"))?;
eof = if body_chunk.is_empty() {
streams::StreamStatus::Ended
} else {
stream_status
{
let sub = DropPollable {
pollable: streams::subscribe_to_input_stream(body_stream),
};
body.put(body_chunk.as_slice());
poll::poll_oneoff(&[sub.pollable]);
let mut eof = streams::StreamStatus::Open;
while eof != streams::StreamStatus::Ended {
let (body_chunk, stream_status) = streams::read(body_stream, u64::MAX)
.map_err(|e| anyhow!("reading response body: {e:?}"))?;
eof = if body_chunk.is_empty() {
streams::StreamStatus::Ended
} else {
stream_status
};
body.put(body_chunk.as_slice());
}
}
poll::drop_pollable(input_stream_pollable);

let mut res = http::Response::builder()
.status(status)
Expand Down
2 changes: 1 addition & 1 deletion wasi/wit/deps.toml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
preview = "https://gitpkg.now.sh/bytecodealliance/wasmtime/crates/wasi?e250334b8ebfba9359802ab7f61bdd7c6085d87a"
preview = "https://gitpkg.now.sh/bytecodealliance/wasmtime/crates/wasi?aec4b25b8f62f409175a3cc6c4a4ed18b446d3ae"
143 changes: 95 additions & 48 deletions wasi/wit/deps/io/streams.wit
Original file line number Diff line number Diff line change
Expand Up @@ -134,58 +134,115 @@ interface streams {
/// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources).
type output-stream = u32

/// Perform a non-blocking write of bytes to a stream.
/// An error for output-stream operations.
///
/// This function returns a `u64` and a `stream-status`. The `u64` indicates
/// the number of bytes from `buf` that were written, which may be less than
/// the length of `buf`. The `stream-status` indicates if further writes to
/// the stream are expected to be read.
/// Contrary to input-streams, a closed output-stream is reported using
/// an error.
enum write-error {
/// The last operation (a write or flush) failed before completion.
last-operation-failed,
/// The stream is closed: no more input will be accepted by the
/// stream. A closed output-stream will return this error on all
/// future operations.
closed
}
/// Check readiness for writing. This function never blocks.
///
/// Returns the number of bytes permitted for the next call to `write`,
/// or an error. Calling `write` with more bytes than this function has
/// permitted will trap.
///
/// When this function returns 0 bytes, the `subscribe-to-output-stream`
/// pollable will become ready when this function will report at least
/// 1 byte, or an error.
check-write: func(
this: output-stream
) -> result<u64, write-error>

/// Perform a write. This function never blocks.
///
/// When the returned `stream-status` is `open`, the `u64` return value may
/// be less than the length of `buf`. This indicates that no more bytes may
/// be written to the stream promptly. In that case the
/// `subscribe-to-output-stream` pollable will indicate when additional bytes
/// may be promptly written.
/// Precondition: check-write gave permit of Ok(n) and contents has a
/// length of less than or equal to n. Otherwise, this function will trap.
///
/// Writing an empty list must return a non-error result with `0` for the
/// `u64` return value, and the current `stream-status`.
/// returns Err(closed) without writing if the stream has closed since
/// the last call to check-write provided a permit.
write: func(
this: output-stream,
/// Data to write
buf: list<u8>
) -> result<tuple<u64, stream-status>>
contents: list<u8>
) -> result<_, write-error>

/// Blocking write of bytes to a stream.
/// Perform a write of up to 4096 bytes, and then flush the stream. Block
/// until all of these operations are complete, or an error occurs.
///
/// This is similar to `write`, except that it blocks until at least one
/// byte can be written.
blocking-write: func(
this: output-stream,
/// Data to write
buf: list<u8>
) -> result<tuple<u64, stream-status>>
/// This is a convenience wrapper around the use of `check-write`,
/// `subscribe-to-output-stream`, `write`, and `flush`, and is implemented
/// with the following pseudo-code:
///
/// ```text
/// let pollable = subscribe-to-output-stream(this);
/// while !contents.is_empty() {
/// // Wait for the stream to become writable
/// poll-oneoff(pollable);
/// let Ok(n) = check-write(this); // eliding error handling
/// let len = min(n, contents.len());
/// let (chunk, rest) = contents.split_at(len);
/// write(this, chunk); // eliding error handling
/// contents = rest;
/// }
/// flush(this);
/// // Wait for completion of `flush`
/// poll-oneoff(pollable);
/// // Check for any errors that arose during `flush`
/// let _ = check-write(this); // eliding error handling
/// ```
blocking-write-and-flush: func(
this: output-stream,
contents: list<u8>
) -> result<_, write-error>

/// Write multiple zero-bytes to a stream.
/// Request to flush buffered output. This function never blocks.
///
/// This function returns a `u64` indicating the number of zero-bytes
/// that were written; it may be less than `len`. Equivelant to a call to
/// `write` with a list of zeroes of the given length.
write-zeroes: func(
/// This tells the output-stream that the caller intends any buffered
/// output to be flushed. the output which is expected to be flushed
/// is all that has been passed to `write` prior to this call.
///
/// Upon calling this function, the `output-stream` will not accept any
/// writes (`check-write` will return `ok(0)`) until the flush has
/// completed. The `subscribe-to-output-stream` pollable will become ready
/// when the flush has completed and the stream can accept more writes.
flush: func(
this: output-stream,
/// The number of zero-bytes to write
len: u64
) -> result<tuple<u64, stream-status>>
) -> result<_, write-error>

/// Request to flush buffered output, and block until flush completes
/// and stream is ready for writing again.
blocking-flush: func(
this: output-stream,
) -> result<_, write-error>

/// Create a `pollable` which will resolve once the output-stream
/// is ready for more writing, or an error has occured. When this
/// pollable is ready, `check-write` will return `ok(n)` with n>0, or an
/// error.
///
/// If the stream is closed, this pollable is always ready immediately.
///
/// The created `pollable` is a child resource of the `output-stream`.
/// Implementations may trap if the `output-stream` is dropped before
/// all derived `pollable`s created with this function are dropped.
subscribe-to-output-stream: func(this: output-stream) -> pollable

/// Write multiple zero bytes to a stream, with blocking.
/// Write zeroes to a stream.
///
/// This is similar to `write-zeroes`, except that it blocks until at least
/// one byte can be written. Equivelant to a call to `blocking-write` with
/// a list of zeroes of the given length.
blocking-write-zeroes: func(
/// this should be used precisely like `write` with the exact same
/// preconditions (must use check-write first), but instead of
/// passing a list of bytes, you simply pass the number of zero-bytes
/// that should be written.
write-zeroes: func(
this: output-stream,
/// The number of zero bytes to write
/// The number of zero-bytes to write
len: u64
) -> result<tuple<u64, stream-status>>
) -> result<_, write-error>

/// Read from one stream and write to another.
///
Expand Down Expand Up @@ -232,16 +289,6 @@ interface streams {
src: input-stream
) -> result<tuple<u64, stream-status>>

/// Create a `pollable` which will resolve once either the specified stream
/// is ready to accept bytes or the `stream-state` has become closed.
///
/// Once the stream-state is closed, this pollable is always ready
/// immediately.
///
/// The created `pollable` is a child resource of the `output-stream`.
/// Implementations may trap if the `output-stream` is dropped before
/// all derived `pollable`s created with this function are dropped.
subscribe-to-output-stream: func(this: output-stream) -> pollable

/// Dispose of the specified `output-stream`, after which it may no longer
/// be used.
Expand Down
13 changes: 13 additions & 0 deletions wasi/wit/deps/preview/test.wit
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,16 @@ world test-command {
import wasi:cli/stdout
import wasi:cli/stderr
}

world test-command-with-sockets {
import wasi:poll/poll
import wasi:io/streams
import wasi:cli/environment
import wasi:cli/stdin
import wasi:cli/stdout
import wasi:cli/stderr
import wasi:sockets/tcp
import wasi:sockets/tcp-create-socket
import wasi:sockets/network
import wasi:sockets/instance-network
}
22 changes: 11 additions & 11 deletions wasi/wit/deps/sockets/ip-name-lookup.wit
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ interface ip-name-lookup {


/// Resolve an internet host name to a list of IP addresses.
///
///
/// See the wasi-socket proposal README.md for a comparison with getaddrinfo.
///
///
/// # Parameters
/// - `name`: The name to look up. IP addresses are not allowed. Unicode domain names are automatically converted
/// to ASCII using IDNA encoding.
Expand All @@ -17,18 +17,18 @@ interface ip-name-lookup {
/// systems without an active IPv6 interface. Notes:
/// - Even when no public IPv6 interfaces are present or active, names like "localhost" can still resolve to an IPv6 address.
/// - Whatever is "available" or "unavailable" is volatile and can change everytime a network cable is unplugged.
///
///
/// This function never blocks. It either immediately fails or immediately returns successfully with a `resolve-address-stream`
/// that can be used to (asynchronously) fetch the results.
///
///
/// At the moment, the stream never completes successfully with 0 items. Ie. the first call
/// to `resolve-next-address` never returns `ok(none)`. This may change in the future.
///
///
/// # Typical errors
/// - `invalid-name`: `name` is a syntactically invalid domain name.
/// - `invalid-name`: `name` is an IP address.
/// - `address-family-not-supported`: The specified `address-family` is not supported. (EAI_FAMILY)
///
///
/// # References:
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getaddrinfo.html>
/// - <https://man7.org/linux/man-pages/man3/getaddrinfo.3.html>
Expand All @@ -41,14 +41,14 @@ interface ip-name-lookup {
type resolve-address-stream = u32

/// Returns the next address from the resolver.
///
///
/// This function should be called multiple times. On each call, it will
/// return the next address in connection order preference. If all
/// addresses have been exhausted, this function returns `none`.
/// After which, you should release the stream with `drop-resolve-address-stream`.
///
///
/// This function never returns IPv4-mapped IPv6 addresses.
///
///
/// # Typical errors
/// - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY)
/// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN)
Expand All @@ -57,12 +57,12 @@ interface ip-name-lookup {
resolve-next-address: func(this: resolve-address-stream) -> result<option<ip-address>, error-code>

/// Dispose of the specified `resolve-address-stream`, after which it may no longer be used.
///
///
/// Note: this function is scheduled to be removed when Resources are natively supported in Wit.
drop-resolve-address-stream: func(this: resolve-address-stream)

/// Create a `pollable` which will resolve once the stream is ready for I/O.
///
///
/// Note: this function is here for WASI Preview2 only.
/// It's planned to be removed when `future` is natively supported in Preview3.
subscribe: func(this: resolve-address-stream) -> pollable
Expand Down
Loading

0 comments on commit ecfc05a

Please sign in to comment.