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

Enable actors to set custom exception handler

parent 752bf425
......@@ -27,6 +27,7 @@
#include <string>
#include <vector>
#include <cstdint>
#include <exception>
#include <type_traits>
#include "caf/node_id.hpp"
......@@ -97,9 +98,6 @@ class abstract_actor : public abstract_channel {
void actor_exited(abstract_actor*, uint32_t reason) {
m_functor(reason);
}
bool matches(const attachable::token&) {
return false;
}
};
attach(attachable_ptr{new functor_attachable(std::move(f))});
}
......@@ -274,6 +272,13 @@ class abstract_actor : public abstract_channel {
bool stop_on_first_hit = 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
const actor_id m_id;
......
......@@ -23,6 +23,9 @@
#include <memory>
#include <cstdint>
#include <typeinfo>
#include <exception>
#include "caf/optional.hpp"
namespace caf {
......@@ -61,16 +64,25 @@ class 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`.
* 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`.
*/
virtual bool matches(const token& what) = 0;
virtual bool matches(const token& what);
/**
* Returns `true` if `what` selects this instance, otherwise `false`.
......
......@@ -22,6 +22,7 @@
#include <atomic>
#include <cstdint>
#include <exception>
#include <functional>
#include <forward_list>
......@@ -422,6 +423,24 @@ class local_actor : public extend<abstract_actor>::with<mixin::memory_cached> {
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 *
**************************************************************************/
......
......@@ -92,6 +92,7 @@ class event_based_resume {
CAF_REQUIRE(!d->is_initialized()
|| (!d->bhvr_stack().empty()
&& d->planned_exit_reason() == exit_reason::not_exited));
std::exception_ptr eptr = nullptr;
try {
if (!d->is_initialized()) {
CAF_LOG_DEBUG("initialize actor");
......@@ -158,20 +159,29 @@ class event_based_resume {
}
}
catch (std::exception& e) {
CAF_LOG_WARNING("actor died because of exception: "
<< detail::demangle(typeid(e))
<< ", what() = " << e.what());
CAF_LOG_INFO("actor died because of an exception: "
<< detail::demangle(typeid(e))
<< ", what() = " << e.what());
if (d->exit_reason() == exit_reason::not_exited) {
d->quit(exit_reason::unhandled_exception);
}
eptr = std::current_exception();
}
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) {
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
return resumable::resume_later;
}
......
......@@ -32,6 +32,7 @@ class no_resume {
resumable::resume_result resume(execution_unit*, size_t) {
CAF_LOG_TRACE("");
uint32_t rsn = exit_reason::normal;
std::exception_ptr eptr = nullptr;
try {
this->act();
}
......@@ -40,6 +41,11 @@ class no_resume {
}
catch (...) {
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);
try {
......
......@@ -225,4 +225,22 @@ std::set<std::string> abstract_actor::message_types() const {
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
......@@ -30,4 +30,16 @@ attachable::token::token(const std::type_info& tinfo, const void* vptr)
// 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
......@@ -944,6 +944,66 @@ void test_constructor_attach() {
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>
int main() {
......@@ -954,6 +1014,9 @@ int main() {
spawn(counting_actor);
await_all_actors_done();
CAF_CHECKPOINT();
test_custom_exception_handler();
await_all_actors_done();
CAF_CHECKPOINT();
test_spawn();
CAF_CHECKPOINT();
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