From babe680ba55a5cbc6f8e7017704950015be7a852 Mon Sep 17 00:00:00 2001 From: Larry Osterman Date: Wed, 4 Jun 2025 13:34:34 -0700 Subject: [PATCH 01/11] Fixed #2506, creating an AsyncRuntime trait which can be used by customers to replace the async runtime --- sdk/core/azure_core/src/lib.rs | 8 +- .../src/common/authorizer.rs | 7 +- .../src/async_runtime}/mod.rs | 88 +++++++++++++++---- .../src/async_runtime/standard_runtime.rs} | 57 ++++++++++-- .../src/async_runtime}/tests.rs | 81 ++++++++++++++--- .../src/async_runtime/tokio_runtime.rs} | 10 ++- sdk/typespec/typespec_client_core/src/lib.rs | 4 + .../typespec_client_core/src/sleep/mod.rs | 43 +-------- .../typespec_client_core/src/sleep/thread.rs | 56 ------------ 9 files changed, 216 insertions(+), 138 deletions(-) rename sdk/{core/azure_core/src/task => typespec/typespec_client_core/src/async_runtime}/mod.rs (60%) rename sdk/{core/azure_core/src/task/standard_spawn.rs => typespec/typespec_client_core/src/async_runtime/standard_runtime.rs} (78%) rename sdk/{core/azure_core/src/task => typespec/typespec_client_core/src/async_runtime}/tests.rs (69%) rename sdk/{core/azure_core/src/task/tokio_spawn.rs => typespec/typespec_client_core/src/async_runtime/tokio_runtime.rs} (65%) delete mode 100644 sdk/typespec/typespec_client_core/src/sleep/thread.rs diff --git a/sdk/core/azure_core/src/lib.rs b/sdk/core/azure_core/src/lib.rs index 172ccca9e8..51b640ec94 100644 --- a/sdk/core/azure_core/src/lib.rs +++ b/sdk/core/azure_core/src/lib.rs @@ -15,7 +15,6 @@ pub mod fs; pub mod hmac; pub mod http; pub mod process; -pub mod task; #[cfg(feature = "test")] pub mod test; @@ -31,3 +30,10 @@ pub use typespec_client_core::{ #[cfg(feature = "xml")] pub use typespec_client_core::xml; + +pub use typespec_client_core::get_async_runtime; +pub use typespec_client_core::set_async_runtime; + +pub mod async_runtime { + pub use typespec_client_core::async_runtime::SpawnedTask; +} diff --git a/sdk/eventhubs/azure_messaging_eventhubs/src/common/authorizer.rs b/sdk/eventhubs/azure_messaging_eventhubs/src/common/authorizer.rs index 103a4ff844..70cbfd6247 100644 --- a/sdk/eventhubs/azure_messaging_eventhubs/src/common/authorizer.rs +++ b/sdk/eventhubs/azure_messaging_eventhubs/src/common/authorizer.rs @@ -5,10 +5,11 @@ use super::recoverable_connection::RecoverableConnection; use crate::error::{ErrorKind, EventHubsError}; use async_lock::Mutex as AsyncMutex; use azure_core::{ + async_runtime::SpawnedTask, credentials::{AccessToken, TokenCredential}, error::ErrorKind as AzureErrorKind, + get_async_runtime, http::Url, - task::{new_task_spawner, SpawnedTask}, Result, }; use azure_core_amqp::AmqpClaimsBasedSecurityApis as _; @@ -113,8 +114,8 @@ impl Authorizer { self.authorization_refresher.get_or_init(|| { debug!("Starting authorization refresh task."); let self_clone = self.clone(); - let spawner = new_task_spawner(); - spawner.spawn(Box::pin(self_clone.refresh_tokens_task())) + let async_runtime = get_async_runtime(); + async_runtime.spawn(Box::pin(self_clone.refresh_tokens_task())) }); } else { debug!("Token already exists for path: {path}"); diff --git a/sdk/core/azure_core/src/task/mod.rs b/sdk/typespec/typespec_client_core/src/async_runtime/mod.rs similarity index 60% rename from sdk/core/azure_core/src/task/mod.rs rename to sdk/typespec/typespec_client_core/src/async_runtime/mod.rs index 1209b4fb66..e3cdc5d230 100644 --- a/sdk/core/azure_core/src/task/mod.rs +++ b/sdk/typespec/typespec_client_core/src/async_runtime/mod.rs @@ -29,12 +29,17 @@ //! ``` //! //! -use std::{fmt::Debug, future::Future, pin::Pin, sync::Arc}; +use std::{ + fmt::Debug, + future::Future, + pin::Pin, + sync::{Arc, OnceLock}, +}; -mod standard_spawn; +mod standard_runtime; #[cfg(feature = "tokio")] -mod tokio_spawn; +mod tokio_runtime; #[cfg(test)] mod tests; @@ -61,9 +66,11 @@ pub type SpawnedTask = Pin< pub type SpawnedTask = Pin>> + 'static>>; -/// An async command runner. +/// An Asynchronous Runtime. /// -pub trait TaskSpawner: Send + Sync + Debug { +/// This trait defines the various +/// +pub trait AsyncRuntime: Send + Sync + Debug { /// Spawn a task that executes a given future and returns the output. /// /// # Arguments @@ -76,13 +83,13 @@ pub trait TaskSpawner: Send + Sync + Debug { /// /// # Example /// ``` - /// use azure_core::task::{new_task_spawner, TaskSpawner}; + /// use azure_core::async_runtime::{get_async_runtime, TaskSpawner}; /// use futures::FutureExt; /// /// #[tokio::main] /// async fn main() { - /// let spawner = new_task_spawner(); - /// let future = spawner.spawn(async { + /// let async_runtime = get_async_runtime(); + /// let future = async_runtime.spawn(async { /// // Simulate some work /// std::thread::sleep(std::time::Duration::from_secs(1)); /// }.boxed()); @@ -99,41 +106,86 @@ pub trait TaskSpawner: Send + Sync + Debug { /// that can be awaited. /// fn spawn(&self, f: TaskFuture) -> SpawnedTask; + + fn sleep(&self, duration: std::time::Duration) -> TaskFuture; } -/// Creates a new [`TaskSpawner`] to enable running tasks asynchronously. +static ASYNC_RUNTIME_IMPLEMENTATION: OnceLock> = OnceLock::new(); + +/// Returns an [`AsyncRuntime`] to enable running operations which need to interact with an +/// asynchronous runtime. /// /// /// The implementation depends on the target architecture and the features enabled: -/// - If the `tokio` feature is enabled, it uses a tokio based spawner. -/// - If the `tokio` feature is not enabled and the target architecture is not `wasm32`, it uses a std::thread based spawner. +/// - If the `tokio` feature is enabled, it uses a tokio based spawner and timer. +/// - If the `tokio` feature is not enabled and the target architecture is not `wasm32`, it uses a std::thread based spawner and timer. /// /// # Returns -/// A new instance of a [`TaskSpawner`] which can be used to spawn background tasks. +/// A new instance of a [`AsyncRuntime`] which can be used to spawn background tasks. /// /// # Example /// /// ``` -/// use azure_core::task::{new_task_spawner, TaskSpawner}; +/// use azure_core::get_async_runtime; /// use futures::FutureExt; /// /// #[tokio::main] /// async fn main() { -/// let spawner = new_task_spawner(); -/// let handle = spawner.spawn(async { +/// let async_runtime = get_async_runtime(); +/// let handle = async_runtime.spawn(async { +/// // Simulate some work +/// std::thread::sleep(std::time::Duration::from_secs(1)); +/// }.boxed()); +/// } +/// ``` +/// +pub fn get_async_runtime() -> Arc { + ASYNC_RUNTIME_IMPLEMENTATION + .get_or_init(|| create_async_runtime()) + .clone() +} + +/// Sets the current [`AsyncRuntime`] to enable running operations which need to interact with an +/// asynchronous runtime. +/// +/// +/// # Returns +/// Ok if the async runtime was set successfully, or an error if it has already been set. +/// +/// # Example +/// +/// ``` +/// use azure_core::async_runtime::{get_async_runtime, AsyncRuntime}; +/// use futures::FutureExt; +/// +/// async fn main() { +/// let async_runtime = set_async_runtime(); +/// let handle = async_runtime.spawn(async { /// // Simulate some work /// std::thread::sleep(std::time::Duration::from_secs(1)); /// }.boxed()); /// } /// ``` /// -pub fn new_task_spawner() -> Arc { +pub fn set_async_runtime(runtime: Arc) -> crate::Result<()> { + let result = ASYNC_RUNTIME_IMPLEMENTATION.set(runtime); + if result.is_err() { + Err(crate::Error::message( + crate::error::ErrorKind::Other, + "Async runtime has already been set.", + )) + } else { + Ok(()) + } +} + +fn create_async_runtime() -> Arc { #[cfg(not(feature = "tokio"))] { - Arc::new(standard_spawn::StdSpawner) + Arc::new(standard_runtime::StdRuntime) } #[cfg(feature = "tokio")] { - Arc::new(tokio_spawn::TokioSpawner) as Arc + Arc::new(tokio_runtime::TokioRuntime) as Arc } } diff --git a/sdk/core/azure_core/src/task/standard_spawn.rs b/sdk/typespec/typespec_client_core/src/async_runtime/standard_runtime.rs similarity index 78% rename from sdk/core/azure_core/src/task/standard_spawn.rs rename to sdk/typespec/typespec_client_core/src/async_runtime/standard_runtime.rs index 9710c4cda0..501c5f93fa 100644 --- a/sdk/core/azure_core/src/task/standard_spawn.rs +++ b/sdk/typespec/typespec_client_core/src/async_runtime/standard_runtime.rs @@ -1,18 +1,21 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use super::{SpawnedTask, TaskFuture, TaskSpawner}; +use super::{AsyncRuntime, SpawnedTask, TaskFuture}; + #[cfg(not(target_arch = "wasm32"))] use futures::{executor::LocalPool, task::SpawnExt}; + +use std::sync::atomic::{AtomicBool, Ordering}; #[cfg(not(target_arch = "wasm32"))] use std::{ future, future::Future, pin::Pin, sync::{Arc, Mutex}, - task::Waker, - task::{Context, Poll}, + task::{Context, Poll, Waker}, thread, + time::Duration, }; #[cfg(not(target_arch = "wasm32"))] use tracing::debug; @@ -78,11 +81,11 @@ impl Future for ThreadJoinFuture { } } -/// A [`TaskSpawner`] using [`std::thread::spawn`]. +/// An [`AsyncRuntime`] using [`std::thread::spawn`]. #[derive(Debug)] -pub struct StdSpawner; +pub struct StdRuntime; -impl TaskSpawner for StdSpawner { +impl AsyncRuntime for StdRuntime { #[cfg_attr(target_arch = "wasm32", allow(unused_variables))] fn spawn(&self, f: TaskFuture) -> SpawnedTask { #[cfg(target_arch = "wasm32")] @@ -143,4 +146,46 @@ impl TaskSpawner for StdSpawner { Box::pin(join_future) } } + + /// Creates a future that resolves after a specified duration of time. + /// + /// Uses a simple thread based implementation for sleep. A more efficient + /// implementation is available by using the `tokio` crate feature. + fn sleep(&self, duration: Duration) -> TaskFuture { + Box::pin(Sleep { + signal: None, + duration, + }) + } +} + +#[derive(Debug)] +pub struct Sleep { + signal: Option>, + duration: Duration, +} + +impl Future for Sleep { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if let Some(signal) = &self.signal { + if signal.load(Ordering::Acquire) { + Poll::Ready(()) + } else { + Poll::Pending + } + } else { + let signal = Arc::new(AtomicBool::new(false)); + let waker = cx.waker().clone(); + let duration = self.duration; + self.get_mut().signal = Some(signal.clone()); + thread::spawn(move || { + thread::sleep(duration); + signal.store(true, Ordering::Release); + waker.wake(); + }); + Poll::Pending + } + } } diff --git a/sdk/core/azure_core/src/task/tests.rs b/sdk/typespec/typespec_client_core/src/async_runtime/tests.rs similarity index 69% rename from sdk/core/azure_core/src/task/tests.rs rename to sdk/typespec/typespec_client_core/src/async_runtime/tests.rs index bd53de4e80..33eac498cd 100644 --- a/sdk/core/azure_core/src/task/tests.rs +++ b/sdk/typespec/typespec_client_core/src/async_runtime/tests.rs @@ -9,11 +9,11 @@ use std::time::Duration; #[cfg(not(feature = "tokio"))] #[test] fn test_task_spawner_execution() { - let spawner = new_task_spawner(); + let runtime = get_async_runtime(); let result = Arc::new(Mutex::new(false)); let result_clone = Arc::clone(&result); - let handle = spawner.spawn( + let handle = runtime.spawn( async move { // Simulate some work crate::sleep::sleep(Duration::from_millis(50)).await; @@ -32,11 +32,11 @@ fn test_task_spawner_execution() { #[cfg(feature = "tokio")] #[tokio::test] async fn tokio_task_spawner_execution() { - let spawner = new_task_spawner(); + let async_runtime = get_async_runtime(); let result = Arc::new(Mutex::new(false)); let result_clone = Arc::clone(&result); - let handle = spawner.spawn( + let handle = async_runtime.spawn( async move { // Simulate some work crate::sleep::sleep(Duration::from_millis(50)).await; @@ -55,7 +55,7 @@ async fn tokio_task_spawner_execution() { #[cfg(feature = "tokio")] #[tokio::test] async fn test_tokio_specific_handling() { - let spawner = Arc::new(tokio_spawn::TokioSpawner); + let spawner = Arc::new(tokio_runtime::TokioRuntime); let task_completed = Arc::new(Mutex::new(false)); let task_completed_clone = Arc::clone(&task_completed); @@ -73,7 +73,7 @@ async fn test_tokio_specific_handling() { #[cfg(feature = "tokio")] #[tokio::test] async fn tokio_multiple_tasks() { - let spawner = Arc::new(tokio_spawn::TokioSpawner); + let spawner = Arc::new(tokio_runtime::TokioRuntime); let counter = Arc::new(Mutex::new(0)); let mut handles = Vec::new(); @@ -101,7 +101,7 @@ async fn tokio_multiple_tasks() { #[cfg(feature = "tokio")] #[tokio::test] async fn tokio_task_execution() { - let spawner = Arc::new(tokio_spawn::TokioSpawner); + let spawner = Arc::new(tokio_runtime::TokioRuntime); let result = Arc::new(Mutex::new(false)); let result_clone = Arc::clone(&result); @@ -126,7 +126,7 @@ async fn tokio_task_execution() { // When the "tokio" feature is not enabled, it uses std::thread::sleep which does not require a tokio runtime. #[test] fn std_specific_handling() { - let spawner = Arc::new(standard_spawn::StdSpawner); + let spawner = Arc::new(standard_runtime::StdRuntime); let task_completed = Arc::new(Mutex::new(false)); let task_completed_clone = Arc::clone(&task_completed); @@ -145,7 +145,7 @@ fn std_specific_handling() { #[test] fn std_multiple_tasks() { - let spawner = Arc::new(standard_spawn::StdSpawner); + let spawner = Arc::new(standard_runtime::StdRuntime); let counter = Arc::new(Mutex::new(0)); let mut handles = Vec::new(); @@ -175,11 +175,11 @@ fn std_multiple_tasks() { #[cfg(not(feature = "tokio"))] #[test] fn std_task_execution() { - let spawner = Arc::new(standard_spawn::StdSpawner); + let runtime = Arc::new(standard_runtime::StdRuntime); let result = Arc::new(Mutex::new(false)); let result_clone = Arc::clone(&result); - let handle = spawner.spawn( + let handle = runtime.spawn( async move { // Simulate some work crate::sleep::sleep(Duration::from_millis(500)).await; @@ -195,3 +195,62 @@ fn std_task_execution() { // Verify the task executed assert!(*result.lock().unwrap()); } + +// 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"))] +#[tokio::test] +async fn test_timeout() { + use super::*; + use std::time::Duration; + use tokio::task::JoinSet; + + let async_runtime = get_async_runtime(); + let mut join_set = JoinSet::default(); + let total = 10000; + for _i in 0..total { + let runtime = async_runtime.clone(); + join_set.spawn(async move { + runtime.sleep(Duration::from_millis(10)).await; + }); + } + + loop { + let res = + tokio::time::timeout(std::time::Duration::from_secs(10), join_set.join_next()).await; + assert!(res.is_ok()); + if let Ok(None) = res { + break; + } + } +} + +#[tokio::test] +async fn test_sleep() { + let runtime = get_async_runtime(); + let start = std::time::Instant::now(); + runtime.sleep(Duration::from_millis(100)).await; + let elapsed = start.elapsed(); + assert!(elapsed >= Duration::from_millis(100)); +} + +#[test] +fn test_get_runtime() { + // Ensure that the runtime can be retrieved without panicking + let _runtime = get_async_runtime(); +} + +// This test is ignored because by default, cargo test runs all tests in parallel, but +// this test sets the runtime, which will fail if run in parallel with other tests that +// get the runtime. +#[test] +#[ignore = "Skipping the runtime set test to avoid conflicts with parallel test execution"] +fn test_set_runtime() { + // Ensure that the runtime can be set without panicking + let runtime = Arc::new(standard_runtime::StdRuntime); + set_async_runtime(runtime.clone()).unwrap(); + + // Ensure that setting the runtime again fails + set_async_runtime(runtime.clone()).unwrap_err(); +} diff --git a/sdk/core/azure_core/src/task/tokio_spawn.rs b/sdk/typespec/typespec_client_core/src/async_runtime/tokio_runtime.rs similarity index 65% rename from sdk/core/azure_core/src/task/tokio_spawn.rs rename to sdk/typespec/typespec_client_core/src/async_runtime/tokio_runtime.rs index 6d48f007b1..a019c91945 100644 --- a/sdk/core/azure_core/src/task/tokio_spawn.rs +++ b/sdk/typespec/typespec_client_core/src/async_runtime/tokio_runtime.rs @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use super::{SpawnedTask, TaskFuture, TaskSpawner}; +use super::{AsyncRuntime, SpawnedTask, TaskFuture}; use std::fmt::Debug; /// A [`TaskSpawner`] using [`tokio::spawn`]. #[derive(Debug)] -pub struct TokioSpawner; +pub struct TokioRuntime; -impl TaskSpawner for TokioSpawner { +impl AsyncRuntime for TokioRuntime { fn spawn(&self, f: TaskFuture) -> SpawnedTask { let handle = ::tokio::spawn(f); Box::pin(async move { @@ -17,4 +17,8 @@ impl TaskSpawner for TokioSpawner { .map_err(|e| Box::new(e) as Box) }) } + + fn sleep(&self, duration: std::time::Duration) -> TaskFuture { + Box::pin(::tokio::time::sleep(duration)) + } } diff --git a/sdk/typespec/typespec_client_core/src/lib.rs b/sdk/typespec/typespec_client_core/src/lib.rs index 8281ae3d9e..e7e2aca221 100644 --- a/sdk/typespec/typespec_client_core/src/lib.rs +++ b/sdk/typespec/typespec_client_core/src/lib.rs @@ -6,6 +6,7 @@ #[macro_use] mod macros; +pub mod async_runtime; pub mod base64; pub mod date; pub mod error; @@ -23,3 +24,6 @@ pub mod xml; pub use crate::error::{Error, Result}; pub use bytes::Bytes; pub use uuid::Uuid; + +pub use async_runtime::{get_async_runtime, set_async_runtime}; +pub use sleep::sleep; diff --git a/sdk/typespec/typespec_client_core/src/sleep/mod.rs b/sdk/typespec/typespec_client_core/src/sleep/mod.rs index f59a29afe8..7317e7670d 100644 --- a/sdk/typespec/typespec_client_core/src/sleep/mod.rs +++ b/sdk/typespec/typespec_client_core/src/sleep/mod.rs @@ -3,45 +3,8 @@ //! Sleep functions. -#[cfg(any(not(feature = "tokio"), target_arch = "wasm32"))] -mod thread; +use crate::get_async_runtime; -#[cfg(any(not(feature = "tokio"), target_arch = "wasm32"))] -pub use self::thread::{sleep, Sleep}; - -#[cfg(all(feature = "tokio", not(target_arch = "wasm32")))] -pub use tokio::time::{sleep, Sleep}; - -// Unit tests -#[cfg(test)] -mod tests { - - // 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"))] - #[tokio::test] - async fn test_timeout() { - use super::*; - use std::time::Duration; - use tokio::task::JoinSet; - - let mut join_set = JoinSet::default(); - let total = 10000; - for _i in 0..total { - join_set.spawn(async move { - sleep(Duration::from_millis(10)).await; - }); - } - - loop { - let res = - tokio::time::timeout(std::time::Duration::from_secs(10), join_set.join_next()) - .await; - assert!(res.is_ok()); - if let Ok(None) = res { - break; - } - } - } +pub async fn sleep(duration: std::time::Duration) { + get_async_runtime().sleep(duration).await } diff --git a/sdk/typespec/typespec_client_core/src/sleep/thread.rs b/sdk/typespec/typespec_client_core/src/sleep/thread.rs deleted file mode 100644 index 1e8684f83b..0000000000 --- a/sdk/typespec/typespec_client_core/src/sleep/thread.rs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -use futures::Future; -use std::{ - pin::Pin, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, - task::{Context, Poll}, - thread, - time::Duration, -}; - -/// Creates a future that resolves after a specified duration of time. -/// -/// Uses a simple thread based implementation for sleep. A more efficient -/// implementation is available by using the `tokio` crate feature. -pub fn sleep(duration: Duration) -> Sleep { - Sleep { - signal: None, - duration, - } -} - -#[derive(Debug)] -pub struct Sleep { - signal: Option>, - duration: Duration, -} - -impl Future for Sleep { - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if let Some(signal) = &self.signal { - if signal.load(Ordering::Acquire) { - Poll::Ready(()) - } else { - Poll::Pending - } - } else { - let signal = Arc::new(AtomicBool::new(false)); - let waker = cx.waker().clone(); - let duration = self.duration; - self.get_mut().signal = Some(signal.clone()); - thread::spawn(move || { - thread::sleep(duration); - signal.store(true, Ordering::Release); - waker.wake(); - }); - Poll::Pending - } - } -} From 91ea3433fc37407a11be50aa6856a310244d2a0b Mon Sep 17 00:00:00 2001 From: Larry Osterman Date: Wed, 4 Jun 2025 14:51:38 -0700 Subject: [PATCH 02/11] CI pipeline fixes --- sdk/core/azure_core/src/lib.rs | 2 +- .../typespec_client_core/CHANGELOG.md | 26 +++++----- .../src/async_runtime/mod.rs | 50 ++++++++++++------- .../src/async_runtime/standard_runtime.rs | 18 +++++-- .../src/async_runtime/tokio_runtime.rs | 6 +-- .../src/{sleep/mod.rs => sleep.rs} | 0 6 files changed, 62 insertions(+), 40 deletions(-) rename sdk/typespec/typespec_client_core/src/{sleep/mod.rs => sleep.rs} (100%) diff --git a/sdk/core/azure_core/src/lib.rs b/sdk/core/azure_core/src/lib.rs index 51b640ec94..5f9c4942a4 100644 --- a/sdk/core/azure_core/src/lib.rs +++ b/sdk/core/azure_core/src/lib.rs @@ -35,5 +35,5 @@ pub use typespec_client_core::get_async_runtime; pub use typespec_client_core::set_async_runtime; pub mod async_runtime { - pub use typespec_client_core::async_runtime::SpawnedTask; + pub use typespec_client_core::async_runtime::{AsyncRuntime, SpawnedTask, TaskFuture}; } diff --git a/sdk/typespec/typespec_client_core/CHANGELOG.md b/sdk/typespec/typespec_client_core/CHANGELOG.md index cf6c29b919..e37b674929 100644 --- a/sdk/typespec/typespec_client_core/CHANGELOG.md +++ b/sdk/typespec/typespec_client_core/CHANGELOG.md @@ -4,8 +4,10 @@ ### Features Added -- Added `#[safe]` attribute helper for `SafeDebug` derive macro to show or hide types and members as appropriate. -- Added module `fmt::as_string` which is used to (de)serialize types in string format. +- Added `#[safe]` attribute helper for `SafeDebug` derive macro to show or hide types and members as appropriate. +- Added module `fmt::as_string` which is used to (de)serialize types in string format. +- Added `get_async_runtime()` and `set_async_runtime()` to allow customers to replace + the default asynchronous runtime with another. ### Breaking Changes @@ -17,29 +19,29 @@ ### Breaking Changes -- The `reqwest_rustls` feature enables `rustls-tls-native-roots-no-provider` instead of `rustls-tls-native-roots` to remove the dependency on the `ring` crate. +- The `reqwest_rustls` feature enables `rustls-tls-native-roots-no-provider` instead of `rustls-tls-native-roots` to remove the dependency on the `ring` crate. ### Other Changes -- Deriving `SafeDebug` formats non-exhaustive types by default. Enable `debug` feature to format normal `Debug` output. -- Updated dependencies. +- Deriving `SafeDebug` formats non-exhaustive types by default. Enable `debug` feature to format normal `Debug` output. +- Updated dependencies. ## 0.2.0 (2025-04-08) ### Breaking Changes -- Consolidated all the `tokio` features into a single feature named `tokio`. Traits remain separate but `tokio` support is enabled with a single feature. -- Removed `Header` re-export from `http` module. It is still defined in the `http::headers` module. -- Removed `http-types` dependency and implemented `Method` instead. -- Removed `Pager`. -- Removed `parsing` module. +- Consolidated all the `tokio` features into a single feature named `tokio`. Traits remain separate but `tokio` support is enabled with a single feature. +- Removed `Header` re-export from `http` module. It is still defined in the `http::headers` module. +- Removed `http-types` dependency and implemented `Method` instead. +- Removed `Pager`. +- Removed `parsing` module. ### Other Changes -- Use `std::sync::LazyLock` added in rustc 1.80 instead of `once_cell::sync::Lazy`. +- Use `std::sync::LazyLock` added in rustc 1.80 instead of `once_cell::sync::Lazy`. ## 0.1.0 (2025-02-18) ### Features Added -- Initial supported release. +- Initial supported release. 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 e3cdc5d230..965a409ae3 100644 --- a/sdk/typespec/typespec_client_core/src/async_runtime/mod.rs +++ b/sdk/typespec/typespec_client_core/src/async_runtime/mod.rs @@ -11,13 +11,13 @@ //! Example usage: //! //! ``` -//! use azure_core::task::{new_task_spawner, TaskSpawner}; +//! use typespec_client_core::get_async_runtime; //! use futures::FutureExt; //! //! #[tokio::main] //! async fn main() { -//! let spawner = new_task_spawner(); -//! let handle = spawner.spawn(async { +//! let async_runtime = get_async_runtime(); +//! let handle = async_runtime.spawn(async { //! // Simulate some work //! std::thread::sleep(std::time::Duration::from_secs(1)); //! }.boxed()); @@ -30,7 +30,6 @@ //! //! use std::{ - fmt::Debug, future::Future, pin::Pin, sync::{Arc, OnceLock}, @@ -45,11 +44,11 @@ mod tokio_runtime; mod tests; #[cfg(not(target_arch = "wasm32"))] -pub(crate) type TaskFuture = Pin + Send + 'static>>; +pub type TaskFuture = Pin + Send + 'static>>; // WASM32 does not support `Send` futures, so we use a non-Send future type. #[cfg(target_arch = "wasm32")] -pub(crate) type TaskFuture = Pin + 'static>>; +pub type TaskFuture = Pin + 'static>>; /// A `SpawnedTask` is a future that represents a running task. /// It can be awaited to block until the task has completed. @@ -70,7 +69,7 @@ pub type SpawnedTask = /// /// This trait defines the various /// -pub trait AsyncRuntime: Send + Sync + Debug { +pub trait AsyncRuntime: Send + Sync { /// Spawn a task that executes a given future and returns the output. /// /// # Arguments @@ -83,7 +82,7 @@ pub trait AsyncRuntime: Send + Sync + Debug { /// /// # Example /// ``` - /// use azure_core::async_runtime::{get_async_runtime, TaskSpawner}; + /// use typespec_client_core::get_async_runtime; /// use futures::FutureExt; /// /// #[tokio::main] @@ -121,12 +120,12 @@ static ASYNC_RUNTIME_IMPLEMENTATION: OnceLock> = OnceLock: /// - If the `tokio` feature is not enabled and the target architecture is not `wasm32`, it uses a std::thread based spawner and timer. /// /// # Returns -/// A new instance of a [`AsyncRuntime`] which can be used to spawn background tasks. +/// An instance of a [`AsyncRuntime`] which can be used to spawn background tasks or perform other asynchronous operations. /// /// # Example /// /// ``` -/// use azure_core::get_async_runtime; +/// use typespec_client_core::get_async_runtime; /// use futures::FutureExt; /// /// #[tokio::main] @@ -155,16 +154,31 @@ pub fn get_async_runtime() -> Arc { /// # Example /// /// ``` -/// use azure_core::async_runtime::{get_async_runtime, AsyncRuntime}; +/// use typespec_client_core::{ +/// set_async_runtime, +/// async_runtime::{AsyncRuntime, TaskFuture, SpawnedTask}}; +/// use std::sync::Arc; /// use futures::FutureExt; /// -/// async fn main() { -/// let async_runtime = set_async_runtime(); -/// let handle = async_runtime.spawn(async { -/// // Simulate some work -/// std::thread::sleep(std::time::Duration::from_secs(1)); -/// }.boxed()); -/// } +/// struct CustomRuntime; +/// +/// impl AsyncRuntime for CustomRuntime { +/// fn spawn(&self, f: TaskFuture) -> SpawnedTask { +/// // Custom implementation for spawning tasks +/// Box::pin(async move { +/// f.await; +/// Ok(()) +/// }) +/// } +/// fn sleep(&self, duration: std::time::Duration) -> TaskFuture { +/// // Custom implementation for sleeping +/// Box::pin(async move { +/// std::thread::sleep(duration); +/// }) +/// } +/// } +/// +/// set_async_runtime(Arc::new(CustomRuntime)).expect("Failed to set async runtime"); /// ``` /// pub fn set_async_runtime(runtime: Arc) -> crate::Result<()> { 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 501c5f93fa..3ca47e394b 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 @@ -6,6 +6,7 @@ use super::{AsyncRuntime, SpawnedTask, TaskFuture}; #[cfg(not(target_arch = "wasm32"))] use futures::{executor::LocalPool, task::SpawnExt}; +#[cfg(not(target_arch = "wasm32"))] use std::sync::atomic::{AtomicBool, Ordering}; #[cfg(not(target_arch = "wasm32"))] use std::{ @@ -15,7 +16,6 @@ use std::{ sync::{Arc, Mutex}, task::{Context, Poll, Waker}, thread, - time::Duration, }; #[cfg(not(target_arch = "wasm32"))] use tracing::debug; @@ -82,8 +82,8 @@ impl Future for ThreadJoinFuture { } /// An [`AsyncRuntime`] using [`std::thread::spawn`]. -#[derive(Debug)] -pub struct StdRuntime; +#[allow(dead_code)] +pub(crate) struct StdRuntime; impl AsyncRuntime for StdRuntime { #[cfg_attr(target_arch = "wasm32", allow(unused_variables))] @@ -151,7 +151,13 @@ impl AsyncRuntime for StdRuntime { /// /// Uses a simple thread based implementation for sleep. A more efficient /// implementation is available by using the `tokio` crate feature. - fn sleep(&self, duration: Duration) -> TaskFuture { + #[cfg_attr(target_arch = "wasm32", allow(unused_variables))] + fn sleep(&self, duration: std::time::Duration) -> TaskFuture { + #[cfg(target_arch = "wasm32")] + { + panic!("std::thread::spawn is not supported on wasm32") + } + #[cfg(not(target_arch = "wasm32"))] Box::pin(Sleep { signal: None, duration, @@ -160,11 +166,13 @@ impl AsyncRuntime for StdRuntime { } #[derive(Debug)] +#[cfg(not(target_arch = "wasm32"))] pub struct Sleep { signal: Option>, - duration: Duration, + duration: std::time::Duration, } +#[cfg(not(target_arch = "wasm32"))] impl Future for Sleep { type Output = (); diff --git a/sdk/typespec/typespec_client_core/src/async_runtime/tokio_runtime.rs b/sdk/typespec/typespec_client_core/src/async_runtime/tokio_runtime.rs index a019c91945..3e222262a8 100644 --- a/sdk/typespec/typespec_client_core/src/async_runtime/tokio_runtime.rs +++ b/sdk/typespec/typespec_client_core/src/async_runtime/tokio_runtime.rs @@ -2,11 +2,9 @@ // Licensed under the MIT License. use super::{AsyncRuntime, SpawnedTask, TaskFuture}; -use std::fmt::Debug; -/// A [`TaskSpawner`] using [`tokio::spawn`]. -#[derive(Debug)] -pub struct TokioRuntime; +/// An [`AsyncRuntime`] using `tokio` based APIs. +pub(crate) struct TokioRuntime; impl AsyncRuntime for TokioRuntime { fn spawn(&self, f: TaskFuture) -> SpawnedTask { diff --git a/sdk/typespec/typespec_client_core/src/sleep/mod.rs b/sdk/typespec/typespec_client_core/src/sleep.rs similarity index 100% rename from sdk/typespec/typespec_client_core/src/sleep/mod.rs rename to sdk/typespec/typespec_client_core/src/sleep.rs From 11c2d4d4d0222c487c96eb2c43dc6244aa642b73 Mon Sep 17 00:00:00 2001 From: Larry Osterman Date: Wed, 4 Jun 2025 15:06:43 -0700 Subject: [PATCH 03/11] wasm32 clippy fixes --- sdk/core/azure_core/CHANGELOG.md | 2 ++ .../src/async_runtime/mod.rs | 5 ++++- .../src/async_runtime/standard_runtime.rs | 8 +++++--- .../src/async_runtime/tokio_runtime.rs | 6 +++++- .../typespec_client_core/src/sleep.rs | 20 +++++++++++++++++++ 5 files changed, 36 insertions(+), 5 deletions(-) diff --git a/sdk/core/azure_core/CHANGELOG.md b/sdk/core/azure_core/CHANGELOG.md index fac5388800..c42cbcf009 100644 --- a/sdk/core/azure_core/CHANGELOG.md +++ b/sdk/core/azure_core/CHANGELOG.md @@ -5,6 +5,8 @@ ### Features Added - Added `#[safe]` attribute helper for `SafeDebug` derive macro to show or hide types and members as appropriate. +- Added `get_async_runtime()` and `set_async_runtime()` to allow customers to replace +the asynchronous runtime used by the Azure SDK. ### Breaking Changes 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 965a409ae3..27db5cecb5 100644 --- a/sdk/typespec/typespec_client_core/src/async_runtime/mod.rs +++ b/sdk/typespec/typespec_client_core/src/async_runtime/mod.rs @@ -106,7 +106,10 @@ pub trait AsyncRuntime: Send + Sync { /// fn spawn(&self, f: TaskFuture) -> SpawnedTask; - fn sleep(&self, duration: std::time::Duration) -> TaskFuture; + fn sleep( + &self, + duration: std::time::Duration, + ) -> Pin + Send + 'static>>; } static ASYNC_RUNTIME_IMPLEMENTATION: OnceLock> = OnceLock::new(); 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 3ca47e394b..9302cb69e2 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 @@ -11,12 +11,11 @@ use std::sync::atomic::{AtomicBool, Ordering}; #[cfg(not(target_arch = "wasm32"))] use std::{ future, - future::Future, - pin::Pin, sync::{Arc, Mutex}, task::{Context, Poll, Waker}, thread, }; +use std::{future::Future, pin::Pin}; #[cfg(not(target_arch = "wasm32"))] use tracing::debug; @@ -152,7 +151,10 @@ 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: std::time::Duration) -> TaskFuture { + fn sleep( + &self, + duration: std::time::Duration, + ) -> Pin + Send + 'static>> { #[cfg(target_arch = "wasm32")] { panic!("std::thread::spawn is not supported on wasm32") diff --git a/sdk/typespec/typespec_client_core/src/async_runtime/tokio_runtime.rs b/sdk/typespec/typespec_client_core/src/async_runtime/tokio_runtime.rs index 3e222262a8..e9b19bd55d 100644 --- a/sdk/typespec/typespec_client_core/src/async_runtime/tokio_runtime.rs +++ b/sdk/typespec/typespec_client_core/src/async_runtime/tokio_runtime.rs @@ -2,6 +2,7 @@ // Licensed under the MIT License. use super::{AsyncRuntime, SpawnedTask, TaskFuture}; +use std::pin::Pin; /// An [`AsyncRuntime`] using `tokio` based APIs. pub(crate) struct TokioRuntime; @@ -16,7 +17,10 @@ impl AsyncRuntime for TokioRuntime { }) } - fn sleep(&self, duration: std::time::Duration) -> TaskFuture { + fn sleep( + &self, + duration: std::time::Duration, + ) -> Pin + Send>> { Box::pin(::tokio::time::sleep(duration)) } } diff --git a/sdk/typespec/typespec_client_core/src/sleep.rs b/sdk/typespec/typespec_client_core/src/sleep.rs index 7317e7670d..5f78389336 100644 --- a/sdk/typespec/typespec_client_core/src/sleep.rs +++ b/sdk/typespec/typespec_client_core/src/sleep.rs @@ -5,6 +5,26 @@ use crate::get_async_runtime; +/// Sleeps for the specified duration using the configured async runtime. +/// +/// # Arguments +/// * `duration` - The duration to sleep for. +/// +/// # Returns +/// A future that resolves when the sleep duration has elapsed. +/// +/// # Example +/// ``` +/// use typespec_client_core::sleep; +/// use std::time::Duration; +/// +/// #[tokio::main] +/// async fn main() { +/// // Sleep for 1 second +/// sleep(Duration::from_secs(1)).await; +/// println!("Slept for 1 second"); +/// } +/// ``` pub async fn sleep(duration: std::time::Duration) { get_async_runtime().sleep(duration).await } From 6499f20c6949cd52b0fc3cd42ad185eade79f2bb Mon Sep 17 00:00:00 2001 From: Larry Osterman Date: Wed, 4 Jun 2025 15:07:44 -0700 Subject: [PATCH 04/11] Fixed typo --- .../typespec_client_core/src/async_runtime/standard_runtime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9302cb69e2..a71a4885fc 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 @@ -157,7 +157,7 @@ impl AsyncRuntime for StdRuntime { ) -> Pin + Send + 'static>> { #[cfg(target_arch = "wasm32")] { - panic!("std::thread::spawn is not supported on wasm32") + panic!("sleep is not supported on wasm32") } #[cfg(not(target_arch = "wasm32"))] Box::pin(Sleep { From d1a62393c142e6d219545fd82a64ade739fbd6b8 Mon Sep 17 00:00:00 2001 From: Larry Osterman Date: Wed, 4 Jun 2025 15:12:50 -0700 Subject: [PATCH 05/11] Minor test cleanup --- .../src/async_runtime/mod.rs | 13 ++++--------- .../src/async_runtime/tests.rs | 17 ++++++++++++++++- 2 files changed, 20 insertions(+), 10 deletions(-) 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 27db5cecb5..e185e89571 100644 --- a/sdk/typespec/typespec_client_core/src/async_runtime/mod.rs +++ b/sdk/typespec/typespec_client_core/src/async_runtime/mod.rs @@ -150,6 +150,8 @@ pub fn get_async_runtime() -> Arc { /// Sets the current [`AsyncRuntime`] to enable running operations which need to interact with an /// asynchronous runtime. /// +/// # Arguments +/// * `runtime` - An instance of a type that implements the [`AsyncRuntime`] trait. /// /// # Returns /// Ok if the async runtime was set successfully, or an error if it has already been set. @@ -167,17 +169,10 @@ pub fn get_async_runtime() -> Arc { /// /// impl AsyncRuntime for CustomRuntime { /// fn spawn(&self, f: TaskFuture) -> SpawnedTask { -/// // Custom implementation for spawning tasks -/// Box::pin(async move { -/// f.await; -/// Ok(()) -/// }) +/// unimplemented!("Custom spawn not implemented"); /// } /// fn sleep(&self, duration: std::time::Duration) -> TaskFuture { -/// // Custom implementation for sleeping -/// Box::pin(async move { -/// std::thread::sleep(duration); -/// }) +/// unimplemented!("Custom sleep not implemented"); /// } /// } /// 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 33eac498cd..9259b62b39 100644 --- a/sdk/typespec/typespec_client_core/src/async_runtime/tests.rs +++ b/sdk/typespec/typespec_client_core/src/async_runtime/tests.rs @@ -241,14 +241,29 @@ fn test_get_runtime() { let _runtime = get_async_runtime(); } +struct DummyRuntime; + +impl AsyncRuntime for DummyRuntime { + fn spawn(&self, _f: TaskFuture) -> SpawnedTask { + unimplemented!("DummyRuntime does not support spawning tasks"); + } + + fn sleep( + &self, + _duration: std::time::Duration, + ) -> Pin + Send + 'static>> { + unimplemented!("DummyRuntime does not support sleeping"); + } +} + // This test is ignored because by default, cargo test runs all tests in parallel, but // this test sets the runtime, which will fail if run in parallel with other tests that // get the runtime. #[test] #[ignore = "Skipping the runtime set test to avoid conflicts with parallel test execution"] fn test_set_runtime() { + let runtime = Arc::new(DummyRuntime); // Ensure that the runtime can be set without panicking - let runtime = Arc::new(standard_runtime::StdRuntime); set_async_runtime(runtime.clone()).unwrap(); // Ensure that setting the runtime again fails From 5e2af59e14a6cc28f50c8d0462367d70f022783a Mon Sep 17 00:00:00 2001 From: Larry Osterman Date: Wed, 4 Jun 2025 15:19:24 -0700 Subject: [PATCH 06/11] PR feedback --- sdk/core/azure_core/src/lib.rs | 5 +---- sdk/typespec/typespec_client_core/src/lib.rs | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/sdk/core/azure_core/src/lib.rs b/sdk/core/azure_core/src/lib.rs index 5f9c4942a4..f9c61437bc 100644 --- a/sdk/core/azure_core/src/lib.rs +++ b/sdk/core/azure_core/src/lib.rs @@ -31,9 +31,6 @@ pub use typespec_client_core::{ #[cfg(feature = "xml")] pub use typespec_client_core::xml; -pub use typespec_client_core::get_async_runtime; -pub use typespec_client_core::set_async_runtime; - pub mod async_runtime { - pub use typespec_client_core::async_runtime::{AsyncRuntime, SpawnedTask, TaskFuture}; + pub use typespec_client_core::async_runtime; } diff --git a/sdk/typespec/typespec_client_core/src/lib.rs b/sdk/typespec/typespec_client_core/src/lib.rs index e7e2aca221..6f2e832cd2 100644 --- a/sdk/typespec/typespec_client_core/src/lib.rs +++ b/sdk/typespec/typespec_client_core/src/lib.rs @@ -25,5 +25,4 @@ pub use crate::error::{Error, Result}; pub use bytes::Bytes; pub use uuid::Uuid; -pub use async_runtime::{get_async_runtime, set_async_runtime}; pub use sleep::sleep; From a84e9886a448a7b9343008a3722ecd5a50ff7a2c Mon Sep 17 00:00:00 2001 From: Larry Osterman Date: Wed, 4 Jun 2025 15:24:29 -0700 Subject: [PATCH 07/11] PR feedback --- sdk/core/azure_core/src/lib.rs | 6 +----- .../azure_messaging_eventhubs/src/common/authorizer.rs | 3 +-- sdk/typespec/typespec_client_core/src/sleep.rs | 2 +- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/sdk/core/azure_core/src/lib.rs b/sdk/core/azure_core/src/lib.rs index f9c61437bc..90c1a7a87f 100644 --- a/sdk/core/azure_core/src/lib.rs +++ b/sdk/core/azure_core/src/lib.rs @@ -23,14 +23,10 @@ pub use constants::*; // Re-export modules in typespec_client_core such that azure_core-based crates don't need to reference it directly. pub use typespec_client_core::{ - base64, create_enum, create_extensible_enum, date, + async_runtime, base64, create_enum, create_extensible_enum, date, error::{self, Error, Result}, fmt, json, sleep, stream, Bytes, Uuid, }; #[cfg(feature = "xml")] pub use typespec_client_core::xml; - -pub mod async_runtime { - pub use typespec_client_core::async_runtime; -} diff --git a/sdk/eventhubs/azure_messaging_eventhubs/src/common/authorizer.rs b/sdk/eventhubs/azure_messaging_eventhubs/src/common/authorizer.rs index 70cbfd6247..6b656845b9 100644 --- a/sdk/eventhubs/azure_messaging_eventhubs/src/common/authorizer.rs +++ b/sdk/eventhubs/azure_messaging_eventhubs/src/common/authorizer.rs @@ -5,10 +5,9 @@ use super::recoverable_connection::RecoverableConnection; use crate::error::{ErrorKind, EventHubsError}; use async_lock::Mutex as AsyncMutex; use azure_core::{ - async_runtime::SpawnedTask, + async_runtime::{get_async_runtime, SpawnedTask}, credentials::{AccessToken, TokenCredential}, error::ErrorKind as AzureErrorKind, - get_async_runtime, http::Url, Result, }; diff --git a/sdk/typespec/typespec_client_core/src/sleep.rs b/sdk/typespec/typespec_client_core/src/sleep.rs index 5f78389336..0a850aee0b 100644 --- a/sdk/typespec/typespec_client_core/src/sleep.rs +++ b/sdk/typespec/typespec_client_core/src/sleep.rs @@ -3,7 +3,7 @@ //! Sleep functions. -use crate::get_async_runtime; +use crate::async_runtime::get_async_runtime; /// Sleeps for the specified duration using the configured async runtime. /// From 24336172c2aad5a1811f27b7bed7fee589af4ab1 Mon Sep 17 00:00:00 2001 From: Larry Osterman Date: Wed, 4 Jun 2025 15:43:39 -0700 Subject: [PATCH 08/11] doctest fixes --- .../typespec_client_core/src/async_runtime/mod.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) 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 e185e89571..b1720c97f5 100644 --- a/sdk/typespec/typespec_client_core/src/async_runtime/mod.rs +++ b/sdk/typespec/typespec_client_core/src/async_runtime/mod.rs @@ -11,7 +11,7 @@ //! Example usage: //! //! ``` -//! use typespec_client_core::get_async_runtime; +//! use typespec_client_core::async_runtime::get_async_runtime; //! use futures::FutureExt; //! //! #[tokio::main] @@ -82,7 +82,7 @@ pub trait AsyncRuntime: Send + Sync { /// /// # Example /// ``` - /// use typespec_client_core::get_async_runtime; + /// use typespec_client_core::async_runtime::get_async_runtime; /// use futures::FutureExt; /// /// #[tokio::main] @@ -128,7 +128,7 @@ static ASYNC_RUNTIME_IMPLEMENTATION: OnceLock> = OnceLock: /// # Example /// /// ``` -/// use typespec_client_core::get_async_runtime; +/// use typespec_client_core::async_runtime::get_async_runtime; /// use futures::FutureExt; /// /// #[tokio::main] @@ -159,9 +159,8 @@ pub fn get_async_runtime() -> Arc { /// # Example /// /// ``` -/// use typespec_client_core::{ -/// set_async_runtime, -/// async_runtime::{AsyncRuntime, TaskFuture, SpawnedTask}}; +/// use typespec_client_core::async_runtime::{ +/// set_async_runtime, AsyncRuntime, TaskFuture, SpawnedTask}; /// use std::sync::Arc; /// use futures::FutureExt; /// From ddcc0291cc7489f01629461514623a736ca04dc9 Mon Sep 17 00:00:00 2001 From: Larry Osterman Date: Thu, 5 Jun 2025 08:53:39 -0700 Subject: [PATCH 09/11] Added text to README.md describing how to set the async runtime used by the SDK --- sdk/core/azure_core/README.md | 63 ++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/sdk/core/azure_core/README.md b/sdk/core/azure_core/README.md index 3de4013a65..92cb6cd593 100644 --- a/sdk/core/azure_core/README.md +++ b/sdk/core/azure_core/README.md @@ -20,12 +20,12 @@ you can find the crates.io package [here][Package (crates.io)]. The main shared concepts of `azure_core` - and Azure SDK libraries using `azure_core` - include: -- Configuring service clients, e.g. configuring retries, logging (`ClientOptions`). -- Accessing HTTP response details (`Response`). -- Paging and asynchronous streams (`Pager`). -- Errors from service requests in a consistent fashion. (`azure_core::Error`). -- Customizing requests (`ClientOptions`). -- Abstractions for representing Azure SDK credentials. (`TokenCredentials`). +- Configuring service clients, e.g. configuring retries, logging (`ClientOptions`). +- Accessing HTTP response details (`Response`). +- Paging and asynchronous streams (`Pager`). +- Errors from service requests in a consistent fashion. (`azure_core::Error`). +- Customizing requests (`ClientOptions`). +- Abstractions for representing Azure SDK credentials. (`TokenCredentials`). ### Thread safety @@ -34,23 +34,25 @@ We guarantee that all client instance methods are thread-safe and independent of ### Additional concepts + [Client options](#configuring-service-clients-using-clientoptions) | [Accessing the response](#accessing-http-response-details-using-responset) | [Handling Errors Results](#handling-errors-results) | [Consuming Service Methods Returning `Pager`](#consuming-service-methods-returning-pagert) + ## Features -- `debug`: enables extra information for developers e.g., emitting all fields in `std::fmt::Debug` implementation. -- `hmac_openssl`: configures HMAC using `openssl`. -- `hmac_rust`: configures HMAC using pure Rust. -- `reqwest` (default): enables and sets `reqwest` as the default `HttpClient`. Enables `reqwest`'s `native-tls` feature. -- `reqwest_deflate` (default): enables deflate compression for `reqwest`. -- `reqwest_gzip` (default): enables gzip compression for `reqwest`. -- `reqwest_rustls`: enables `reqwest`'s `rustls-tls-native-roots-no-provider` feature, -- `tokio`: enables and sets `tokio` as the default async runtime. -- `xml`: enables XML support. +- `debug`: enables extra information for developers e.g., emitting all fields in `std::fmt::Debug` implementation. +- `hmac_openssl`: configures HMAC using `openssl`. +- `hmac_rust`: configures HMAC using pure Rust. +- `reqwest` (default): enables and sets `reqwest` as the default `HttpClient`. Enables `reqwest`'s `native-tls` feature. +- `reqwest_deflate` (default): enables deflate compression for `reqwest`. +- `reqwest_gzip` (default): enables gzip compression for `reqwest`. +- `reqwest_rustls`: enables `reqwest`'s `rustls-tls-native-roots-no-provider` feature, +- `tokio`: enables and sets `tokio` as the default async runtime. +- `xml`: enables XML support. ## Examples @@ -213,7 +215,38 @@ async fn main() -> Result<(), Box> { } ``` +### Replacing the async runtime + +Internally, the Azure SDK uses either the `tokio` async runtime (with the `tokio` feature), or it implements asynchronous functionality using functions in the `std` namespace. + +If your application uses a different asynchronous runtime, you can replace the asynchronous runtime used for internal functions by providing your own implementation of the `azure_core::async_runtime::AsyncRuntime` trait. + +You provide the implementation by calling the `set_async_runtime()` API: + +```rust no_run +use azure_core::async_runtime::{ + set_async_runtime, AsyncRuntime, TaskFuture, SpawnedTask}; +use std::sync::Arc; +use futures::FutureExt; + +struct CustomRuntime; + +impl AsyncRuntime for CustomRuntime { + fn spawn(&self, f: TaskFuture) -> SpawnedTask { + unimplemented!("Custom spawn not implemented"); + } + fn sleep(&self, duration: std::time::Duration) -> TaskFuture { + unimplemented!("Custom sleep not implemented"); + } + } + + set_async_runtime(Arc::new(CustomRuntime)).expect("Failed to set async runtime"); +``` + +There can only be one async runtime set in a given process, so attempts to set the async runtime multiple times will fail. + + ## Troubleshooting ### Logging From 73b0561c8c2326caddaa48bb74c625ae19eb9c54 Mon Sep 17 00:00:00 2001 From: Larry Osterman Date: Mon, 16 Jun 2025 11:13:29 -0700 Subject: [PATCH 10/11] Update sdk/core/azure_core/CHANGELOG.md Co-authored-by: Heath Stewart --- sdk/core/azure_core/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/core/azure_core/CHANGELOG.md b/sdk/core/azure_core/CHANGELOG.md index 9a8a0f85ff..5108792222 100644 --- a/sdk/core/azure_core/CHANGELOG.md +++ b/sdk/core/azure_core/CHANGELOG.md @@ -19,7 +19,7 @@ the asynchronous runtime used by the Azure SDK. - Added `#[safe]` attribute helper for `SafeDebug` derive macro to show or hide types and members as appropriate. - Added `Page` trait to facilitate the `ItemIterator`. -- Added `PageIterator` to asynchronously iterator all pages. +- Added `PageIterator` to asynchronously iterate all pages. ### Breaking Changes From 71f146c96c2c360844d944a8e0072d946c42537c Mon Sep 17 00:00:00 2001 From: Larry Osterman Date: Mon, 16 Jun 2025 11:27:26 -0700 Subject: [PATCH 11/11] Policheck feedback --- .../typespec_client_core/src/async_runtime/tests.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 9259b62b39..7ff3ad5643 100644 --- a/sdk/typespec/typespec_client_core/src/async_runtime/tests.rs +++ b/sdk/typespec/typespec_client_core/src/async_runtime/tests.rs @@ -241,18 +241,18 @@ fn test_get_runtime() { let _runtime = get_async_runtime(); } -struct DummyRuntime; +struct TestRuntime; -impl AsyncRuntime for DummyRuntime { +impl AsyncRuntime for TestRuntime { fn spawn(&self, _f: TaskFuture) -> SpawnedTask { - unimplemented!("DummyRuntime does not support spawning tasks"); + unimplemented!("TestRuntime does not support spawning tasks"); } fn sleep( &self, _duration: std::time::Duration, ) -> Pin + Send + 'static>> { - unimplemented!("DummyRuntime does not support sleeping"); + unimplemented!("TestRuntime does not support sleeping"); } } @@ -262,7 +262,7 @@ impl AsyncRuntime for DummyRuntime { #[test] #[ignore = "Skipping the runtime set test to avoid conflicts with parallel test execution"] fn test_set_runtime() { - let runtime = Arc::new(DummyRuntime); + let runtime = Arc::new(TestRuntime); // Ensure that the runtime can be set without panicking set_async_runtime(runtime.clone()).unwrap();