irtt_client/managed/
blocking.rs1use std::{
2 thread::{self, JoinHandle},
3 time::Duration,
4};
5
6use thiserror::Error;
7use tokio::runtime::{Builder, Runtime};
8
9use super::{
10 ManagedClient, ManagedClientConfig, ManagedClientHandle, ManagedClientTask, ManagedConfigError,
11 ManagedEventSubscription, ManagedOutcome, ManagedSubscribeError, ManagedTargetConfig,
12};
13
14const BLOCKING_RUNTIME_SHUTDOWN_TIMEOUT: Duration = Duration::from_millis(100);
15
16pub(super) struct WorkerRuntime {
18 runtime: Option<Runtime>,
19}
20
21impl WorkerRuntime {
22 pub(super) fn new(runtime: Runtime) -> Self {
23 Self {
24 runtime: Some(runtime),
25 }
26 }
27
28 fn block_on<F: std::future::Future>(&self, task: F) -> F::Output {
29 self.runtime
30 .as_ref()
31 .expect("worker runtime is available until shutdown")
32 .block_on(task)
33 }
34
35 pub(super) fn shutdown(mut self) {
37 self.runtime
38 .take()
39 .expect("worker runtime is available until shutdown")
40 .shutdown_timeout(BLOCKING_RUNTIME_SHUTDOWN_TIMEOUT);
41 }
42}
43
44impl Drop for WorkerRuntime {
45 fn drop(&mut self) {
46 if let Some(runtime) = self.runtime.take() {
47 runtime.shutdown_background();
48 }
49 }
50}
51
52#[derive(Debug, Error)]
54pub enum BlockingManagedStartError {
55 #[error(transparent)]
57 Config(#[from] ManagedConfigError),
58
59 #[error(transparent)]
61 Subscribe(#[from] ManagedSubscribeError),
62
63 #[error("failed to create blocking managed Tokio runtime: {source}")]
65 Runtime {
66 #[source]
67 source: std::io::Error,
68 },
69
70 #[error("failed to spawn blocking managed worker thread: {source}")]
72 ThreadSpawn {
73 #[source]
74 source: std::io::Error,
75 },
76}
77
78#[derive(Debug, Error, Eq, PartialEq)]
80pub enum BlockingManagedJoinError {
81 #[error("blocking managed worker thread panicked")]
83 WorkerPanicked,
84}
85
86#[must_use = "dropping the blocking managed owner requests stop and joins its worker"]
93pub struct BlockingManagedClient {
94 handle: ManagedClientHandle,
95 worker: Option<JoinHandle<ManagedOutcome>>,
96}
97
98impl BlockingManagedClient {
99 pub fn start(
101 config: ManagedClientConfig,
102 targets: Vec<ManagedTargetConfig>,
103 ) -> Result<Self, BlockingManagedStartError> {
104 let (task, handle) = ManagedClient::task(config, targets)?;
105 Self::start_task(task, handle)
106 }
107
108 pub fn start_with_subscription(
115 config: ManagedClientConfig,
116 targets: Vec<ManagedTargetConfig>,
117 ) -> Result<(Self, ManagedEventSubscription), BlockingManagedStartError> {
118 let (task, handle) = ManagedClient::task(config, targets)?;
119 let subscription = handle.subscribe()?;
120 let owner = Self::start_task(task, handle)?;
121 Ok((owner, subscription))
122 }
123
124 fn start_task(
125 task: ManagedClientTask,
126 handle: ManagedClientHandle,
127 ) -> Result<Self, BlockingManagedStartError> {
128 let runtime = Builder::new_current_thread()
129 .enable_io()
130 .enable_time()
131 .build()
132 .map_err(|source| BlockingManagedStartError::Runtime { source })?;
133 let runtime = WorkerRuntime::new(runtime);
134 let worker = thread::Builder::new()
135 .name("irtt-managed".to_owned())
136 .spawn(move || {
137 let outcome = runtime.block_on(task);
138 runtime.shutdown();
139 outcome
140 })
141 .map_err(|source| BlockingManagedStartError::ThreadSpawn { source })?;
142
143 Ok(Self {
144 handle,
145 worker: Some(worker),
146 })
147 }
148
149 pub fn handle(&self) -> ManagedClientHandle {
151 self.handle.clone()
152 }
153
154 pub fn join(mut self) -> Result<ManagedOutcome, BlockingManagedJoinError> {
156 self.join_worker()
157 }
158
159 fn join_worker(&mut self) -> Result<ManagedOutcome, BlockingManagedJoinError> {
160 self.worker
161 .take()
162 .expect("blocking managed worker is joined at most once")
163 .join()
164 .map_err(|_| BlockingManagedJoinError::WorkerPanicked)
165 }
166}
167
168impl Drop for BlockingManagedClient {
169 fn drop(&mut self) {
170 if self.worker.is_some() {
171 let _receipt = self.handle.stop();
172 let _ = self.join_worker();
173 }
174 }
175}