Add retry capability on connection errors

This fixes bug #2
This commit is contained in:
King_DuckZ 2020-09-15 00:45:39 +02:00
parent 2c8e557bb4
commit 36b0c6f6f0
12 changed files with 143 additions and 17 deletions

View file

@ -8,6 +8,8 @@ fetch_extra_delay=2
api_key=my_key_here api_key=my_key_here
store_raw_json=true store_raw_json=true
json_store_mode=xz json_store_mode=xz
max_connection_retries=3
error_retry_timeout=360
[timing] [timing]
items=604800 items=604800

View file

@ -74,6 +74,14 @@ namespace {
constexpr const char g_creators_time[] = "creators"; constexpr const char g_creators_time[] = "creators";
constexpr const char g_creators_time_def[] = "600"; 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) { bool equal (std::string_view a, std::string_view b) {
return a.size() == b.size() and std::equal( return a.size() == b.size() and std::equal(
a.begin(), a.end(), 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<std::size_t>(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<unsigned int>(val);
}
} //namespace duck } //namespace duck

View file

@ -42,6 +42,9 @@ public:
std::size_t creators_timeout() const; std::size_t creators_timeout() const;
oro::SourceFormat json_store_mode() const; oro::SourceFormat json_store_mode() const;
std::size_t error_retry_timeout() const;
unsigned int max_connection_retries() const;
private: private:
kamokan::IniFile m_ini; kamokan::IniFile m_ini;
}; };

View file

@ -55,6 +55,24 @@ namespace {
private: private:
eve::Eventia* m_eventia; 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<const TimerSettings*>(this);
ret.min_wait = mwait;
return ret;
}
};
} //unnamed namespace } //unnamed namespace
void test(oro::Api* api, oro::OriginsDB* db, const AppConfig& app_conf) { 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<oro::DBOperation::Items> TimerItems; typedef TimerOroApi<oro::DBOperation::Items> TimerItems;
typedef TimerOroApi<oro::DBOperation::Icons> TimerIcons; typedef TimerOroApi<oro::DBOperation::Icons> TimerIcons;
typedef TimerOroApi<oro::DBOperation::Creators> TimerCreators; typedef TimerOroApi<oro::DBOperation::Creators> TimerCreators;
typedef TimerSettings TSet;
std::cout << "Running with " << app_conf.worker_threads() << " worker threads\n"; std::cout << "Running with " << app_conf.worker_threads() << " worker threads\n";
eve::Eventia worker; eve::Eventia worker;
EventiaThreadPool pool(&worker, app_conf.worker_threads()); EventiaThreadPool pool(&worker, app_conf.worker_threads());
const double ed = static_cast<double>(app_conf.fetch_extra_delay());
auto sig_int = worker.make_event<SignalInt>(&worker); auto sig_int = worker.make_event<SignalInt>(&worker);
const oro::SourceFormat sf =
(app_conf.store_raw_json() ? app_conf.json_store_mode() : oro::SourceFormat::None);
const double w1 = static_cast<double>(app_conf.items_timeout()); const double w1 = static_cast<double>(app_conf.items_timeout());
const double w2 = static_cast<double>(app_conf.icons_timeout()); const double w2 = static_cast<double>(app_conf.icons_timeout());
const double w3 = static_cast<double>(app_conf.shops_timeout()); const double w3 = static_cast<double>(app_conf.shops_timeout());
const double w4 = static_cast<double>(app_conf.creators_timeout()); const double w4 = static_cast<double>(app_conf.creators_timeout());
auto timer_items = worker.make_event<TimerItems>(TSet{w1, ed, sf}, &pool, api, db); TimerSettingsHelper tset{
auto timer_icons = worker.make_event<TimerIcons>(TSet{w2, ed, sf}, &pool, api, db); static_cast<double>(app_conf.fetch_extra_delay()),
auto timer_shops = worker.make_event<TimerShops>(TSet{w3, ed, sf}, &pool, api, db); static_cast<double>(app_conf.error_retry_timeout()),
auto timer_creat = worker.make_event<TimerCreators>(TSet{w4, ed, sf}, &pool, api, db); (app_conf.store_raw_json() ? app_conf.json_store_mode() : oro::SourceFormat::None),
app_conf.max_connection_retries()
};
auto timer_items = worker.make_event<TimerItems>(tset(w1), &pool, api, db);
auto timer_icons = worker.make_event<TimerIcons>(tset(w2), &pool, api, db);
auto timer_shops = worker.make_event<TimerShops>(tset(w3), &pool, api, db);
auto timer_creat = worker.make_event<TimerCreators>(tset(w4), &pool, api, db);
try { try {
pool.submit(worker.event_functor()); pool.submit(worker.event_functor());

View file

@ -24,6 +24,14 @@
namespace nap { namespace nap {
enum class ConnectionResult : unsigned int {
Ok,
CouldntResolveProxy,
CouldntResolveHost,
CouldntConnect,
TimedOut
};
struct HttpResponse { struct HttpResponse {
std::unique_ptr<char[]> raw; std::unique_ptr<char[]> raw;
std::vector<std::pair<std::string_view, std::string_view>> header_list; std::vector<std::pair<std::string_view, std::string_view>> header_list;
@ -31,7 +39,8 @@ struct HttpResponse {
std::string_view body; std::string_view body;
std::string_view http_ver; std::string_view http_ver;
std::string_view code_desc; std::string_view code_desc;
unsigned int code; unsigned int code{0};
ConnectionResult conn_result{ConnectionResult::Ok};
}; };
} //namespace nap } //namespace nap

View file

@ -21,6 +21,7 @@
#include "private/mime_split.hpp" #include "private/mime_split.hpp"
#include "duckhandy/int_conv.hpp" #include "duckhandy/int_conv.hpp"
#include "api_nap_exception.hpp" #include "api_nap_exception.hpp"
#include <curl_exception.h>
#include <optional> #include <optional>
#include <algorithm> #include <algorithm>
#include <cctype> #include <cctype>
@ -269,7 +270,13 @@ ApiOutput<Creators> ApiNap::fame_list(bool with_raw) {
template <typename T, typename F> template <typename T, typename F>
ApiOutput<T> ApiNap::fetch_and_parse (const char* endpoint, F&& data_fill, bool with_raw) { ApiOutput<T> 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) if (200 != resp.code)
throw ServerError(resp); throw ServerError(resp);

View file

@ -64,4 +64,14 @@ std::string_view ContentTypeError::content_type() const noexcept {
return {m_content_type.data()}; 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 } //namespace oro

View file

@ -38,11 +38,21 @@ private:
class ContentTypeError : public std::runtime_error { class ContentTypeError : public std::runtime_error {
public: 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; std::string_view content_type() const noexcept;
private: private:
std::array<char, 32> m_content_type; std::array<char, 32> 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 } //namespace oro

View file

@ -76,10 +76,12 @@ TimerBase::TimerBase (
eve::Timer(initial_timer(*db, type, 2.0), ctx), eve::Timer(initial_timer(*db, type, 2.0), ctx),
m_extra_delay(settings.extra_delay), m_extra_delay(settings.extra_delay),
m_min_wait(settings.min_wait), m_min_wait(settings.min_wait),
m_err_timeout(settings.err_timeout),
m_pool(pool), m_pool(pool),
m_oro_api(oro_api), m_oro_api(oro_api),
m_db(db), 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_pool);
assert(m_oro_api); 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); 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 } //namespace duck

View file

@ -37,15 +37,25 @@ namespace duck {
class EventiaThreadPool; class EventiaThreadPool;
struct TimerSettings { 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), min_wait(min_wait),
extra_delay(extra_delay), 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 min_wait;
double extra_delay; double extra_delay;
double err_timeout;
oro::SourceFormat src_store_mode; oro::SourceFormat src_store_mode;
unsigned int err_retries;
}; };
class TimerBase : public eve::Timer { class TimerBase : public eve::Timer {
@ -73,15 +83,21 @@ protected:
oro::OriginsDB& db(); oro::OriginsDB& db();
void reset_db_access_time (oro::DBOperation op); void reset_db_access_time (oro::DBOperation op);
protected:
double error_timeout() const noexcept;
unsigned int error_retries() const noexcept;
private: private:
virtual void fetch_data(oro::SourceFormat store_mode) = 0; virtual void fetch_data(oro::SourceFormat store_mode) = 0;
double m_extra_delay; double m_extra_delay;
double m_min_wait; double m_min_wait;
double m_err_timeout;
EventiaThreadPool* m_pool; EventiaThreadPool* m_pool;
oro::Api* m_oro_api; oro::Api* m_oro_api;
oro::OriginsDB* m_db; oro::OriginsDB* m_db;
oro::SourceFormat m_source_store_mode; oro::SourceFormat m_source_store_mode;
unsigned int m_err_retries;
}; };
} //namespace duck } //namespace duck

View file

@ -26,6 +26,7 @@
# include <restc-cpp/error.h> # include <restc-cpp/error.h>
#elif defined(OROTOOL_WITH_NAP) #elif defined(OROTOOL_WITH_NAP)
# include "oro/api_nap_exception.hpp" # include "oro/api_nap_exception.hpp"
# include <curl/curl.h>
#endif #endif
namespace duck { namespace duck {
@ -62,7 +63,8 @@ inline TimerOroApi<Op>::TimerOroApi (
oro::Api* oro_api, oro::Api* oro_api,
oro::OriginsDB* db 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<Op>::fetch_data (oro::SourceFormat store_mode) {
throw; 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 #endif
m_retries_left = error_retries();
if (429 == status_code) { if (429 == status_code) {
oro::Header head; oro::Header head;
head.date.ts = std::chrono::time_point_cast<oro::timestamp_t::duration>(std::chrono::system_clock::now()); head.date.ts = std::chrono::time_point_cast<oro::timestamp_t::duration>(std::chrono::system_clock::now());

View file

@ -39,6 +39,8 @@ public:
private: private:
virtual void fetch_data (oro::SourceFormat store_mode) override; virtual void fetch_data (oro::SourceFormat store_mode) override;
unsigned int m_retries_left;
}; };
} //namespace duck } //namespace duck