rust异步库-tokio的一些资源限制

在rust中,async-std和tokio作为使用者较多的两个异步运行时刻库,有着各自的优点。而rust-ipfs是ipfs的rust实现,采用的runtime便是tokio,底层网络库则是基于rust-libp2p。为了尝试将底层的rust-libp2p修改为libp2p-rs,我们在原仓库的基础上fork了一份代码进行移植,目前已完成。现在分享一个在移植过程中遇到的挂起问题。

问题描述

首先我起了一个go-ipfs的daemon,通过ipfs id命令获取到multiaddress信息。之后运行rust-ipfs的例子程序simple,确保其与go-ipfs连接成功。在ipfs中,一个dag node最大承载的block数是174个,也就是43.5MiB。我通过go存储了一个大约为77MiB的文件,通过rust去取的时候,发现最多拿到128个块,测试代码就没有任何响应了。

查找时间过长?

由于cid获取block的逻辑中有超时限制,当超过30s没有任何返回时,Bitswap会抛出Error的错误提示,所以一开始简单地以为是查找过程耗时比较长,就放在一旁未处理。但是过了大约十几分钟后,发现控制台并未抛出任何BitswapError的信息,发现事情可能并没有想的那么简单。

blockstore挂起

通过一层层的打印日志并排错,最后定位问题在get_block()方法的调用。

首先在本地的blockstore查找cid对应的block,如果找不到再通过bitswap去查。测试的时候blockstore用的是tokio::Mutex包裹的Hashmap,挂起的问题就出现在从Hashmap中获取block这一步,也就是图中的383行。

tokio资源限制

借助于tokio的一篇文章,我们发现了上述问题的解决方法。

由于tokio不是抢占式调度的,所以可能出现某一个task一直在执行,导致其他的task无法被调度,一直处于饥饿的情况。在一些语言中,可以通过注入yield point的方式去中断执行,但是rust语言的generator似乎没有提供类似的功能。

因此,为了解决这个问题,tokio在0.2.14版本中引入了budget的概念。这可以理解为配额,tokio的每个资源都会知道这个值。budget的默认值是128,是官方经过测试之后得到的一个效果较好的值,每个异步的操作都会减少budget的值,当减到0以后,task会返回到调度器中,同时重置budget,等待下一次被调度。

在tokio::block_on中,会执行budget的检测:



图中所示的coop::budget()会初始化budget变量为128,然后对传入的future进行轮询操作,在这里传入的是Mutex内部的Acquire。Acquire实现了Future,在poll时首先会去检查budget。如果budget足够或者budget不受限制,返回Ready,执行余下的操作,否则返回Pending。

同时,还有一个地方需要注意。budget的reset只有在tokio的worker thread中才会生效,其他库的executor并不知道budget的存在,即不会执行reset的操作。举个例子,如果在tokio的executor中使用了futures的block_on,会导致执行一定次数之后,block_on内部的代码运行逻辑被挂住,导致挂起的问题出现。

而在我们的代码中,实际上正好碰到了以上的问题:

首先,main函数使用了#[tokio::main],这会自动生成一个tokio的executor:

其次,测试代码的方法中使用了future::executor的block_on,这会导致代码只在future的executor中执行,当get_block()查找本地blockstore的时候,如果在128次之内无法执行结束,就会导致挂起:

结论

综上所述,问题的解决方法就是不要在tokio的executor中执行其他库的executor::block_on()。


Netwarps 由国内资深的云计算和分布式技术开发团队组成,该团队在金融、电力、通信及互联网行业有非常丰富的落地经验。Netwarps 目前在深圳、北京均设立了研发中心,团队规模30+,其中大部分为具备十年以上开发经验的技术人员,分别来自互联网、金融、云计算、区块链以及科研机构等专业领域。
Netwarps 专注于安全存储技术产品的研发与应用,主要产品有去中心化文件系统(DFS)、去中心化计算平台(DCP),致力于提供基于去中心化网络技术实现的分布式存储和分布式计算平台,具有高可用、低功耗和低网络的技术特点,适用于物联网、工业互联网等场景。
公众号:Netwarps

初学rust,相信你会像我一样被rust庞杂的语法震惊到下面的代码看起来像是java和JavaScript的结合体 {代码...} {代码...} 关于 => 的用法rust作为一门只有6岁的新语言,从js、java、python等高级语言中吸收了很多语法=> 在JavaScript中表示箭头函数,是一种为了为了在定义函数时省略function关键字的语法糖而在rust...