Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add exception handling to signal emission #42

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.5)

project(pal_sigslot VERSION 1.2.2 LANGUAGES CXX)
project(pal_sigslot VERSION 2.0.0 LANGUAGES CXX)

### main project
set(SIGSLOT_MAIN_PROJECT OFF)
Expand Down
35 changes: 31 additions & 4 deletions include/sigslot/signal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -528,13 +528,29 @@ inline std::shared_ptr<B> make_shared(Arg && ... arg) {
}
#endif

using exception_vector = std::vector<std::exception_ptr>;

class signal_exception_wrapper final : public std::exception {
public:
explicit signal_exception_wrapper(exception_vector &&exceptions) : m_exceptions(
std::forward<exception_vector>(exceptions)) {
}

const exception_vector &get_exceptions() const { return m_exceptions; }

private:
exception_vector m_exceptions;
};

// Adapt a signal into a cheap function object, for easy signal chaining
template <typename SigT>
struct signal_wrapper {
template <typename... U>
void operator()(U && ...u) {
(*m_sig)(std::forward<U>(u)...);
exception_vector exceptions = (*m_sig)(std::forward<U>(u)...);
if (!exceptions.empty()) {
throw signal_exception_wrapper(std::move(exceptions));
}
}

SigT *m_sig{};
Expand Down Expand Up @@ -1212,20 +1228,31 @@ class signal_base final : public detail::cleanable {
* @param a... arguments to emit
*/
template <typename... U>
void operator()(U && ...a) {
std::vector<std::exception_ptr> operator()(U && ...a) {
if (m_block) {
return;
return {};
}

detail::exception_vector caught_exceptions;

// Reference to the slots to execute them out of the lock
// a copy may occur if another thread writes to it.
cow_copy_type<list_type, Lockable> ref = slots_reference();

for (const auto &group : detail::cow_read(ref)) {
for (const auto &s : group.slts) {
s->operator()(a...);
try {
s->operator()(a...);
} catch (const detail::signal_exception_wrapper &e) {
const auto &exceptions = e.get_exceptions();
caught_exceptions.insert(caught_exceptions.end(), exceptions.cbegin(), exceptions.cend());
} catch (...) {
caught_exceptions.push_back(std::current_exception());
}
}
}

return caught_exceptions;
}

/**
Expand Down
79 changes: 79 additions & 0 deletions test/signal-exceptions.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#include <cassert>
#include <sigslot/signal.hpp>

class TestingClass final {
public:
TestingClass(bool should_throw, std::atomic_int &cumulative) : m_should_throw(should_throw),
m_cumulative(cumulative) {
}

void function(int x) const {
m_cumulative += x;
if (m_should_throw) {
throw std::runtime_error("Throwing variant");
}
}

private:
bool m_should_throw;
std::atomic_int &m_cumulative;
};

void test_plain_exceptions() {
constexpr int k_emitted_value{3};

std::atomic_int cumulative{0};

TestingClass t1{true, cumulative};
TestingClass t2{false, cumulative};
TestingClass t3{true, cumulative};

sigslot::signal<int> signal;

signal.connect(&TestingClass::function, &t1, 1);
signal.connect(&TestingClass::function, &t2, 2);
signal.connect(&TestingClass::function, &t3, 3);

std::vector<std::exception_ptr> exceptions;
try {
exceptions = signal(k_emitted_value);
} catch (...) {
assert(!static_cast<bool>("Should not get exceptions outside signals!"));
}

assert(exceptions.size() == 2);
assert(cumulative == (signal.slot_count() * k_emitted_value));
}

void test_connected_signal_exceptions() {
constexpr int k_emitted_value{3};

std::atomic_int cumulative{0};
TestingClass tc{true, cumulative};

// Create two signals
sigslot::signal<int> signal1;
sigslot::signal<int> signal2;

// Connect the second signal with the first one
sigslot::connect(signal1, signal2);

// Connect the TestingClass function method to the second signal
signal2.connect(&TestingClass::function, &tc);

std::vector<std::exception_ptr> exceptions;
// Emit the first signal
try {
exceptions = signal1(k_emitted_value);
} catch (...) {
assert(!static_cast<bool>("Should not get exceptions outside signals!"));
}

assert(exceptions.size() == 1);
assert(cumulative == k_emitted_value);
}

int main() {
test_plain_exceptions();
test_connected_signal_exceptions();
}