Seastar中when_all等待多future异常及编译相关疑问
问题背景
以下是相关代码:
#include <seastar/core/app-template.hh> #include <seastar/core/coroutine.hh> #include <seastar/core/sleep.hh> #include <seastar/core/when_all.hh> /*Here I want all the futures to execute*/ seastar::future<> all_futures() { seastar::sleep(2s).then([]{std::cout << "2s\n";}); seastar::sleep(1s).then([]{std::cout << "1s\n";}); return seastar::sleep(3s).then([]{std::cout << "3s\n";});//CHANGE HERE TO VALUE < 2s } seastar::future<int> get_int() { std::cout << "Entering winter sleep\n"; return seastar::sleep(5s).then([]{return 2;}); //CHANGE HERE TO VALUE < 3s } seastar::future<> f_sleeping() { std::cout << "Sleeping... " << std::flush; using namespace std::chrono_literals; //Here I want to wait for all the futures and only exit after all futures finished return when_all(all_futures(),get_int().then([](const int val){std::cout << "got: " << val << '\n'; })).discard_result(); } int main(int argc, char** argv) { app_template app; return app.run(argc, argv, [&app] { return f_sleeping(); }); }
原代码运行时符合预期,但修改标记为//CHANGE HERE TO VALUE行的sleep时长(分别改为小于2s和小于3s)后,部分future未被等待。
错误信息翻译
错误:无法将‘seastar::when_all(FutOrFuncs&& ...) [with FutOrFuncs = {seastar::future<>, seastar::future<>, seastar::future<>}]((* & std::move(seastar::future<>&>(one))), (* & std::move(seastar::future<>&>(three))))’从‘future<std::tuple<seastar::future
, seastar::future<>, seastar::future<> >>’转换为‘future<>’
172 | return when_all(std::move(two), std::move(one), std::move(three));
|~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
| |
| future<std::tuple<seastar::future, seastar::future<>, seastar::future<> >>
问题解答
1. 如何正确等待任意数量的future?
要正确等待所有future,核心是不能丢弃任何需要执行的future,必须将所有待执行的future收集并通过when_all等待:
- 对于固定数量的future,直接将所有future作为参数传入
when_all,最后调用discard_result()将结果转换为future<>:
seastar::future<> all_futures() { auto f1 = seastar::sleep(2s).then([]{std::cout << "2s\n";}); auto f2 = seastar::sleep(1s).then([]{std::cout << "1s\n";}); auto f3 = seastar::sleep(3s).then([]{std::cout << "3s\n";}); return seastar::when_all(std::move(f1), std::move(f2), std::move(f3)).discard_result(); }
- 对于任意数量(动态)的future,可将其存入容器(如
std::vector<seastar::future<>>),使用when_all的迭代器版本:
seastar::future<> wait_dynamic_futures() { std::vector<seastar::future<>> futures; futures.emplace_back(seastar::sleep(1s).then([]{std::cout << "1s\n";})); futures.emplace_back(seastar::sleep(2s).then([]{std::cout << "2s\n";})); futures.emplace_back(seastar::sleep(3s).then([]{std::cout << "3s\n";})); return seastar::when_all(futures.begin(), futures.end()).discard_result(); }
关键注意点:所有需要执行的future必须被变量持有,否则Seastar会自动取消未被引用的future,导致对应的回调不会执行。
2. 该异常是Seastar库的bug,还是C++编译阶段无法检测此类问题?
这既不是Seastar的bug,也不是编译阶段无法检测的问题,而是开发者的逻辑错误:
- Seastar的future设计规则是:如果一个future没有被任何对象持有或等待,会被自动取消,避免资源泄漏。原代码中
all_futures函数里的前两个sleepfuture创建后直接被丢弃,没有被等待,当第三个sleep提前完成时,函数返回,前两个future被销毁并取消,因此不会执行回调。 - C++编译阶段无法检测这类问题,因为编译器无法跟踪运行时对象的引用关系,无法判断开发者是否打算等待某个future。
3. 查阅when_all声明可知其接受迭代器对,但代码未传入迭代器却能编译运行且无警告,原因是什么?
Seastar的when_all提供了多个重载版本:
- 一个版本接受迭代器对,用于等待容器中的所有future;
- 另一个版本接受可变参数(
FutOrFuncs&&...),可以直接传入任意数量的future或可调用对象,返回一个future<std::tuple<...>>,其中tuple的元素对应每个输入future的结果类型。
代码中使用的是可变参数重载版本,只要传入的是合法的future对象,就会匹配该重载,因此可以正常编译运行,不会出现警告或错误。
内容的提问来源于stack exchange,提问作者Deborah C

