diff --git a/examples/main/05_loop_block/Makefile b/examples/main/05_loop_block/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..06b2b090e8035c489c181e6d373f0c51182061e6 --- /dev/null +++ b/examples/main/05_loop_block/Makefile @@ -0,0 +1,22 @@ +EXE_NAME := example/main/05_loop_block + +CPP_SRC_FILES = \ + app.cpp \ + main.cpp \ + +CXXFLAGS := -DLOG_MODULE_ID='"$(EXE_NAME)"' $(CXXFLAGS) +LDFLAGS += \ + -ltbox_main \ + -ltbox_terminal \ + -ltbox_network \ + -ltbox_eventx \ + -ltbox_eventx \ + -ltbox_event \ + -ltbox_log \ + -ltbox_util \ + -ltbox_base \ + -lpthread \ + -ldl \ + -rdynamic + +include ${TOP_DIR}/tools/exe_common.mk diff --git a/examples/main/05_loop_block/README.txt b/examples/main/05_loop_block/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..6e311f1dfd44cc1f7d23adc943aa73251afb5fd6 --- /dev/null +++ b/examples/main/05_loop_block/README.txt @@ -0,0 +1 @@ +这个示例演示主线程被阻塞下的提示功能 diff --git a/examples/main/05_loop_block/app.cpp b/examples/main/05_loop_block/app.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4b92b9aa1363a63d543a71e3b343eaea306a0f9a --- /dev/null +++ b/examples/main/05_loop_block/app.cpp @@ -0,0 +1,24 @@ +#include "app.h" +#include +#include + +App::App(tbox::main::Context &ctx) : + Module("app", ctx) +{ } + +bool App::onStart() { + timer_ = ctx().timer_pool()->doEvery( + std::chrono::seconds(1), + [] (tbox::eventx::TimerPool::TimerToken) { + LogDbg("begin sleep 2s"); + std::this_thread::sleep_for(std::chrono::seconds(2)); + LogDbg("end"); + } + ); + return true; +} + +void App::onStop() { + ctx().timer_pool()->cancel(timer_); + timer_.reset(); +} diff --git a/examples/main/05_loop_block/app.h b/examples/main/05_loop_block/app.h new file mode 100644 index 0000000000000000000000000000000000000000..6ccdd6707bd9363758873fe22d2fd44fc9508d5e --- /dev/null +++ b/examples/main/05_loop_block/app.h @@ -0,0 +1,20 @@ +#ifndef TBOX_MAIN_EXAMPLE_SAMPLE_H_20211226 +#define TBOX_MAIN_EXAMPLE_SAMPLE_H_20211226 + +#include +#include + +class App : public tbox::main::Module +{ + public: + App(tbox::main::Context &ctx); + + protected: + virtual bool onStart() override; + virtual void onStop() override; + + private: + tbox::eventx::TimerPool::TimerToken timer_; +}; + +#endif //TBOX_MAIN_EXAMPLE_SAMPLE_H_20211226 diff --git a/examples/main/05_loop_block/main.cpp b/examples/main/05_loop_block/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..67ca64d097a44234d57df5227b9798fd58c9db50 --- /dev/null +++ b/examples/main/05_loop_block/main.cpp @@ -0,0 +1,31 @@ +#include +#include "app.h" + +namespace tbox { +namespace main { + +void RegisterApps(Module &apps, Context &ctx) +{ + apps.add(new App(ctx)); +} + +std::string GetAppDescribe() +{ + return "This loop block error example"; +} + +std::string GetAppBuildTime() +{ + return __DATE__ " " __TIME__; +} + +void GetAppVersion(int &major, int &minor, int &rev, int &build) +{ + major = 0; + minor = 0; + rev = 1; + build = 0; +} + +} +} diff --git a/modules/eventx/Makefile b/modules/eventx/Makefile index fd537b48f5dd7d73b14bcad48ec215cf5d8be99a..9f025ee643bfb3e2dca928f89f6f402ecae7e96b 100644 --- a/modules/eventx/Makefile +++ b/modules/eventx/Makefile @@ -8,11 +8,13 @@ HEAD_FILES = \ timer_pool.h \ timeout_monitor.h \ request_pool.hpp \ + loop_wdog.h \ CPP_SRC_FILES = \ thread_pool.cpp \ timer_pool.cpp \ timeout_monitor.cpp \ + loop_wdog.cpp \ CXXFLAGS := -DLOG_MODULE_ID='"eventx"' $(CXXFLAGS) @@ -21,6 +23,7 @@ TEST_CPP_SRC_FILES = \ timer_pool_test.cpp \ timeout_monitor_test.cpp \ request_pool_test.cpp \ + loop_wdog_test.cpp \ TEST_LDFLAGS := $(LDFLAGS) -ltbox_event -ltbox_base ENABLE_SHARED_LIB = no diff --git a/modules/eventx/loop_wdog.cpp b/modules/eventx/loop_wdog.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8a9dfdbbaad9e81ebcf54f5f69d418e1067140ce --- /dev/null +++ b/modules/eventx/loop_wdog.cpp @@ -0,0 +1,137 @@ +#include +#include +#include +#include +#include +#include + +#include "loop_wdog.h" + +#include +#include +#include + +namespace tbox { +namespace eventx { + +namespace { + +void OnLoopBlock(const std::string &name); + +struct LoopInfo { + LoopInfo(event::Loop *l, const std::string &n) : + loop(l), name(n), + tag(std::make_shared(true)) + { } + + event::Loop* loop; + std::string name; + std::shared_ptr tag; +}; + +using LoopInfoVec = std::vector; + +LoopInfoVec _loop_info_vec; //! 线程信息表 +std::mutex _mutex_lock; //! 锁 +std::thread* _sp_thread = nullptr; //! 线程对象 +bool _keep_running = false; //! 线程是否继续工作标记 + +LoopWDog::LoopBlockCallback _loop_die_cb = OnLoopBlock; //! 回调函数 + +void SendLoopFunc() { + std::lock_guard lg(_mutex_lock); + for (auto &loop_info : _loop_info_vec) { + if (loop_info.loop->isRunning()) { + auto tag = loop_info.tag; + if (*tag) { + *tag = false; + loop_info.loop->runInLoop([tag] { *tag = true; }); + } + } + } +} + +void CheckLoopTag() { + std::lock_guard lg(_mutex_lock); + for (auto loop_info: _loop_info_vec) { + auto tag = loop_info.tag; + if (!(*tag)) { + _loop_die_cb(loop_info.name); + } + } +} + +//! 监控线程函数 +void ThreadProc() { + while (_keep_running) { + SendLoopFunc(); + + for (int i = 0; i < 10 && _keep_running; ++i) + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + if (_keep_running) + CheckLoopTag(); + + for (int i = 0; i < 40 && _keep_running; ++i) + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } +} + +//! 默认线程超时执行函数 +void OnLoopBlock(const std::string &name) { + LogWarn("loop \"%s\" block!", name.c_str()); +} + +} + +void LoopWDog::SetLoopBlockCallback(const LoopBlockCallback &cb) { + assert(cb != nullptr); + _loop_die_cb = cb; +} + +void LoopWDog::Start() { + std::lock_guard lg(_mutex_lock); + if (!_keep_running) { + _keep_running = true; + _sp_thread = new std::thread(ThreadProc); + } +} + +void LoopWDog::Stop() { + std::lock_guard lg(_mutex_lock); + if (_keep_running) { + _keep_running = false; + _sp_thread->join(); + CHECK_DELETE_RESET_OBJ(_sp_thread); + _loop_info_vec.clear(); + } +} + +void LoopWDog::Register(event::Loop *loop, const std::string &name) { + std::lock_guard lg(_mutex_lock); + auto iter = std::find_if(_loop_info_vec.begin(), _loop_info_vec.end(), + [loop, name] (const LoopInfo &loop_info) { + return loop_info.loop == loop; + } + ); + + if (iter == _loop_info_vec.end()) { //! 如果没有找到那么创建 + _loop_info_vec.emplace_back(LoopInfo(loop, name)); + } +} + +void LoopWDog::Unregister(event::Loop *loop) { + std::lock_guard lg(_mutex_lock); + auto iter = std::remove_if(_loop_info_vec.begin(), _loop_info_vec.end(), + [loop] (const LoopInfo &loop_info) { + return loop_info.loop == loop; + } + ); + + if (iter != _loop_info_vec.end()) { + _loop_info_vec.erase(iter, _loop_info_vec.end()); + } +} + +} +} diff --git a/modules/eventx/loop_wdog.h b/modules/eventx/loop_wdog.h new file mode 100644 index 0000000000000000000000000000000000000000..a0489c13396dec8ca17dfb96fe5725f7fcf6c4b6 --- /dev/null +++ b/modules/eventx/loop_wdog.h @@ -0,0 +1,38 @@ +#ifndef TBOX_LOOP_WDOG_H_20221110 +#define TBOX_LOOP_WDOG_H_20221110 + +#include +#include + +namespace tbox { +namespace eventx { + +/** + * Loop看门狗(Loop阻塞监控器) + * + * 正常情况下,Loop线程是不可以执行有阻塞性的动作的。如果Loop发生了阻塞, + * 希望能被立即暴露出来。LoopWDog就是实现该功能而设计的。 + * 它每5秒让已注册的Loop执行心跳操作,然后等待1秒去检查这些Loop是否已经执行了心跳操作。 + * 如果Loop所在线程发生了阻塞,Loop所在的线程不能及时地执行心跳动作,从而可以判定Loop + * 所在的线程是否已发生阻塞,达到对Loop进行监控的目的 + */ +class LoopWDog { + public: + using LoopBlockCallback = std::function; + + //! 在 main() 中调用 + static void Start(); //! 启动线程监护 + static void Stop(); //! 停止线程监护 + + //! (可选) 设置Loop阻塞时的回调 + static void SetLoopBlockCallback(const LoopBlockCallback &cb); + + //! 注册与删除要被监控的Loop + static void Register(event::Loop *loop, const std::string &loop_name); + static void Unregister(event::Loop *loop); +}; + +} +} + +#endif //TBOX_LOOP_WDOG_H_20221110 diff --git a/modules/eventx/loop_wdog_test.cpp b/modules/eventx/loop_wdog_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4b68a4bbf0796bc89b901707ba8c8d8ef1e4fe40 --- /dev/null +++ b/modules/eventx/loop_wdog_test.cpp @@ -0,0 +1,113 @@ +#include +#include +#include "loop_wdog.h" +#include +//#include +//#include + +namespace tbox { +namespace eventx { + +using namespace event; +using namespace std::chrono; + +TEST(LoopWDog, Normal) +{ + LoopWDog::Start(); + + auto sp_loop = Loop::New(); + SetScopeExitAction([=] {delete sp_loop;}); + LoopWDog::Register(sp_loop, "main_loop"); + + int die_cb_count = 0; + LoopWDog::SetLoopBlockCallback( + [&](const std::string &name) { ++die_cb_count; } + ); + + sp_loop->exitLoop(std::chrono::seconds(6)); + sp_loop->runLoop(); + + LoopWDog::Unregister(sp_loop); + LoopWDog::Stop(); + + EXPECT_EQ(die_cb_count, 0); +} + +TEST(LoopWDog, MainLoopBlock) +{ + LoopWDog::Start(); + + auto sp_loop = Loop::New(); + SetScopeExitAction([=] {delete sp_loop;}); + LoopWDog::Register(sp_loop, "main_loop"); + + int die_cb_count = 0; + LoopWDog::SetLoopBlockCallback( + [&](const std::string &name) { + EXPECT_EQ(name, "main_loop"); + ++die_cb_count; + } + ); + + sp_loop->runInLoop([] { std::this_thread::sleep_for(std::chrono::seconds(7)); }); + sp_loop->exitLoop(std::chrono::seconds(8)); + + sp_loop->runLoop(); + + LoopWDog::Unregister(sp_loop); + LoopWDog::Stop(); + + EXPECT_EQ(die_cb_count, 1); +} + +TEST(LoopWDog, WorkLoopBlock) +{ + //LogOutput_Initialize(); + LoopWDog::Start(); + + auto sp_loop = Loop::New(); + auto sp_work_loop = Loop::New(); + + SetScopeExitAction([=] { + delete sp_loop; + delete sp_work_loop; + } + ); + + int die_cb_count = 0; + LoopWDog::SetLoopBlockCallback( + [&](const std::string &name) { + EXPECT_EQ(name, "work_loop"); + ++die_cb_count; + } + ); + + LoopWDog::Register(sp_loop, "main_loop"); + LoopWDog::Register(sp_work_loop, "work_loop"); + + std::thread t([=] { sp_work_loop->runLoop(); }); + sp_work_loop->runInLoop( + [] { + //LogTrace("begin"); + std::this_thread::sleep_for(std::chrono::seconds(7)); + //LogTrace("end"); + } + ); + + sp_loop->exitLoop(std::chrono::seconds(8)); + sp_loop->runLoop(); + + sp_work_loop->runInLoop([sp_work_loop] { sp_work_loop->exitLoop(); }); + + LoopWDog::Unregister(sp_work_loop); + LoopWDog::Unregister(sp_loop); + + t.join(); + + EXPECT_EQ(die_cb_count, 1); + LoopWDog::Stop(); + //LogOutput_Cleanup(); +} + +} +} diff --git a/modules/main/main.cpp b/modules/main/main.cpp index 7609ef97123d5c51bcf0865793ad479e87d5a8aa..c51aa3cb858b23101f7f4f7fb7870f14c7557cc9 100644 --- a/modules/main/main.cpp +++ b/modules/main/main.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include #include @@ -109,16 +109,10 @@ int Main(int argc, char **argv) void Run(ContextImp &ctx, Module &apps, int loop_exit_wait) { - auto feeddog_timer = ctx.loop()->newTimerEvent(); auto stop_signal = ctx.loop()->newSignalEvent(); //! 预定在离开时自动释放对象,确保无内存泄漏 - SetScopeExitAction( - [=] { - delete stop_signal; - delete feeddog_timer; - } - ); + SetScopeExitAction([stop_signal] { delete stop_signal; }); stop_signal->initialize({SIGINT, SIGTERM}, event::Event::Mode::kOneshot); stop_signal->setCallback( @@ -131,15 +125,10 @@ void Run(ContextImp &ctx, Module &apps, int loop_exit_wait) } ); - //! 创建喂狗定时器 - feeddog_timer->initialize(std::chrono::seconds(2), event::Event::Mode::kPersist); - feeddog_timer->setCallback(util::ThreadWDog::FeedDog); - //! 启动前准备 - util::ThreadWDog::Start(); - util::ThreadWDog::Register("main", 3); + eventx::LoopWDog::Start(); + eventx::LoopWDog::Register(ctx.loop(), "main"); - feeddog_timer->enable(); stop_signal->enable(); LogInfo("Start!"); @@ -154,8 +143,8 @@ void Run(ContextImp &ctx, Module &apps, int loop_exit_wait) LogInfo("Stoped"); - util::ThreadWDog::Unregister(); - util::ThreadWDog::Stop(); + eventx::LoopWDog::Unregister(ctx.loop()); + eventx::LoopWDog::Stop(); } }