Skip to main content

sz_rust_core/
runtime.rs

1//! SZ-Rust Runtime — Swoole/Worker 适配主入口
2//!
3//! ## PHP 对齐
4//!
5//! 对齐 PHP `topthink/think-swoole` 的运行时模型:
6//!
7//! | PHP Swoole | Rust SZ-Rust | 说明 |
8//! |------------|--------------|------|
9//! | `Swoole\Runtime::enableCoroutine()` | `SzRuntime::block_on(fut)` | 协程入口 |
10//! | `swoole_cpu_num()` | `num_cpus::get()` | 默认 worker 数 |
11//! | `worker_num` 配置项 | `SzRuntime::with_worker_threads(n)` | 自定义 worker 数 |
12//! | `Swoole\Process::signal()` | `tokio::signal` + `CancellationToken` | 信号处理 |
13//! | `Swoole\Timer::tick()` | `tokio::time::interval` + `tokio::select!` | 定时任务 |
14//! | `swoole_event::defer()` | `SzRuntime::spawn(fut)` | 异步任务 |
15//!
16//! ## 模块结构
17//!
18//! | 模块 | 对齐 PHP | 子任务 |
19//! |------|---------|--------|
20//! | `runtime::worker` | `worker_num` 配置 | 9.2 |
21//! | `runtime::spawn` | `swoole_event::defer` | 9.3 |
22//! | `runtime::queue` | `think-queue` 消费者 | 9.4 |
23//! | `runtime::mqtt` | 长连接 | 9.5 |
24//! | `runtime::websocket` | `think-worker` | 9.6 |
25//! | `runtime::scheduler` | `Crontab` | 9.7 |
26//! | `runtime::shutdown` | 优雅关闭 | 9.8 |
27//! | `runtime::signal` | `SIGTERM/SIGINT` | 9.9 |
28//!
29//! ## 关键决策
30//!
31//! - **不修改 `sz-orm-scheduler` 内部 API**:保持 `CronScheduler::start()`/`stop()` 向后兼容,
32//!   在 sz-rust 侧用 `tokio::time::interval` + `try_fire_due()` 重写循环。
33//! - **使用 `CancellationToken` 统一关闭广播**:替代 `AtomicBool` + `oneshot`,支持父子层级。
34//! - **双平台信号处理**:Unix 用 `SignalKind::terminate/interrupt`,Windows 用 `ctrl_c/ctrl_close`。
35
36pub 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// P2: Addon 热加载探索(可选 feature: hot-reload)
46#[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
63/// SZ-Rust 异步运行时
64///
65/// 对齐 PHP `think-swoole` 的运行时模型:基于 tokio multi_thread runtime,
66/// worker 数量默认 = CPU 核数(对齐 `swoole_cpu_num()`)。
67///
68/// ## 设计
69///
70/// - 封装 `tokio::runtime::Runtime`,对外暴露 `block_on` / `spawn` 入口
71/// - 持有 `CancellationToken`,所有后台任务通过 `shutdown_token()` 监听关闭信号
72/// - `shutdown_timeout` 触发关闭流程:先 `cancel()` 通知所有任务,等待超时后 drop runtime
73///
74/// ## 用法
75///
76/// ```rust,ignore
77/// use sz_rust_core::runtime::SzRuntime;
78/// use std::time::Duration;
79///
80/// let rt = SzRuntime::new();
81/// assert!(rt.worker_threads() > 0);
82///
83/// // spawn 后台任务
84/// let handle = rt.spawn(async { 42 });
85/// assert_eq!(rt.block_on(handle).unwrap(), 42);
86///
87/// // 优雅关闭
88/// assert!(rt.shutdown_timeout(Duration::from_millis(100)));
89/// ```
90pub struct SzRuntime {
91    /// 内部 tokio runtime(multi_thread)
92    runtime: tokio::runtime::Runtime,
93    /// worker 线程数(对齐 swoole `worker_num`)
94    worker_threads: usize,
95    /// blocking 线程数(对齐 tokio `max_blocking_threads`)
96    blocking_threads: usize,
97    /// 关闭令牌:所有后台任务通过 `shutdown_token()` 获取子 token 监听关闭
98    shutdown_token: CancellationToken,
99}
100
101impl SzRuntime {
102    /// 创建 SzRuntime,worker_threads = `num_cpus::get()`(对齐 `swoole_cpu_num()`)
103    pub fn new() -> Self {
104        Self::with_worker_threads(num_cpus::get())
105    }
106
107    /// 创建 SzRuntime,自定义 worker_threads(对齐 `worker_num` 配置项)
108    ///
109    /// - `worker_threads = 0` 会被强制为 1
110    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    /// 自定义 blocking 线程数(链式调用)
129    ///
130    /// - `blocking_threads = 0` 会被强制为 1
131    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    /// IO 密集型预设(worker = num_cpus × 2, blocking = 1024)
146    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    /// CPU 密集型预设(worker = num_cpus / 2, blocking = 256)
165    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    /// 均衡预设(worker = num_cpus, blocking = 512,默认)
184    pub fn for_balanced() -> Self {
185        Self::with_worker_threads(num_cpus::get())
186    }
187
188    /// 获取 worker 线程数
189    pub fn worker_threads(&self) -> usize {
190        self.worker_threads
191    }
192
193    /// 获取 blocking 线程数
194    pub fn blocking_threads(&self) -> usize {
195        self.blocking_threads
196    }
197
198    /// 获取关闭令牌的克隆
199    ///
200    /// 后台任务通过 `rt.shutdown_token().cancelled().await` 监听关闭信号。
201    pub fn shutdown_token(&self) -> CancellationToken {
202        self.shutdown_token.clone()
203    }
204
205    /// 在 runtime 上 spawn 异步任务(对齐 `swoole_event::defer`)
206    ///
207    /// 返回 `JoinHandle`,可 await 获取结果。
208    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    /// 在 runtime 上阻塞运行 future(对齐 `Swoole\Runtime::enableCoroutine` 入口)
217    ///
218    /// 调用线程会阻塞直到 future 完成。
219    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    /// 触发优雅关闭:`cancel()` 通知所有后台任务,等待 `timeout` 后 drop runtime
227    ///
228    /// - 返回 `true`:runtime 已关闭
229    /// - 注意:Drop runtime 时 tokio 会等待所有任务完成,超时由调用方控制
230    pub fn shutdown_timeout(self, timeout: Duration) -> bool {
231        self.shutdown_token.cancel();
232        // 给后台任务响应关闭信号的时间
233        self.runtime.block_on(async {
234            let _ = tokio::time::timeout(timeout, async {
235                // 等待一小段时间让任务响应 cancel
236                tokio::time::sleep(Duration::from_millis(10)).await;
237            })
238            .await;
239        });
240        // Drop runtime 会等待所有任务完成(可能阻塞)
241        drop(self.runtime);
242        true
243    }
244
245    /// 获取内部 runtime 句柄(用于在不持有 SzRuntime 时 spawn 任务)
246    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        // shutdown_timeout 已消费 rt,token 已 cancel
301        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            // 模拟后台任务:监听 cancel
310            token.cancelled().await;
311            99
312        });
313        // 触发关闭
314        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        // 验证可以创建多个独立的 runtime 实例
339        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    // ===== P3 6.1: blocking_threads 与预设配置测试 =====
348
349    #[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}