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
store_raw_json=true
json_store_mode=xz
max_connection_retries=3
error_retry_timeout=360
[timing]
items=604800

View file

@ -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<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

View file

@ -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;
};

View file

@ -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<const TimerSettings*>(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<oro::DBOperation::Items> TimerItems;
typedef TimerOroApi<oro::DBOperation::Icons> TimerIcons;
typedef TimerOroApi<oro::DBOperation::Creators> 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<double>(app_conf.fetch_extra_delay());
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 w2 = static_cast<double>(app_conf.icons_timeout());
const double w3 = static_cast<double>(app_conf.shops_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);
auto timer_icons = worker.make_event<TimerIcons>(TSet{w2, ed, sf}, &pool, api, db);
auto timer_shops = worker.make_event<TimerShops>(TSet{w3, ed, sf}, &pool, api, db);
auto timer_creat = worker.make_event<TimerCreators>(TSet{w4, ed, sf}, &pool, api, db);
TimerSettingsHelper tset{
static_cast<double>(app_conf.fetch_extra_delay()),
static_cast<double>(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<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 {
pool.submit(worker.event_functor());

View file

@ -24,6 +24,14 @@
namespace nap {
enum class ConnectionResult : unsigned int {
Ok,
CouldntResolveProxy,
CouldntResolveHost,
CouldntConnect,
TimedOut
};
struct HttpResponse {
std::unique_ptr<char[]> raw;
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 http_ver;
std::string_view code_desc;
unsigned int code;
unsigned int code{0};
ConnectionResult conn_result{ConnectionResult::Ok};
};
} //namespace nap

View file

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

View file

@ -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

View file

@ -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<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

View file

@ -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

View file

@ -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

View file

@ -26,6 +26,7 @@
# include <restc-cpp/error.h>
#elif defined(OROTOOL_WITH_NAP)
# include "oro/api_nap_exception.hpp"
# include <curl/curl.h>
#endif
namespace duck {
@ -62,7 +63,8 @@ inline TimerOroApi<Op>::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<Op>::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<oro::timestamp_t::duration>(std::chrono::system_clock::now());

View file

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