From 36b0c6f6f0284a247f17a74875c4cf582911b684 Mon Sep 17 00:00:00 2001 From: King_DuckZ Date: Tue, 15 Sep 2020 00:45:39 +0200 Subject: [PATCH] Add retry capability on connection errors This fixes bug #2 --- orotool.conf | 2 ++ src/app_config.cpp | 18 +++++++++++++++++ src/app_config.hpp | 3 +++ src/evloop.cpp | 37 ++++++++++++++++++++++++++--------- src/nap/http_response.hpp | 11 ++++++++++- src/oro/api_nap.cpp | 9 ++++++++- src/oro/api_nap_exception.cpp | 10 ++++++++++ src/oro/api_nap_exception.hpp | 12 +++++++++++- src/timer_base.cpp | 12 +++++++++++- src/timer_base.hpp | 20 +++++++++++++++++-- src/timer_oro_api.cpp | 24 +++++++++++++++++++++-- src/timer_oro_api.hpp | 2 ++ 12 files changed, 143 insertions(+), 17 deletions(-) diff --git a/orotool.conf b/orotool.conf index 7b71d65..14cd9f0 100644 --- a/orotool.conf +++ b/orotool.conf @@ -8,6 +8,8 @@ fetch_extra_delay=2 api_key=my_key_here store_raw_json=true json_store_mode=xz +max_connection_retries=3 +error_retry_timeout=360 [timing] items=604800 diff --git a/src/app_config.cpp b/src/app_config.cpp index 97a91b1..2e04801 100644 --- a/src/app_config.cpp +++ b/src/app_config.cpp @@ -74,6 +74,14 @@ namespace { constexpr const char g_creators_time[] = "creators"; constexpr const char g_creators_time_def[] = "600"; + constexpr const char g_max_conn_retries_sect[] = "options"; + constexpr const char g_max_conn_retries[] = "max_connection_retries"; + constexpr const char g_max_conn_retries_def[] = "0"; + + constexpr const char g_err_retry_timeout_sect[] = "options"; + constexpr const char g_err_retry_timeout[] = "error_retry_timeout"; + constexpr const char g_err_retry_timeout_def[] = "600"; + bool equal (std::string_view a, std::string_view b) { return a.size() == b.size() and std::equal( a.begin(), a.end(), @@ -235,4 +243,14 @@ oro::SourceFormat AppConfig::json_store_mode() const { } } +std::size_t AppConfig::error_retry_timeout() const { + std::string_view val = value_ifp(m_ini, g_err_retry_timeout_sect, g_err_retry_timeout, g_err_retry_timeout_def, false); + return std::max(dhandy::int_conv(val), g_min_update_timeout); +} + +unsigned int AppConfig::max_connection_retries() const { + std::string_view val = value_ifp(m_ini, g_max_conn_retries_sect, g_max_conn_retries, g_max_conn_retries_def, false); + return dhandy::int_conv(val); +} + } //namespace duck diff --git a/src/app_config.hpp b/src/app_config.hpp index 0dcde2d..87d8f53 100644 --- a/src/app_config.hpp +++ b/src/app_config.hpp @@ -42,6 +42,9 @@ public: std::size_t creators_timeout() const; oro::SourceFormat json_store_mode() const; + std::size_t error_retry_timeout() const; + unsigned int max_connection_retries() const; + private: kamokan::IniFile m_ini; }; diff --git a/src/evloop.cpp b/src/evloop.cpp index 326bd3c..77a1763 100644 --- a/src/evloop.cpp +++ b/src/evloop.cpp @@ -55,6 +55,24 @@ namespace { private: eve::Eventia* m_eventia; }; + + struct TimerSettingsHelper : TimerSettings { + TimerSettingsHelper ( + double ed, + double et, + oro::SourceFormat sf, + unsigned int er + ) : + TimerSettings {0.0, ed, et, sf, er} + { + } + + TimerSettings operator() (double mwait) const { + TimerSettings ret = *static_cast(this); + ret.min_wait = mwait; + return ret; + } + }; } //unnamed namespace void test(oro::Api* api, oro::OriginsDB* db, const AppConfig& app_conf) { @@ -62,27 +80,28 @@ void test(oro::Api* api, oro::OriginsDB* db, const AppConfig& app_conf) { typedef TimerOroApi TimerItems; typedef TimerOroApi TimerIcons; typedef TimerOroApi TimerCreators; - typedef TimerSettings TSet; std::cout << "Running with " << app_conf.worker_threads() << " worker threads\n"; eve::Eventia worker; EventiaThreadPool pool(&worker, app_conf.worker_threads()); - const double ed = static_cast(app_conf.fetch_extra_delay()); - auto sig_int = worker.make_event(&worker); - const oro::SourceFormat sf = - (app_conf.store_raw_json() ? app_conf.json_store_mode() : oro::SourceFormat::None); const double w1 = static_cast(app_conf.items_timeout()); const double w2 = static_cast(app_conf.icons_timeout()); const double w3 = static_cast(app_conf.shops_timeout()); const double w4 = static_cast(app_conf.creators_timeout()); - auto timer_items = worker.make_event(TSet{w1, ed, sf}, &pool, api, db); - auto timer_icons = worker.make_event(TSet{w2, ed, sf}, &pool, api, db); - auto timer_shops = worker.make_event(TSet{w3, ed, sf}, &pool, api, db); - auto timer_creat = worker.make_event(TSet{w4, ed, sf}, &pool, api, db); + TimerSettingsHelper tset{ + static_cast(app_conf.fetch_extra_delay()), + static_cast(app_conf.error_retry_timeout()), + (app_conf.store_raw_json() ? app_conf.json_store_mode() : oro::SourceFormat::None), + app_conf.max_connection_retries() + }; + auto timer_items = worker.make_event(tset(w1), &pool, api, db); + auto timer_icons = worker.make_event(tset(w2), &pool, api, db); + auto timer_shops = worker.make_event(tset(w3), &pool, api, db); + auto timer_creat = worker.make_event(tset(w4), &pool, api, db); try { pool.submit(worker.event_functor()); diff --git a/src/nap/http_response.hpp b/src/nap/http_response.hpp index f69949e..f3e52ef 100644 --- a/src/nap/http_response.hpp +++ b/src/nap/http_response.hpp @@ -24,6 +24,14 @@ namespace nap { +enum class ConnectionResult : unsigned int { + Ok, + CouldntResolveProxy, + CouldntResolveHost, + CouldntConnect, + TimedOut +}; + struct HttpResponse { std::unique_ptr raw; std::vector> header_list; @@ -31,7 +39,8 @@ struct HttpResponse { std::string_view body; std::string_view http_ver; std::string_view code_desc; - unsigned int code; + unsigned int code{0}; + ConnectionResult conn_result{ConnectionResult::Ok}; }; } //namespace nap diff --git a/src/oro/api_nap.cpp b/src/oro/api_nap.cpp index 3f9a9d1..bd4d6dd 100644 --- a/src/oro/api_nap.cpp +++ b/src/oro/api_nap.cpp @@ -21,6 +21,7 @@ #include "private/mime_split.hpp" #include "duckhandy/int_conv.hpp" #include "api_nap_exception.hpp" +#include #include #include #include @@ -269,7 +270,13 @@ ApiOutput ApiNap::fame_list(bool with_raw) { template ApiOutput ApiNap::fetch_and_parse (const char* endpoint, F&& data_fill, bool with_raw) { - auto resp = m_qrest.fetch(m_prefix + endpoint); + nap::HttpResponse resp; + try { + resp = m_qrest.fetch(m_prefix + endpoint); + } + catch (const curl::curl_easy_exception& err) { + throw ConnectionError(err.what(), err.get_code()); + } if (200 != resp.code) throw ServerError(resp); diff --git a/src/oro/api_nap_exception.cpp b/src/oro/api_nap_exception.cpp index df461db..3f2bbd8 100644 --- a/src/oro/api_nap_exception.cpp +++ b/src/oro/api_nap_exception.cpp @@ -64,4 +64,14 @@ std::string_view ContentTypeError::content_type() const noexcept { return {m_content_type.data()}; } +ConnectionError::ConnectionError (const std::string& reason, unsigned int code) : + std::runtime_error("Connection error: " + reason), + m_curl_code(code) +{ +} + +unsigned int ConnectionError::curl_code() const { + return m_curl_code; +} + } //namespace oro diff --git a/src/oro/api_nap_exception.hpp b/src/oro/api_nap_exception.hpp index f5eb70b..39a0431 100644 --- a/src/oro/api_nap_exception.hpp +++ b/src/oro/api_nap_exception.hpp @@ -38,11 +38,21 @@ private: class ContentTypeError : public std::runtime_error { public: - explicit ContentTypeError (std::string_view type, std::string_view subtype); + ContentTypeError (std::string_view type, std::string_view subtype); std::string_view content_type() const noexcept; private: std::array m_content_type; }; +class ConnectionError : public std::runtime_error { +public: + ConnectionError (const std::string& reason, unsigned int code); + + unsigned int curl_code() const; + +private: + unsigned int m_curl_code; +}; + } //namespace oro diff --git a/src/timer_base.cpp b/src/timer_base.cpp index 2c802b9..7a61c47 100644 --- a/src/timer_base.cpp +++ b/src/timer_base.cpp @@ -76,10 +76,12 @@ TimerBase::TimerBase ( eve::Timer(initial_timer(*db, type, 2.0), ctx), m_extra_delay(settings.extra_delay), m_min_wait(settings.min_wait), + m_err_timeout(settings.err_timeout), m_pool(pool), m_oro_api(oro_api), m_db(db), - m_source_store_mode(settings.src_store_mode) + m_source_store_mode(settings.src_store_mode), + m_err_retries(settings.err_retries) { assert(m_pool); assert(m_oro_api); @@ -133,4 +135,12 @@ void TimerBase::update_db (const oro::Creators& creators, const oro::Header& hea db().update(creators, calc_next_update(header, m_min_wait, m_extra_delay), source); } +double TimerBase::error_timeout() const noexcept { + return m_err_timeout; +} + +unsigned int TimerBase::error_retries() const noexcept { + return m_err_retries; +} + } //namespace duck diff --git a/src/timer_base.hpp b/src/timer_base.hpp index 7b969a3..3bcf5dd 100644 --- a/src/timer_base.hpp +++ b/src/timer_base.hpp @@ -37,15 +37,25 @@ namespace duck { class EventiaThreadPool; struct TimerSettings { - TimerSettings (double min_wait, double extra_delay, oro::SourceFormat store_mode) : + TimerSettings ( + double min_wait, + double extra_delay, + double err_timeout, + oro::SourceFormat store_mode, + unsigned int err_retries + ) : min_wait(min_wait), extra_delay(extra_delay), - src_store_mode(store_mode) + err_timeout(err_timeout), + src_store_mode(store_mode), + err_retries(err_retries) { } double min_wait; double extra_delay; + double err_timeout; oro::SourceFormat src_store_mode; + unsigned int err_retries; }; class TimerBase : public eve::Timer { @@ -73,15 +83,21 @@ protected: oro::OriginsDB& db(); void reset_db_access_time (oro::DBOperation op); +protected: + double error_timeout() const noexcept; + unsigned int error_retries() const noexcept; + private: virtual void fetch_data(oro::SourceFormat store_mode) = 0; double m_extra_delay; double m_min_wait; + double m_err_timeout; EventiaThreadPool* m_pool; oro::Api* m_oro_api; oro::OriginsDB* m_db; oro::SourceFormat m_source_store_mode; + unsigned int m_err_retries; }; } //namespace duck diff --git a/src/timer_oro_api.cpp b/src/timer_oro_api.cpp index 80af66d..143b1fa 100644 --- a/src/timer_oro_api.cpp +++ b/src/timer_oro_api.cpp @@ -26,6 +26,7 @@ # include #elif defined(OROTOOL_WITH_NAP) # include "oro/api_nap_exception.hpp" +# include #endif namespace duck { @@ -62,7 +63,8 @@ inline TimerOroApi::TimerOroApi ( oro::Api* oro_api, oro::OriginsDB* db ) : - TimerBase(ctx, Op, settings, pool, oro_api, db) + TimerBase(ctx, Op, settings, pool, oro_api, db), + m_retries_left(TimerBase::error_retries()) { } @@ -111,9 +113,27 @@ inline void TimerOroApi::fetch_data (oro::SourceFormat store_mode) { throw; } } + catch (const oro::ConnectionError& err) { + switch (err.curl_code()) { + case CURLE_OPERATION_TIMEDOUT: + case CURLE_COULDNT_RESOLVE_PROXY: + case CURLE_COULDNT_RESOLVE_HOST: + case CURLE_COULDNT_CONNECT: + if (m_retries_left) { + --m_retries_left; + this->set_timer(error_timeout()); + return; + } + else { + throw; + } + default: + throw; + } + } #endif - + m_retries_left = error_retries(); if (429 == status_code) { oro::Header head; head.date.ts = std::chrono::time_point_cast(std::chrono::system_clock::now()); diff --git a/src/timer_oro_api.hpp b/src/timer_oro_api.hpp index 7eef958..4c03fdc 100644 --- a/src/timer_oro_api.hpp +++ b/src/timer_oro_api.hpp @@ -39,6 +39,8 @@ public: private: virtual void fetch_data (oro::SourceFormat store_mode) override; + + unsigned int m_retries_left; }; } //namespace duck