Quit cleanly when an exception is thrown
The trick is to break the ev loop from within its same thread. This can be done with the async event for example, so I repurposed the one I already had to call stop() in case an error has been set. Otherwise its callback is a no-op just like before.
This commit is contained in:
parent
4cb47eb82e
commit
497d797e32
12 changed files with 102 additions and 58 deletions
|
@ -49,6 +49,10 @@ void Event::set_exception (std::exception_ptr ptr) {
|
||||||
m_eventia->set_exception(ptr);
|
m_eventia->set_exception(ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Event::is_stopped() const noexcept {
|
||||||
|
return m_was_stopped;
|
||||||
|
}
|
||||||
|
|
||||||
#if !defined(NDEBUG)
|
#if !defined(NDEBUG)
|
||||||
std::string Event::demangle (const char* name) {
|
std::string Event::demangle (const char* name) {
|
||||||
//see:
|
//see:
|
||||||
|
|
|
@ -37,6 +37,7 @@ protected:
|
||||||
void set_exception (std::exception_ptr ptr);
|
void set_exception (std::exception_ptr ptr);
|
||||||
|
|
||||||
template <typename EV> void stop_ifn(EV& eve);
|
template <typename EV> void stop_ifn(EV& eve);
|
||||||
|
bool is_stopped() const noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void on_loop_stopping_local() noexcept;
|
void on_loop_stopping_local() noexcept;
|
||||||
|
|
|
@ -47,20 +47,22 @@ struct Eventia::LocalData {
|
||||||
explicit LocalData(Eventia* eventia);
|
explicit LocalData(Eventia* eventia);
|
||||||
|
|
||||||
std::mutex ev_mutex;
|
std::mutex ev_mutex;
|
||||||
|
std::mutex exception_mutex;
|
||||||
boost::container::flat_map<unsigned int, std::function<void()>> stop_cbs;
|
boost::container::flat_map<unsigned int, std::function<void()>> stop_cbs;
|
||||||
ev::async async;
|
ev::async async;
|
||||||
ev::default_loop loop;
|
ev::default_loop loop;
|
||||||
Context context;
|
Context context;
|
||||||
std::promise<void> loop_end_promise;
|
std::promise<void> loop_end_promise;
|
||||||
std::future<void> loop_end;
|
std::future<void> loop_end;
|
||||||
std::atomic_bool mark_loop_end;
|
std::atomic_bool errored_out;
|
||||||
|
std::exception_ptr last_exception;
|
||||||
};
|
};
|
||||||
|
|
||||||
Eventia::LocalData::LocalData(Eventia* eventia) :
|
Eventia::LocalData::LocalData(Eventia* eventia) :
|
||||||
loop(ev::AUTO),
|
loop(ev::AUTO),
|
||||||
context{ &loop, &ev_mutex, &async, eventia },
|
context{ &loop, &ev_mutex, &async, eventia },
|
||||||
loop_end(loop_end_promise.get_future()),
|
loop_end(loop_end_promise.get_future()),
|
||||||
mark_loop_end(true)
|
errored_out(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,57 +71,64 @@ Eventia::Eventia() :
|
||||||
{
|
{
|
||||||
assert(nullptr == ev_userdata(m_local->loop));
|
assert(nullptr == ev_userdata(m_local->loop));
|
||||||
m_local->async.set(m_local->loop);
|
m_local->async.set(m_local->loop);
|
||||||
m_local->async.set<Eventia, &Eventia::do_nothing>(this);
|
m_local->async.set<Eventia, &Eventia::on_async>(this);
|
||||||
m_local->async.start();
|
m_local->async.start();
|
||||||
ev_set_userdata(m_local->loop, &m_local->context);
|
ev_set_userdata(m_local->loop, &m_local->context);
|
||||||
ev_set_loop_release_cb(m_local->loop, &eve::unlock_mutex_libev, &eve::lock_mutex_libev);
|
ev_set_loop_release_cb(m_local->loop, &eve::unlock_mutex_libev, &eve::lock_mutex_libev);
|
||||||
}
|
}
|
||||||
|
|
||||||
Eventia::~Eventia() noexcept {
|
Eventia::~Eventia() noexcept = default;
|
||||||
try {
|
|
||||||
this->stop();
|
|
||||||
this->wait();
|
|
||||||
}
|
|
||||||
catch (std::runtime_error& err) {
|
|
||||||
std::cerr << "Exception in main ev loop: " << err.what() << '\n';
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
std::cerr << "Unknown exception in main ev loop\n";
|
|
||||||
assert(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::function<void()> Eventia::event_functor() {
|
std::function<void()> Eventia::event_functor() {
|
||||||
return std::function<void()>([this]() {
|
return std::function<void()>([this]() {
|
||||||
m_local->mark_loop_end = true;
|
m_local->errored_out = false;
|
||||||
|
m_local->last_exception = nullptr;
|
||||||
std::unique_lock<std::mutex> lock(m_local->ev_mutex);
|
std::unique_lock<std::mutex> lock(m_local->ev_mutex);
|
||||||
m_local->loop.run(0);
|
m_local->loop.run(0);
|
||||||
#if !defined(NDEBUG)
|
#if !defined(NDEBUG)
|
||||||
std::cout << "ev loop stopped\n";
|
std::cout << "ev loop stopped\n";
|
||||||
#endif
|
#endif
|
||||||
if (m_local->mark_loop_end)
|
if (not m_local->errored_out) {
|
||||||
m_local->loop_end_promise.set_value();
|
m_local->loop_end_promise.set_value();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::unique_lock lock(m_local->exception_mutex);
|
||||||
|
m_local->loop_end_promise.set_exception(m_local->last_exception);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Eventia::do_nothing() {
|
void Eventia::stop() noexcept {
|
||||||
}
|
|
||||||
|
|
||||||
void Eventia::stop() {
|
|
||||||
#if !defined(NDEBUG)
|
#if !defined(NDEBUG)
|
||||||
std::cout << "Stopping Eventia (" << m_local->stop_cbs.size() << " observers)...\n";
|
try {
|
||||||
|
std::cout << "Stopping Eventia (" << m_local->stop_cbs.size() << " observers)...\n";
|
||||||
|
}
|
||||||
|
catch (...) {}
|
||||||
#endif
|
#endif
|
||||||
for (auto& stop_cb : m_local->stop_cbs) {
|
for (auto& stop_cb : m_local->stop_cbs) {
|
||||||
stop_cb.second();
|
stop_cb.second();
|
||||||
}
|
}
|
||||||
m_local->async.send();
|
|
||||||
m_local->async.stop();
|
|
||||||
m_local->loop.break_loop(ev::ALL);
|
m_local->loop.break_loop(ev::ALL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Eventia::wait() {
|
void Eventia::wait() {
|
||||||
if (m_local->loop_end.valid())
|
m_local->loop_end.get();
|
||||||
m_local->loop_end.get();
|
}
|
||||||
|
|
||||||
|
void Eventia::set_exception (std::exception_ptr ptr) noexcept {
|
||||||
|
try {
|
||||||
|
std::unique_lock lock(m_local->exception_mutex);
|
||||||
|
if (m_local->errored_out)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_local->last_exception = ptr;
|
||||||
|
m_local->errored_out = true;
|
||||||
|
m_local->async.send();
|
||||||
|
}
|
||||||
|
catch (const std::system_error& err) {
|
||||||
|
std::cerr << "Unexpected exception in Eventia::set_exception(), this is bad! Exception message: " <<
|
||||||
|
err.what() << '\n';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Eventia::lock_mutex_libev() noexcept {
|
void Eventia::lock_mutex_libev() noexcept {
|
||||||
|
@ -136,16 +145,17 @@ void Eventia::unlock_mutex_libev() noexcept {
|
||||||
m_local->ev_mutex.unlock();
|
m_local->ev_mutex.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Eventia::set_exception (std::exception_ptr ptr) {
|
|
||||||
m_local->mark_loop_end = false;
|
|
||||||
this->stop();
|
|
||||||
m_local->loop_end_promise.set_exception(ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Context& Eventia::context() {
|
const Context& Eventia::context() {
|
||||||
return m_local->context;
|
return m_local->context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Eventia::on_async() {
|
||||||
|
if (m_local->errored_out) {
|
||||||
|
m_local->async.stop();
|
||||||
|
this->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unsigned int Eventia::register_stop_callback (std::function<void()>&& func) {
|
unsigned int Eventia::register_stop_callback (std::function<void()>&& func) {
|
||||||
const unsigned int id = static_cast<unsigned int>(m_local->stop_cbs.size());
|
const unsigned int id = static_cast<unsigned int>(m_local->stop_cbs.size());
|
||||||
m_local->stop_cbs.insert(std::make_pair(id, std::move(func)));
|
m_local->stop_cbs.insert(std::make_pair(id, std::move(func)));
|
||||||
|
|
|
@ -33,17 +33,15 @@ public:
|
||||||
~Eventia() noexcept;
|
~Eventia() noexcept;
|
||||||
|
|
||||||
std::function<void()> event_functor();
|
std::function<void()> event_functor();
|
||||||
void do_nothing();
|
|
||||||
|
|
||||||
template <typename T, typename... Args>
|
template <typename T, typename... Args>
|
||||||
T make_event(Args&&... args) {
|
T make_event(Args&&... args) {
|
||||||
return T{this->context(), std::forward<Args>(args)...};
|
return T{this->context(), std::forward<Args>(args)...};
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop();
|
void stop() noexcept;
|
||||||
void wait();
|
void wait();
|
||||||
|
void set_exception (std::exception_ptr ptr) noexcept;
|
||||||
void set_exception (std::exception_ptr ptr);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct LocalData;
|
struct LocalData;
|
||||||
|
@ -51,6 +49,7 @@ private:
|
||||||
void lock_mutex_libev() noexcept;
|
void lock_mutex_libev() noexcept;
|
||||||
void unlock_mutex_libev() noexcept;
|
void unlock_mutex_libev() noexcept;
|
||||||
const Context& context();
|
const Context& context();
|
||||||
|
void on_async();
|
||||||
|
|
||||||
unsigned int register_stop_callback (std::function<void()>&& cb);
|
unsigned int register_stop_callback (std::function<void()>&& cb);
|
||||||
void unregister_stop_callback (unsigned int id) noexcept;
|
void unregister_stop_callback (unsigned int id) noexcept;
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#if !defined(NDEBUG)
|
#if !defined(NDEBUG)
|
||||||
# include <iostream>
|
# include <iostream>
|
||||||
#endif
|
#endif
|
||||||
|
#include <exception>
|
||||||
|
|
||||||
namespace eve {
|
namespace eve {
|
||||||
|
|
||||||
|
@ -64,17 +65,22 @@ Timer::~Timer() noexcept {
|
||||||
Timer::on_loop_stopping();
|
Timer::on_loop_stopping();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Timer::on_timer_ev() {
|
void Timer::on_timer_ev() noexcept {
|
||||||
double& elapsed = m_local->elapsed;
|
try {
|
||||||
const double& timer_value = m_local->timer_value;
|
double& elapsed = m_local->elapsed;
|
||||||
const double& timeout = m_local->timeout;
|
const double& timer_value = m_local->timer_value;
|
||||||
|
const double& timeout = m_local->timeout;
|
||||||
|
|
||||||
elapsed += timer_value;
|
elapsed += timer_value;
|
||||||
if (elapsed >= timeout) {
|
if (elapsed >= timeout) {
|
||||||
this->on_timer();
|
this->on_timer();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_local->timer.start(std::min(timeout - elapsed, g_timer_max), 0.0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
catch (...) {
|
||||||
m_local->timer.start(std::min(timeout - elapsed, g_timer_max), 0.0);
|
set_exception(std::current_exception());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +90,13 @@ void Timer::set_timer (double delay) {
|
||||||
m_local->timeout = delay;
|
m_local->timeout = delay;
|
||||||
m_local->timer_value = std::min(g_timer_max, m_local->timeout);
|
m_local->timer_value = std::min(g_timer_max, m_local->timeout);
|
||||||
|
|
||||||
|
if (this->is_stopped()) {
|
||||||
|
#if !defined(NDEBUG)
|
||||||
|
std::cout << "Timer::set_timer(" << delay << ") refusing to act because underlying event was stopped\n";
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
m_local->timer.stop();
|
m_local->timer.stop();
|
||||||
ev_now_update(*m_local->context.loop);
|
ev_now_update(*m_local->context.loop);
|
||||||
m_local->timer.start(m_local->timer_value, 0.0);
|
m_local->timer.start(m_local->timer_value, 0.0);
|
||||||
|
|
|
@ -41,7 +41,7 @@ protected:
|
||||||
private:
|
private:
|
||||||
struct LocalData;
|
struct LocalData;
|
||||||
|
|
||||||
void on_timer_ev();
|
void on_timer_ev() noexcept;
|
||||||
virtual void on_loop_stopping() noexcept override;
|
virtual void on_loop_stopping() noexcept override;
|
||||||
|
|
||||||
std::unique_ptr<LocalData> m_local;
|
std::unique_ptr<LocalData> m_local;
|
||||||
|
|
|
@ -37,6 +37,10 @@ EventiaThreadPool::EventiaThreadPool (eve::Eventia* eventia, std::uint32_t num_t
|
||||||
|
|
||||||
EventiaThreadPool::~EventiaThreadPool() = default;
|
EventiaThreadPool::~EventiaThreadPool() = default;
|
||||||
|
|
||||||
|
void EventiaThreadPool::wait() noexcept {
|
||||||
|
this->destroy();
|
||||||
|
}
|
||||||
|
|
||||||
void EventiaThreadPool::set_exception (std::exception_ptr ptr) {
|
void EventiaThreadPool::set_exception (std::exception_ptr ptr) {
|
||||||
m_eventia->set_exception(ptr);
|
m_eventia->set_exception(ptr);
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,8 @@ public:
|
||||||
template <typename Func, typename... Args>
|
template <typename Func, typename... Args>
|
||||||
auto submit(Func&& func, Args&&... args);
|
auto submit(Func&& func, Args&&... args);
|
||||||
|
|
||||||
|
void wait() noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void set_exception (std::exception_ptr ptr);
|
void set_exception (std::exception_ptr ptr);
|
||||||
|
|
||||||
|
@ -63,7 +65,7 @@ auto EventiaThreadPool::submit(Func&& func, Args&&... args) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
catch (...) {
|
catch (...) {
|
||||||
roar11::ThreadPool::destroy();
|
roar11::ThreadPool::invalidate();
|
||||||
this->set_exception(std::current_exception());
|
this->set_exception(std::current_exception());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -67,7 +67,6 @@ void test(oro::Api* api, oro::OriginsDB* db, const AppConfig& app_conf) {
|
||||||
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());
|
||||||
pool.submit(worker.event_functor());
|
|
||||||
|
|
||||||
const double ed = static_cast<double>(app_conf.fetch_extra_delay());
|
const double ed = static_cast<double>(app_conf.fetch_extra_delay());
|
||||||
|
|
||||||
|
@ -85,7 +84,14 @@ void test(oro::Api* api, oro::OriginsDB* db, const AppConfig& app_conf) {
|
||||||
auto timer_shops = worker.make_event<TimerShops>(TSet{w3, 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);
|
auto timer_creat = worker.make_event<TimerCreators>(TSet{w4, ed, sf}, &pool, api, db);
|
||||||
|
|
||||||
worker.wait();
|
try {
|
||||||
|
pool.submit(worker.event_functor());
|
||||||
|
worker.wait();
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
pool.wait();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
#if !defined(NDEBUG)
|
#if !defined(NDEBUG)
|
||||||
std::cout << "all tasks completed\n";
|
std::cout << "all tasks completed\n";
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -184,7 +184,7 @@ namespace roar11
|
||||||
return submit(std::move(boundTask));
|
return submit(std::move(boundTask));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/**
|
/**
|
||||||
* Invalidates the queue and joins all running threads.
|
* Invalidates the queue and joins all running threads.
|
||||||
*/
|
*/
|
||||||
|
@ -195,6 +195,11 @@ namespace roar11
|
||||||
join();
|
join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void invalidate() noexcept
|
||||||
|
{
|
||||||
|
m_workQueue.invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Constantly running function each thread uses to acquire work items from the queue.
|
* Constantly running function each thread uses to acquire work items from the queue.
|
||||||
|
|
|
@ -76,8 +76,11 @@ namespace roar11
|
||||||
void push(T value)
|
void push(T value)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock{m_mutex};
|
std::lock_guard<std::mutex> lock{m_mutex};
|
||||||
m_queue.push(std::move(value));
|
if (m_valid)
|
||||||
m_condition.notify_one();
|
{
|
||||||
|
m_queue.push(std::move(value));
|
||||||
|
m_condition.notify_one();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -99,7 +99,7 @@ inline void TimerOroApi<Op>::fetch_data (oro::SourceFormat store_mode) {
|
||||||
//server received too many requests, give up for now
|
//server received too many requests, give up for now
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw err;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#elif defined(OROTOOL_WITH_NAP)
|
#elif defined(OROTOOL_WITH_NAP)
|
||||||
|
@ -108,13 +108,10 @@ inline void TimerOroApi<Op>::fetch_data (oro::SourceFormat store_mode) {
|
||||||
if (429 == status_code) {
|
if (429 == status_code) {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this->set_exception(std::current_exception());
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
catch (...) {
|
|
||||||
this->set_exception(std::current_exception());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (429 == status_code) {
|
if (429 == status_code) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue