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
45#[cfg(feature = "hot-reload")]
47pub mod hot_reload;
48
49pub use mqtt::{MqttRuntime, MqttRuntimeConfig};
50pub use queue::{QueueConsumer, QueueRuntime, QueueRuntimeConfig};
51pub use scheduler::SchedulerRuntime;
52pub use shutdown::GracefulShutdown;
53pub use signal::shutdown_signal;
54pub use spawn::spawn_with_token;
55pub use websocket::{WebSocketRuntime, WebSocketRuntimeConfig};
56pub use worker::WorkerConfig;
57
58use std::future::Future;
59use std::time::Duration;
60
61use tokio_util::sync::CancellationToken;
62
63pub struct SzRuntime {
91 runtime: tokio::runtime::Runtime,
93 worker_threads: usize,
95 shutdown_token: CancellationToken,
97}
98
99impl SzRuntime {
100 pub fn new() -> Self {
102 Self::with_worker_threads(num_cpus::get())
103 }
104
105 pub fn with_worker_threads(worker_threads: usize) -> Self {
109 let n = worker_threads.max(1);
110 let runtime = tokio::runtime::Builder::new_multi_thread()
111 .worker_threads(n)
112 .enable_all()
113 .thread_name("sz-rust-worker")
114 .build()
115 .expect("Failed to create tokio runtime");
116 Self {
117 runtime,
118 worker_threads: n,
119 shutdown_token: CancellationToken::new(),
120 }
121 }
122
123 pub fn worker_threads(&self) -> usize {
125 self.worker_threads
126 }
127
128 pub fn shutdown_token(&self) -> CancellationToken {
132 self.shutdown_token.clone()
133 }
134
135 pub fn spawn<F>(&self, future: F) -> tokio::task::JoinHandle<F::Output>
139 where
140 F: Future + Send + 'static,
141 F::Output: Send + 'static,
142 {
143 self.runtime.spawn(future)
144 }
145
146 pub fn block_on<F>(&self, future: F) -> F::Output
150 where
151 F: Future,
152 {
153 self.runtime.block_on(future)
154 }
155
156 pub fn shutdown_timeout(self, timeout: Duration) -> bool {
161 self.shutdown_token.cancel();
162 self.runtime.block_on(async {
164 let _ = tokio::time::timeout(timeout, async {
165 tokio::time::sleep(Duration::from_millis(10)).await;
167 })
168 .await;
169 });
170 drop(self.runtime);
172 true
173 }
174
175 pub fn handle(&self) -> tokio::runtime::Handle {
177 self.runtime.handle().clone()
178 }
179}
180
181impl Default for SzRuntime {
182 fn default() -> Self {
183 Self::new()
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_new_default_worker_threads() {
193 let rt = SzRuntime::new();
194 assert_eq!(rt.worker_threads(), num_cpus::get());
195 }
196
197 #[test]
198 fn test_with_worker_threads_custom() {
199 let rt = SzRuntime::with_worker_threads(2);
200 assert_eq!(rt.worker_threads(), 2);
201 }
202
203 #[test]
204 fn test_with_worker_threads_zero_falls_back_to_one() {
205 let rt = SzRuntime::with_worker_threads(0);
206 assert_eq!(rt.worker_threads(), 1);
207 }
208
209 #[test]
210 fn test_spawn_and_block_on() {
211 let rt = SzRuntime::with_worker_threads(1);
212 let handle = rt.spawn(async { 42 });
213 let result = rt.block_on(handle).unwrap();
214 assert_eq!(result, 42);
215 }
216
217 #[test]
218 fn test_block_on_directly() {
219 let rt = SzRuntime::with_worker_threads(1);
220 let result = rt.block_on(async { 100 });
221 assert_eq!(result, 100);
222 }
223
224 #[test]
225 fn test_shutdown_token_cancellation() {
226 let rt = SzRuntime::with_worker_threads(1);
227 let token = rt.shutdown_token();
228 assert!(!token.is_cancelled());
229 assert!(rt.shutdown_timeout(Duration::from_millis(50)));
230 assert!(token.is_cancelled());
232 }
233
234 #[test]
235 fn test_spawn_with_token_cancellation() {
236 let rt = SzRuntime::with_worker_threads(1);
237 let token = rt.shutdown_token();
238 let handle = rt.spawn(async move {
239 token.cancelled().await;
241 99
242 });
243 let token2 = rt.shutdown_token();
245 token2.cancel();
246 let result = rt.block_on(handle).unwrap();
247 assert_eq!(result, 99);
248 }
249
250 #[test]
251 fn test_handle_can_spawn() {
252 let rt = SzRuntime::with_worker_threads(1);
253 let handle = rt.handle();
254 let task = handle.spawn(async { 7 });
255 let result = rt.block_on(task).unwrap();
256 assert_eq!(result, 7);
257 }
258
259 #[test]
260 fn test_default_impl_equals_new() {
261 let rt1 = SzRuntime::default();
262 let rt2 = SzRuntime::new();
263 assert_eq!(rt1.worker_threads(), rt2.worker_threads());
264 }
265
266 #[test]
267 fn test_multiple_runtime_instances() {
268 let rt1 = SzRuntime::with_worker_threads(1);
270 let rt2 = SzRuntime::with_worker_threads(1);
271 let h1 = rt1.spawn(async { 1 });
272 let h2 = rt2.spawn(async { 2 });
273 assert_eq!(rt1.block_on(h1).unwrap(), 1);
274 assert_eq!(rt2.block_on(h2).unwrap(), 2);
275 }
276}