Skip to content

Commit 36b1667

Browse files
magodoLarryOstermanCopilotheaths
authored
typespec_client_core: New web_runtime to support wasm32-unknown-unknown (#2838)
The `typespec_client_core::sleep::sleep` is using the async_runtime's sleep implementation, which is complicated to support `wasm32`, and will panic now when targeting to `wasm32`. Instead of make the `standard_runtime` to support wasm32 sleep (I wonder if it's feasible), we can instead just introduce another `sleep` implementation to simply use the `gloo_timers::future::sleep`. Also, this PR adds a `cfg_attr` for conditionally remove the `Send` trait from the `async_trait` for the `RetryPolicy`, as the other parts of the code do. Fix #2754 Supersedes: #2770 Co-authored-by: Larry Osterman <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: Heath Stewart <[email protected]>
1 parent b8f592a commit 36b1667

File tree

14 files changed

+246
-122
lines changed

14 files changed

+246
-122
lines changed

Cargo.lock

Lines changed: 49 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ fe2o3-amqp-types = { version = "0.14" }
101101
flate2 = "1.1.0"
102102
futures = "0.3"
103103
getrandom = { version = "0.3" }
104+
gloo-timers = { version = "0.3" }
104105
hmac = { version = "0.12" }
105106
litemap = "0.7.4"
106107
log = "0.4"
@@ -143,6 +144,8 @@ tracing = "0.1.40"
143144
tracing-subscriber = "0.3"
144145
url = "2.2"
145146
uuid = { version = "1.18", features = ["v4"] }
147+
wasm-bindgen-futures = "0.4"
148+
wasm-bindgen-test = "0.3"
146149
zerofrom = "0.1.5"
147150
zip = { version = "4.0.0", default-features = false, features = ["deflate"] }
148151

eng/dict/crates.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ fe2o3-amqp-types
3232
flate2
3333
futures
3434
getrandom
35+
gloo
3536
hmac
3637
litemap
3738
log

sdk/typespec/typespec_client_core/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
### Features Added
66

7+
- Added support for WASM to the `async_runtime` module.
8+
79
### Breaking Changes
810

911
- Removed the `fs` module including the `FileStream` and `FileStreamBuilder` types. Moved to `examples/` to copy if needed.

sdk/typespec/typespec_client_core/Cargo.toml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ base64.workspace = true
1616
bytes.workspace = true
1717
dyn-clone.workspace = true
1818
futures.workspace = true
19+
gloo-timers = { workspace = true, optional = true }
1920
pin-project.workspace = true
2021
quick-xml = { workspace = true, optional = true }
2122
rand.workspace = true
@@ -30,6 +31,7 @@ typespec = { workspace = true, default-features = false }
3031
typespec_macros = { workspace = true, optional = true }
3132
url.workspace = true
3233
uuid.workspace = true
34+
wasm-bindgen-futures = { workspace = true, optional = true }
3335

3436
[target.'cfg(not(target_family = "wasm"))'.dependencies]
3537
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "time"] }
@@ -39,11 +41,19 @@ getrandom.workspace = true
3941
tokio = { workspace = true, features = ["macros", "rt", "time"] }
4042

4143
[dev-dependencies]
42-
tokio = { workspace = true, features = ["fs"] }
4344
tracing.workspace = true
4445
tracing-subscriber.workspace = true
4546
typespec_macros.path = "../typespec_macros"
4647

48+
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
49+
tokio = { workspace = true, features = ["fs"] }
50+
51+
[target.'cfg(target_family = "wasm")'.dev-dependencies]
52+
tokio.workspace = true
53+
getrandom = { workspace = true, features = ["wasm_js"] }
54+
uuid = { workspace = true, features = ["v4", "js"] }
55+
wasm-bindgen-test.workspace = true
56+
4757
[features]
4858
default = ["http", "json", "reqwest", "reqwest_deflate", "reqwest_gzip"]
4959
debug = ["typespec_macros?/debug"]
@@ -59,8 +69,13 @@ reqwest_rustls = [
5969
] # Remove dependency on banned `ring` crate; requires manually configuring crypto provider.
6070
test = [] # Enables extra tracing including error bodies that may contain PII.
6171
tokio = ["tokio/sync", "tokio/time"]
72+
wasm_bindgen = ["dep:wasm-bindgen-futures", "gloo-timers/futures"]
6273
xml = ["dep:quick-xml"]
6374

75+
[[example]]
76+
name = "core_binary_data_request"
77+
required-features = ["tokio"]
78+
6479
[[example]]
6580
name = "core_stream_response"
6681
required-features = ["derive"]
@@ -76,6 +91,7 @@ features = [
7691
"reqwest_gzip",
7792
"reqwest_rustls",
7893
"tokio",
94+
"wasm_bindgen",
7995
"xml",
8096
]
8197

sdk/typespec/typespec_client_core/src/async_runtime/mod.rs

Lines changed: 45 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -7,41 +7,38 @@
77
//!
88
//! It abstracts away the underlying implementation details, allowing for different task execution strategies based on the target architecture and features enabled.
99
//!
10-
//!
11-
//! Example usage:
10+
//! # Examples
1211
//!
1312
//! ```
1413
//! use typespec_client_core::async_runtime::get_async_runtime;
15-
//! use futures::FutureExt;
16-
//!
17-
//! #[tokio::main]
18-
//! async fn main() {
19-
//! let async_runtime = get_async_runtime();
20-
//! let handle = async_runtime.spawn(async {
21-
//! // Simulate some work
22-
//! std::thread::sleep(std::time::Duration::from_secs(1));
23-
//! }.boxed());
2414
//!
25-
//! handle.await.expect("Task should complete successfully");
26-
//!
27-
//! println!("Task completed");
28-
//! }
15+
//! # #[tokio::main]
16+
//! # async fn main() {
17+
//! let async_runtime = get_async_runtime();
18+
//! let handle = async_runtime.spawn(Box::pin(async {
19+
//! // Simulate some work
20+
//! std::thread::sleep(std::time::Duration::from_secs(1));
21+
//! }));
22+
//! handle.await.expect("Task should complete successfully");
23+
//! println!("Task completed");
24+
//! # }
2925
//! ```
30-
//!
31-
//!
3226
use crate::time::Duration;
3327
use std::{
3428
future::Future,
3529
pin::Pin,
3630
sync::{Arc, OnceLock},
3731
};
3832

39-
#[cfg_attr(feature = "tokio", allow(dead_code))]
33+
#[cfg_attr(any(feature = "tokio", feature = "wasm_bindgen"), allow(dead_code))]
4034
mod standard_runtime;
4135

4236
#[cfg(feature = "tokio")]
4337
mod tokio_runtime;
4438

39+
#[cfg(all(target_arch = "wasm32", feature = "wasm_bindgen"))]
40+
mod web_runtime;
41+
4542
#[cfg(test)]
4643
mod tests;
4744

@@ -80,35 +77,35 @@ pub trait AsyncRuntime: Send + Sync {
8077
/// from its environment by reference, as it will be executed in a different thread or context.
8178
///
8279
/// # Returns
80+
///
8381
/// A future which can be awaited to block until the task has completed.
8482
///
85-
/// # Example
83+
/// # Examples
84+
///
8685
/// ```
8786
/// use typespec_client_core::async_runtime::get_async_runtime;
88-
/// use futures::FutureExt;
8987
///
90-
/// #[tokio::main]
91-
/// async fn main() {
92-
/// let async_runtime = get_async_runtime();
93-
/// let future = async_runtime.spawn(async {
94-
/// // Simulate some work
95-
/// std::thread::sleep(std::time::Duration::from_secs(1));
96-
/// }.boxed());
97-
/// future.await.expect("Task should complete successfully");
98-
/// }
88+
/// # #[tokio::main]
89+
/// # async fn main() {
90+
/// let async_runtime = get_async_runtime();
91+
/// let handle = async_runtime.spawn(Box::pin(async {
92+
/// // Simulate some work
93+
/// std::thread::sleep(std::time::Duration::from_secs(1));
94+
/// }));
95+
/// handle.await.expect("Task should complete successfully");
96+
/// # }
9997
/// ```
10098
///
101-
/// # Note
99+
/// # Notes
102100
///
103101
/// This trait intentionally does not use the *`async_trait`* macro because when the
104102
/// `async_trait` attribute is applied to a trait implementation, the rewritten
105103
/// method cannot directly return a future, instead they wrap the return value
106104
/// in a future, and we want the `spawn` method to directly return a future
107105
/// that can be awaited.
108-
///
109106
fn spawn(&self, f: TaskFuture) -> SpawnedTask;
110107

111-
fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
108+
fn sleep(&self, duration: Duration) -> TaskFuture;
112109
}
113110

114111
static ASYNC_RUNTIME_IMPLEMENTATION: OnceLock<Arc<dyn AsyncRuntime>> = OnceLock::new();
@@ -124,22 +121,20 @@ static ASYNC_RUNTIME_IMPLEMENTATION: OnceLock<Arc<dyn AsyncRuntime>> = OnceLock:
124121
/// # Returns
125122
/// An instance of a [`AsyncRuntime`] which can be used to spawn background tasks or perform other asynchronous operations.
126123
///
127-
/// # Example
124+
/// # Examples
128125
///
129126
/// ```
130127
/// use typespec_client_core::async_runtime::get_async_runtime;
131-
/// use futures::FutureExt;
132128
///
133-
/// #[tokio::main]
134-
/// async fn main() {
135-
/// let async_runtime = get_async_runtime();
136-
/// let handle = async_runtime.spawn(async {
137-
/// // Simulate some work
138-
/// std::thread::sleep(std::time::Duration::from_secs(1));
139-
/// }.boxed());
140-
/// }
129+
/// # #[tokio::main]
130+
/// # async fn main() {
131+
/// let async_runtime = get_async_runtime();
132+
/// let handle = async_runtime.spawn(Box::pin(async {
133+
/// // Simulate some work
134+
/// std::thread::sleep(std::time::Duration::from_secs(1));
135+
/// }));
136+
/// # }
141137
/// ```
142-
///
143138
pub fn get_async_runtime() -> Arc<dyn AsyncRuntime> {
144139
ASYNC_RUNTIME_IMPLEMENTATION
145140
.get_or_init(|| create_async_runtime())
@@ -155,7 +150,7 @@ pub fn get_async_runtime() -> Arc<dyn AsyncRuntime> {
155150
/// # Returns
156151
/// Ok if the async runtime was set successfully, or an error if it has already been set.
157152
///
158-
/// # Example
153+
/// # Examples
159154
///
160155
/// ```
161156
/// use typespec_client_core::async_runtime::{
@@ -190,12 +185,16 @@ pub fn set_async_runtime(runtime: Arc<dyn AsyncRuntime>) -> crate::Result<()> {
190185
}
191186

192187
fn create_async_runtime() -> Arc<dyn AsyncRuntime> {
193-
#[cfg(not(feature = "tokio"))]
188+
#[cfg(all(target_arch = "wasm32", feature = "wasm_bindgen"))]
194189
{
195-
Arc::new(standard_runtime::StdRuntime)
190+
Arc::new(web_runtime::WasmBindgenRuntime) as Arc<dyn AsyncRuntime>
196191
}
197192
#[cfg(feature = "tokio")]
198193
{
199194
Arc::new(tokio_runtime::TokioRuntime) as Arc<dyn AsyncRuntime>
200195
}
196+
#[cfg(not(any(feature = "tokio", feature = "wasm_bindgen")))]
197+
{
198+
Arc::new(standard_runtime::StdRuntime) as Arc<dyn AsyncRuntime>
199+
}
201200
}

sdk/typespec/typespec_client_core/src/async_runtime/standard_runtime.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use std::{
1414
task::{Context, Poll, Waker},
1515
thread,
1616
};
17+
#[cfg(not(target_arch = "wasm32"))]
1718
use std::{future::Future, pin::Pin};
1819
#[cfg(not(target_arch = "wasm32"))]
1920
use tracing::debug;
@@ -149,7 +150,7 @@ impl AsyncRuntime for StdRuntime {
149150
/// Uses a simple thread based implementation for sleep. A more efficient
150151
/// implementation is available by using the `tokio` crate feature.
151152
#[cfg_attr(target_arch = "wasm32", allow(unused_variables))]
152-
fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>> {
153+
fn sleep(&self, duration: Duration) -> TaskFuture {
153154
#[cfg(target_arch = "wasm32")]
154155
{
155156
panic!("sleep is not supported on wasm32")

0 commit comments

Comments
 (0)