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 blocking_threads: usize,
97 shutdown_token: CancellationToken,
99}
100
101impl SzRuntime {
102 pub fn new() -> Self {
104 Self::with_worker_threads(num_cpus::get())
105 }
106
107 pub fn with_worker_threads(worker_threads: usize) -> Self {
111 let n = worker_threads.max(1);
112 let blocking = 512;
113 let runtime = tokio::runtime::Builder::new_multi_thread()
114 .worker_threads(n)
115 .max_blocking_threads(blocking)
116 .enable_all()
117 .thread_name("sz-rust-worker")
118 .build()
119 .expect("Failed to create tokio runtime");
120 Self {
121 runtime,
122 worker_threads: n,
123 blocking_threads: blocking,
124 shutdown_token: CancellationToken::new(),
125 }
126 }
127
128 pub fn with_blocking_threads(mut self, blocking_threads: usize) -> Self {
132 let b = blocking_threads.max(1);
133 let runtime = tokio::runtime::Builder::new_multi_thread()
134 .worker_threads(self.worker_threads)
135 .max_blocking_threads(b)
136 .enable_all()
137 .thread_name("sz-rust-worker")
138 .build()
139 .expect("Failed to create tokio runtime");
140 self.runtime = runtime;
141 self.blocking_threads = b;
142 self
143 }
144
145 pub fn for_io_intensive() -> Self {
147 let worker = (num_cpus::get() * 2).max(1);
148 let blocking = 1024;
149 let runtime = tokio::runtime::Builder::new_multi_thread()
150 .worker_threads(worker)
151 .max_blocking_threads(blocking)
152 .enable_all()
153 .thread_name("sz-rust-io")
154 .build()
155 .expect("Failed to create tokio runtime");
156 Self {
157 runtime,
158 worker_threads: worker,
159 blocking_threads: blocking,
160 shutdown_token: CancellationToken::new(),
161 }
162 }
163
164 pub fn for_cpu_intensive() -> Self {
166 let worker = (num_cpus::get() / 2).max(1);
167 let blocking = 256;
168 let runtime = tokio::runtime::Builder::new_multi_thread()
169 .worker_threads(worker)
170 .max_blocking_threads(blocking)
171 .enable_all()
172 .thread_name("sz-rust-cpu")
173 .build()
174 .expect("Failed to create tokio runtime");
175 Self {
176 runtime,
177 worker_threads: worker,
178 blocking_threads: blocking,
179 shutdown_token: CancellationToken::new(),
180 }
181 }
182
183 pub fn for_balanced() -> Self {
185 Self::with_worker_threads(num_cpus::get())
186 }
187
188 pub fn worker_threads(&self) -> usize {
190 self.worker_threads
191 }
192
193 pub fn blocking_threads(&self) -> usize {
195 self.blocking_threads
196 }
197
198 pub fn shutdown_token(&self) -> CancellationToken {
202 self.shutdown_token.clone()
203 }
204
205 pub fn spawn<F>(&self, future: F) -> tokio::task::JoinHandle<F::Output>
209 where
210 F: Future + Send + 'static,
211 F::Output: Send + 'static,
212 {
213 self.runtime.spawn(future)
214 }
215
216 pub fn block_on<F>(&self, future: F) -> F::Output
220 where
221 F: Future,
222 {
223 self.runtime.block_on(future)
224 }
225
226 pub fn shutdown_timeout(self, timeout: Duration) -> bool {
231 self.shutdown_token.cancel();
232 self.runtime.block_on(async {
234 let _ = tokio::time::timeout(timeout, async {
235 tokio::time::sleep(Duration::from_millis(10)).await;
237 })
238 .await;
239 });
240 drop(self.runtime);
242 true
243 }
244
245 pub fn handle(&self) -> tokio::runtime::Handle {
247 self.runtime.handle().clone()
248 }
249}
250
251impl Default for SzRuntime {
252 fn default() -> Self {
253 Self::new()
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn test_new_default_worker_threads() {
263 let rt = SzRuntime::new();
264 assert_eq!(rt.worker_threads(), num_cpus::get());
265 }
266
267 #[test]
268 fn test_with_worker_threads_custom() {
269 let rt = SzRuntime::with_worker_threads(2);
270 assert_eq!(rt.worker_threads(), 2);
271 }
272
273 #[test]
274 fn test_with_worker_threads_zero_falls_back_to_one() {
275 let rt = SzRuntime::with_worker_threads(0);
276 assert_eq!(rt.worker_threads(), 1);
277 }
278
279 #[test]
280 fn test_spawn_and_block_on() {
281 let rt = SzRuntime::with_worker_threads(1);
282 let handle = rt.spawn(async { 42 });
283 let result = rt.block_on(handle).unwrap();
284 assert_eq!(result, 42);
285 }
286
287 #[test]
288 fn test_block_on_directly() {
289 let rt = SzRuntime::with_worker_threads(1);
290 let result = rt.block_on(async { 100 });
291 assert_eq!(result, 100);
292 }
293
294 #[test]
295 fn test_shutdown_token_cancellation() {
296 let rt = SzRuntime::with_worker_threads(1);
297 let token = rt.shutdown_token();
298 assert!(!token.is_cancelled());
299 assert!(rt.shutdown_timeout(Duration::from_millis(50)));
300 assert!(token.is_cancelled());
302 }
303
304 #[test]
305 fn test_spawn_with_token_cancellation() {
306 let rt = SzRuntime::with_worker_threads(1);
307 let token = rt.shutdown_token();
308 let handle = rt.spawn(async move {
309 token.cancelled().await;
311 99
312 });
313 let token2 = rt.shutdown_token();
315 token2.cancel();
316 let result = rt.block_on(handle).unwrap();
317 assert_eq!(result, 99);
318 }
319
320 #[test]
321 fn test_handle_can_spawn() {
322 let rt = SzRuntime::with_worker_threads(1);
323 let handle = rt.handle();
324 let task = handle.spawn(async { 7 });
325 let result = rt.block_on(task).unwrap();
326 assert_eq!(result, 7);
327 }
328
329 #[test]
330 fn test_default_impl_equals_new() {
331 let rt1 = SzRuntime::default();
332 let rt2 = SzRuntime::new();
333 assert_eq!(rt1.worker_threads(), rt2.worker_threads());
334 }
335
336 #[test]
337 fn test_multiple_runtime_instances() {
338 let rt1 = SzRuntime::with_worker_threads(1);
340 let rt2 = SzRuntime::with_worker_threads(1);
341 let h1 = rt1.spawn(async { 1 });
342 let h2 = rt2.spawn(async { 2 });
343 assert_eq!(rt1.block_on(h1).unwrap(), 1);
344 assert_eq!(rt2.block_on(h2).unwrap(), 2);
345 }
346
347 #[test]
350 fn test_default_blocking_threads_is_512() {
351 let rt = SzRuntime::new();
352 assert_eq!(rt.blocking_threads(), 512);
353 }
354
355 #[test]
356 fn test_with_blocking_threads_custom() {
357 let rt = SzRuntime::with_worker_threads(2).with_blocking_threads(256);
358 assert_eq!(rt.worker_threads(), 2);
359 assert_eq!(rt.blocking_threads(), 256);
360 }
361
362 #[test]
363 fn test_with_blocking_threads_zero_falls_back_to_one() {
364 let rt = SzRuntime::with_worker_threads(1).with_blocking_threads(0);
365 assert_eq!(rt.blocking_threads(), 1);
366 }
367
368 #[test]
369 fn test_for_io_intensive_worker_doubled() {
370 let rt = SzRuntime::for_io_intensive();
371 assert_eq!(rt.worker_threads(), (num_cpus::get() * 2).max(1));
372 assert_eq!(rt.blocking_threads(), 1024);
373 }
374
375 #[test]
376 fn test_for_cpu_intensive_worker_halved() {
377 let rt = SzRuntime::for_cpu_intensive();
378 assert_eq!(rt.worker_threads(), (num_cpus::get() / 2).max(1));
379 assert_eq!(rt.blocking_threads(), 256);
380 }
381
382 #[test]
383 fn test_for_balanced_equals_default() {
384 let rt = SzRuntime::for_balanced();
385 assert_eq!(rt.worker_threads(), num_cpus::get());
386 assert_eq!(rt.blocking_threads(), 512);
387 }
388
389 #[test]
390 fn test_io_intensive_spawn_works() {
391 let rt = SzRuntime::for_io_intensive();
392 let handle = rt.spawn(async { 42 });
393 assert_eq!(rt.block_on(handle).unwrap(), 42);
394 }
395
396 #[test]
397 fn test_cpu_intensive_spawn_works() {
398 let rt = SzRuntime::for_cpu_intensive();
399 let handle = rt.spawn(async { 42 });
400 assert_eq!(rt.block_on(handle).unwrap(), 42);
401 }
402
403 #[test]
404 fn test_balanced_spawn_works() {
405 let rt = SzRuntime::for_balanced();
406 let handle = rt.spawn(async { 42 });
407 assert_eq!(rt.block_on(handle).unwrap(), 42);
408 }
409
410 #[test]
411 fn test_with_blocking_threads_chain_spawn_works() {
412 let rt = SzRuntime::with_worker_threads(2).with_blocking_threads(128);
413 let handle = rt.spawn(async { 42 });
414 assert_eq!(rt.block_on(handle).unwrap(), 42);
415 assert_eq!(rt.blocking_threads(), 128);
416 }
417
418 #[test]
419 fn test_presets_have_distinct_blocking_threads() {
420 let io_rt = SzRuntime::for_io_intensive();
421 let cpu_rt = SzRuntime::for_cpu_intensive();
422 let balanced_rt = SzRuntime::for_balanced();
423
424 assert_ne!(io_rt.blocking_threads(), cpu_rt.blocking_threads());
425 assert_ne!(balanced_rt.blocking_threads(), cpu_rt.blocking_threads());
426 }
427}