1pub mod mqtt;
37pub mod queue;
38pub mod scheduler;
39pub mod shutdown;
40pub mod signal;
41pub mod spawn;
42pub mod websocket;
43pub mod worker;
44
45pub use mqtt::{MqttRuntime, MqttRuntimeConfig};
46pub use queue::{QueueConsumer, QueueRuntime, QueueRuntimeConfig};
47pub use scheduler::SchedulerRuntime;
48pub use shutdown::GracefulShutdown;
49pub use signal::shutdown_signal;
50pub use spawn::spawn_with_token;
51pub use websocket::{WebSocketRuntime, WebSocketRuntimeConfig};
52pub use worker::WorkerConfig;
53
54use std::future::Future;
55use std::time::Duration;
56
57use tokio_util::sync::CancellationToken;
58
59pub struct SzRuntime {
87 runtime: tokio::runtime::Runtime,
89 worker_threads: usize,
91 shutdown_token: CancellationToken,
93}
94
95impl SzRuntime {
96 pub fn new() -> Self {
98 Self::with_worker_threads(num_cpus::get())
99 }
100
101 pub fn with_worker_threads(worker_threads: usize) -> Self {
105 let n = worker_threads.max(1);
106 let runtime = tokio::runtime::Builder::new_multi_thread()
107 .worker_threads(n)
108 .enable_all()
109 .thread_name("sz-rust-worker")
110 .build()
111 .expect("Failed to create tokio runtime");
112 Self {
113 runtime,
114 worker_threads: n,
115 shutdown_token: CancellationToken::new(),
116 }
117 }
118
119 pub fn worker_threads(&self) -> usize {
121 self.worker_threads
122 }
123
124 pub fn shutdown_token(&self) -> CancellationToken {
128 self.shutdown_token.clone()
129 }
130
131 pub fn spawn<F>(&self, future: F) -> tokio::task::JoinHandle<F::Output>
135 where
136 F: Future + Send + 'static,
137 F::Output: Send + 'static,
138 {
139 self.runtime.spawn(future)
140 }
141
142 pub fn block_on<F>(&self, future: F) -> F::Output
146 where
147 F: Future,
148 {
149 self.runtime.block_on(future)
150 }
151
152 pub fn shutdown_timeout(self, timeout: Duration) -> bool {
157 self.shutdown_token.cancel();
158 self.runtime.block_on(async {
160 let _ = tokio::time::timeout(timeout, async {
161 tokio::time::sleep(Duration::from_millis(10)).await;
163 })
164 .await;
165 });
166 drop(self.runtime);
168 true
169 }
170
171 pub fn handle(&self) -> tokio::runtime::Handle {
173 self.runtime.handle().clone()
174 }
175}
176
177impl Default for SzRuntime {
178 fn default() -> Self {
179 Self::new()
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn test_new_default_worker_threads() {
189 let rt = SzRuntime::new();
190 assert_eq!(rt.worker_threads(), num_cpus::get());
191 }
192
193 #[test]
194 fn test_with_worker_threads_custom() {
195 let rt = SzRuntime::with_worker_threads(2);
196 assert_eq!(rt.worker_threads(), 2);
197 }
198
199 #[test]
200 fn test_with_worker_threads_zero_falls_back_to_one() {
201 let rt = SzRuntime::with_worker_threads(0);
202 assert_eq!(rt.worker_threads(), 1);
203 }
204
205 #[test]
206 fn test_spawn_and_block_on() {
207 let rt = SzRuntime::with_worker_threads(1);
208 let handle = rt.spawn(async { 42 });
209 let result = rt.block_on(handle).unwrap();
210 assert_eq!(result, 42);
211 }
212
213 #[test]
214 fn test_block_on_directly() {
215 let rt = SzRuntime::with_worker_threads(1);
216 let result = rt.block_on(async { 100 });
217 assert_eq!(result, 100);
218 }
219
220 #[test]
221 fn test_shutdown_token_cancellation() {
222 let rt = SzRuntime::with_worker_threads(1);
223 let token = rt.shutdown_token();
224 assert!(!token.is_cancelled());
225 assert!(rt.shutdown_timeout(Duration::from_millis(50)));
226 assert!(token.is_cancelled());
228 }
229
230 #[test]
231 fn test_spawn_with_token_cancellation() {
232 let rt = SzRuntime::with_worker_threads(1);
233 let token = rt.shutdown_token();
234 let handle = rt.spawn(async move {
235 token.cancelled().await;
237 99
238 });
239 let token2 = rt.shutdown_token();
241 token2.cancel();
242 let result = rt.block_on(handle).unwrap();
243 assert_eq!(result, 99);
244 }
245
246 #[test]
247 fn test_handle_can_spawn() {
248 let rt = SzRuntime::with_worker_threads(1);
249 let handle = rt.handle();
250 let task = handle.spawn(async { 7 });
251 let result = rt.block_on(task).unwrap();
252 assert_eq!(result, 7);
253 }
254
255 #[test]
256 fn test_default_impl_equals_new() {
257 let rt1 = SzRuntime::default();
258 let rt2 = SzRuntime::new();
259 assert_eq!(rt1.worker_threads(), rt2.worker_threads());
260 }
261
262 #[test]
263 fn test_multiple_runtime_instances() {
264 let rt1 = SzRuntime::with_worker_threads(1);
266 let rt2 = SzRuntime::with_worker_threads(1);
267 let h1 = rt1.spawn(async { 1 });
268 let h2 = rt2.spawn(async { 2 });
269 assert_eq!(rt1.block_on(h1).unwrap(), 1);
270 assert_eq!(rt2.block_on(h2).unwrap(), 2);
271 }
272}