From b10846b4fe8409afdf2c6154143db01c20e44ffa Mon Sep 17 00:00:00 2001 From: wen2so Date: Thu, 17 Jul 2025 11:10:51 +0800 Subject: [PATCH 1/2] feat: add heartbeat feature Detect heartbeat status every 5 seconds. If no heartbeat is received within 5 seconds, the program will exit automatically. --- .../Controllers/HealthCheckController.cpp | 8 +++++ .../Controllers/HealthCheckController.h | 1 + OdbDesignServer/OdbDesignServerApp.cpp | 31 ++++++++++++++++++- OdbDesignServer/OdbDesignServerApp.h | 10 +++++- swagger/odbdesign-server-0.9-swagger.yaml | 7 +++++ 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/OdbDesignServer/Controllers/HealthCheckController.cpp b/OdbDesignServer/Controllers/HealthCheckController.cpp index 875ca269..4ab690fb 100644 --- a/OdbDesignServer/Controllers/HealthCheckController.cpp +++ b/OdbDesignServer/Controllers/HealthCheckController.cpp @@ -22,6 +22,7 @@ namespace Odb::App::Server register_route_handler("/healthz/live", std::bind(&HealthCheckController::health_check_live, this, std::placeholders::_1)); register_route_handler("/healthz/ready", std::bind(&HealthCheckController::health_check_ready, this, std::placeholders::_1)); register_route_handler("/healthz/started", std::bind(&HealthCheckController::health_check_started, this, std::placeholders::_1)); + register_route_handler("/healthz/heartbeat", std::bind(&HealthCheckController::health_check_heartbeat, this, std::placeholders::_1)); } crow::response HealthCheckController::health_check_live(const crow::request& req) @@ -38,4 +39,11 @@ namespace Odb::App::Server { return crow::response(crow::status::OK, "txt", "healthy: started"); } + + crow::response HealthCheckController::health_check_heartbeat(const crow::request& req) + { + // update last heartbeat + OdbDesignServerApp::inst_->updateLastHeartbeat(); + return crow::response(crow::status::OK, "txt", "heartbeat received"); + } } diff --git a/OdbDesignServer/Controllers/HealthCheckController.h b/OdbDesignServer/Controllers/HealthCheckController.h index d9f12b41..0e418139 100644 --- a/OdbDesignServer/Controllers/HealthCheckController.h +++ b/OdbDesignServer/Controllers/HealthCheckController.h @@ -18,6 +18,7 @@ namespace Odb::App::Server crow::response health_check_live(const crow::request& req); crow::response health_check_ready(const crow::request& req); crow::response health_check_started(const crow::request& req); + crow::response health_check_heartbeat(const crow::request& req); }; } diff --git a/OdbDesignServer/OdbDesignServerApp.cpp b/OdbDesignServer/OdbDesignServerApp.cpp index dc472683..d15ea02a 100644 --- a/OdbDesignServer/OdbDesignServerApp.cpp +++ b/OdbDesignServer/OdbDesignServerApp.cpp @@ -1,3 +1,6 @@ +#include +#include +#include #include "OdbDesignServerApp.h" #include "Controllers/HelloWorldController.h" #include "Controllers/FileUploadController.h" @@ -11,9 +14,13 @@ using namespace Odb::Lib::App; namespace Odb::App::Server { + OdbDesignServerApp* OdbDesignServerApp::inst_ = nullptr; OdbDesignServerApp::OdbDesignServerApp(int argc, char* argv[]) : OdbServerAppBase(argc, argv) - { + { + inst_ = this; + // set last heartbeat time to now + lastHeartbeat_.store(std::chrono::steady_clock::now(), std::memory_order_relaxed); } //OdbDesignServerApp::~OdbDesignServerApp() @@ -44,6 +51,24 @@ namespace Odb::App::Server m_vecControllers.push_back(std::make_shared(*this)); } + void monitorHeartbeat() + { + auto lastTime = OdbDesignServerApp::inst_->lastHeartbeat_.load(std::memory_order_relaxed); + while (true) + { + std::this_thread::sleep_for(std::chrono::seconds(1)); + auto now = std::chrono::steady_clock::now(); + auto diff = now - lastTime; + // check heartbeat + if (diff > std::chrono::seconds(5)) + { + std::cerr << "Heartbeat timeout, exiting..." << std::endl; + exit(0); + } + lastTime = OdbDesignServerApp::inst_->lastHeartbeat_.load(std::memory_order_relaxed); + } + } + bool OdbDesignServerApp::preServerRun() { // CORS @@ -70,6 +95,10 @@ namespace Odb::App::Server auto basicRequestAuth = std::make_unique(BasicRequestAuthentication(disableAuth)); request_auth(std::move(basicRequestAuth)); + // start heart beat monitor + std::thread heartbeatMonitor(monitorHeartbeat); + heartbeatMonitor.detach(); // run in background + return true; } } \ No newline at end of file diff --git a/OdbDesignServer/OdbDesignServerApp.h b/OdbDesignServer/OdbDesignServerApp.h index 0995631c..e657b9bd 100644 --- a/OdbDesignServer/OdbDesignServerApp.h +++ b/OdbDesignServer/OdbDesignServerApp.h @@ -14,7 +14,15 @@ namespace Odb::App::Server //Utils::ExitCode Run() override; - protected: + static OdbDesignServerApp* inst_; + // store last heartbeat time + std::atomic lastHeartbeat_; + void updateLastHeartbeat() + { + lastHeartbeat_.store(std::chrono::steady_clock::now(), std::memory_order_relaxed); + } + + protected: void add_controllers() override; diff --git a/swagger/odbdesign-server-0.9-swagger.yaml b/swagger/odbdesign-server-0.9-swagger.yaml index 5aa475cc..33e9e2bd 100644 --- a/swagger/odbdesign-server-0.9-swagger.yaml +++ b/swagger/odbdesign-server-0.9-swagger.yaml @@ -606,6 +606,13 @@ paths: "200": description: "" /healthz/ready: + get: + tags: ["health check"] + parameters: [] + responses: + "200": + description: "" + /healthz/heartbeat: get: tags: ["health check"] parameters: [] From 75ed67fc6b4892afece35a876c4275783f23613b Mon Sep 17 00:00:00 2001 From: wen2so Date: Thu, 17 Jul 2025 13:56:43 +0800 Subject: [PATCH 2/2] feat: add heartbeat interval configuration option - Add heartbeatInterval() method to OdbDesignArgs class - Implement heartbeat interval configuration in OdbDesignServerApp - Update usage string to include new heartbeat interval option --- OdbDesignLib/App/OdbDesignArgs.cpp | 6 ++++++ OdbDesignLib/App/OdbDesignArgs.h | 2 ++ OdbDesignServer/OdbDesignServerApp.cpp | 4 +++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/OdbDesignLib/App/OdbDesignArgs.cpp b/OdbDesignLib/App/OdbDesignArgs.cpp index e44513d6..fa236670 100644 --- a/OdbDesignLib/App/OdbDesignArgs.cpp +++ b/OdbDesignLib/App/OdbDesignArgs.cpp @@ -53,6 +53,11 @@ namespace Odb::Lib::App return boolArg("disable-authentication", DEFAULT_DISABLE_AUTH); } + int OdbDesignArgs::heartbeatInterval() const + { + return intArg("heartbeat-interval", DEFAULT_HEARTBEAT_INTERVAL); + } + std::string OdbDesignArgs::getUsageString() const { std::stringstream ss; @@ -66,6 +71,7 @@ namespace Odb::Lib::App ss << " --load-design Design to load on startup (default: " << DEFAULT_LOAD_DESIGN << ")\n"; ss << " --load-all Load all designs on startup (default: " << (DEFAULT_LOAD_ALL ? "true" : "false") << ")\n"; ss << " --disable-authentication Disable authentication (default: " << (DEFAULT_DISABLE_AUTH ? "true" : "false") << ")\n"; + ss << " --heartbeat-interval Heartbeat interval in seconds (default: " << DEFAULT_HEARTBEAT_INTERVAL << ")\n"; ss << " --help Print this help message\n"; return ss.str(); } diff --git a/OdbDesignLib/App/OdbDesignArgs.h b/OdbDesignLib/App/OdbDesignArgs.h index 93b0973b..721e031c 100644 --- a/OdbDesignLib/App/OdbDesignArgs.h +++ b/OdbDesignLib/App/OdbDesignArgs.h @@ -19,6 +19,7 @@ namespace Odb::Lib::App std::string loadDesign() const; bool loadAll() const; bool disableAuthentication() const; + int heartbeatInterval() const; protected: // Inherited via CommandLineArgs @@ -34,6 +35,7 @@ namespace Odb::Lib::App constexpr static const char* DEFAULT_LOAD_DESIGN = ""; constexpr static const bool DEFAULT_LOAD_ALL = false; constexpr static const bool DEFAULT_DISABLE_AUTH = false; + constexpr static const int DEFAULT_HEARTBEAT_INTERVAL = 5; }; } diff --git a/OdbDesignServer/OdbDesignServerApp.cpp b/OdbDesignServer/OdbDesignServerApp.cpp index d15ea02a..19860286 100644 --- a/OdbDesignServer/OdbDesignServerApp.cpp +++ b/OdbDesignServer/OdbDesignServerApp.cpp @@ -15,10 +15,12 @@ using namespace Odb::Lib::App; namespace Odb::App::Server { OdbDesignServerApp* OdbDesignServerApp::inst_ = nullptr; + int heartbeatInterval; OdbDesignServerApp::OdbDesignServerApp(int argc, char* argv[]) : OdbServerAppBase(argc, argv) { inst_ = this; + heartbeatInterval = args().heartbeatInterval(); // set last heartbeat time to now lastHeartbeat_.store(std::chrono::steady_clock::now(), std::memory_order_relaxed); } @@ -60,7 +62,7 @@ namespace Odb::App::Server auto now = std::chrono::steady_clock::now(); auto diff = now - lastTime; // check heartbeat - if (diff > std::chrono::seconds(5)) + if (diff > std::chrono::seconds(heartbeatInterval)) { std::cerr << "Heartbeat timeout, exiting..." << std::endl; exit(0);