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
// @trace TEST-ENG-004-PROCESS [req:REQ-ENG-004@PROCESS] [level:system]
// @trace REQ-ENG-004@PROCESS [level:system]
// @trace TMG-LOOP-001 [timing:max_interval_ms]
//
// # TASK-17b @PROCESS 时序测试 — TMG-LOOP-001 Event Loop Tick 时序
//
// **核心断言**: bao_runtime 的事件循环 tick 延迟满足 SPEC 时序约束
// (TMG-LOOP-001 max_interval_ms=20ms)。SPEC 03-PROCESS.html 给出各 phase 目标:
//
// - io_poll : 1 ms (epoll/kqueue 非阻塞)
// - timer_check : 0.1 ms (最小堆查找)
// - microtask_drain : 5 ms (SM JobQueue drain)
//
// 测试驱动 `bun_event_loop::MiniEventLoop`(bao_runtime 的 event loop 后端),
// 测量 `tick_without_idle` / `tick_once` 的墙钟延迟。任何 epoll 阻塞、
// task queue O(n²) 退化、timer heap 全扫描都会导致延迟超过阈值。
//
// 注意:`tick_once` 在空 loop 上会进入 `epoll_wait` 阻塞 — 时序测试用
// `tick_without_idle`(只 drain concurrent+task queue,不进 OS 事件循环)。
// 这是单次 tick 的延迟基线测量,等同于生产环境 SM JobQueue drain 的延迟。
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::time::{Duration, Instant};
use bun_event_loop::AnyTaskWithExtraContext::AnyTaskWithExtraContext;
use bun_event_loop::MiniEventLoop::MiniEventLoop;
// Pull in bao_uloop's `#[no_mangle] extern "C"` symbols (uws_get_loop etc.)
// — `MiniEventLoop::init()` reaches them via `UwsLoop::get()` but the linker
// GCs unreferenced no-mangle symbols without an explicit Rust reference.
fn force_uloop_link() {
// Product path owns residual/RealImpl (no bao_native_stubs co-link).
bao_uloop::force_link();
let _ = bun_runtime::dispatch::__bun_run_file_poll
as unsafe extern "Rust" fn(*mut bun_io::posix_event_loop::FilePoll, i64);
}
#[derive(Debug)]
struct CounterCtx {
fired: AtomicUsize,
}
fn increment_task(ctx: *mut CounterCtx, _extra: *mut std::ffi::c_void) {
unsafe {
(*ctx).fired.fetch_add(1, Ordering::SeqCst);
}
}
// ════════════════════════════════════════════════════════════════════
// §1 单次 tick 时序 — 空 loop drain 延迟基线
// ════════════════════════════════════════════════════════════════════
/// 空 loop 单次 tick_without_idle 延迟 < 5ms(TMG-LOOP-001 timer_check + microtask drain)
// Arrange — TMG-LOOP-001: 空 loop tick 基线
// @trace REQ-ENG-004@PROCESS [level:system]
#[test]
fn event_loop_empty_tick_under_threshold() {
force_uloop_link();
let mut loop_ = MiniEventLoop::init();
// Act — 单次 tick_without_idle(无 task,纯 drain 队列 + timer check)
let start = Instant::now();
loop_.tick_without_idle(core::ptr::null_mut());
let elapsed = start.elapsed();
// Assert — TMG-LOOP-001: 单次空 tick < 5ms (timer_check 0.1ms + drain 5ms 容差)
assert!(
elapsed < Duration::from_millis(20),
"TMG-LOOP-001 violation: empty tick_without_idle must be < 20ms (max_interval_ms), got {:?}",
elapsed
);
}
/// 空 loop tick 重复 100 次,最大延迟 < 20ms(TMG-LOOP-001 稳定性)
// Arrange — TMG-LOOP-001: 多次 tick 稳定性
// @trace REQ-ENG-004@PROCESS [level:system]
#[test]
fn event_loop_repeated_empty_tick_stable_under_threshold() {
force_uloop_link();
let mut loop_ = MiniEventLoop::init();
// Act — 100 次 tick_without_idle,测最大延迟
let mut max_elapsed = Duration::ZERO;
for _ in 0..100 {
let start = Instant::now();
loop_.tick_without_idle(core::ptr::null_mut());
let elapsed = start.elapsed();
if elapsed > max_elapsed {
max_elapsed = elapsed;
}
}
// Assert — TMG-LOOP-001: 100 次重复,最大延迟 < 20ms(max_interval_ms)
assert!(
max_elapsed < Duration::from_millis(20),
"TMG-LOOP-001 violation: 100x empty tick max < 20ms, got {:?}",
max_elapsed
);
}
// ════════════════════════════════════════════════════════════════════
// §2 单 task tick 时序 — 单个任务 drain 延迟
// ════════════════════════════════════════════════════════════════════
/// 单 task 入队后 tick 延迟 < 20ms(TMG-LOOP-001 单 task dispatch)
// Arrange — TMG-LOOP-001: 单 task dispatch
// @trace REQ-ENG-004@PROCESS [level:system]
#[test]
fn event_loop_single_task_tick_under_threshold() {
force_uloop_link();
let mut loop_ = MiniEventLoop::init();
let ctx = Box::new(CounterCtx {
fired: AtomicUsize::new(0),
});
let ctx_ptr = Box::into_raw(ctx);
let task_ptr = AnyTaskWithExtraContext::from_callback_auto_deinit(ctx_ptr, increment_task);
let task_nn = unsafe { core::ptr::NonNull::new_unchecked(task_ptr) };
loop_.enqueue_task_concurrent(task_nn);
// Act — tick_without_idle 处理 1 个 task (避免 epoll 阻塞)
let start = Instant::now();
loop_.tick_without_idle(core::ptr::null_mut());
let elapsed = start.elapsed();
// Assert — TMG-LOOP-001: 单 task tick < 20ms
let fired = unsafe { (*ctx_ptr).fired.load(Ordering::SeqCst) };
unsafe {
drop(Box::from_raw(ctx_ptr));
}
assert_eq!(fired, 1, "task must fire exactly once");
assert!(
elapsed < Duration::from_millis(20),
"TMG-LOOP-001 violation: single task tick < 20ms, got {:?}",
elapsed
);
}
/// 多 task(10 个)逐次入队 + tick,总延迟 < 200ms(TMG-LOOP-001 批量 dispatch)
// Arrange — TMG-LOOP-001: 批量 task dispatch(逐次,避免 auto-deinit 二次释放)
// @trace REQ-ENG-004@PROCESS [level:system]
#[test]
fn event_loop_batch_task_tick_under_threshold() {
force_uloop_link();
let mut loop_ = MiniEventLoop::init();
let ctx = Arc::new(AtomicUsize::new(0));
// Act — 入队 + tick 10 次,每次独立 ctx(auto-deinit 安全)
let start = Instant::now();
let mut total_fired = 0usize;
for i in 0..10 {
let local_ctx = Arc::new(AtomicUsize::new(0));
let ctx_ptr = Arc::into_raw(local_ctx) as *mut CounterCtx;
let task_ptr = AnyTaskWithExtraContext::from_callback_auto_deinit(ctx_ptr, increment_task);
let task_nn = unsafe { core::ptr::NonNull::new_unchecked(task_ptr) };
loop_.enqueue_task_concurrent(task_nn);
loop_.tick_without_idle(core::ptr::null_mut());
let fired = unsafe { (*ctx_ptr).fired.load(Ordering::SeqCst) };
// reclaim Arc (wrapper was freed inside callback; reclaim Arc manually)
unsafe {
drop(Arc::from_raw(ctx_ptr as *const CounterCtx));
}
total_fired += fired;
let _ = i; // silence unused
}
let elapsed = start.elapsed();
let _ = ctx; // silence unused
// Assert — TMG-LOOP-001: 10 task + 10 tick 累积 < 200ms (单次 20ms)
assert_eq!(total_fired, 10, "all 10 tasks must fire");
assert!(
elapsed < Duration::from_millis(200),
"TMG-LOOP-001 violation: 10 task tick batch < 200ms, got {:?}",
elapsed
);
}
// ════════════════════════════════════════════════════════════════════
// §3 tick + is_done callback 时序 — 立即返回路径
// ════════════════════════════════════════════════════════════════════
/// is_done=true 立即返回的 tick 延迟 < 20ms(TMG-LOOP-001 fast-exit 路径)
// Arrange — TMG-LOOP-001: fast-exit 路径
// @trace REQ-ENG-004@PROCESS [level:system]
#[test]
fn event_loop_tick_done_callback_fast_exit_under_threshold() {
force_uloop_link();
let mut loop_ = MiniEventLoop::init();
let done = Arc::new(AtomicBool::new(true));
let done_clone = done.clone();
// Act — is_done=true 的 tick 必须立即返回(minimal_event_loop_tests 模式)
let start = Instant::now();
loop_.tick(core::ptr::null_mut(), |_ctx| {
done_clone.load(Ordering::SeqCst)
});
let elapsed = start.elapsed();
// Assert — TMG-LOOP-001: fast-exit < 20ms
assert!(done.load(Ordering::Relaxed), "done flag unchanged");
assert!(
elapsed < Duration::from_millis(20),
"TMG-LOOP-001 violation: fast-exit tick < 20ms, got {:?}",
elapsed
);
}
/// 多次 tick_without_idle — 总延迟随次数线性增长,< 100ms
// Arrange — TMG-LOOP-001: 多次 tick 累积延迟
// @trace REQ-ENG-004@PROCESS [level:system]
#[test]
fn event_loop_multi_tick_stable_under_threshold() {
force_uloop_link();
let mut loop_ = MiniEventLoop::init();
// Act — 10 次 tick_without_idle(避免 epoll 阻塞)
let start = Instant::now();
for _ in 0..10 {
loop_.tick_without_idle(core::ptr::null_mut());
}
let elapsed = start.elapsed();
// Assert — TMG-LOOP-001: 10 次 tick 累积 < 100ms (单次 10ms)
assert!(
elapsed < Duration::from_millis(100),
"TMG-LOOP-001 violation: 10 ticks < 100ms (10ms/tick), got {:?}",
elapsed
);
}
// ════════════════════════════════════════════════════════════════════
// §4 init + 多次 tick 稳定性 — 整体生命周期延迟
// ════════════════════════════════════════════════════════════════════
/// MiniEventLoop::init 延迟 < 50ms(TMG-LOOP-001 loop 初始化)
// Arrange — TMG-LOOP-001: loop 初始化时序
// @trace REQ-ENG-004@PROCESS [level:system]
#[test]
fn event_loop_init_under_threshold() {
force_uloop_link();
// Act — MiniEventLoop::init 内部走 UwsLoop::get() + uSockets loop 创建
let start = Instant::now();
let _loop_ = MiniEventLoop::init();
let elapsed = start.elapsed();
// Assert — TMG-LOOP-001: init < 50ms (单线程 + epoll fd 创建)
assert!(
elapsed < Duration::from_millis(50),
"TMG-LOOP-001 violation: MiniEventLoop::init < 50ms, got {:?}",
elapsed
);
}
/// 完整生命周期:init → 10 tick → drop,总延迟 < 200ms
// Arrange — TMG-LOOP-001: 完整生命周期
// @trace REQ-ENG-004@PROCESS [level:system]
#[test]
fn event_loop_lifecycle_total_under_threshold() {
force_uloop_link();
// Act — init + 10 tick + 隐式 drop 的总延迟
let start = Instant::now();
{
let mut loop_ = MiniEventLoop::init();
for _ in 0..10 {
loop_.tick_without_idle(core::ptr::null_mut());
}
}
let elapsed = start.elapsed();
// Assert — TMG-LOOP-001: 完整生命周期 < 200ms
assert!(
elapsed < Duration::from_millis(200),
"TMG-LOOP-001 violation: full lifecycle (init + 10 tick + drop) < 200ms, got {:?}",
elapsed
);
}