diff --git a/Cargo.lock b/Cargo.lock index 1d866d8941..d3f94b4138 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -938,9 +938,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dyn-clone" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "either" @@ -1255,6 +1255,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" @@ -1411,7 +1423,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.0", + "socket2", "tokio", "tower-service", "tracing", @@ -1624,9 +1636,9 @@ checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libredox" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" +checksum = "360e552c93fa0e8152ab463bc4c4837fce76a225df11dfaeea66c313de5e61f7" dependencies = [ "bitflags", "libc", @@ -1685,6 +1697,16 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "minicov" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" +dependencies = [ + "cc", + "walkdir", +] + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2156,9 +2178,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.15" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags", ] @@ -2321,9 +2343,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc_version" @@ -2349,9 +2371,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.29" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ "once_cell", "rustls-pki-types", @@ -2680,16 +2702,6 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "socket2" version = "0.6.0" @@ -2907,9 +2919,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.46.1" +version = "1.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35" dependencies = [ "backtrace", "bytes", @@ -2919,9 +2931,9 @@ dependencies = [ "pin-project-lite", "signal-hook-registry", "slab", - "socket2 0.5.10", + "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3146,6 +3158,7 @@ dependencies = [ "dyn-clone", "futures", "getrandom 0.3.3", + "gloo-timers", "pin-project", "quick-xml", "rand 0.9.2", @@ -3161,6 +3174,8 @@ dependencies = [ "typespec_macros", "url", "uuid", + "wasm-bindgen-futures", + "wasm-bindgen-test", ] [[package]] @@ -3363,6 +3378,30 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-bindgen-test" +version = "0.3.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3" +dependencies = [ + "js-sys", + "minicov", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "wasm-streams" version = "0.4.2" @@ -3431,6 +3470,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-sys" version = "0.52.0" @@ -3455,7 +3500,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -3476,10 +3521,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -3588,9 +3634,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 45abfa0628..136694123b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,6 +89,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" @@ -128,6 +129,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/eng/dict/crates.txt b/eng/dict/crates.txt index 01bc133f8a..6a2163073c 100644 --- a/eng/dict/crates.txt +++ b/eng/dict/crates.txt @@ -32,6 +32,7 @@ fe2o3-amqp-types flate2 futures getrandom +gloo hmac litemap log diff --git a/sdk/typespec/typespec_client_core/Cargo.toml b/sdk/typespec/typespec_client_core/Cargo.toml index 7032a61fb6..d5dea44f4a 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 @@ -30,6 +31,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"] } @@ -43,6 +45,9 @@ tokio.workspace = true tracing.workspace = true tracing-subscriber.workspace = true typespec_macros.path = "../typespec_macros" +wasm-bindgen-test = "0.3" +getrandom = { version = "0.3" , features = ["wasm_js"]} +uuid = { version = "1", features = ["v4", "js"] } [features] default = ["http", "json", "reqwest", "reqwest_deflate", "reqwest_gzip"] @@ -58,6 +63,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"] decimal = ["dep:rust_decimal"] 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..8a6607cfd8 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(all(target_arch = "wasm32", feature = "wasm-bindgen"))] +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(all(target_arch = "wasm32", 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/tests.rs b/sdk/typespec/typespec_client_core/src/async_runtime/tests.rs index c1c1d71008..0dfc151cc6 100644 --- a/sdk/typespec/typespec_client_core/src/async_runtime/tests.rs +++ b/sdk/typespec/typespec_client_core/src/async_runtime/tests.rs @@ -3,10 +3,12 @@ use super::*; use crate::time::Duration; -use futures::FutureExt; use std::sync::{Arc, Mutex}; -#[cfg(not(feature = "tokio"))] +#[cfg(not(feature = "wasm-bindgen"))] +use futures::FutureExt; + +#[cfg(not(any(feature = "tokio", feature = "wasm-bindgen")))] #[test] fn test_task_spawner_execution() { let runtime = get_async_runtime(); @@ -124,6 +126,7 @@ async fn tokio_task_execution() { // When the "tokio" feature is enabled, the azure_core::sleep::sleep function uses tokio::time::sleep which requires a tokio runtime. // When the "tokio" feature is not enabled, it uses std::thread::sleep which does not require a tokio runtime. +#[cfg(not(target_arch = "wasm32"))] #[test] fn std_specific_handling() { let spawner = Arc::new(standard_runtime::StdRuntime); @@ -144,6 +147,7 @@ fn std_specific_handling() { } #[test] +#[cfg(not(target_arch = "wasm32"))] fn std_multiple_tasks() { let spawner = Arc::new(standard_runtime::StdRuntime); let counter = Arc::new(Mutex::new(0)); @@ -172,7 +176,7 @@ fn std_multiple_tasks() { // When the "tokio" feature is enabled, the azure_core::sleep::sleep function uses tokio::time::sleep which requires a tokio runtime. // When the "tokio" feature is not enabled, it uses std::thread::sleep which does not require a tokio runtime. -#[cfg(not(feature = "tokio"))] +#[cfg(not(any(feature = "tokio", feature = "wasm-bindgen")))] #[test] fn std_task_execution() { let runtime = Arc::new(standard_runtime::StdRuntime); @@ -199,7 +203,7 @@ fn std_task_execution() { // Basic test that launches 10k futures and waits for them to complete: // it has a high chance of failing if there is a race condition in the sleep method; // otherwise, it runs quickly. -#[cfg(not(feature = "tokio"))] +#[cfg(not(any(feature = "tokio", feature = "wasm-bindgen")))] #[tokio::test] async fn test_timeout() { use super::*; @@ -226,6 +230,7 @@ async fn test_timeout() { } } +#[cfg(not(target_arch = "wasm32"))] #[tokio::test] async fn test_sleep() { let runtime = get_async_runtime(); @@ -235,6 +240,37 @@ async fn test_sleep() { assert!(elapsed >= Duration::milliseconds(100)); } +#[cfg(feature = "wasm-bindgen")] +use wasm_bindgen_test::*; + +#[cfg(feature = "wasm-bindgen")] +#[wasm_bindgen_test] +async fn test_sleep() { + let runtime = get_async_runtime(); + runtime.sleep(Duration::milliseconds(100)).await; +} + +#[cfg(feature = "wasm-bindgen")] +#[wasm_bindgen_test] +async fn wasm_bindgen_task_execution() { + let spawner = Arc::new(web_runtime::WasmBindgenRuntime); + let result = Arc::new(Mutex::new(false)); + let result_clone = Arc::clone(&result); + + let handle = spawner.spawn(Box::pin(async move { + // Simulate some work + crate::sleep::sleep(Duration::milliseconds(50)).await; + let mut value = result_clone.lock().unwrap(); + *value = true; + })); + + // Wait for task completion + handle.await.expect("Task should complete successfully"); + + // Verify the task executed + assert!(*result.lock().unwrap()); +} + #[test] fn test_get_runtime() { // Ensure that the runtime can be retrieved without panicking @@ -248,7 +284,7 @@ impl AsyncRuntime for TestRuntime { unimplemented!("TestRuntime does not support spawning tasks"); } - fn sleep(&self, _duration: Duration) -> Pin + Send + 'static>> { + fn sleep(&self, _duration: Duration) -> TaskFuture { unimplemented!("TestRuntime does not support sleeping"); } } 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..7986be5d6a --- /dev/null +++ b/sdk/typespec/typespec_client_core/src/async_runtime/web_runtime.rs @@ -0,0 +1,35 @@ +// 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 `wasm-bindgen-futures` for task spawning and `gloo-timers` for sleep functionality. +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. + } + }) + } +} 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 f50a14fdf0..8710a693de 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 @@ -89,7 +89,8 @@ impl Deref for RetryPolicyCount { /// /// `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. ///