.await
Ok(val) => println!(“The task completed with {:?}”, val),
while let Ok((stream, _)) = listener.accept().await {
});
spawn(handle(stream));
}
两个部门团结在一起,diviner 为异步/期待的 Rust 代码提供了一个 FoundationDB 范例简直定性测试办理方案。这里提供几个示例,以展示操纵时间的本领,这答允我们以更快的方法测试超时,以及测试并发的 bugs 的本领。利用确定性 seed,diviner 将确定性地运行,让你有时机可以无限次地调试你的代码。它的美好之处在于,它只是一个异步/期待的 Rust 代码,我们没有往 diviner 中引入任何新对象。
client
实际上,在一个模仿情况中尚有许多长处:当你的项目宣布时,人们大概开始利用它这意味着他们会实验在很多差异的呆板上运行你的代码。然后,这些呆板将会摸索你的措施大概导致的差异的执行状态,在某种水平上,我们可以认为所有这些呆板都在为你的措施举办测试,寻找 bugs。为了担保你项目标质量,在抱负的环境下,你应该在所有这些差异的呆板之前找到新的 bugs。此刻,问题就酿成了罗列出所有大概的状态和寻找 bugs 方面的比赛。对付一些风行的产物,用户运行的呆板数量很容易高出项目维护者所拥有的呆板数量。问题来了:如何才气在利用少少的呆板的环境下,找到更多的 bugs?
测试与基准测试是差异的
https://www.youtube.com/watch?v=fFSPwJFXVlw
.expect(“client write 1 error!”);
let content = &buf[4..l + 4];
panic!(“Invalid response!”);
buf.extend_from_slice(&t[..n]);
net::{TcpListener, TcpStream},
}
假如我们仔细思考一下,做一个 FoundationDB 范例简直定性模仿测试所需要的只是一个基于 actor 的代码,然后我们就可以按照测试需求对它们举办重组。令人感动的工作在于,Rust,我们敬爱的用于构建高机能的漫衍式软件的办理方案,已经提供了一个异步/期待设计,这很像 actor 模子(好吧,我并不能算是一名计较机科学传授,我把这个问题留给那些更有资格去评判异步/期待是不是 actor 模子的人)。为了使其越发有趣,Rust 的可切换运行时间的设计使其成为这种确定性模仿测试思想下的最佳选择:我们所需要做的,就是在测试中利用差异的运行时间,问题就会获得办理。
.expect(“client write 1 error!”);
}
这里的要害是,测试与基准测试是差异的。测试的目标毫不是得到实际的运行时间,而是摸索措施可以回收的所有路径。就像 TLA+ 会摸索你设计中所有的状态一样,假如一个模仿可以摸索一段代码可以回收的所有的执行路径,那么就已经足够了。利用 actor 模子从头组织的代码,你的逻辑会被自然地支解成许多个小的原子块,只要你可以或许列举出一个措施可以有效执行的所有差异的执行顺序,那么就算是用一个单线程的测试框架也可以摸索出多线程办理方案中大概导致的所有路径!
https://www.youtube.com/watch?v=4fFDFbi3toc
let l = LittleEndian::read_u32(&buf) as usize;
当一个测试在一个单线程的情况中运行时,,一个典范的多核呆板就可以用来同时运行多个测试;
一个设计成单线程和具有确定性的运行时间,因此我们可以操作它来构建确定性模仿测试;
let result = e.block_on(async {
fn main() {
Rust:一个基于 Actor 模仿测试的最佳选择
Ok(())
Err(err) => println!(“The task has panicked: {:?}”, err),
match result {
buf = buf.drain(0..l + 4).collect();
在已往的几个月里,我一直在进修 TLA+,我此刻坚信 TLA+ 是构建巨大的、多线程的、高机能的、(也大概是漫衍式的)系统的名贵东西。在为我所有的项目写下第一行代码之前,我简直更喜欢在 TLA+ 中先构建一个设计。但 TLA+ 只能辅佐你思考你的设计,并修复个中的设计缺陷。我们还需要思量到另一面:实际执行该系统。
}
接下来就让我们有请出本文的主角:Diviner
一旦有了这个想法,我以为它真的太伟大了,不浮夸的说,我花了我所有的夜晚和周末来实现这个想法,这才有了 diviner。它由两部构成:
async fn handle(stream: Tcpstream) {
spawn(async {
let data: Vec<u8> = vec![4, 0, 0, 0, 0x64, 0x61, 0x64, 0x61];
loop {
spawn, Environment,
let addr = “127.0.0.1:18000”;
use std::io;
}
let mut output: Vec<u8> = vec![0; 4];
server(addr.to_string()).await.expect(“server boot error!”);
我们可以有一个已经颠末 TLA+ 验证的设计,但假如你写的代码是较量容易受到某些并发性 bugs 的进攻的,而这些 bugs 又是有必然概率会产生的,那这时候该怎么办呢?
.write(&data[..i])
let mut t = vec![0; 1024];
async fn server(addr: String) -> Result<(), io::Error> {
}
}
这个问题的谜底,雷同于 FoundationDB 办理方案中的模仿设计:首先,我们在一个 actor 模子的框架下组织逻辑,因此我们可以利用一个单线程模仿测试执行器来运行测试;然后,我们模仿所有和情况相关的代码,如计时器、网络 IOs、文件 IOs 等等。通过这种方法,我们可以将我们项目标焦点,也就是大大都 bug 产生的处所,提炼成一段单线程的,持续的代码,有以下长处:
Diviner
client.read(&mut output).await.expect(“client read error!”);
此刻只有一个问题了:固然这个办理方案很好,也被证明很是有效,但我们能在其他处所利用它吗?我们是否会受到 C++ actor 框架的限制?谜底是,虽然不会!
.await
}
这才是让我欢快的处所。此刻,diviner 还处于很是早期的阶段,我将在有空的时候继承在 diviner 中添加缺少的部门(好比 async-std 中所缺少的封装器)。假如你也有乐趣,接待来试试,让我知道你的感觉。use diviner::{
这个例子展示的是一个典范的新手错误:TCP/IP 协议是基于流的,而不是基于包的。固然你大概可以提供一个 1 KB 的缓冲区,可是协议可以通过任意数量的字节反馈你,在极度环境下大概只有 1 byte 的数据。在真正的测试中,这是很难模仿的,因为你需要建设一个 TCP/IP 很是拥挤的情况,它只有一个很是小的很是拥挤的窗口。可是有了 diviner,在测试中调解它将会长短常简朴的。你写的代码,只是利用了 TcpListener/TCPStream,就像是 async-std 中同名的布局一样。是的,你将不得不通过 diviner 来导入它们,可是通过内联函数和 newtype 模式,机能完全不会受到影响。一旦你愿意做出这样的牺牲,我相信你将会发明一个全新的世界。
let n = stream.read(&mut t).await.expect(“read error!”);
};
在现有的 Rust 异步库上面实现的封装器。封装器在正常模式下(通过内联函数和 newtypes)将直接编译成现有的实现,但在启用了非凡的 simulation 成果后,它们将被编译成与上述运行时间集成好的模仿版本,以便举办确定性测试。此刻我是从 async-std 开始,但在将来大概会添加更多的封装器。
});
if n == 0 {
一直以来我对确定性执行的问题就很感乐趣。我们在多线程模子上花了很大时间。我们大部门人都应该碰着过一些只在必然概率范畴内产生的 bug。纵然你已经筹备好了一个修复措施,你也不能确定它是不是还会再次产生,你所能做的不外是测试,测试,再测试,并但愿这样的问题不会再次呈现。
我们可以确定地举办调试并拍着胸脯说这是 100% 确定的,这是每一位工程师的空想,而这个问题已经被办理了。
当所有的 IOs 都被模仿出来之后,我们可以在测试中运行更少的代码(好比,我们可以跳过整个 TCP/IP 栈),从而更快地举办测试;
https://github.com/xxuejie/diviner
幸运的是,我有一个乐趣喜好,就是成为电脑学的考古专家:我会时不时地把一些相对较老的视频翻出来,从头寓目一遍,以得到新的领略。这是一个已往的履历,在这个行业有许多新的发现,都只是新瓶装旧酒。当我最近翻出 FoundationDB 的视频并再次寓目时,我发明我之前犯了一个很是很是严重的错误。这确实是一件大事。
for i in 1..data.len() {
break;
虽然,也有一些在办理方案上的实验,好比 rr。但在这个规模尚有一个真正的英华,那就是 FoundationDB。假如你不是很相识 FoundationDB,出格是不清楚它们是如何举办测试的,我强烈推荐以下两个视频:
}
let mut buf = vec![];
.write(&data[i..])
诚恳说,我之前就看过这些视频,但当时候较量早,我对他们的办理方案印象并不是很深刻,原因在于:他们的模仿框架是在一个单线程中持续运行的,这与真正的配置相差甚远,在真正的配置中,你是有多个线程在配合运行的。因此你从模仿中获得的机能数据没什么意义。
我尚有一个例子,我但愿在将来的几天内可以将其实现:
}
let e = Environment::new();
if buf.len() >= l + 4 {
有了模仿的 IOs,我们可以更容易地模仿异常环境,好比网络堵塞;
if &output[..] != &data[4..] {
use byteorder::{ByteOrder, LittleEndian};
client
所有这些长处都意味着模仿方案可以答允我们在更少的时间内对代码举办更多的测试,让我们有时机在寻找 bugs 的竞速游戏中取告捷利。在 FoundationDB 的示例中,他们预计在已往的几年时间里,通过这种设计,他们已经积聚了相当于一万亿 CPU-hours 的模仿压力测试。直到本日为止,我还没有看到一个更高级的测试框架设计。
let mut listener = TcpListener::bind(addr).await?;
它们所做的,是在 C++ 之上构建一个 actor 模子,然后利用 actor 模子来编写完整的数据库逻辑。因此,它们可以将基于 actor 的代码完全注入到一个确定性的测试框架中,以测试各类并发性问题。
stream.write(content).await.expect(“write error!”);
郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。