I have a class that contains a boost::signals2::scoped_connection as a data member. This connection contains a slot which, when triggered, will print another data member in the class to stdout:
struct A {
A(boost::signals2::signal<void()>& connection)
: scoped_connection_{connection.connect([this]() {
std::cout << foo_ << std::endl;
})} {}
std::string foo_{"foo"};
boost::signals2::scoped_connection scoped_connection_;
};
This code can segfault in a multi-threaded scenario if a thread fires the signal (hence executing the lambda), but another thread destroys the object at the same time:
using Signal = boost::signals2::signal<void()>;
struct A {
A(Signal& connection)
: scoped_connection_{connection.connect([this]() {
std::this_thread::sleep_for(std::chrono::seconds(5));
// foo is destroyed at this point...
std::cout << foo_ << std::endl; // segfault
})} {}
std::string foo_{"Kaboom"};
boost::signals2::scoped_connection scoped_connection_;
};
int main() {
Signal sig;
auto a = std::make_unique<A>(sig);
std::thread t([&sig]() { sig(); });
// give the thread some time to start and signal the slots
std::this_thread::sleep_for(std::chrono::seconds(1));
// destroy a as the callback is executing...
a.reset();
t.join();
return 0;
}
This code segfaults because the destruction of scoped_connection is not synchronized with the execution of the slot's callback. In other words scoped_connection can be destroyed even if the callback is currently running.
This was surprising behavior for me, I would have expected scoped_connection's destructor to block execution until the callback has finished.
Now I'm faced with the challenge of making above's code thread-safe by synchronizing destruction with callback execution and haven't been able to find a satisfactory solution. Maybe some of you can shed some light of a possible approach I can try out.
The
scoped_connection's release only synchronizes on invocation. This means that manipulating the signal doesn't create a data race. Whatever you share in the handler itself is your own responsibility.So you could play fast and loose and "detect" that the slot was disconnected:
However that's still intrinsically racy (though it will be very rare to hit, and probably never given the example timings).
Better, take shared ownership of the shared resource:
Live On Coliru
Printing
Of course the current example could be much simplified: https://coliru.stacked-crooked.com/a/8faf77323a390921 but I'm assuming your code represents more interesting code