feat(client): adopt an asynchronous Tokio runtime for the client frontend
This commit is contained in:
8 files changed
+258
-325
No files matched your search
@@ -4,7 +4,10 @@ use std::{
|
||||
};
|
||||
|
||||
use thiserror::Error;
|
||||
use tokio::runtime::{Builder, Runtime};
|
||||
use tokio::{
|
||||
runtime::{Builder, Runtime},
|
||||
sync::oneshot,
|
||||
};
|
||||
|
||||
use super::{
|
||||
ManagedClient, ManagedClientConfig, ManagedClientHandle, ManagedClientTask, ManagedConfigError,
|
||||
@@ -83,16 +86,19 @@ pub enum BlockingManagedJoinError {
|
||||
WorkerPanicked,
|
||||
}
|
||||
|
||||
/// Blocking completion owner for one unified managed Tokio task.
|
||||
/// Dedicated-thread completion owner for one unified managed Tokio task.
|
||||
///
|
||||
/// This owner runs exactly one [`ManagedClient`] task on one dedicated OS thread
|
||||
/// with its own current-thread Tokio runtime. Dropping a live owner requests a
|
||||
/// graceful stop and synchronously joins that worker, so drop may block while
|
||||
/// with its own current-thread Tokio runtime. Synchronous callers use [`Self::join`];
|
||||
/// async frontends use [`Self::join_async`] without sharing the measurement executor.
|
||||
/// Dropping a live owner requests graceful stop and synchronously joins that
|
||||
/// worker, so drop may block while
|
||||
/// bounded managed cleanup completes.
|
||||
#[must_use = "dropping the blocking managed owner requests stop and joins its worker"]
|
||||
pub struct BlockingManagedClient {
|
||||
handle: ManagedClientHandle,
|
||||
worker: Option<JoinHandle<ManagedOutcome>>,
|
||||
completion: oneshot::Receiver<()>,
|
||||
}
|
||||
|
||||
impl BlockingManagedClient {
|
||||
@@ -131,11 +137,13 @@ impl BlockingManagedClient {
|
||||
.build()
|
||||
.map_err(|source| BlockingManagedStartError::Runtime { source })?;
|
||||
let runtime = WorkerRuntime::new(runtime);
|
||||
let (completed, completion) = oneshot::channel();
|
||||
let worker = thread::Builder::new()
|
||||
.name("irtt-managed".to_owned())
|
||||
.spawn(move || {
|
||||
let outcome = runtime.block_on(task);
|
||||
runtime.shutdown();
|
||||
let _ = completed.send(());
|
||||
outcome
|
||||
})
|
||||
.map_err(|source| BlockingManagedStartError::ThreadSpawn { source })?;
|
||||
@@ -143,6 +151,7 @@ impl BlockingManagedClient {
|
||||
Ok(Self {
|
||||
handle,
|
||||
worker: Some(worker),
|
||||
completion,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -156,6 +165,15 @@ impl BlockingManagedClient {
|
||||
self.join_worker()
|
||||
}
|
||||
|
||||
/// Await worker completion, then join its OS thread and obtain its outcome.
|
||||
///
|
||||
/// The task and runtime remain isolated from the caller's executor. Cancelling
|
||||
/// this future drops the owner, requesting stop and joining as usual.
|
||||
pub async fn join_async(mut self) -> Result<ManagedOutcome, BlockingManagedJoinError> {
|
||||
let _ = (&mut self.completion).await;
|
||||
self.join_worker()
|
||||
}
|
||||
|
||||
fn join_worker(&mut self) -> Result<ManagedOutcome, BlockingManagedJoinError> {
|
||||
self.worker
|
||||
.take()
|
||||
|
||||
@@ -197,3 +197,25 @@ fn concurrent_submission_and_stop_or_seal_never_lose_an_accepted_receipt() {
|
||||
.expect("every accepted concurrent submission must receive a terminal disposition");
|
||||
});
|
||||
}
|
||||
|
||||
// Joining an isolated worker must leave the caller's executor free to stop it.
|
||||
#[test]
|
||||
fn async_worker_join_allows_concurrent_stop_and_preserves_final_status() {
|
||||
let owner = BlockingManagedClient::start(config(), vec![]).unwrap();
|
||||
let handle = owner.handle();
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async {
|
||||
let joined = owner.join_async();
|
||||
let stop = async { handle.stop().await };
|
||||
let (outcome, ()) =
|
||||
tokio::time::timeout(Duration::from_secs(2), async { tokio::join!(joined, stop) })
|
||||
.await
|
||||
.expect("async join must allow stop on the caller's executor");
|
||||
let outcome = outcome.unwrap();
|
||||
assert_eq!(outcome.end_reason, ManagedEndReason::StopRequested);
|
||||
assert_eq!(handle.status().final_outcome.as_deref(), Some(&outcome));
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user