sz-rust-core 0.6.0

SZ-Rust 核心库:HTTP 服务器、路由、控制器、中间件,对标 ThinkPHP 8
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
//! SZ-Rust Runtime — Swoole/Worker 适配主入口
//!
//! ## PHP 对齐
//!
//! 对齐 PHP `topthink/think-swoole` 的运行时模型:
//!
//! | PHP Swoole | Rust SZ-Rust | 说明 |
//! |------------|--------------|------|
//! | `Swoole\Runtime::enableCoroutine()` | `SzRuntime::block_on(fut)` | 协程入口 |
//! | `swoole_cpu_num()` | `num_cpus::get()` | 默认 worker 数 |
//! | `worker_num` 配置项 | `SzRuntime::with_worker_threads(n)` | 自定义 worker 数 |
//! | `Swoole\Process::signal()` | `tokio::signal` + `CancellationToken` | 信号处理 |
//! | `Swoole\Timer::tick()` | `tokio::time::interval` + `tokio::select!` | 定时任务 |
//! | `swoole_event::defer()` | `SzRuntime::spawn(fut)` | 异步任务 |
//!
//! ## 模块结构
//!
//! | 模块 | 对齐 PHP | 子任务 |
//! |------|---------|--------|
//! | `runtime::worker` | `worker_num` 配置 | 9.2 |
//! | `runtime::spawn` | `swoole_event::defer` | 9.3 |
//! | `runtime::queue` | `think-queue` 消费者 | 9.4 |
//! | `runtime::mqtt` | 长连接 | 9.5 |
//! | `runtime::websocket` | `think-worker` | 9.6 |
//! | `runtime::scheduler` | `Crontab` | 9.7 |
//! | `runtime::shutdown` | 优雅关闭 | 9.8 |
//! | `runtime::signal` | `SIGTERM/SIGINT` | 9.9 |
//!
//! ## 关键决策
//!
//! - **不修改 `sz-orm-scheduler` 内部 API**:保持 `CronScheduler::start()`/`stop()` 向后兼容,
//!   在 sz-rust 侧用 `tokio::time::interval` + `try_fire_due()` 重写循环。
//! - **使用 `CancellationToken` 统一关闭广播**:替代 `AtomicBool` + `oneshot`,支持父子层级。
//! - **双平台信号处理**:Unix 用 `SignalKind::terminate/interrupt`,Windows 用 `ctrl_c/ctrl_close`。

pub mod mqtt;
pub mod queue;
pub mod scheduler;
pub mod shutdown;
pub mod signal;
pub mod spawn;
pub mod websocket;
pub mod worker;

// P2: Addon 热加载探索(可选 feature: hot-reload)
#[cfg(feature = "hot-reload")]
pub mod hot_reload;

pub use mqtt::{MqttRuntime, MqttRuntimeConfig};
pub use queue::{QueueConsumer, QueueRuntime, QueueRuntimeConfig};
pub use scheduler::SchedulerRuntime;
pub use shutdown::GracefulShutdown;
pub use signal::shutdown_signal;
pub use spawn::spawn_with_token;
pub use websocket::{WebSocketRuntime, WebSocketRuntimeConfig};
pub use worker::WorkerConfig;

use std::future::Future;
use std::time::Duration;

use tokio_util::sync::CancellationToken;

/// SZ-Rust 异步运行时
///
/// 对齐 PHP `think-swoole` 的运行时模型:基于 tokio multi_thread runtime,
/// worker 数量默认 = CPU 核数(对齐 `swoole_cpu_num()`)。
///
/// ## 设计
///
/// - 封装 `tokio::runtime::Runtime`,对外暴露 `block_on` / `spawn` 入口
/// - 持有 `CancellationToken`,所有后台任务通过 `shutdown_token()` 监听关闭信号
/// - `shutdown_timeout` 触发关闭流程:先 `cancel()` 通知所有任务,等待超时后 drop runtime
///
/// ## 用法
///
/// ```rust,ignore
/// use sz_rust_core::runtime::SzRuntime;
/// use std::time::Duration;
///
/// let rt = SzRuntime::new();
/// assert!(rt.worker_threads() > 0);
///
/// // spawn 后台任务
/// let handle = rt.spawn(async { 42 });
/// assert_eq!(rt.block_on(handle).unwrap(), 42);
///
/// // 优雅关闭
/// assert!(rt.shutdown_timeout(Duration::from_millis(100)));
/// ```
pub struct SzRuntime {
    /// 内部 tokio runtime(multi_thread)
    runtime: tokio::runtime::Runtime,
    /// worker 线程数(对齐 swoole `worker_num`)
    worker_threads: usize,
    /// blocking 线程数(对齐 tokio `max_blocking_threads`)
    blocking_threads: usize,
    /// 关闭令牌:所有后台任务通过 `shutdown_token()` 获取子 token 监听关闭
    shutdown_token: CancellationToken,
}

impl SzRuntime {
    /// 创建 SzRuntime,worker_threads = `num_cpus::get()`(对齐 `swoole_cpu_num()`)
    pub fn new() -> Self {
        Self::with_worker_threads(num_cpus::get())
    }

    /// 创建 SzRuntime,自定义 worker_threads(对齐 `worker_num` 配置项)
    ///
    /// - `worker_threads = 0` 会被强制为 1
    pub fn with_worker_threads(worker_threads: usize) -> Self {
        let n = worker_threads.max(1);
        let blocking = 512;
        let runtime = tokio::runtime::Builder::new_multi_thread()
            .worker_threads(n)
            .max_blocking_threads(blocking)
            .enable_all()
            .thread_name("sz-rust-worker")
            .build()
            .expect("Failed to create tokio runtime");
        Self {
            runtime,
            worker_threads: n,
            blocking_threads: blocking,
            shutdown_token: CancellationToken::new(),
        }
    }

    /// 自定义 blocking 线程数(链式调用)
    ///
    /// - `blocking_threads = 0` 会被强制为 1
    pub fn with_blocking_threads(mut self, blocking_threads: usize) -> Self {
        let b = blocking_threads.max(1);
        let runtime = tokio::runtime::Builder::new_multi_thread()
            .worker_threads(self.worker_threads)
            .max_blocking_threads(b)
            .enable_all()
            .thread_name("sz-rust-worker")
            .build()
            .expect("Failed to create tokio runtime");
        self.runtime = runtime;
        self.blocking_threads = b;
        self
    }

    /// IO 密集型预设(worker = num_cpus × 2, blocking = 1024)
    pub fn for_io_intensive() -> Self {
        let worker = (num_cpus::get() * 2).max(1);
        let blocking = 1024;
        let runtime = tokio::runtime::Builder::new_multi_thread()
            .worker_threads(worker)
            .max_blocking_threads(blocking)
            .enable_all()
            .thread_name("sz-rust-io")
            .build()
            .expect("Failed to create tokio runtime");
        Self {
            runtime,
            worker_threads: worker,
            blocking_threads: blocking,
            shutdown_token: CancellationToken::new(),
        }
    }

    /// CPU 密集型预设(worker = num_cpus / 2, blocking = 256)
    pub fn for_cpu_intensive() -> Self {
        let worker = (num_cpus::get() / 2).max(1);
        let blocking = 256;
        let runtime = tokio::runtime::Builder::new_multi_thread()
            .worker_threads(worker)
            .max_blocking_threads(blocking)
            .enable_all()
            .thread_name("sz-rust-cpu")
            .build()
            .expect("Failed to create tokio runtime");
        Self {
            runtime,
            worker_threads: worker,
            blocking_threads: blocking,
            shutdown_token: CancellationToken::new(),
        }
    }

    /// 均衡预设(worker = num_cpus, blocking = 512,默认)
    pub fn for_balanced() -> Self {
        Self::with_worker_threads(num_cpus::get())
    }

    /// 获取 worker 线程数
    pub fn worker_threads(&self) -> usize {
        self.worker_threads
    }

    /// 获取 blocking 线程数
    pub fn blocking_threads(&self) -> usize {
        self.blocking_threads
    }

    /// 获取关闭令牌的克隆
    ///
    /// 后台任务通过 `rt.shutdown_token().cancelled().await` 监听关闭信号。
    pub fn shutdown_token(&self) -> CancellationToken {
        self.shutdown_token.clone()
    }

    /// 在 runtime 上 spawn 异步任务(对齐 `swoole_event::defer`)
    ///
    /// 返回 `JoinHandle`,可 await 获取结果。
    pub fn spawn<F>(&self, future: F) -> tokio::task::JoinHandle<F::Output>
    where
        F: Future + Send + 'static,
        F::Output: Send + 'static,
    {
        self.runtime.spawn(future)
    }

    /// 在 runtime 上阻塞运行 future(对齐 `Swoole\Runtime::enableCoroutine` 入口)
    ///
    /// 调用线程会阻塞直到 future 完成。
    pub fn block_on<F>(&self, future: F) -> F::Output
    where
        F: Future,
    {
        self.runtime.block_on(future)
    }

    /// 触发优雅关闭:`cancel()` 通知所有后台任务,等待 `timeout` 后 drop runtime
    ///
    /// - 返回 `true`:runtime 已关闭
    /// - 注意:Drop runtime 时 tokio 会等待所有任务完成,超时由调用方控制
    pub fn shutdown_timeout(self, timeout: Duration) -> bool {
        self.shutdown_token.cancel();
        // 给后台任务响应关闭信号的时间
        self.runtime.block_on(async {
            let _ = tokio::time::timeout(timeout, async {
                // 等待一小段时间让任务响应 cancel
                tokio::time::sleep(Duration::from_millis(10)).await;
            })
            .await;
        });
        // Drop runtime 会等待所有任务完成(可能阻塞)
        drop(self.runtime);
        true
    }

    /// 获取内部 runtime 句柄(用于在不持有 SzRuntime 时 spawn 任务)
    pub fn handle(&self) -> tokio::runtime::Handle {
        self.runtime.handle().clone()
    }
}

impl Default for SzRuntime {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_new_default_worker_threads() {
        let rt = SzRuntime::new();
        assert_eq!(rt.worker_threads(), num_cpus::get());
    }

    #[test]
    fn test_with_worker_threads_custom() {
        let rt = SzRuntime::with_worker_threads(2);
        assert_eq!(rt.worker_threads(), 2);
    }

    #[test]
    fn test_with_worker_threads_zero_falls_back_to_one() {
        let rt = SzRuntime::with_worker_threads(0);
        assert_eq!(rt.worker_threads(), 1);
    }

    #[test]
    fn test_spawn_and_block_on() {
        let rt = SzRuntime::with_worker_threads(1);
        let handle = rt.spawn(async { 42 });
        let result = rt.block_on(handle).unwrap();
        assert_eq!(result, 42);
    }

    #[test]
    fn test_block_on_directly() {
        let rt = SzRuntime::with_worker_threads(1);
        let result = rt.block_on(async { 100 });
        assert_eq!(result, 100);
    }

    #[test]
    fn test_shutdown_token_cancellation() {
        let rt = SzRuntime::with_worker_threads(1);
        let token = rt.shutdown_token();
        assert!(!token.is_cancelled());
        assert!(rt.shutdown_timeout(Duration::from_millis(50)));
        // shutdown_timeout 已消费 rt,token 已 cancel
        assert!(token.is_cancelled());
    }

    #[test]
    fn test_spawn_with_token_cancellation() {
        let rt = SzRuntime::with_worker_threads(1);
        let token = rt.shutdown_token();
        let handle = rt.spawn(async move {
            // 模拟后台任务:监听 cancel
            token.cancelled().await;
            99
        });
        // 触发关闭
        let token2 = rt.shutdown_token();
        token2.cancel();
        let result = rt.block_on(handle).unwrap();
        assert_eq!(result, 99);
    }

    #[test]
    fn test_handle_can_spawn() {
        let rt = SzRuntime::with_worker_threads(1);
        let handle = rt.handle();
        let task = handle.spawn(async { 7 });
        let result = rt.block_on(task).unwrap();
        assert_eq!(result, 7);
    }

    #[test]
    fn test_default_impl_equals_new() {
        let rt1 = SzRuntime::default();
        let rt2 = SzRuntime::new();
        assert_eq!(rt1.worker_threads(), rt2.worker_threads());
    }

    #[test]
    fn test_multiple_runtime_instances() {
        // 验证可以创建多个独立的 runtime 实例
        let rt1 = SzRuntime::with_worker_threads(1);
        let rt2 = SzRuntime::with_worker_threads(1);
        let h1 = rt1.spawn(async { 1 });
        let h2 = rt2.spawn(async { 2 });
        assert_eq!(rt1.block_on(h1).unwrap(), 1);
        assert_eq!(rt2.block_on(h2).unwrap(), 2);
    }

    // ===== P3 6.1: blocking_threads 与预设配置测试 =====

    #[test]
    fn test_default_blocking_threads_is_512() {
        let rt = SzRuntime::new();
        assert_eq!(rt.blocking_threads(), 512);
    }

    #[test]
    fn test_with_blocking_threads_custom() {
        let rt = SzRuntime::with_worker_threads(2).with_blocking_threads(256);
        assert_eq!(rt.worker_threads(), 2);
        assert_eq!(rt.blocking_threads(), 256);
    }

    #[test]
    fn test_with_blocking_threads_zero_falls_back_to_one() {
        let rt = SzRuntime::with_worker_threads(1).with_blocking_threads(0);
        assert_eq!(rt.blocking_threads(), 1);
    }

    #[test]
    fn test_for_io_intensive_worker_doubled() {
        let rt = SzRuntime::for_io_intensive();
        assert_eq!(rt.worker_threads(), (num_cpus::get() * 2).max(1));
        assert_eq!(rt.blocking_threads(), 1024);
    }

    #[test]
    fn test_for_cpu_intensive_worker_halved() {
        let rt = SzRuntime::for_cpu_intensive();
        assert_eq!(rt.worker_threads(), (num_cpus::get() / 2).max(1));
        assert_eq!(rt.blocking_threads(), 256);
    }

    #[test]
    fn test_for_balanced_equals_default() {
        let rt = SzRuntime::for_balanced();
        assert_eq!(rt.worker_threads(), num_cpus::get());
        assert_eq!(rt.blocking_threads(), 512);
    }

    #[test]
    fn test_io_intensive_spawn_works() {
        let rt = SzRuntime::for_io_intensive();
        let handle = rt.spawn(async { 42 });
        assert_eq!(rt.block_on(handle).unwrap(), 42);
    }

    #[test]
    fn test_cpu_intensive_spawn_works() {
        let rt = SzRuntime::for_cpu_intensive();
        let handle = rt.spawn(async { 42 });
        assert_eq!(rt.block_on(handle).unwrap(), 42);
    }

    #[test]
    fn test_balanced_spawn_works() {
        let rt = SzRuntime::for_balanced();
        let handle = rt.spawn(async { 42 });
        assert_eq!(rt.block_on(handle).unwrap(), 42);
    }

    #[test]
    fn test_with_blocking_threads_chain_spawn_works() {
        let rt = SzRuntime::with_worker_threads(2).with_blocking_threads(128);
        let handle = rt.spawn(async { 42 });
        assert_eq!(rt.block_on(handle).unwrap(), 42);
        assert_eq!(rt.blocking_threads(), 128);
    }

    #[test]
    fn test_presets_have_distinct_blocking_threads() {
        let io_rt = SzRuntime::for_io_intensive();
        let cpu_rt = SzRuntime::for_cpu_intensive();
        let balanced_rt = SzRuntime::for_balanced();

        assert_ne!(io_rt.blocking_threads(), cpu_rt.blocking_threads());
        assert_ne!(balanced_rt.blocking_threads(), cpu_rt.blocking_threads());
    }
}