Is there a way to have a boost mutex throw an exception on any waiting threads? I have a problem where an object is deleted but do to the nature of the software library it is possible threads are still waiting at a mutex within the object and a rather nasty exception is thrown when the mutex is closed. I guess I could use a multiple mutex counter but that could cause performance degradation. What I'd like to have happen is the mutex throw an exception on any threads that are waiting when it is closed so that the stack is unrolled. Is there a clean way to do this that is platform-independent?
Such a concept of a mutex that throws when it is destroyed seems innocuous enough, but when it comes time to implement it, it reveals a flaw in how you are thinking about mutexes.
Let's take a look at some example code to get an idea of the pitfalls of such an approach.
Note: Please do not use the code below, it will cause nothing but endless hours of torment and suffering debugging synchronization problems.
class throwing_mutex
{
private:
mutex m_;
condition_variable cv_;
bool destroyed_;
bool locked_;
public:
void lock()
{
std::unique_lock<std::mutex> lock(m_);
cv_.wait(lock, [&]() {return !locked_ || destroyed_;}); // Wait until the mutex is unlocked or destroyed.
if (destroyed_) throw runtime_error("The exception was terminated while waiting.");
locked_ = true;
}
void unlock()
{
std::unique_lock<std::mutex> lock(m_);
locked_ = false;
lock.unlock();
cv_.notify_one();
}
~throwing_mutex()
{
std::unique_lock<std::mutex> lock(m_);
destroyed_ = true;
lock.unlock();
cv_.notify_all(); // Let all waiters know we are dead.
}
};
Upon destruction, everyone waiting on the throwing_mutex
will have an exception thrown. But this opens up a pretty big race condition.
We've handled the case where everyone is waiting for the mutex -- they will safely throw. What we have not handled is the case where anyone is on their way to calling lock()
but not quite there yet. When they finally get to the point where they can call lock()
, the throwing_mutex
has already been destroyed. The bug we've just introduced by means of our flawed methodology is called use-after-free. If we are lucky, the error will present itself early and clearly, but sometimes we aren't so lucky and we will be tormented for hours or days. There is no way that our throwing_mutex
class can ever solve that problem and any code that would need such a class does not have well thought out ownership semantics.
So, how do we solve this problem if it isn't by means of a mutex that throws? We fix the lifetime of the mutex and the object[s] that are locked by it.
Presumably, this is mutex is a member of a class. If that is the case, it means delaying destruction until everyone who depends on the object is done with it. This is conveyed with the use of a shared_ptr
. Without getting into the nitty-gritty of ownership-semantics, that is the best this can be answered. Hopefully I've changed your way of thinking of the problem enough to stray you away from your original plan and toward something that will work more reliably.