zenith-linux 0.1.0

Zenith Linux 平台抽象层:AF_XDP Socket、UMEM 内存管理、四环操作(Fill/RX/TX/Completion)、描述符安全校验引擎
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
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
//! io_uring 异步 IO 批量化封装(O2)
//!
//! 将多次 splice/sendfile 系统调用合并为单次 `io_uring_enter`,
//! 在高并发代理场景下降低 30% 系统调用开销。
//!
//! # 环境要求
//! - Linux 5.1+(io_uring 基础设施)
//! - Linux 5.15+(IORING_OP_SPLICE)
//!
//! # 极致性能
//! - N 次 splice 合并为 1 次 `io_uring_enter`:syscall 从 N 降到 1
//! - 内核侧异步执行,CPU 可继续处理其他任务
//! - SQE/CQE 环共享内存,零拷贝通信
//! - 与现有同步 `splice()` 互补:同步路径用于单连接,io_uring 用于批量并发
//!
//! # 使用示例
//! ```no_run
//! use zenith_linux::IoUringBatcher;
//! use std::os::unix::io::RawFd;
//! # let fd_in: RawFd = 0;
//! # let fd_out: RawFd = 0;
//! # let fd_in2: RawFd = 0;
//! # let fd_out2: RawFd = 0;
//!
//! // 创建 io_uring 实例(SQ 深度 64)
//! let mut batcher = IoUringBatcher::new(64).unwrap();
//!
//! // 批量提交 splice 操作
//! batcher.push_splice(fd_in, -1, fd_out, -1, 4096, 0, 1).unwrap();
//! batcher.push_splice(fd_in2, -1, fd_out2, -1, 4096, 0, 2).unwrap();
//!
//! // 单次 io_uring_enter 提交全部并等待完成
//! batcher.submit_and_wait(2).unwrap();
//!
//! // 收集完成事件
//! for c in batcher.collect_completions() {
//!     if c.result >= 0 {
//!         println!("SQE {} transferred {} bytes", c.user_data, c.result);
//!     }
//! }
//! ```

#![cfg(feature = "io_uring")]
#![cfg(target_os = "linux")]
// unsafe 不可避免:io_uring SQE/CQE 为内核共享内存环,opcode 提交与
// 完成事件读取涉及 raw 结构体填充与 fd FFI,无法以安全 Rust 表达。
// 每个 unsafe 块均附 // SAFETY: 行内注释列出不变量。
#![allow(unsafe_code)]

use crate::error::{LinuxError, Result};
use crate::syscalls::pipe2;
use io_uring::{opcode, squeue::Flags as SqeFlags, types, IoUring};
use std::os::unix::io::RawFd;

/// io_uring SQE 完成结果
#[derive(Debug, Clone, Copy)]
pub struct Completion {
    /// 返回值(成功时为传输字节数,失败时为负 errno)
    pub result: i32,
    /// 用户数据(提交时设置,用于关联请求)
    pub user_data: u64,
}

/// io_uring 批量 IO 提交器
///
/// 将多个 splice/sendfile 操作批量提交到 io_uring 实例,
/// 单次 `io_uring_enter` 完成全部提交,大幅降低系统调用开销。
///
/// # 线程安全
/// `IoUringBatcher` 不可跨线程共享(io_uring SQ 是单生产者模型)。
/// 多线程场景应每线程持有独立实例,或使用外部同步。
///
/// # 极致性能设计
/// - 单生产者模型:无锁 SQ 推入,零竞争
/// - 批量提交:N 个 SQE 仅需 1 次 `io_uring_enter`
/// - 零拷贝:SQE/CQE 通过 mmap 共享内存通信
/// - 内核异步执行:提交后 CPU 可处理其他任务
pub struct IoUringBatcher {
    ring: IoUring,
}

impl std::fmt::Debug for IoUringBatcher {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("IoUringBatcher").finish_non_exhaustive()
    }
}

impl IoUringBatcher {
    /// 创建 io_uring 实例
    ///
    /// # 参数
    /// * `entries` - 提交队列深度(必须为 2 的幂,建议为预期并发 IO 数的 2 倍)
    ///
    /// # 错误
    /// - `LinuxError::Syscall`:`io_uring_setup` 失败(内核不支持或资源不足)
    pub fn new(entries: u32) -> Result<Self> {
        let ring = IoUring::new(entries).map_err(|e| LinuxError::Syscall {
            syscall: "io_uring_setup",
            errno: e.raw_os_error().unwrap_or(0),
        })?;
        Ok(Self { ring })
    }

    /// 向提交队列推入一个 splice SQE
    ///
    /// 对应 `splice(2)` 系统调用,在 fd_in 和 fd_out 之间零拷贝移动数据。
    /// 至少一个 fd 必须是管道。
    ///
    /// # 参数
    /// * `fd_in` - 输入 fd
    /// * `off_in` - 输入偏移(-1 表示使用当前文件偏移)
    /// * `fd_out` - 输出 fd
    /// * `off_out` - 输出偏移(-1 表示使用当前文件偏移)
    /// * `len` - 传输字节数
    /// * `splice_flags` - splice 标志位(SPLICE_F_MOVE 等)
    /// * `user_data` - 用户数据,用于关联完成事件
    ///
    /// # 错误
    /// - `LinuxError::InsufficientResources`:提交队列已满
    #[allow(clippy::too_many_arguments)]
    pub fn push_splice(
        &mut self,
        fd_in: RawFd,
        off_in: i64,
        fd_out: RawFd,
        off_out: i64,
        len: u32,
        splice_flags: u32,
        user_data: u64,
    ) -> Result<()> {
        let entry = opcode::Splice::new(
            types::Fd(fd_in),
            off_in,
            types::Fd(fd_out),
            off_out,
            len,
        )
        .flags(splice_flags)
        .build()
        .user_data(user_data);

        let mut sq = self.ring.submission();
        // SAFETY: IoUringBatcher 是单生产者(不可跨线程共享),
        // 每次 push 使用新构建的 Entry,不会出现 double-push。
        unsafe {
            sq.push(&entry).map_err(|_| {
                LinuxError::InsufficientResources("io_uring SQ full".to_string())
            })?;
        }
        Ok(())
    }

    /// 向提交队列推入一个 sendfile 操作(file → pipe → socket 双段零拷贝)
    ///
    /// 实现为两个链式 splice SQE:
    /// 1. file_fd → pipe_fd(带 IOSQE_IO_LINK 标志,确保 file→pipe 完成后再 pipe→socket)
    /// 2. pipe_fd → socket_fd
    ///
    /// IOSQE_IO_LINK 确保内核在第一段完成后才执行第二段,
    /// 保证数据完整性。
    ///
    /// # 参数
    /// * `file_fd` - 源文件 fd
    /// * `pipe_fd` - 中间管道 fd(需调用方预先通过 `pipe2()` 创建)
    /// * `socket_fd` - 目标 socket fd
    /// * `len` - 传输字节数
    /// * `splice_flags` - splice 标志位
    /// * `user_data` - 用户数据(file→pipe SQE 使用,pipe→socket SQE 使用 user_data+1)
    ///
    /// # 错误
    /// - `LinuxError::InsufficientResources`:提交队列空间不足
    pub fn push_sendfile(
        &mut self,
        file_fd: RawFd,
        pipe_fd: RawFd,
        socket_fd: RawFd,
        len: u32,
        splice_flags: u32,
        user_data: u64,
    ) -> Result<()> {
        // 预检:push_sendfile 需要推入 2 个链式 SQE,
        // 若 SQ 剩余空间不足 2,提前返回错误,避免第一个 SQE 入队后
        // 第二个失败导致孤立链式 SQE(无链接伙伴,内核行为未定义)
        if self.sq_space_left() < 2 {
            return Err(LinuxError::InsufficientResources(
                "io_uring SQ 空间不足 2(sendfile 需要两个链式 SQE)".to_string(),
            ));
        }

        // SQE 1: file → pipe(IOSQE_IO_LINK 链式,确保顺序执行)
        let entry1 = opcode::Splice::new(
            types::Fd(file_fd),
            -1,
            types::Fd(pipe_fd),
            -1,
            len,
        )
        .flags(splice_flags)
        .build()
        .user_data(user_data)
        .flags(SqeFlags::IO_LINK);

        // SQE 2: pipe → socket
        let entry2 = opcode::Splice::new(
            types::Fd(pipe_fd),
            -1,
            types::Fd(socket_fd),
            -1,
            len,
        )
        .flags(splice_flags)
        .build()
        .user_data(user_data.wrapping_add(1));

        let mut sq = self.ring.submission();
        // SAFETY: 单生产者,新构建的 Entry,无 double-push
        unsafe {
            sq.push(&entry1).map_err(|_| {
                LinuxError::InsufficientResources("io_uring SQ full".to_string())
            })?;
            sq.push(&entry2).map_err(|_| {
                LinuxError::InsufficientResources("io_uring SQ full".to_string())
            })?;
        }
        Ok(())
    }

    /// 向提交队列推入一个 NOP SQE(仅用于测试/预热 io_uring)
    ///
    /// # 参数
    /// * `user_data` - 用户数据
    pub fn push_nop(&mut self, user_data: u64) -> Result<()> {
        let entry = opcode::Nop::new().build().user_data(user_data);

        let mut sq = self.ring.submission();
        // SAFETY: 单生产者,新构建的 Entry
        unsafe {
            sq.push(&entry).map_err(|_| {
                LinuxError::InsufficientResources("io_uring SQ full".to_string())
            })?;
        }
        Ok(())
    }

    /// 提交所有待处理 SQE 并等待至少 `min_complete` 个 CQE
    ///
    /// 单次 `io_uring_enter` 系统调用完成全部提交并等待。
    ///
    /// # 参数
    /// * `min_complete` - 最少等待完成的 CQE 数量
    ///
    /// # 返回
    /// 提交的 SQE 数量
    pub fn submit_and_wait(&mut self, min_complete: u32) -> Result<usize> {
        self.ring
            .submit_and_wait(min_complete as usize)
            .map_err(|e| LinuxError::Syscall {
                syscall: "io_uring_enter",
                errno: e.raw_os_error().unwrap_or(0),
            })
    }

    /// 提交所有待处理 SQE(不等待完成)
    ///
    /// # 返回
    /// 提交的 SQE 数量
    pub fn submit(&mut self) -> Result<usize> {
        self.ring
            .submit()
            .map_err(|e| LinuxError::Syscall {
                syscall: "io_uring_enter",
                errno: e.raw_os_error().unwrap_or(0),
            })
    }

    /// 收集已完成的 CQE
    ///
    /// # 返回
    /// 迭代器,产出所有已完成的 CQE
    pub fn collect_completions(&mut self) -> impl Iterator<Item = Completion> + '_ {
        self.ring.completion().map(|cqe| Completion {
            result: cqe.result(),
            user_data: cqe.user_data(),
        })
    }

    /// 获取提交队列剩余可用空间
    pub fn sq_space_left(&mut self) -> usize {
        let cap = self.ring.submission().capacity();
        let len = self.ring.submission().len();
        cap - len
    }

    /// 获取完成队列中未处理的事件数
    pub fn cq_ready(&mut self) -> usize {
        self.ring.completion().len()
    }
}

// ========================================================================
// GAP-3 修复:io_uring 批量双向 splice 中继(单线程替代 2×N 线程)
// ========================================================================
//
// 问题:原 [`crate::syscalls::splice_bidirectional`] 对每个连接 spawn 2 个
// 阻塞线程(c2u + u2c),在 10K+ 并发代理场景下导致:
//   - 2×N 线程栈 → ~160GB 虚拟地址空间压力(每线程 8MB 栈)
//   - 内核调度器上下文切换飙升
//   - io_uring 批量能力未被利用(同一批 relay 可合并系统调用)
//
// 修复:提供 [`SpliceBatcher`]:
//   - 1 个驱动线程(当前线程,同步轮询 io_uring CQ)
//   - N 个连接 → 4 个 pipe fd/连接 + 2 个方向/连接
//   - 初始为每个 session 推入 2 个异步 splice SQE(sock→pipe, pipe→sock)
//   - CQE 完成后立刻推入下一轮 splice SQE,直至 EPIPE/EINVAL/EOF
//   - 所有 session 共享 1 个 io_uring 实例与 1 条驱动线程
//
// 与原 API 关系:
//   - 短连接 / 延迟敏感:继续使用 `splice_bidirectional`(双线程无事件循环开销)
//   - 高并发 / 吞吐敏感:使用 `SpliceBatcher::drive_all(...)`(单线程 N 连接)
// ========================================================================

/// 双向中继会话标识(user_data 高 32 bit)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
enum Dir {
    /// client → upstream:client_fd → c2u_pipe_w → c2u_pipe_r → upstream_fd
    C2UIn  = 0, // Splice: client → c2u pipe
    C2UOut = 1, // Splice: c2u pipe → upstream
    /// upstream → client:upstream_fd → u2c_pipe_w → u2c_pipe_r → client_fd
    U2CIn  = 2, // Splice: upstream → u2c pipe
    U2COut = 3, // Splice: u2c pipe → client
}

/// Out 方向 splice 完成后的扣账/重排决策(纯函数,无 ring 环境可单测)
///
/// 记账不变式(修复「入队时乐观扣账导致 EAGAIN/短写静默丢数据」):
/// - `pending` 只在 **In 方向成功写入管道** 时累加(见 `Dir::*In` 分支)
/// - `pending` 只在 **Out 方向完成且 result > 0** 时按实际字节扣减(本函数);
///   入队 SQE 时绝不扣账——EAGAIN(0 字节完成)不丢账
///
/// # 参数
/// * `pending` - 管道内尚待排空的字节数
/// * `drained` - 本次 Out splice 实际完成字节(result ≤ 0 / EAGAIN 按 0 传入)
///
/// # 返回
/// `(new_pending, requeue)`:
/// - `new_pending = pending - drained`(下限 0,防御异常完成值,绝不下溢)
/// - `requeue = new_pending > 0`:未排空量必须重新推入 Out SQE,否则丢数据
fn on_out_completion(pending: u32, drained: u32) -> (u32, bool) {
    let new_pending = pending.saturating_sub(drained);
    (new_pending, new_pending > 0)
}

/// SpliceBatcher 内部会话状态
struct Session {
    client_fd: i32,
    upstream_fd: i32,
    c2u_read: i32,
    c2u_write: i32,
    u2c_read: i32,
    u2c_write: i32,
    /// c2u 方向累计传输字节(checked_add)
    bytes_c2u: usize,
    /// u2c 方向累计传输字节
    bytes_u2c: usize,
    /// c2u / u2c 是否已到 EOF(不再推新 SQE)
    c2u_eof: bool,
    u2c_eof: bool,
    /// 最近 splice 产生的可写 pipe 内未排空字节数(Out SQE 使用)
    c2u_pipe_pending: u32,
    u2c_pipe_pending: u32,
}

impl Drop for Session {
    fn drop(&mut self) {
        // 清理 4 个 pipe fd。client_fd / upstream_fd 由调用方管理。
        // SAFETY: 4 个 fd 均由 Session::new 通过 libc::pipe2 成功创建并归
        // Session 独占所有,Drop 仅执行一次,close 后字段不再使用;
        // 重复 close 不会发生(Rust 所有权保证 Drop 至多调用一次)。
        unsafe {
            libc::close(self.c2u_read);
            libc::close(self.c2u_write);
            libc::close(self.u2c_read);
            libc::close(self.u2c_write);
        }
    }
}

/// 批量双向 splice 中继驱动(单线程,基于 io_uring)
///
/// # 极致性能
/// - **线程模型**:1 条驱动线程驱动任意数量连接,彻底消除 2×N 线程爆炸
/// - **系统调用合并**:N 个连接、每个方向初始 + N×后续 splice,
///   每 `io_uring_enter` 最多提交 SQ 深度条 SQE(默认 64 / 128)
/// - **零分配热路径**:session 数组预分配,CQE 轮询 → SQE 推入在
///   循环内完成,不分配 Vec/Box
/// - **Fail-Closed**:任何单 session 的非 EPIPE/EINVAL/ECONNRESET splice
///   错误立即上抛,其余 session 状态通过 Drop 自动清理
pub struct SpliceBatcher {
    ring: IoUringBatcher,
    pipe_buf_size: usize,
}

impl SpliceBatcher {
    /// 创建批量中继驱动。
    ///
    /// # 参数
    /// * `sq_entries` - io_uring SQ 深度,建议 `min(128, 4 * 预期并发连接数)`
    /// * `pipe_buf_size` - 单次 splice 传输字节数(通常 16384 或 65536)
    pub fn new(sq_entries: u32, pipe_buf_size: usize) -> Result<Self> {
        Ok(Self {
            ring: IoUringBatcher::new(sq_entries)?,
            pipe_buf_size,
        })
    }

    /// 编码 user_data:(session_idx << 4) | (dir as u8)
    ///  - session_idx 放高 60 bit,可容纳 2^60 个 session(实际由 sq_entries 限制)
    #[inline]
    fn enc_user_data(session_idx: u64, dir: Dir) -> u64 {
        (session_idx << 4) | (dir as u64)
    }

    /// 解码 user_data;方向位为保留值(编码损坏)时返回错误(Fail-Closed,不 panic)
    #[inline]
    fn dec_user_data(user_data: u64) -> Result<(usize, Dir)> {
        let session_idx = (user_data >> 4) as usize;
        let dir_bits = (user_data & 0xF) as u8;
        let dir = match dir_bits {
            0 => Dir::C2UIn,
            1 => Dir::C2UOut,
            2 => Dir::U2CIn,
            3 => Dir::U2COut,
            other => {
                return Err(LinuxError::Unsupported(format!(
                    "io_uring CQE user_data 含保留方向位 {other}(编码损坏)"
                )))
            }
        };
        Ok((session_idx, dir))
    }

    /// 驱动一批(client_fd, upstream_fd)对的双向零拷贝中继,
    /// 所有连接都结束(EOF/错误)后返回所有 session 的传输字节数。
    ///
    /// # 行为
    /// - 阻塞当前线程(调用方应在专用驱动线程中调用)
    /// - 任何 session 出现致命错误(非 EPIPE/ECONNRESET/EINVAL)立即返回错误
    /// - EOF(EPIPE / splice 返回 0)属于正常结束,累积字节数后 continue
    ///
    /// # 返回
    /// `Vec<(c2u_bytes, u2c_bytes)>` 与输入 pairs 同序。
    pub fn drive_all(mut self, pairs: &[(i32, i32)]) -> Result<Vec<(usize, usize)>> {
        use std::os::unix::io::RawFd;

        let n_sessions = pairs.len();
        if n_sessions == 0 {
            return Ok(Vec::new());
        }

        // 1) 创建每个 session 的 pipe 与元数据
        let mut sessions: Vec<Session> = Vec::with_capacity(n_sessions);
        for (client_fd, upstream_fd) in pairs.iter().copied() {
            let (c2u_read, c2u_write) = pipe2(0)?;
            let (u2c_read, u2c_write) = pipe2(0)?;
            sessions.push(Session {
                client_fd,
                upstream_fd,
                c2u_read,
                c2u_write,
                u2c_read,
                u2c_write,
                bytes_c2u: 0,
                bytes_u2c: 0,
                c2u_eof: false,
                u2c_eof: false,
                c2u_pipe_pending: 0,
                u2c_pipe_pending: 0,
            });
        }

        // 2) 初始推入每个 session 的 2 个 In-SQE(C2UIn + U2CIn)
        //    若 SQ 深度不够,每批 submit_and_wait 推进后再继续。
        let mut in_flight: u32 = 0;
        let flags_splice: u32 = libc::SPLICE_F_MOVE | libc::SPLICE_F_NONBLOCK;
        let len = self.pipe_buf_size as u32;

        for (i, s) in sessions.iter().enumerate() {
            let remaining = self.ring.sq_space_left();
            if remaining < 2 {
                self.ring.submit_and_wait(in_flight.min(1))?;
                // 处理完这批 CQE 再继续(不超过 SQ 上限)
            }
            // C2UIn: client → c2u pipe
            self.ring.push_splice(
                s.client_fd as RawFd, -1,
                s.c2u_write as RawFd, -1,
                len, flags_splice,
                Self::enc_user_data(i as u64, Dir::C2UIn),
            )?;
            // U2CIn: upstream → u2c pipe
            self.ring.push_splice(
                s.upstream_fd as RawFd, -1,
                s.u2c_write as RawFd, -1,
                len, flags_splice,
                Self::enc_user_data(i as u64, Dir::U2CIn),
            )?;
            in_flight = in_flight.saturating_add(2);
        }

        // 3) 主循环:等待 CQE → 解码 → 更新 session → 推下一轮 SQE
        let mut active_sessions = n_sessions;
        while active_sessions > 0 {
            // 等待至少 1 个 CQE(非忙等,由 io_uring_enter 睡眠)
            let waited = self.ring.submit_and_wait(1)?;
            let _ = waited;

            // GAP-3 E0499 修复:先把已完成 CQE collect 到栈上 Vec,
            // 释放对 self.ring 的 &mut 借用。随后在循环内可安全地
            // 调用 push_splice / sq_space_left(都需 &mut self.ring),
            // 不再存在双重可变借用。
            let completions: Vec<Completion> = self.ring.collect_completions().collect();
            for comp in completions {
                in_flight = in_flight.saturating_sub(1);
                let (s_idx, dir) = Self::dec_user_data(comp.user_data)?;
                // Fail-Closed:损坏的 session 索引不得越界 panic
                let s = sessions.get_mut(s_idx).ok_or_else(|| {
                    LinuxError::Unsupported(format!(
                        "io_uring CQE session 索引 {s_idx} 越界(共 {n_sessions} 个会话)"
                    ))
                })?;

                // —— 错误码处理(Fail-Closed:EPIPE/ECONNRESET → EOF,其他致命 → 上抛)
                // `eagain` 标记本次完成是否为 EAGAIN(暂时无数据):EAGAIN 时
                // transferred=0 但**不是** EOF,需重推;而真正的 splice 返回 0 表示源已 EOF。
                let (transferred, eagain): (u32, bool) = if comp.result < 0 {
                    let err = -comp.result;
                    match err {
                        libc::EPIPE | libc::ECONNRESET | libc::ESHUTDOWN => {
                            // 方向 EOF(对端关闭)
                            match dir {
                                Dir::C2UIn | Dir::C2UOut => {
                                    if !s.c2u_eof { s.c2u_eof = true; }
                                    // Out 方向对端已关:管内残量不可达,清账
                                    // 防止重排自旋/会话悬挂(In 侧 EOF 不影响
                                    // 残量向存活对端的排空,故仅 Out 清账)
                                    if matches!(dir, Dir::C2UOut) {
                                        s.c2u_pipe_pending = 0;
                                    }
                                }
                                Dir::U2CIn | Dir::U2COut => {
                                    if !s.u2c_eof { s.u2c_eof = true; }
                                    if matches!(dir, Dir::U2COut) {
                                        s.u2c_pipe_pending = 0;
                                    }
                                }
                            }
                            (0, false)
                        }
                        libc::EAGAIN => {
                            // SPLICE_F_NONBLOCK + 暂时无数据 → 重新推入同方向 In
                            // transferred = 0,下方 match 内的逻辑会重 push
                            (0, true)
                        }
                        _ => {
                            return Err(LinuxError::Syscall {
                                syscall: "io_uring SPLICE",
                                errno: err,
                            });
                        }
                    }
                } else {
                    (comp.result as u32, false)
                };

                // —— 根据方向推进状态机
                match dir {
                    Dir::C2UIn => {
                        if transferred > 0 {
                            // client → c2u 管道写入 n 字节;现在需要推 Out SQE 排空到 upstream
                            s.bytes_c2u = s.bytes_c2u.checked_add(transferred as usize)
                                .ok_or_else(|| LinuxError::InsufficientResources(
                                    "c2u splice byte count overflow".to_string()
                                ))?;
                            s.c2u_pipe_pending = s.c2u_pipe_pending.saturating_add(transferred);
                        } else if !eagain {
                            // splice 返回 0(非 EAGAIN)= client_fd 已到 EOF。
                            // 若不置 EOF,会无限重推 C2UIn SQE 造成忙等/悬挂。
                            s.c2u_eof = true;
                        }
                        // 继续推入下一轮 C2UIn(除非 EOF)
                        if !s.c2u_eof && self.ring.sq_space_left() > 0 {
                            let _ = self.ring.push_splice(
                                s.client_fd as RawFd, -1,
                                s.c2u_write as RawFd, -1,
                                len, flags_splice,
                                Self::enc_user_data(s_idx as u64, Dir::C2UIn),
                            );
                            in_flight = in_flight.saturating_add(1);
                        }
                        // 若管道有待排空,推 C2UOut。**入队不扣账**:实际扣账
                        // 在 Out 完成时按 result>0 的真实字节进行(见
                        // on_out_completion 分支),杜绝 EAGAIN/短写丢数据
                        if s.c2u_pipe_pending > 0 && self.ring.sq_space_left() > 0 {
                            let to_drain = s.c2u_pipe_pending.min(len);
                            let _ = self.ring.push_splice(
                                s.c2u_read as RawFd, -1,
                                s.upstream_fd as RawFd, -1,
                                to_drain, flags_splice,
                                Self::enc_user_data(s_idx as u64, Dir::C2UOut),
                            );
                            in_flight = in_flight.saturating_add(1);
                        }
                    }
                    Dir::C2UOut => {
                        // 按实际完成字节扣账(EAGAIN/0 字节不丢账),未排空量重排
                        let (new_pending, requeue) =
                            on_out_completion(s.c2u_pipe_pending, transferred);
                        s.c2u_pipe_pending = new_pending;
                        if requeue && self.ring.sq_space_left() > 0 {
                            let to_drain = new_pending.min(len);
                            let _ = self.ring.push_splice(
                                s.c2u_read as RawFd, -1,
                                s.upstream_fd as RawFd, -1,
                                to_drain, flags_splice,
                                Self::enc_user_data(s_idx as u64, Dir::C2UOut),
                            );
                            in_flight = in_flight.saturating_add(1);
                        }
                        // C2U 方向若 In 已 EOF 且 pipe 全排空 → 标记 session 半关闭
                        if s.c2u_eof && s.c2u_pipe_pending == 0 {
                            // SAFETY: shutdown 语义安全,错误忽略(fd 可能已对端关闭)
                            unsafe { let _ = libc::shutdown(s.upstream_fd, libc::SHUT_WR); }
                        }
                    }
                    Dir::U2CIn => {
                        if transferred > 0 {
                            s.bytes_u2c = s.bytes_u2c.checked_add(transferred as usize)
                                .ok_or_else(|| LinuxError::InsufficientResources(
                                    "u2c splice byte count overflow".to_string()
                                ))?;
                            s.u2c_pipe_pending = s.u2c_pipe_pending.saturating_add(transferred);
                        } else if !eagain {
                            // splice 返回 0(非 EAGAIN)= upstream_fd 已到 EOF。
                            // 若不置 EOF,会无限重推 U2CIn SQE 造成忙等/悬挂。
                            s.u2c_eof = true;
                        }
                        if !s.u2c_eof && self.ring.sq_space_left() > 0 {
                            let _ = self.ring.push_splice(
                                s.upstream_fd as RawFd, -1,
                                s.u2c_write as RawFd, -1,
                                len, flags_splice,
                                Self::enc_user_data(s_idx as u64, Dir::U2CIn),
                            );
                            in_flight = in_flight.saturating_add(1);
                        }
                        // 同 C2U:入队不扣账,扣账在 U2COut 完成时进行
                        if s.u2c_pipe_pending > 0 && self.ring.sq_space_left() > 0 {
                            let to_drain = s.u2c_pipe_pending.min(len);
                            let _ = self.ring.push_splice(
                                s.u2c_read as RawFd, -1,
                                s.client_fd as RawFd, -1,
                                to_drain, flags_splice,
                                Self::enc_user_data(s_idx as u64, Dir::U2COut),
                            );
                            in_flight = in_flight.saturating_add(1);
                        }
                    }
                    Dir::U2COut => {
                        // 按实际完成字节扣账(EAGAIN/0 字节不丢账),未排空量重排
                        let (new_pending, requeue) =
                            on_out_completion(s.u2c_pipe_pending, transferred);
                        s.u2c_pipe_pending = new_pending;
                        if requeue && self.ring.sq_space_left() > 0 {
                            let to_drain = new_pending.min(len);
                            let _ = self.ring.push_splice(
                                s.u2c_read as RawFd, -1,
                                s.client_fd as RawFd, -1,
                                to_drain, flags_splice,
                                Self::enc_user_data(s_idx as u64, Dir::U2COut),
                            );
                            in_flight = in_flight.saturating_add(1);
                        }
                        if s.u2c_eof && s.u2c_pipe_pending == 0 {
                            // SAFETY: shutdown 语义安全
                            unsafe { let _ = libc::shutdown(s.client_fd, libc::SHUT_WR); }
                        }
                    }
                }

                // —— 判定 session 是否双向结束(结束后不再推进)
                let s_done = s.c2u_eof
                    && s.u2c_eof
                    && s.c2u_pipe_pending == 0
                    && s.u2c_pipe_pending == 0;
                if s_done {
                    active_sessions = active_sessions.saturating_sub(1);
                }
            }
        }

        // 4) 所有 session 结束,汇总结果并通过 Session::Drop 清理 pipes
        let mut results = Vec::with_capacity(sessions.len());
        for s in sessions.drain(..) {
            results.push((s.bytes_c2u, s.bytes_u2c));
        }
        Ok(results)
    }
}

impl std::fmt::Debug for SpliceBatcher {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("SpliceBatcher")
            .field("pipe_buf_size", &self.pipe_buf_size)
            .finish_non_exhaustive()
    }
}

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

    #[test]
    fn test_io_uring_create() {
        // 创建 io_uring 实例(SQ 深度 8)
        let result = IoUringBatcher::new(8);
        // 可能在不支持 io_uring 的内核上失败
        if let Ok(mut batcher) = result {
            assert!(batcher.sq_space_left() > 0);
        }
    }

    #[test]
    fn test_io_uring_nop() {
        let mut batcher = match IoUringBatcher::new(8) {
            Ok(b) => b,
            Err(_) => return, // 内核不支持 io_uring,跳过
        };

        // 推入 2 个 NOP
        batcher.push_nop(100).unwrap();
        batcher.push_nop(200).unwrap();

        // 提交并等待 2 个完成
        batcher.submit_and_wait(2).unwrap();

        // 收集完成事件
        let completions: Vec<_> = batcher.collect_completions().collect();
        assert_eq!(completions.len(), 2);

        // NOP 返回 0
        for c in &completions {
            assert_eq!(c.result, 0);
        }

        // 验证 user_data
        let mut user_data_set: Vec<u64> = completions.iter().map(|c| c.user_data).collect();
        user_data_set.sort_unstable();
        assert_eq!(user_data_set, vec![100, 200]);
    }

    #[test]
    fn test_dec_user_data_roundtrip_and_invalid() {
        // 纯逻辑:编码/解码往返
        let (idx, dir) = SpliceBatcher::dec_user_data(SpliceBatcher::enc_user_data(7, Dir::C2UOut))
            .unwrap();
        assert_eq!(idx, 7);
        assert!(matches!(dir, Dir::C2UOut));

        let (idx, dir) = SpliceBatcher::dec_user_data(SpliceBatcher::enc_user_data(42, Dir::U2CIn))
            .unwrap();
        assert_eq!(idx, 42);
        assert!(matches!(dir, Dir::U2CIn));

        // 保留方向位(4..=15)必须返回错误而非 panic
        for bad in 4u64..=15 {
            assert!(
                SpliceBatcher::dec_user_data((1 << 4) | bad).is_err(),
                "方向位 {bad} 必须 Fail-Closed"
            );
        }
    }

    #[test]
    fn test_io_uring_sq_space() {
        let mut batcher = match IoUringBatcher::new(4) {
            Ok(b) => b,
            Err(_) => return,
        };

        let initial_space = batcher.sq_space_left();
        assert!(initial_space >= 4);

        batcher.push_nop(1).unwrap();
        assert_eq!(batcher.sq_space_left(), initial_space - 1);

        batcher.push_nop(2).unwrap();
        assert_eq!(batcher.sq_space_left(), initial_space - 2);

        // 提交清理 SQ
        batcher.submit().unwrap();
    }
}

// ========================================================================
// UDP 批量收发(io_uring RECVMSG/SENDMSG 批量化)
// ========================================================================
//
// 将 serve_udp_loop 的每包 2 次 syscall(recv_from + send_to)合并为
// ≤2 次 io_uring_enter:
// - 接收:预武装 recv slot 池 → submit_and_wait(1) → 批量收割完成的包
// - 发送:批量 push_sendmsg → 单次 submit_and_wait 全部完成
//
// # 安全模型(unsafe 全部封装于本模块,上层零 unsafe)
// - slot 由 `Box` 持有(堆地址稳定),msghdr/iovec/sockaddr 指针绝不悬垂
// - slot 在 CQE 收割前标记 in_use,收割后才复用(内核写入与 Rust 访问无竞态)
// - user_data 编码:bit 63 = 类型(0=recv, 1=send),bit 0-62 = slot 索引
// - AEAD/解析错误不致命:单包失败仅记日志,不中断批量循环

use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};

/// user_data 类型标记:发送(bit 63 = 1)
const UD_SEND_FLAG: u64 = 1u64 << 63;

/// UDP 收发槽位(msghdr 存储,CQE 到达前必须保持有效)
///
/// 由 `Box` 持有保证堆地址稳定:slot 内 msghdr 含指向自身 iov/data/addr
/// 的指针,Box 移动时堆数据不动,指针绝不悬垂。
#[derive(Debug)]
struct UdpSlot {
    msg: libc::msghdr,
    iov: libc::iovec,
    data: Vec<u8>,
    addr: libc::sockaddr_storage,
    in_use: bool,
}

impl UdpSlot {
    /// 创建清零槽位(指针字段在 arm/push 时现填,避免自引用初始化问题)
    fn new(packet_size: usize) -> Box<Self> {
        Box::new(Self {
            // SAFETY: libc::msghdr 为全 POD 的 C 结构体,全零位模式合法;
            // 指针字段在 arm/push 时按 CQE 生命周期现填,清零态绝不解引用。
            msg: unsafe { std::mem::zeroed() },
            iov: libc::iovec {
                iov_base: std::ptr::null_mut(),
                iov_len: 0,
            },
            data: vec![0u8; packet_size],
            // SAFETY: libc::sockaddr_storage 为全 POD 的 C 结构体,全零位
            // 模式合法;recvmsg 写入前内核不读取其内容。
            addr: unsafe { std::mem::zeroed() },
            in_use: false,
        })
    }
}

/// SocketAddr → sockaddr_storage(返回 storage 与有效长度)
fn addr_to_storage(addr: &SocketAddr) -> (libc::sockaddr_storage, u32) {
    // SAFETY: sockaddr_storage 为全 POD 的 C 结构体,全零位模式合法;
    // 随后按地址族逐字节覆写有效字段,未覆写字节保持零(sin_zero 语义)。
    let mut storage: libc::sockaddr_storage = unsafe { std::mem::zeroed() };
    match addr {
        SocketAddr::V4(v4) => {
            let sa = libc::sockaddr_in {
                sin_family: libc::AF_INET as libc::sa_family_t,
                sin_port: v4.port().to_be(),
                sin_addr: libc::in_addr {
                    s_addr: u32::from_ne_bytes(v4.ip().octets()),
                },
                sin_zero: [0; 8],
            };
            let len = std::mem::size_of::<libc::sockaddr_in>() as u32;
            // SAFETY: storage 足够大(sockaddr_storage >= sockaddr_in),逐字节拷贝
            unsafe {
                std::ptr::copy_nonoverlapping(
                    &sa as *const libc::sockaddr_in as *const u8,
                    &mut storage as *mut libc::sockaddr_storage as *mut u8,
                    len as usize,
                );
            }
            (storage, len)
        }
        SocketAddr::V6(v6) => {
            let sa = libc::sockaddr_in6 {
                sin6_family: libc::AF_INET6 as libc::sa_family_t,
                sin6_port: v6.port().to_be(),
                sin6_flowinfo: v6.flowinfo(),
                sin6_addr: libc::in6_addr {
                    s6_addr: v6.ip().octets(),
                },
                sin6_scope_id: v6.scope_id(),
            };
            let len = std::mem::size_of::<libc::sockaddr_in6>() as u32;
            // SAFETY: 同上
            unsafe {
                std::ptr::copy_nonoverlapping(
                    &sa as *const libc::sockaddr_in6 as *const u8,
                    &mut storage as *mut libc::sockaddr_storage as *mut u8,
                    len as usize,
                );
            }
            (storage, len)
        }
    }
}

/// sockaddr_storage → SocketAddr(未知 family 返回 None,fail-closed)
fn storage_to_addr(storage: &libc::sockaddr_storage) -> Option<SocketAddr> {
    match storage.ss_family as i32 {
        libc::AF_INET => {
            // SAFETY: family 已校验为 AF_INET,布局与 sockaddr_in 一致
            let sa = unsafe { &*(storage as *const _ as *const libc::sockaddr_in) };
            Some(SocketAddr::new(
                std::net::IpAddr::V4(Ipv4Addr::from(u32::from_be(sa.sin_addr.s_addr))),
                u16::from_be(sa.sin_port),
            ))
        }
        libc::AF_INET6 => {
            // SAFETY: family 已校验为 AF_INET6
            let sa = unsafe { &*(storage as *const _ as *const libc::sockaddr_in6) };
            Some(SocketAddr::new(
                std::net::IpAddr::V6(Ipv6Addr::from(sa.sin6_addr.s6_addr)),
                u16::from_be(sa.sin6_port),
            ))
        }
        _ => None,
    }
}

/// UDP 批量收发器(io_uring RECVMSG/SENDMSG)
///
/// # 线程安全
/// 单生产者单消费者模型,不可跨线程共享(与 IoUringBatcher 一致)。
///
/// # 使用流程
/// ```text
/// loop {
///     io.arm_recv()?;                    // 空闲 recv slot 全部武装
///     io.submit_and_wait(1)?;            // 单次 syscall 等待 ≥1 包到达
///     for (data, from) in io.collect_recv() {
///         let responses = handle(data, from);
///         for (pkt, addr) in responses {
///             io.push_send(&pkt, &addr)?; // slot 满时先 flush_send
///         }
///     }
///     io.flush_send()?;                  // 单次 syscall 完成全部发送
/// }
/// ```
// clippy::vec_box 为误报:UdpSlot 内联持有 msghdr/iovec,其地址在 arm/push
// 时注册进 SQE 供内核异步读写;Box 提供地址稳定性,Vec 重分配不得移动
// UdpSlot 本体,否则内核将写入已迁移的悬垂地址。禁止改为 Vec<UdpSlot>。
#[allow(clippy::vec_box)]
pub struct UdpBatchIo {
    ring: IoUring,
    fd: RawFd,
    recv_slots: Vec<Box<UdpSlot>>,
    send_slots: Vec<Box<UdpSlot>>,
    /// 已推入 SQ 但未收割的 send 数(flush_send 的等待目标)
    pending_send: u32,
    /// flush_send 期间提前到达的接收包(暂存,collect_recv 优先返回)。
    /// CQ 为 recv/send 共享:flush_send 的 submit_and_wait 会一并收割 recv CQE,
    /// 数据必须暂存而非丢弃(否则新连接 Initial 丢失导致对端超时)。
    early_recv: Vec<(Vec<u8>, SocketAddr)>,
}

impl std::fmt::Debug for UdpBatchIo {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("UdpBatchIo")
            .field("fd", &self.fd)
            .field("recv_slots", &self.recv_slots.len())
            .field("send_slots", &self.send_slots.len())
            .field("pending_send", &self.pending_send)
            .finish()
    }
}

impl UdpBatchIo {
    /// 创建 UDP 批量收发器
    ///
    /// # 参数
    /// * `fd` - UDP socket fd(调用方保有所有权,本结构不关闭)
    /// * `recv_count` - 接收槽位数(建议 32,单次最多批量收 32 包)
    /// * `send_count` - 发送槽位数(建议 64,响应扇出通常大于接收)
    /// * `packet_size` - 单包缓冲大小(QUIC 建议 2048,覆盖 MTU 1280 + 余量)
    pub fn new(fd: RawFd, recv_count: usize, send_count: usize, packet_size: usize) -> Result<Self> {
        // SQ 深度需容纳全部 recv + send slot(向上取 2 的幂)
        let entries = ((recv_count + send_count) as u32)
            .checked_next_power_of_two()
            .ok_or_else(|| LinuxError::InsufficientResources("SQ depth overflow".into()))?;
        let ring = IoUring::new(entries).map_err(|e| LinuxError::Syscall {
            syscall: "io_uring_setup",
            errno: e.raw_os_error().unwrap_or(0),
        })?;
        let mut recv_slots = Vec::with_capacity(recv_count);
        for _ in 0..recv_count {
            recv_slots.push(UdpSlot::new(packet_size));
        }
        let mut send_slots = Vec::with_capacity(send_count);
        for _ in 0..send_count {
            send_slots.push(UdpSlot::new(packet_size));
        }
        Ok(Self {
            ring,
            fd,
            recv_slots,
            send_slots,
            pending_send: 0,
            early_recv: Vec::new(),
        })
    }

    /// 武装所有空闲 recv slot(推入 RECVMSG SQE)
    ///
    /// # 返回
    /// 本次武装的 slot 数
    pub fn arm_recv(&mut self) -> Result<usize> {
        let mut armed = 0usize;
        for (idx, slot) in self.recv_slots.iter_mut().enumerate() {
            if slot.in_use {
                continue;
            }
            // 重建 msghdr(data/addr 指针现填;slot 由 Box 持有地址稳定)
            slot.iov = libc::iovec {
                iov_base: slot.data.as_mut_ptr() as *mut libc::c_void,
                iov_len: slot.data.len(),
            };
            slot.msg = libc::msghdr {
                msg_name: &mut slot.addr as *mut libc::sockaddr_storage as *mut libc::c_void,
                msg_namelen: std::mem::size_of::<libc::sockaddr_storage>() as u32,
                msg_iov: &mut slot.iov as *mut libc::iovec,
                msg_iovlen: 1,
                msg_control: std::ptr::null_mut(),
                msg_controllen: 0,
                msg_flags: 0,
            };
            let entry = opcode::RecvMsg::new(types::Fd(self.fd), &mut slot.msg as *mut libc::msghdr)
                .build()
                .user_data(idx as u64); // bit63=0 → recv
            let mut sq = self.ring.submission();
            // SAFETY: 单生产者;slot 在 CQE 收割前标记 in_use 不复用,
            // msghdr 指针指向 Box 内稳定堆内存,内核写入安全。
            unsafe {
                sq.push(&entry).map_err(|_| {
                    LinuxError::InsufficientResources("io_uring SQ full (recv)".into())
                })?;
            }
            slot.in_use = true;
            armed += 1;
        }
        Ok(armed)
    }

    /// 提交并等待至少 `min_complete` 个 CQE(单次 io_uring_enter)
    pub fn submit_and_wait(&mut self, min_complete: u32) -> Result<usize> {
        self.ring
            .submit_and_wait(min_complete as usize)
            .map_err(|e| LinuxError::Syscall {
                syscall: "io_uring_enter",
                errno: e.raw_os_error().unwrap_or(0),
            })
    }

    /// 收割完成的接收包(数据拷贝出 + slot 释放)
    ///
    /// 同时收割 send CQE(释放 send slot,发送错误仅记日志不中断)。
    ///
    /// # 返回
    /// (数据, 源地址) 列表;result<0 的包被丢弃(内核错误如 ECONNRESET)。
    /// **flush_send 期间暂存的提前到达包(early_recv)优先返回**——绝不滞留:
    /// 滞留会使 early_recv 永久非空,主循环跳过 submit_and_wait 陷入空转,
    /// 且暂存的新连接 Initial 永远不被处理(对端握手超时)。
    pub fn collect_recv(&mut self) -> Vec<(Vec<u8>, SocketAddr)> {
        let mut out = std::mem::take(&mut self.early_recv);
        let cq: Vec<(u64, i32)> = self
            .ring
            .completion()
            .map(|cqe| (cqe.user_data(), cqe.result()))
            .collect();
        for (user_data, result) in cq {
            if user_data & UD_SEND_FLAG != 0 {
                // send CQE:释放 slot
                let idx = (user_data & !UD_SEND_FLAG) as usize;
                if let Some(slot) = self.send_slots.get_mut(idx) {
                    slot.in_use = false;
                }
                self.pending_send = self.pending_send.saturating_sub(1);
                if result < 0 {
                    tracing::warn!(errno = -result, "io_uring sendmsg failed");
                }
                continue;
            }
            // recv CQE
            let idx = user_data as usize;
            let Some(slot) = self.recv_slots.get_mut(idx) else {
                continue;
            };
            slot.in_use = false;
            if result <= 0 {
                // 0=对端关闭(UDP 不适用),<0=内核错误(如 ECONNRESET/EINTR)
                continue;
            }
            let n = (result as usize).min(slot.data.len());
            let Some(from) = storage_to_addr(&slot.addr) else {
                continue; // 未知 address family,fail-closed 丢弃
            };
            out.push((slot.data[..n].to_vec(), from));
        }
        out
    }

    /// 推入一个发送包(占用 send slot)
    ///
    /// # 返回
    /// - `Ok(true)`:已推入 SQ
    /// - `Ok(false)`:无空闲 send slot(调用方应先 `flush_send` 再重试)
    /// - `Err`:SQ 满或数据超出 slot 缓冲
    pub fn push_send(&mut self, data: &[u8], addr: &SocketAddr) -> Result<bool> {
        let Some((idx, slot)) = self
            .send_slots
            .iter_mut()
            .enumerate()
            .find(|(_, s)| !s.in_use)
        else {
            return Ok(false);
        };
        if data.len() > slot.data.len() {
            return Err(LinuxError::InsufficientResources(format!(
                "send data {}B > slot buffer {}B",
                data.len(),
                slot.data.len()
            )));
        }
        slot.data[..data.len()].copy_from_slice(data);
        let (storage, addr_len) = addr_to_storage(addr);
        slot.addr = storage;
        slot.iov = libc::iovec {
            iov_base: slot.data.as_mut_ptr() as *mut libc::c_void,
            iov_len: data.len(),
        };
        slot.msg = libc::msghdr {
            msg_name: &mut slot.addr as *mut libc::sockaddr_storage as *mut libc::c_void,
            msg_namelen: addr_len,
            msg_iov: &mut slot.iov as *mut libc::iovec,
            msg_iovlen: 1,
            msg_control: std::ptr::null_mut(),
            msg_controllen: 0,
            msg_flags: 0,
        };
        let entry = opcode::SendMsg::new(types::Fd(self.fd), &slot.msg as *const libc::msghdr)
            .build()
            .user_data(UD_SEND_FLAG | idx as u64);
        let mut sq = self.ring.submission();
        // SAFETY: 单生产者;slot 在 CQE 收割前 in_use 不复用,指针稳定
        unsafe {
            sq.push(&entry).map_err(|_| {
                LinuxError::InsufficientResources("io_uring SQ full (send)".into())
            })?;
        }
        slot.in_use = true;
        self.pending_send = self.pending_send.saturating_add(1);
        Ok(true)
    }

    /// 提交全部发送并等待完成(单次 io_uring_enter),收割 send CQE 释放 slot
    ///
    /// # 返回
    /// 成功发送的包数(result >= 0)
    pub fn flush_send(&mut self) -> Result<usize> {
        let mut sent = 0usize;
        // 关键正确性(min_complete 语义陷阱):
        // io_uring_enter(min_complete=N) 等待 CQ **总深度** ≥ N(含残留 recv CQE),
        // 而非"本批 send 完成 N 个"。一次性 submit_and_wait(pending_send) 会被
        // 残留 recv CQE 干扰提前返回,send slot 在内核完成前被释放,
        // 内核异步写入已复用 slot → 数据竞争、包内容错乱(对端解密失败)。
        // 必须循环收割直到 pending_send == 0,确保 slot 释放时内核已完成。
        while self.pending_send > 0 {
            // 至少等待 1 个新 CQE(CQ 有残留时立即返回收割,无残留时阻塞)
            self.ring
                .submit_and_wait(1)
                .map_err(|e| LinuxError::Syscall {
                    syscall: "io_uring_enter",
                    errno: e.raw_os_error().unwrap_or(0),
                })?;
            let cq: Vec<(u64, i32)> = self
                .ring
                .completion()
                .map(|cqe| (cqe.user_data(), cqe.result()))
                .collect();
            for (user_data, result) in cq {
                if user_data & UD_SEND_FLAG != 0 {
                    let idx = (user_data & !UD_SEND_FLAG) as usize;
                    if let Some(slot) = self.send_slots.get_mut(idx) {
                        slot.in_use = false;
                    }
                    self.pending_send = self.pending_send.saturating_sub(1);
                    if result >= 0 {
                        sent += 1;
                    } else {
                        tracing::warn!(errno = -result, "io_uring sendmsg failed");
                    }
                } else {
                    // recv CQE 在 flush_send 期间到达:数据暂存 early_recv
                    // (下一轮 collect_recv 优先返回),slot 释放重新武装。
                    // 绝不丢弃:新连接 Initial 在此丢失会导致对端握手超时。
                    let idx = user_data as usize;
                    if let Some(slot) = self.recv_slots.get_mut(idx) {
                        slot.in_use = false;
                        if result > 0 {
                            let n = (result as usize).min(slot.data.len());
                            if let Some(from) = storage_to_addr(&slot.addr) {
                                self.early_recv.push((slot.data[..n].to_vec(), from));
                            }
                        }
                    }
                }
            }
        }
        Ok(sent)
    }

    /// 空闲 send slot 数
    pub fn send_slot_free(&self) -> usize {
        self.send_slots.iter().filter(|s| !s.in_use).count()
    }

    /// early_recv 暂存是否为空(调用方据此决定是否跳过 submit_and_wait 阻塞)
    pub fn early_recv_empty(&self) -> bool {
        self.early_recv.is_empty()
    }
}