diff --git a/include/optionx_cpp/platforms/IntradeBarPlatform/BalanceManager.hpp b/include/optionx_cpp/platforms/IntradeBarPlatform/BalanceManager.hpp index 7fa770b..92463cc 100644 --- a/include/optionx_cpp/platforms/IntradeBarPlatform/BalanceManager.hpp +++ b/include/optionx_cpp/platforms/IntradeBarPlatform/BalanceManager.hpp @@ -60,6 +60,8 @@ namespace optionx::platforms::intrade_bar { int64_t m_disconnected_domain_retry_period_ms = time_shield::MS_PER_15_SEC; ///< Disconnected host/domain recovery period. bool m_has_balance_update = false; ///< Flag indicating if a balance update is in progress. bool m_check_host_in_progress = false; + std::uint32_t m_consecutive_host_check_failures = 0; ///< Consecutive failed host checks while connected. + static constexpr std::uint32_t kHostCheckFailureThreshold = 2; std::uint64_t m_balance_request_generation = 0; ///< Monotonic balance request generation. std::uint64_t m_active_balance_request_generation = 0; ///< Currently active balance request generation. std::uint64_t m_host_request_generation = 0; ///< Monotonic host/domain request generation. @@ -414,6 +416,7 @@ namespace optionx::platforms::intrade_bar { m_task_manager.shutdown(); invalidate_async_requests("connected"); + m_consecutive_host_check_failures = 0; LOGIT_INFO( "Intrade Bar balance: starting connected balance polling. period_ms=", @@ -444,15 +447,33 @@ namespace optionx::platforms::intrade_bar { if (!finish_host_request(generation, "connected-host-check")) { return; } - if (!success) { - auto account_info = get_account_info(); - if (account_info->connect) { - account_info->connect = false; - using Status = events::AccountInfoUpdateEvent::Status; - const std::string error_text("Ping to current host failed."); - LOGIT_ERROR(error_text); - notify(events::AccountInfoUpdateEvent(account_info, Status::DISCONNECTED, error_text)); + if (success) { + if (m_consecutive_host_check_failures != 0) { + LOGIT_INFO( + "Intrade Bar balance: current host check recovered after failures=", + m_consecutive_host_check_failures); } + m_consecutive_host_check_failures = 0; + return; + } + + ++m_consecutive_host_check_failures; + LOGIT_WARN( + "Intrade Bar balance: current host check failed. consecutive_failures=", + m_consecutive_host_check_failures, + "; disconnect_threshold=", + kHostCheckFailureThreshold); + if (m_consecutive_host_check_failures < kHostCheckFailureThreshold) { + return; + } + + auto account_info = get_account_info(); + if (account_info->connect) { + account_info->connect = false; + using Status = events::AccountInfoUpdateEvent::Status; + const std::string error_text("Ping to current host failed."); + LOGIT_ERROR(error_text); + notify(events::AccountInfoUpdateEvent(account_info, Status::DISCONNECTED, error_text)); } }); }); @@ -464,6 +485,7 @@ namespace optionx::platforms::intrade_bar { m_task_manager.shutdown(); invalidate_async_requests("disconnected"); + m_consecutive_host_check_failures = 0; LOGIT_INFO( "Intrade Bar balance: starting disconnected host recovery. period_ms=", m_disconnected_domain_retry_period_ms); diff --git a/include/optionx_cpp/platforms/IntradeBarPlatform/RequestManager.hpp b/include/optionx_cpp/platforms/IntradeBarPlatform/RequestManager.hpp index 1f682d2..f4012fb 100644 --- a/include/optionx_cpp/platforms/IntradeBarPlatform/RequestManager.hpp +++ b/include/optionx_cpp/platforms/IntradeBarPlatform/RequestManager.hpp @@ -976,7 +976,10 @@ namespace optionx::platforms::intrade_bar { LOGIT_TRACE0(); auto& client = get_http_client(); - client.set_head_only(true); // Use HEAD to avoid downloading body + // Use a regular GET here. Some proxies and broker frontends handle + // HEAD-like requests inconsistently, which can produce a local 499 + // even while the authenticated transport is still healthy. + client.set_head_only(false); client.set_retry_attempts(3, time_shield::MS_PER_SEC); client.set_timeout(5); client.set_connect_timeout(5); @@ -991,7 +994,11 @@ namespace optionx::platforms::intrade_bar { bool success = response->status_code == 200; LOGIT_PRINT_DEBUG("Current host ping check:", " success:", success, - "; status:", response->status_code); + "; status:", response->status_code, + "; error_code:", response->error_code.value(), + "; error:", response->error_code.message(), + "; retry_attempt:", response->retry_attempt, + "; message:", response->error_message); check_callback(success); }; diff --git a/tests/intrade_bar_api/intrade_bar_api_response_test.cpp b/tests/intrade_bar_api/intrade_bar_api_response_test.cpp index 1c3dc77..04faa9f 100644 --- a/tests/intrade_bar_api/intrade_bar_api_response_test.cpp +++ b/tests/intrade_bar_api/intrade_bar_api_response_test.cpp @@ -187,6 +187,20 @@ struct LocalLoginServer { response->write(SimpleWeb::StatusCode::success_ok, "ok"); }; + server.resource["^/$"]["GET"] = [this]( + std::shared_ptr response, + std::shared_ptr) { + ++host_health_get_requests; + response->write(SimpleWeb::StatusCode::success_ok, "ok"); + }; + + server.resource["^/$"]["HEAD"] = [this]( + std::shared_ptr response, + std::shared_ptr) { + ++host_health_head_requests; + response->write(SimpleWeb::StatusCode::client_error_not_found); + }; + server.resource["^/login$"]["POST"] = [this]( std::shared_ptr response, std::shared_ptr) { @@ -263,6 +277,8 @@ struct LocalLoginServer { std::atomic login_requests{0}; std::atomic auth_redirect_requests{0}; std::atomic profile_requests{0}; + std::atomic host_health_get_requests{0}; + std::atomic host_health_head_requests{0}; std::string login_redirect; }; @@ -823,6 +839,43 @@ TEST(IntradeBarLogin, MergesLoginCookiesCaseInsensitively) { EXPECT_NE(cookies.find("challenge=fake"), std::string::npos); } +TEST(IntradeBarHostHealth, ChecksCurrentHostWithGet) { + LocalLoginServer server; + ASSERT_TRUE(server.start()); + + TestPlatform platform; + HttpClientComponent http_client(platform); + RequestManager request_manager(platform, http_client); + + auto auth_data = std::make_shared(); + auth_data->host = server.host(); + auth_data->auto_find_domain = false; + + events::AuthDataEvent auth_event(auth_data); + request_manager.on_event(&auth_event); + http_client.get_http_client().set_retry_attempts(0, 0); + + bool callback_received = false; + bool host_available = false; + request_manager.request_check_current_host_available( + [&](bool success) { + callback_received = true; + host_available = success; + }); + + for (int i = 0; i < 500 && !callback_received; ++i) { + http_client.process(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + EXPECT_TRUE(callback_received); + EXPECT_TRUE(host_available); + EXPECT_EQ(server.host_health_get_requests.load(), 1); + EXPECT_EQ(server.host_health_head_requests.load(), 0); + + platform.shutdown(); +} + TEST(IntradeBarLogin, FollowsOpaqueRedirectAndReturnsIssuedCookies) { LocalLoginServer server; ASSERT_TRUE(server.start());