Skip to content

typespec_client_core: New web_runtime to support wasm32-unknown-unknown #2770

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -122,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"] }

Expand Down
1 change: 1 addition & 0 deletions sdk/typespec/typespec_client_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ tokio = { workspace = true, features = ["macros", "rt-multi-thread", "time"] }
getrandom.workspace = true
tokio = { workspace = true, features = ["macros", "rt", "time"] }
gloo-timers = { workspace = true, features = ["futures"] }
wasm-bindgen-futures.workspace = true

[dev-dependencies]
tokio.workspace = true
Expand Down
13 changes: 10 additions & 3 deletions sdk/typespec/typespec_client_core/src/async_runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ mod standard_runtime;
#[cfg(feature = "tokio")]
mod tokio_runtime;

#[cfg(target_arch = "wasm32")]
mod web_runtime;

#[cfg(test)]
mod tests;

Expand Down Expand Up @@ -107,7 +110,7 @@ pub trait AsyncRuntime: Send + Sync {
///
fn spawn(&self, f: TaskFuture) -> SpawnedTask;

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

static ASYNC_RUNTIME_IMPLEMENTATION: OnceLock<Arc<dyn AsyncRuntime>> = OnceLock::new();
Expand Down Expand Up @@ -189,12 +192,16 @@ pub fn set_async_runtime(runtime: Arc<dyn AsyncRuntime>) -> crate::Result<()> {
}

fn create_async_runtime() -> Arc<dyn AsyncRuntime> {
#[cfg(not(feature = "tokio"))]
#[cfg(target_arch = "wasm32")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much better, but IMHO this should probably be tied to a specific feature which is the gloo runtime, unless you can assert safely that gloo is the runtime of choice for all wasm developers (I'm not familiar enough with wasm development to have an opinion here).

This is similar to how we've tied the tokio runtime to the tokio feature - because the tokio runtime is not a globally accepted async runtime, we hide its implementation behind a feature.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LarryOsterman The gloo-timers is provided by https://github.com/rustwasm, which is built on top of wasm-bindgen and js-sys, providing a more user friendly API. So if we really want to introduce a feature, then I'd prpose to name it "wasm-bindgen(as thespawnis based onwasm-bindgen-futures, and the sleepis based ongloo-timers, which in turn based on wasm-bindgen`).

Whilst, I believe even we use the target for this create_async_runtime(), it only means we provide a default runtime for the users. They are still able to call set_async_runtime() to choose the runtime of their choice. Otherwise, if we do:

    #[cfg(feature = "wasm-bindgen")]
    {
        Arc::new(web_runtime::WasmBindgenRuntime) as Arc<dyn AsyncRuntime>
    }
    #[cfg(feature = "tokio")]
    {
        Arc::new(tokio_runtime::TokioRuntime) as Arc<dyn AsyncRuntime>
    }
    #[cfg(not(any(feature = "tokio", feature = "wasm-bindgen")))]
    {
        Arc::new(standard_runtime::StdRuntime)
    }

For users who target to wasm32-unknown-unknown, without specifying the wasm-bindgen feature, then they'll get a StdRuntime by default, which will only panic in any case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can totally get behind a wasm-bindgen feature name.

Thank you for your patience here :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LarryOsterman Sure, I've pushed a new commit to introduce that feature name, made the wasm dependencies optional, and only bring them by this new feature.

{
Arc::new(standard_runtime::StdRuntime)
Arc::new(web_runtime::WebRuntime) as Arc<dyn AsyncRuntime>
}
#[cfg(feature = "tokio")]
{
Arc::new(tokio_runtime::TokioRuntime) as Arc<dyn AsyncRuntime>
}
#[cfg(not(any(feature = "tokio", target_arch = "wasm32")))]
{
Arc::new(standard_runtime::StdRuntime)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,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))]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems this line is no longer needed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to do #2770 (comment), this is still needed to allow a user to select the StdRuntime even target to wasm32.

fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>> {
fn sleep(&self, duration: Duration) -> TaskFuture {
#[cfg(target_arch = "wasm32")]
{
panic!("sleep is not supported on wasm32")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

use super::{AsyncRuntime, SpawnedTask, TaskFuture};
use crate::time::Duration;
use std::pin::Pin;

/// An [`AsyncRuntime`] using `tokio` based APIs.
pub(crate) struct WebRuntime;

impl AsyncRuntime for WebRuntime {
fn spawn(&self, f: TaskFuture) -> SpawnedTask {
Box::pin(async {
wasm_bindgen_futures::spawn_local(f);
Ok(())
})
}

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;
}
})
}
}
16 changes: 1 addition & 15 deletions sdk/typespec/typespec_client_core/src/sleep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,8 @@

//! Sleep functions.

use crate::time::Duration;
use crate::{async_runtime::get_async_runtime, time::Duration};

#[cfg(not(target_family = "wasm"))]
use crate::async_runtime::get_async_runtime;

#[cfg(not(target_family = "wasm"))]
/// Sleeps for the specified duration using the configured async runtime.
///
/// # Arguments
Expand All @@ -31,13 +27,3 @@ use crate::async_runtime::get_async_runtime;
pub async fn sleep(duration: Duration) {
get_async_runtime().sleep(duration).await
}

#[cfg(target_family = "wasm")]
pub async fn sleep(duration: Duration) {
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;
}
}
Loading