Repository: bytecodealliance/wasmtime
Pull request #7416 (2023-10-30 21:33:50)

Add support for `::` in `--dir` to old CLI
In Wasmtime 13 and prior the `--dir` argument was unconditionally used to open a host dir as the same name and in the guest. In Wasmtime 14+ though this argument is being repurposed with an optional trailing `::GUEST` to configure the guest directory. This means that `--dir`-with-remapping behavior is actually unusable without the environment variable configuration from #7385 due to it parsing differently in the old and the new.

This commit updates the situation by adding `::`-parsing to the old CLI meaning that both the old and the new parse this the same way. This will break any scripts that open host directories with two colons in their path, but that seems niche enough we can handle that later.

<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->



Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 983eb0681..65b555e89 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2570,9 +2570,9 @@ dependencies = [
 
 [[package]]
 name = "target-lexicon"
-version = "0.12.3"
+version = "0.12.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1"
+checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a"
 
 [[package]]
 name = "tempfile"
diff --git a/Cargo.toml b/Cargo.toml
index ee34a6039..b9f539873 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -195,7 +195,7 @@ byte-array-literals = { path = "crates/wasi-preview1-component-adapter/byte-arra
 regalloc2 = "0.9.3"
 
 # cap-std family:
-target-lexicon = { version = "0.12.3", default-features = false, features = ["std"] }
+target-lexicon = { version = "0.12.12", default-features = false, features = ["std"] }
 cap-std = "2.0.0"
 cap-rand = { version = "2.0.0", features = ["small_rng"] }
 cap-fs-ext = "2.0.0"
@@ -206,7 +206,7 @@ fs-set-times = "0.20.0"
 system-interface = { version = "0.26.0", features = ["cap_std_impls"] }
 io-lifetimes = { version = "2.0.2", default-features = false }
 io-extras = "0.18.0"
-rustix = "0.38.8"
+rustix = "0.38.21"
 is-terminal = "0.4.0"
 # wit-bindgen:
 wit-bindgen = { version = "0.13.1", default-features = false }
diff --git a/cranelift/codegen/src/isa/aarch64/inst/unwind/systemv.rs b/cranelift/codegen/src/isa/aarch64/inst/unwind/systemv.rs
index a40e49e3d..efd1b0a2f 100644
--- a/cranelift/codegen/src/isa/aarch64/inst/unwind/systemv.rs
+++ b/cranelift/codegen/src/isa/aarch64/inst/unwind/systemv.rs
@@ -77,7 +77,6 @@ mod tests {
     use crate::settings::{builder, Flags};
     use crate::Context;
     use gimli::write::Address;
-    use std::str::FromStr;
     use target_lexicon::triple;
 
     #[test]
diff --git a/cranelift/codegen/src/isa/mod.rs b/cranelift/codegen/src/isa/mod.rs
index 4508c68d7..8e68e7aab 100644
--- a/cranelift/codegen/src/isa/mod.rs
+++ b/cranelift/codegen/src/isa/mod.rs
@@ -115,7 +115,6 @@ pub const ALL_ARCHITECTURES: &[&str] = &["x86_64", "aarch64", "s390x", "riscv64"
 /// Look for a supported ISA with the given `name`.
 /// Return a builder that can create a corresponding `TargetIsa`.
 pub fn lookup_by_name(name: &str) -> Result<Builder, LookupError> {
-    use alloc::str::FromStr;
     lookup(triple!(name))
 }
 
diff --git a/cranelift/codegen/src/isa/riscv64/inst/unwind/systemv.rs b/cranelift/codegen/src/isa/riscv64/inst/unwind/systemv.rs
index 75bd6cebf..4bb40f7db 100644
--- a/cranelift/codegen/src/isa/riscv64/inst/unwind/systemv.rs
+++ b/cranelift/codegen/src/isa/riscv64/inst/unwind/systemv.rs
@@ -72,7 +72,6 @@ mod tests {
     use crate::settings::{builder, Flags};
     use crate::Context;
     use gimli::write::Address;
-    use std::str::FromStr;
     use target_lexicon::triple;
 
     #[test]
diff --git a/cranelift/codegen/src/isa/s390x/inst/unwind/systemv.rs b/cranelift/codegen/src/isa/s390x/inst/unwind/systemv.rs
index 5f0a657c7..441e4134b 100644
--- a/cranelift/codegen/src/isa/s390x/inst/unwind/systemv.rs
+++ b/cranelift/codegen/src/isa/s390x/inst/unwind/systemv.rs
@@ -108,7 +108,6 @@ mod tests {
     use crate::settings::{builder, Flags};
     use crate::Context;
     use gimli::write::Address;
-    use std::str::FromStr;
     use target_lexicon::triple;
 
     #[test]
diff --git a/cranelift/codegen/src/isa/x64/inst/unwind/systemv.rs b/cranelift/codegen/src/isa/x64/inst/unwind/systemv.rs
index 68a1ecf2e..f31277cb3 100644
--- a/cranelift/codegen/src/isa/x64/inst/unwind/systemv.rs
+++ b/cranelift/codegen/src/isa/x64/inst/unwind/systemv.rs
@@ -104,7 +104,6 @@ mod tests {
     use crate::settings::{builder, Flags};
     use crate::Context;
     use gimli::write::Address;
-    use std::str::FromStr;
     use target_lexicon::triple;
 
     #[test]
diff --git a/crates/c-api/include/wasmtime/async.h b/crates/c-api/include/wasmtime/async.h
index 1b490003a..1ff8ccef7 100644
--- a/crates/c-api/include/wasmtime/async.h
+++ b/crates/c-api/include/wasmtime/async.h
@@ -86,7 +86,7 @@ WASMTIME_CONFIG_PROP(void, async_stack_size, uint64_t)
  * \param interval the amount of fuel at which to yield. A value of 0 will
  *        disable yielding.
  */
-WASM_API_EXTERN void
+WASM_API_EXTERN wasmtime_error_t*
 wasmtime_context_fuel_async_yield_interval(wasmtime_context_t *context,
                                            uint64_t interval);
 
diff --git a/crates/wasi-http/wit/deps/clocks/monotonic-clock.wit b/crates/wasi-http/wit/deps/clocks/monotonic-clock.wit
index a3f7fe25c..df3758b8a 100644
--- a/crates/wasi-http/wit/deps/clocks/monotonic-clock.wit
+++ b/crates/wasi-http/wit/deps/clocks/monotonic-clock.wit
@@ -11,22 +11,34 @@
 interface monotonic-clock {
     use wasi:io/poll@0.2.0-rc-2023-11-05.{pollable};
 
-    /// A timestamp in nanoseconds.
+    /// An instant in time, in nanoseconds. An instant is relative to an
+    /// unspecified initial value, and can only be compared to instances from
+    /// the same monotonic-clock.
     type instant = u64;
 
+    /// A duration of time, in nanoseconds.
+    type duration = u64;
+
     /// Read the current value of the clock.
     ///
     /// The clock is monotonic, therefore calling this function repeatedly will
     /// produce a sequence of non-decreasing values.
     now: func() -> instant;
 
-    /// Query the resolution of the clock.
-    resolution: func() -> instant;
+    /// Query the resolution of the clock. Returns the duration of time
+    /// corresponding to a clock tick.
+    resolution: func() -> duration;
 
-    /// Create a `pollable` which will resolve once the specified time has been
-    /// reached.
-    subscribe: func(
+    /// Create a `pollable` which will resolve once the specified instant
+    /// occured.
+    subscribe-instant: func(
         when: instant,
-        absolute: bool
+    ) -> pollable;
+
+    /// Create a `pollable` which will resolve once the given duration has
+    /// elapsed, starting at the time at which this function was called.
+    /// occured.
+    subscribe-duration: func(
+        when: duration,
     ) -> pollable;
 }
diff --git a/crates/wasi-preview1-component-adapter/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs
index 08978baa5..267a3ac65 100644
--- a/crates/wasi-preview1-component-adapter/src/lib.rs
+++ b/crates/wasi-preview1-component-adapter/src/lib.rs
@@ -1748,12 +1748,15 @@ pub unsafe extern "C" fn poll_oneoff(
                                 clock.timeout
                             };
 
-                            monotonic_clock::subscribe(timeout, false)
+                            monotonic_clock::subscribe_duration(timeout)
                         }
 
                         CLOCKID_MONOTONIC => {
-                            let s = monotonic_clock::subscribe(clock.timeout, absolute);
-                            s
+                            if absolute {
+                                monotonic_clock::subscribe_instant(clock.timeout)
+                            } else {
+                                monotonic_clock::subscribe_duration(clock.timeout)
+                            }
                         }
 
                         _ => return Err(ERRNO_INVAL),
diff --git a/crates/wasi/src/preview2/host/clocks.rs b/crates/wasi/src/preview2/host/clocks.rs
index 688780edb..52942f365 100644
--- a/crates/wasi/src/preview2/host/clocks.rs
+++ b/crates/wasi/src/preview2/host/clocks.rs
@@ -1,7 +1,7 @@
 #![allow(unused_variables)]
 
 use crate::preview2::bindings::{
-    clocks::monotonic_clock::{self, Instant},
+    clocks::monotonic_clock::{self, Duration as WasiDuration, Instant},
     clocks::timezone::{self, TimezoneDisplay},
     clocks::wall_clock::{self, Datetime},
 };
@@ -43,6 +43,23 @@ impl<T: WasiView> wall_clock::Host for T {
     }
 }
 
+fn subscribe_to_duration(
+    table: &mut crate::preview2::Table,
+    duration: tokio::time::Duration,
+) -> anyhow::Result<Resource<Pollable>> {
+    let sleep = if let Some(deadline) = tokio::time::Instant::now().checked_add(duration) {
+        // NB: this resource created here is not actually exposed to wasm, it's
+        // only an internal implementation detail used to match the signature
+        // expected by `subscribe`.
+        table.push(Deadline::Instant(deadline))?
+    } else {
+        // If the user specifies a time so far in the future we can't
+        // represent it, wait forever rather than trap.
+        table.push(Deadline::Never)?
+    };
+    subscribe(table, sleep)
+}
+
 impl<T: WasiView> monotonic_clock::Host for T {
     fn now(&mut self) -> anyhow::Result<Instant> {
         Ok(self.ctx().monotonic_clock.now())
@@ -52,30 +69,33 @@ impl<T: WasiView> monotonic_clock::Host for T {
         Ok(self.ctx().monotonic_clock.resolution())
     }
 
-    fn subscribe(&mut self, when: Instant, absolute: bool) -> anyhow::Result<Resource<Pollable>> {
+    fn subscribe_instant(&mut self, when: Instant) -> anyhow::Result<Resource<Pollable>> {
         let clock_now = self.ctx().monotonic_clock.now();
-        let duration = if absolute {
-            Duration::from_nanos(when.saturating_sub(clock_now))
+        let duration = if when > clock_now {
+            Duration::from_nanos(when - clock_now)
         } else {
-            Duration::from_nanos(when)
+            Duration::from_nanos(0)
         };
-        let deadline = tokio::time::Instant::now()
-            .checked_add(duration)
-            .ok_or_else(|| anyhow::anyhow!("time overflow: duration {duration:?}"))?;
-        // NB: this resource created here is not actually exposed to wasm, it's
-        // only an internal implementation detail used to match the signature
-        // expected by `subscribe`.
-        let sleep = self.table_mut().push(Sleep(deadline))?;
-        subscribe(self.table_mut(), sleep)
+        subscribe_to_duration(&mut self.table_mut(), duration)
+    }
+
+    fn subscribe_duration(&mut self, duration: WasiDuration) -> anyhow::Result<Resource<Pollable>> {
+        subscribe_to_duration(&mut self.table_mut(), Duration::from_nanos(duration))
     }
 }
 
-struct Sleep(tokio::time::Instant);
+enum Deadline {
+    Instant(tokio::time::Instant),
+    Never,
+}
 
 #[async_trait::async_trait]
-impl Subscribe for Sleep {
+impl Subscribe for Deadline {
     async fn ready(&mut self) {
-        tokio::time::sleep_until(self.0).await;
+        match self {
+            Deadline::Instant(instant) => tokio::time::sleep_until(*instant).await,
+            Deadline::Never => std::future::pending().await,
+        }
     }
 }
 
diff --git a/crates/wasi/src/preview2/host/udp.rs b/crates/wasi/src/preview2/host/udp.rs
index a97bd1bcd..e2d1e239f 100644
--- a/crates/wasi/src/preview2/host/udp.rs
+++ b/crates/wasi/src/preview2/host/udp.rs
@@ -97,13 +97,19 @@ impl<T: WasiView> udp::HostUdpSocket for T {
             _ => return Err(ErrorCode::InvalidState.into()),
         }
 
+        // We disconnect & (re)connect in two distinct steps for two reasons:
+        // - To leave our socket instance in a consistent state in case the
+        //   connect fails.
+        // - When reconnecting to a different address, Linux sometimes fails
+        //   if there isn't a disconnect in between.
+
+        // Step #1: Disconnect
         if let UdpState::Connected = socket.udp_state {
-            // FIXME: Allow multiple (dis)connects. This needs to be supported by rustix first.
-            // rustix::net::disconnect(socket.udp_socket())?;
-            // socket.udp_state = UdpState::Bound;
-            return Err(ErrorCode::NotSupported.into());
+            disconnect(socket.udp_socket())?;
+            socket.udp_state = UdpState::Bound;
         }
 
+        // Step #2: (Re)connect
         if let Some(connect_addr) = remote_address {
             rustix::net::connect(socket.udp_socket(), &connect_addr)?;
             socket.udp_state = UdpState::Connected;
@@ -481,3 +487,12 @@ impl Subscribe for OutgoingDatagramStream {
         }
     }
 }
+
+fn disconnect<Fd: rustix::fd::AsFd>(sockfd: Fd) -> rustix::io::Result<()> {
+    match rustix::net::connect_unspec(sockfd) {
+        // BSD platforms return an error even if the socket was disconnected successfully.
+        #[cfg(target_os = "macos")]
+        Err(rustix::io::Errno::INVAL | rustix::io::Errno::AFNOSUPPORT) => Ok(()),
+        r => r,
+    }
+}
diff --git a/crates/wasi/src/preview2/preview1.rs b/crates/wasi/src/preview2/preview1.rs
index ab5a9cbd5..4b8172305 100644
--- a/crates/wasi/src/preview2/preview1.rs
+++ b/crates/wasi/src/preview2/preview1.rs
@@ -2072,9 +2072,15 @@ impl<
                         }
                         _ => return Err(types::Errno::Inval.into()),
                     };
-                    monotonic_clock::Host::subscribe(self, timeout, absolute)
-                        .context("failed to call `monotonic_clock::subscribe`")
-                        .map_err(types::Error::trap)?
+                    if absolute {
+                        monotonic_clock::Host::subscribe_instant(self, timeout)
+                            .context("failed to call `monotonic_clock::subscribe_instant`")
+                            .map_err(types::Error::trap)?
+                    } else {
+                        monotonic_clock::Host::subscribe_duration(self, timeout)
+                            .context("failed to call `monotonic_clock::subscribe_duration`")
+                            .map_err(types::Error::trap)?
+                    }
                 }
                 types::SubscriptionU::FdRead(types::SubscriptionFdReadwrite {
                     file_descriptor,
diff --git a/crates/wasi/wit/deps/clocks/monotonic-clock.wit b/crates/wasi/wit/deps/clocks/monotonic-clock.wit
index a3f7fe25c..df3758b8a 100644
--- a/crates/wasi/wit/deps/clocks/monotonic-clock.wit
+++ b/crates/wasi/wit/deps/clocks/monotonic-clock.wit
@@ -11,22 +11,34 @@
 interface monotonic-clock {
     use wasi:io/poll@0.2.0-rc-2023-11-05.{pollable};
 
-    /// A timestamp in nanoseconds.
+    /// An instant in time, in nanoseconds. An instant is relative to an
+    /// unspecified initial value, and can only be compared to instances from
+    /// the same monotonic-clock.
     type instant = u64;
 
+    /// A duration of time, in nanoseconds.
+    type duration = u64;
+
     /// Read the current value of the clock.
     ///
     /// The clock is monotonic, therefore calling this function repeatedly will
     /// produce a sequence of non-decreasing values.
     now: func() -> instant;
 
-    /// Query the resolution of the clock.
-    resolution: func() -> instant;
+    /// Query the resolution of the clock. Returns the duration of time
+    /// corresponding to a clock tick.
+    resolution: func() -> duration;
 
-    /// Create a `pollable` which will resolve once the specified time has been
-    /// reached.
-    subscribe: func(
+    /// Create a `pollable` which will resolve once the specified instant
+    /// occured.
+    subscribe-instant: func(
         when: instant,
-        absolute: bool
+    ) -> pollable;
+
+    /// Create a `pollable` which will resolve once the given duration has
+    /// elapsed, starting at the time at which this function was called.
+    /// occured.
+    subscribe-duration: func(
+        when: duration,
     ) -> pollable;
 }
diff --git a/src/old_cli.rs b/src/old_cli.rs
index 0675d3ec2..ec73aab63 100644
--- a/src/old_cli.rs
+++ b/src/old_cli.rs
@@ -748,7 +748,10 @@ impl RunCommand {
         let mut dirs = Vec::new();
 
         for host in old_dirs {
-            dirs.push((host.clone(), host));
+            let mut parts = host.splitn(2, "::");
+            let host = parts.next().unwrap();
+            let guest = parts.next().unwrap_or(host);
+            dirs.push((host.to_string(), guest.to_string()));
         }
 
         if preview2 {


Test patch:
diff --git a/crates/test-programs/src/bin/preview1_poll_oneoff_files.rs b/crates/test-programs/src/bin/preview1_poll_oneoff_files.rs
index d1dfccaef..57778f88b 100644
--- a/crates/test-programs/src/bin/preview1_poll_oneoff_files.rs
+++ b/crates/test-programs/src/bin/preview1_poll_oneoff_files.rs
@@ -84,7 +84,7 @@ unsafe fn test_timeout() {
     );
     assert!(
         after - before >= timeout,
-        "poll_oneoff should sleep for the specified interval"
+        "poll_oneoff should sleep for the specified interval of {timeout}. before: {before}, after: {after}"
     );
 }
 
@@ -122,7 +122,7 @@ unsafe fn test_sleep() {
     );
     assert!(
         after - before >= timeout,
-        "poll_oneoff should sleep for the specified interval"
+        "poll_oneoff should sleep for the specified interval of {timeout}. before: {before}, after: {after}"
     );
 }
 
diff --git a/crates/test-programs/src/bin/preview2_ip_name_lookup.rs b/crates/test-programs/src/bin/preview2_ip_name_lookup.rs
index 219ebba45..fc0b25de8 100644
--- a/crates/test-programs/src/bin/preview2_ip_name_lookup.rs
+++ b/crates/test-programs/src/bin/preview2_ip_name_lookup.rs
@@ -19,7 +19,7 @@ fn main() {
     // the resolution and allows errors.
     let addresses = ip_name_lookup::resolve_addresses(&network, "github.com", None, false).unwrap();
     let lookup = addresses.subscribe();
-    let timeout = monotonic_clock::subscribe(1_000_000_000, false);
+    let timeout = monotonic_clock::subscribe_duration(1_000_000_000);
     let ready = poll::poll_list(&[&lookup, &timeout]);
     assert!(ready.len() > 0);
     match ready[0] {
diff --git a/crates/test-programs/src/bin/preview2_sleep.rs b/crates/test-programs/src/bin/preview2_sleep.rs
index a5f4d76a3..15de238dc 100644
--- a/crates/test-programs/src/bin/preview2_sleep.rs
+++ b/crates/test-programs/src/bin/preview2_sleep.rs
@@ -8,20 +8,20 @@ fn main() {
 
 fn sleep_10ms() {
     let dur = 10_000_000;
-    let p = monotonic_clock::subscribe(monotonic_clock::now() + dur, true);
+    let p = monotonic_clock::subscribe_instant(monotonic_clock::now() + dur);
     poll::poll_one(&p);
-    let p = monotonic_clock::subscribe(dur, false);
+    let p = monotonic_clock::subscribe_duration(dur);
     poll::poll_one(&p);
 }
 
 fn sleep_0ms() {
-    let p = monotonic_clock::subscribe(monotonic_clock::now(), true);
+    let p = monotonic_clock::subscribe_instant(monotonic_clock::now());
     poll::poll_one(&p);
-    let p = monotonic_clock::subscribe(0, false);
+    let p = monotonic_clock::subscribe_duration(0);
     poll::poll_one(&p);
 }
 
 fn sleep_backwards_in_time() {
-    let p = monotonic_clock::subscribe(monotonic_clock::now() - 1, true);
+    let p = monotonic_clock::subscribe_instant(monotonic_clock::now() - 1);
     poll::poll_one(&p);
 }
diff --git a/crates/test-programs/src/bin/preview2_udp_connect.rs b/crates/test-programs/src/bin/preview2_udp_connect.rs
new file mode 100644
index 000000000..5100525e8
--- /dev/null
+++ b/crates/test-programs/src/bin/preview2_udp_connect.rs
@@ -0,0 +1,41 @@
+use test_programs::wasi::sockets::network::{
+    ErrorCode, IpAddress, IpAddressFamily, IpSocketAddress, Network,
+};
+use test_programs::wasi::sockets::udp::UdpSocket;
+
+fn test_udp_connect_disconnect_reconnect(net: &Network, family: IpAddressFamily) {
+    let unspecified_addr = IpSocketAddress::new(IpAddress::new_unspecified(family), 0);
+    let remote1 = IpSocketAddress::new(IpAddress::new_loopback(family), 4321);
+    let remote2 = IpSocketAddress::new(IpAddress::new_loopback(family), 4320);
+
+    let client = UdpSocket::new(family).unwrap();
+    client.blocking_bind(&net, unspecified_addr).unwrap();
+
+    _ = client.stream(None).unwrap();
+    assert_eq!(client.remote_address(), Err(ErrorCode::InvalidState));
+
+    _ = client.stream(None).unwrap();
+    assert_eq!(client.remote_address(), Err(ErrorCode::InvalidState));
+
+    _ = client.stream(Some(remote1)).unwrap();
+    assert_eq!(client.remote_address(), Ok(remote1));
+
+    _ = client.stream(Some(remote1)).unwrap();
+    assert_eq!(client.remote_address(), Ok(remote1));
+
+    _ = client.stream(Some(remote2)).unwrap();
+    assert_eq!(client.remote_address(), Ok(remote2));
+
+    _ = client.stream(None).unwrap();
+    assert_eq!(client.remote_address(), Err(ErrorCode::InvalidState));
+
+    _ = client.stream(Some(remote1)).unwrap();
+    assert_eq!(client.remote_address(), Ok(remote1));
+}
+
+fn main() {
+    let net = Network::default();
+
+    test_udp_connect_disconnect_reconnect(&net, IpAddressFamily::Ipv4);
+    test_udp_connect_disconnect_reconnect(&net, IpAddressFamily::Ipv6);
+}
diff --git a/crates/test-programs/src/sockets.rs b/crates/test-programs/src/sockets.rs
index 3033582b6..74a65d74f 100644
--- a/crates/test-programs/src/sockets.rs
+++ b/crates/test-programs/src/sockets.rs
@@ -159,7 +159,7 @@ impl OutgoingDatagramStream {
     }
 
     pub fn blocking_send(&self, mut datagrams: &[OutgoingDatagram]) -> Result<(), ErrorCode> {
-        let timeout = monotonic_clock::subscribe(TIMEOUT_NS, false);
+        let timeout = monotonic_clock::subscribe_duration(TIMEOUT_NS);
 
         while !datagrams.is_empty() {
             let permit = self.blocking_check_send(&timeout)?;
@@ -180,7 +180,7 @@ impl OutgoingDatagramStream {
 
 impl IncomingDatagramStream {
     pub fn blocking_receive(&self, count: Range<u64>) -> Result<Vec<IncomingDatagram>, ErrorCode> {
-        let timeout = monotonic_clock::subscribe(TIMEOUT_NS, false);
+        let timeout = monotonic_clock::subscribe_duration(TIMEOUT_NS);
         let pollable = self.subscribe();
         let mut datagrams = vec![];
 
diff --git a/crates/wasi/tests/all/async_.rs b/crates/wasi/tests/all/async_.rs
index c1813476b..553e3846f 100644
--- a/crates/wasi/tests/all/async_.rs
+++ b/crates/wasi/tests/all/async_.rs
@@ -319,6 +319,10 @@ async fn preview2_tcp_bind() {
     run(PREVIEW2_TCP_BIND_COMPONENT, false).await.unwrap()
 }
 #[test_log::test(tokio::test(flavor = "multi_thread"))]
+async fn preview2_udp_connect() {
+    run(PREVIEW2_UDP_CONNECT_COMPONENT, false).await.unwrap()
+}
+#[test_log::test(tokio::test(flavor = "multi_thread"))]
 async fn preview2_udp_sample_application() {
     run(PREVIEW2_UDP_SAMPLE_APPLICATION_COMPONENT, false)
         .await
diff --git a/crates/wasi/tests/all/sync.rs b/crates/wasi/tests/all/sync.rs
index 4347e01d9..0e087ab8c 100644
--- a/crates/wasi/tests/all/sync.rs
+++ b/crates/wasi/tests/all/sync.rs
@@ -262,6 +262,10 @@ fn preview2_tcp_bind() {
     run(PREVIEW2_TCP_BIND_COMPONENT, false).unwrap()
 }
 #[test_log::test]
+fn preview2_udp_connect() {
+    run(PREVIEW2_UDP_CONNECT_COMPONENT, false).unwrap()
+}
+#[test_log::test]
 fn preview2_udp_sample_application() {
     run(PREVIEW2_UDP_SAMPLE_APPLICATION_COMPONENT, false).unwrap()
 }
diff --git a/tests/all/cli_tests.rs b/tests/all/cli_tests.rs
index 049d910f6..b4bc8c86c 100644
--- a/tests/all/cli_tests.rs
+++ b/tests/all/cli_tests.rs
@@ -1160,6 +1160,19 @@ warning: this CLI invocation of Wasmtime is going to break in the future -- for
     );
     assert_eq!(String::from_utf8_lossy(&output.stderr), "");
 
+    // the `--dir` flag prints no warning when used with `::`
+    let dir = tempfile::tempdir()?;
+    std::fs::write(dir.path().join("bar.txt"), b"And stood awhile in thought")?;
+    let output = get_wasmtime_command()?
+        .args(&[
+            "run",
+            &format!("--dir={}::/", dir.path().to_str().unwrap()),
+            test_programs_artifacts::CLI_FILE_READ,
+        ])
+        .output()?;
+    assert_eq!(String::from_utf8_lossy(&output.stdout), "");
+    assert_eq!(String::from_utf8_lossy(&output.stderr), "");
+
     Ok(())
 }