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
//! 多客户端并发写。
// 线程同步:Barrier 保证多客户端同时开闸写
use std::sync::{Arc, Barrier};
// 多线程并发客户端
use std::thread;
// 集群与等领导者辅助
use raft_rust::cluster::{wait_for_leader, Cluster};
// 测试结果统一 Result 传播
use raft_rust::error::Result;
// 验证多客户端并发 Put 最终都能提交,且键值互不覆盖丢失
#[test]
// 四客户端齐开闸并发写,最终状态机含全部独立键
fn concurrent_puts() -> Result<()> {
// 三节点集群提供并发写入口
let cluster = Cluster::spawn(&[1, 2, 3]);
// 主线程客户端:先等领导再开闸
let mut client = cluster.client();
// 先等领导者稳定,避免冷启动阶段全部 Abort
wait_for_leader(&mut client)?;
// 4 个工作线程 + 主线程同步开闸(new(4) 只等 4 个 worker)
let barrier = Arc::new(Barrier::new(4));
// 收集各 worker 句柄以便 join
let mut handles = vec![];
// 启动 4 个并发客户端线程
for t in 0..4 {
// 每个线程独立 client,模拟多会话
let mut c = cluster.client();
// 共享 barrier,保证四线程同时开闸
let b = barrier.clone();
// 将 worker 迁入独立线程
handles.push(thread::spawn(move || {
// 四线程齐备后再开始写,放大并发冲突
b.wait();
// 每线程写 10 个独立键,避免业务层互相覆盖
for i in 0..10 {
// 线程 id 前缀保证键空间隔离
let key = format!("t{t}-k{i}");
// 与键一一对应的期望值
let val = format!("v{i}");
// 允许短暂 Abort 重试。
for _ in 0..30 {
// 领导切换/短暂拒绝时重试直至 Put 成功
if c.put(&key, &val).is_ok() {
// 本键已提交,进入下一键
break;
// 结束当前作用域
}
// 退避避免打爆集群
thread::sleep(std::time::Duration::from_millis(20));
// 结束当前作用域
}
// 结束当前作用域
}
// 业务逻辑步骤
}));
// 结束当前作用域
}
// 等待全部 worker 完成,任一 panic 则测试失败
for h in handles {
// join 失败说明 worker 内断言/panic
h.join().expect("thread panicked");
// 结束当前作用域
}
// 主线程再开 client 校验最终状态机
let mut c = cluster.client();
// 逐键校验 4×10 次写入均已提交且值正确
for t in 0..4 {
// 校验该线程写出的 10 个键
for i in 0..10 {
// 重建期望键名
let key = format!("t{t}-k{i}");
// 并发写不得丢键或写错值
assert_eq!(c.get(&key)?, Some(format!("v{i}")), "missing {key}");
// 结束当前作用域
}
// 结束当前作用域
}
// 并发写最终一致性成立
Ok(())
// 结束当前作用域
}