Commit 7dde71bb authored by Dominik Charousset's avatar Dominik Charousset

Enable actors to set custom exception handler

parent 752bf425
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <cstdint> #include <cstdint>
#include <exception>
#include <type_traits> #include <type_traits>
#include "caf/node_id.hpp" #include "caf/node_id.hpp"
...@@ -97,9 +98,6 @@ class abstract_actor : public abstract_channel { ...@@ -97,9 +98,6 @@ class abstract_actor : public abstract_channel {
void actor_exited(abstract_actor*, uint32_t reason) { void actor_exited(abstract_actor*, uint32_t reason) {
m_functor(reason); m_functor(reason);
} }
bool matches(const attachable::token&) {
return false;
}
}; };
attach(attachable_ptr{new functor_attachable(std::move(f))}); attach(attachable_ptr{new functor_attachable(std::move(f))});
} }
...@@ -274,6 +272,13 @@ class abstract_actor : public abstract_channel { ...@@ -274,6 +272,13 @@ class abstract_actor : public abstract_channel {
bool stop_on_first_hit = false, bool stop_on_first_hit = false,
bool dry_run = false); bool dry_run = false);
/** @cond PRIVATE */
/*
* Tries to run a custom exception handler for `eptr`.
*/
optional<uint32_t> handle(const std::exception_ptr& eptr);
/** @endcond */
// cannot be changed after construction // cannot be changed after construction
const actor_id m_id; const actor_id m_id;
......
...@@ -23,6 +23,9 @@ ...@@ -23,6 +23,9 @@
#include <memory> #include <memory>
#include <cstdint> #include <cstdint>
#include <typeinfo> #include <typeinfo>
#include <exception>
#include "caf/optional.hpp"
namespace caf { namespace caf {
...@@ -61,16 +64,25 @@ class attachable { ...@@ -61,16 +64,25 @@ class attachable {
virtual ~attachable(); virtual ~attachable();
/**
* Executed if the actor did not handle an exception and must
* not return `none` if this attachable did handle `eptr`.
* Note that the first handler to handle `eptr` "wins" and no other
* handler will be invoked.
* @returns The exit reason the actor should use.
*/
virtual optional<uint32_t> handle_exception(const std::exception_ptr& eptr);
/** /**
* Executed if the actor finished execution with given `reason`. * Executed if the actor finished execution with given `reason`.
* The default implementation does nothing. * The default implementation does nothing.
*/ */
virtual void actor_exited(abstract_actor* self, uint32_t reason) = 0; virtual void actor_exited(abstract_actor* self, uint32_t reason);
/** /**
* Returns `true` if `what` selects this instance, otherwise `false`. * Returns `true` if `what` selects this instance, otherwise `false`.
*/ */
virtual bool matches(const token& what) = 0; virtual bool matches(const token& what);
/** /**
* Returns `true` if `what` selects this instance, otherwise `false`. * Returns `true` if `what` selects this instance, otherwise `false`.
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include <atomic> #include <atomic>
#include <cstdint> #include <cstdint>
#include <exception>
#include <functional> #include <functional>
#include <forward_list> #include <forward_list>
...@@ -422,6 +423,24 @@ class local_actor : public extend<abstract_actor>::with<mixin::memory_cached> { ...@@ -422,6 +423,24 @@ class local_actor : public extend<abstract_actor>::with<mixin::memory_cached> {
on_sync_failure(fun); on_sync_failure(fun);
} }
/**
* Sets a custom exception handler for this actor. If multiple handlers are
* defined, only the functor that was added *last* is being executed.
*/
template <class F>
void set_exception_handler(F f) {
struct functor_attachable : attachable {
F m_functor;
functor_attachable(F arg) : m_functor(std::move(arg)) {
// nop
}
optional<uint32_t> handle_exception(const std::exception_ptr& eptr) {
return m_functor(eptr);
}
};
attach(attachable_ptr{new functor_attachable(std::move(f))});
}
/************************************************************************** /**************************************************************************
* here be dragons: end of public interface * * here be dragons: end of public interface *
**************************************************************************/ **************************************************************************/
......
...@@ -92,6 +92,7 @@ class event_based_resume { ...@@ -92,6 +92,7 @@ class event_based_resume {
CAF_REQUIRE(!d->is_initialized() CAF_REQUIRE(!d->is_initialized()
|| (!d->bhvr_stack().empty() || (!d->bhvr_stack().empty()
&& d->planned_exit_reason() == exit_reason::not_exited)); && d->planned_exit_reason() == exit_reason::not_exited));
std::exception_ptr eptr = nullptr;
try { try {
if (!d->is_initialized()) { if (!d->is_initialized()) {
CAF_LOG_DEBUG("initialize actor"); CAF_LOG_DEBUG("initialize actor");
...@@ -158,20 +159,29 @@ class event_based_resume { ...@@ -158,20 +159,29 @@ class event_based_resume {
} }
} }
catch (std::exception& e) { catch (std::exception& e) {
CAF_LOG_WARNING("actor died because of exception: " CAF_LOG_INFO("actor died because of an exception: "
<< detail::demangle(typeid(e)) << detail::demangle(typeid(e))
<< ", what() = " << e.what()); << ", what() = " << e.what());
if (d->exit_reason() == exit_reason::not_exited) { if (d->exit_reason() == exit_reason::not_exited) {
d->quit(exit_reason::unhandled_exception); d->quit(exit_reason::unhandled_exception);
} }
eptr = std::current_exception();
} }
catch (...) { catch (...) {
CAF_LOG_WARNING("actor died because of an unknown exception"); CAF_LOG_INFO("actor died because of an unknown exception");
if (d->exit_reason() == exit_reason::not_exited) { if (d->exit_reason() == exit_reason::not_exited) {
d->quit(exit_reason::unhandled_exception); d->quit(exit_reason::unhandled_exception);
} }
eptr = std::current_exception();
} }
if (!done_cb()) { if (eptr) {
auto opt_reason = d->handle(eptr);
if (opt_reason) {
// use exit reason defined by custom handler
d->planned_exit_reason(*opt_reason);
}
}
if (!actor_done()) {
// actor has been "revived", try running it again later // actor has been "revived", try running it again later
return resumable::resume_later; return resumable::resume_later;
} }
......
...@@ -32,6 +32,7 @@ class no_resume { ...@@ -32,6 +32,7 @@ class no_resume {
resumable::resume_result resume(execution_unit*, size_t) { resumable::resume_result resume(execution_unit*, size_t) {
CAF_LOG_TRACE(""); CAF_LOG_TRACE("");
uint32_t rsn = exit_reason::normal; uint32_t rsn = exit_reason::normal;
std::exception_ptr eptr = nullptr;
try { try {
this->act(); this->act();
} }
...@@ -40,6 +41,11 @@ class no_resume { ...@@ -40,6 +41,11 @@ class no_resume {
} }
catch (...) { catch (...) {
rsn = exit_reason::unhandled_exception; rsn = exit_reason::unhandled_exception;
eptr = std::current_exception();
}
if (eptr) {
auto opt_reason = this->handle(eptr);
rsn = *opt_reason;
} }
this->planned_exit_reason(rsn); this->planned_exit_reason(rsn);
try { try {
......
...@@ -225,4 +225,22 @@ std::set<std::string> abstract_actor::message_types() const { ...@@ -225,4 +225,22 @@ std::set<std::string> abstract_actor::message_types() const {
return std::set<std::string>{}; return std::set<std::string>{};
} }
optional<uint32_t> abstract_actor::handle(const std::exception_ptr& eptr) {
{ // lifetime scope of guard
guard_type guard{m_mtx};
for (auto i = m_attachables_head.get(); i != nullptr; i = i->next.get()) {
try {
auto result = i->handle_exception(eptr);
if (result) {
return *result;
}
}
catch (...) {
// ignore exceptions
}
}
}
return none;
}
} // namespace caf } // namespace caf
...@@ -30,4 +30,16 @@ attachable::token::token(const std::type_info& tinfo, const void* vptr) ...@@ -30,4 +30,16 @@ attachable::token::token(const std::type_info& tinfo, const void* vptr)
// nop // nop
} }
optional<uint32_t> attachable::handle_exception(const std::exception_ptr&) {
return none;
}
void attachable::actor_exited(abstract_actor*, uint32_t) {
// nop
}
bool attachable::matches(const token&) {
return false;
}
} // namespace caf } // namespace caf
...@@ -944,6 +944,66 @@ void test_constructor_attach() { ...@@ -944,6 +944,66 @@ void test_constructor_attach() {
anon_send(spawn<spawner>(), atom("die")); anon_send(spawn<spawner>(), atom("die"));
} }
class exception_testee : public event_based_actor {
public:
exception_testee() {
set_exception_handler([](const std::exception_ptr& eptr) -> optional<uint32_t> {
return exit_reason::user_defined + 2;
});
}
behavior make_behavior() override {
return {
others() >> [] {
throw std::runtime_error("whatever");
}
};
}
};
void test_custom_exception_handler() {
auto handler = [](const std::exception_ptr& eptr) -> optional<uint32_t> {
try {
std::rethrow_exception(eptr);
}
catch (std::runtime_error&) {
return exit_reason::user_defined;
}
catch (...) {
// "fall through"
}
return exit_reason::user_defined + 1;
};
scoped_actor self;
auto testee1 = self->spawn<monitored>([=](event_based_actor* eb_self) {
eb_self->set_exception_handler(handler);
throw std::runtime_error("ping");
});
auto testee2 = self->spawn<monitored>([=](event_based_actor* eb_self) {
eb_self->set_exception_handler(handler);
throw std::logic_error("pong");
});
auto testee3 = self->spawn<exception_testee, monitored>();
self->send(testee3, "foo");
// receive all down messages
auto i = 0;
self->receive_for(i, 3)(
[&](const down_msg& dm) {
if (dm.source == testee1) {
CAF_CHECK_EQUAL(dm.reason, exit_reason::user_defined);
}
else if (dm.source == testee2) {
CAF_CHECK_EQUAL(dm.reason, exit_reason::user_defined + 1);
}
else if (dm.source == testee3) {
CAF_CHECK_EQUAL(dm.reason, exit_reason::user_defined + 2);
}
else {
CAF_CHECK(false); // report error
}
}
);
}
} // namespace <anonymous> } // namespace <anonymous>
int main() { int main() {
...@@ -954,6 +1014,9 @@ int main() { ...@@ -954,6 +1014,9 @@ int main() {
spawn(counting_actor); spawn(counting_actor);
await_all_actors_done(); await_all_actors_done();
CAF_CHECKPOINT(); CAF_CHECKPOINT();
test_custom_exception_handler();
await_all_actors_done();
CAF_CHECKPOINT();
test_spawn(); test_spawn();
CAF_CHECKPOINT(); CAF_CHECKPOINT();
await_all_actors_done(); await_all_actors_done();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment