项目基于C++实现,用到了状态机在不同状态之间切换来执行不同的流程。
问题描述:
状态机在特定情况下无法再继续执行状态切换。
项目代码还是比较复杂的,所以这里只是摘取了关键代码段(当然看这个代码段可能很容易看出问题所在)。下面test里面的helloWorldFunc模拟实际的状态切换调用,从输出结果上"hello world"没有输出,所以并没有进入这个状态。
#include#include #include using namespace std; struct transition { using ptr = std::unique_ptr ; using executor = std::function ; transition(const executor& executor) : execute(executor) { } static void run(const executor& executor) { transition::run(std::make_unique (executor)); } static void run (transition::ptr transition) { thread_local transition::ptr current; if (current) { // if in a transition already queue for execution after current is completed current->next = std::move(transition); return; } for (current = std::move(transition); current; current = std::move(current->next)) { current->execute(); } } executor execute; ptr next; }; int main() { auto helloWorldFunc = []() { cout << "hello world" << endl; }; auto exceptionFunc = []() { throw std::exception(); }; try { transition::run(exceptionFunc); } catch (...) { cout << "exception thrown" << endl; } try { transition::run(helloWorldFunc); } catch (...) { cout << "exception thrown" << endl; } return 0; }
原因分析:
注意看上面代码中用到了thread_local变量current来保留当前执行的状态,一般情况下状态执行完的话 current会释放,这样当切换下一个状态的时候可以正常执行。但是如果前一个状态transition::run(exceptionFunc)在执行current->execute()的时候如果抛出了异常,那么current无法被置为空,这样的话当执行transition::run(helloWorldFunc)的时候,这个状态会被添加到前一个状态的next里面,并且一直没有机会被执行,所以状态机再也无法切换状态了。
解决方案:
解决方案的话在执行current->execute()的时候加上异常捕获,当捕获到异常的时候将current置成空。
try {
for (current = std::move(transition); current; current = std::move(current->next)) {
current->execute();
}
} catch (...) {
current.reset();
}
总结:
- 在实际的项目中看这种问题还是有难度的,实际的项目中执行transition::run的地方并没有加异常捕获,异常捕获的代码是加在一个thread pool里面的执行体里的,然后状态切换task被enqueue到这个thread pool来执行,幸好在捕获异常的地方输出了一行日志,不然我估计看不出来root cause。所以我们代码中如果加了异常捕获的话一定要加日志输出error信息方便看问题。
- 类似状态机这种可能会执行不同函数的场景,要多想想被执行的函数会不会throw exception,如果throw了exception要考虑好异常处理。



