diff --git a/Cargo.lock b/Cargo.lock index 685cb51fa2..97a7253834 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -1149,6 +1149,18 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "half" version = "2.6.0" @@ -2833,6 +2845,7 @@ dependencies = [ "dyn-clone", "futures", "getrandom 0.3.3", + "gloo-timers", "pin-project", "quick-xml", "rand 0.9.1", @@ -2847,6 +2860,7 @@ dependencies = [ "typespec_macros", "url", "uuid", + "wasm-bindgen-futures", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f7f7f592e8..4e3c89a5e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,6 +83,7 @@ fe2o3-amqp-types = { version = "0.14" } flate2 = "1.1.0" futures = "0.3" getrandom = { version = "0.3" } +gloo-timers = { version = "0.3" } hmac = { version = "0.12" } litemap = "0.7.4" log = "0.4" @@ -121,6 +122,7 @@ tracing = "0.1.40" tracing-subscriber = "0.3" url = "2.2" uuid = { version = "1.17", features = ["v4"] } +wasm-bindgen-futures = "0.4" zerofrom = "0.1.5" zip = { version = "4.0.0", default-features = false, features = ["deflate"] } diff --git a/sdk/typespec/typespec_client_core/Cargo.toml b/sdk/typespec/typespec_client_core/Cargo.toml index a9396c1403..37878e6048 100644 --- a/sdk/typespec/typespec_client_core/Cargo.toml +++ b/sdk/typespec/typespec_client_core/Cargo.toml @@ -16,6 +16,7 @@ base64.workspace = true bytes.workspace = true dyn-clone.workspace = true futures.workspace = true +gloo-timers = { workspace = true, optional = true } pin-project.workspace = true quick-xml = { workspace = true, optional = true } rand.workspace = true @@ -29,6 +30,7 @@ typespec = { workspace = true, default-features = false } typespec_macros = { workspace = true, optional = true } url.workspace = true uuid.workspace = true +wasm-bindgen-futures = { workspace = true, optional = true } [target.'cfg(not(target_family = "wasm"))'.dependencies] tokio = { workspace = true, features = ["macros", "rt-multi-thread", "time"] } @@ -57,6 +59,7 @@ reqwest_rustls = [ ] # Remove dependency on banned `ring` crate; requires manually configuring crypto provider. test = [] # Enables extra tracing including error bodies that may contain PII. tokio = ["tokio/fs", "tokio/sync", "tokio/time", "tokio/io-util"] +wasm-bindgen = ["dep:wasm-bindgen-futures", "gloo-timers/futures"] xml = ["dep:quick-xml"] [[example]] diff --git a/sdk/typespec/typespec_client_core/src/async_runtime/mod.rs b/sdk/typespec/typespec_client_core/src/async_runtime/mod.rs index a1e3073bfe..97ca216515 100644 --- a/sdk/typespec/typespec_client_core/src/async_runtime/mod.rs +++ b/sdk/typespec/typespec_client_core/src/async_runtime/mod.rs @@ -41,6 +41,9 @@ mod standard_runtime; #[cfg(feature = "tokio")] mod tokio_runtime; +#[cfg(target_arch = "wasm32")] +mod web_runtime; + #[cfg(test)] mod tests; @@ -107,7 +110,7 @@ pub trait AsyncRuntime: Send + Sync { /// fn spawn(&self, f: TaskFuture) -> SpawnedTask; - fn sleep(&self, duration: Duration) -> Pin + Send + 'static>>; + fn sleep(&self, duration: Duration) -> TaskFuture; } static ASYNC_RUNTIME_IMPLEMENTATION: OnceLock> = OnceLock::new(); @@ -189,12 +192,16 @@ pub fn set_async_runtime(runtime: Arc) -> crate::Result<()> { } fn create_async_runtime() -> Arc { - #[cfg(not(feature = "tokio"))] + #[cfg(feature = "wasm-bindgen")] { - Arc::new(standard_runtime::StdRuntime) + Arc::new(web_runtime::WasmBindgenRuntime) as Arc } #[cfg(feature = "tokio")] { Arc::new(tokio_runtime::TokioRuntime) as Arc } + #[cfg(not(any(feature = "tokio", feature = "wasm-bindgen")))] + { + Arc::new(standard_runtime::StdRuntime) + } } diff --git a/sdk/typespec/typespec_client_core/src/async_runtime/standard_runtime.rs b/sdk/typespec/typespec_client_core/src/async_runtime/standard_runtime.rs index 739d85f1f8..2d590b57e5 100644 --- a/sdk/typespec/typespec_client_core/src/async_runtime/standard_runtime.rs +++ b/sdk/typespec/typespec_client_core/src/async_runtime/standard_runtime.rs @@ -14,6 +14,7 @@ use std::{ task::{Context, Poll, Waker}, thread, }; +#[cfg(not(target_arch = "wasm32"))] use std::{future::Future, pin::Pin}; #[cfg(not(target_arch = "wasm32"))] use tracing::debug; @@ -150,7 +151,7 @@ impl AsyncRuntime for StdRuntime { /// Uses a simple thread based implementation for sleep. A more efficient /// implementation is available by using the `tokio` crate feature. #[cfg_attr(target_arch = "wasm32", allow(unused_variables))] - fn sleep(&self, duration: Duration) -> Pin + Send + 'static>> { + fn sleep(&self, duration: Duration) -> TaskFuture { #[cfg(target_arch = "wasm32")] { panic!("sleep is not supported on wasm32") diff --git a/sdk/typespec/typespec_client_core/src/async_runtime/web_runtime.rs b/sdk/typespec/typespec_client_core/src/async_runtime/web_runtime.rs new file mode 100644 index 0000000000..ca971b3ac0 --- /dev/null +++ b/sdk/typespec/typespec_client_core/src/async_runtime/web_runtime.rs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +use super::{AsyncRuntime, SpawnedTask, TaskFuture}; +use crate::time::Duration; +use futures::channel::oneshot; + +/// An [`AsyncRuntime`] using `tokio` based APIs. +pub(crate) struct WasmBindgenRuntime; + +impl AsyncRuntime for WasmBindgenRuntime { + fn spawn(&self, f: TaskFuture) -> SpawnedTask { + let (tx, rx) = oneshot::channel(); + + wasm_bindgen_futures::spawn_local(async move { + let result = f.await; + let _ = tx.send(result); + }); + + Box::pin(async { + rx.await + .map_err(|e| Box::new(e) as Box) + }) + } + + fn sleep(&self, duration: Duration) -> TaskFuture { + Box::pin(async move { + if let Ok(d) = duration.try_into() { + gloo_timers::future::sleep(d).await; + } else { + // This means the duration is negative, don't sleep at all. + return; + } + }) + } +} diff --git a/sdk/typespec/typespec_client_core/src/http/policies/retry/mod.rs b/sdk/typespec/typespec_client_core/src/http/policies/retry/mod.rs index b4ea0e5098..d7b0e7dec0 100644 --- a/sdk/typespec/typespec_client_core/src/http/policies/retry/mod.rs +++ b/sdk/typespec/typespec_client_core/src/http/policies/retry/mod.rs @@ -76,7 +76,8 @@ pub fn get_retry_after(headers: &Headers, now: DateTimeFn) -> Option { /// /// `wait` can be implemented in more complex cases where a simple test of time /// is not enough. -#[async_trait] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait RetryPolicy: std::fmt::Debug + Send + Sync { /// Determine if no more retries should be performed. ///