11

Assuming

  1. no undefined behaviour occurs,
  2. no deadlocks occur,
  3. mutexes are locked and unlocked in the correct order by the correct threads the correct number of times,
  4. non-recursive mutexes are not locked multiple times,
  5. locking recursive mutexes does not exceed the maximum level of ownership,
  6. no predicates passed to condition variables throw, and
  7. only clocks, time points, and durations provided by the standard library are used with the std:: mutexes and condition variables

is it guaranteed that operating on the different types of std:: mutexes and condition variables (other than on constructing them) does not throw any exceptions (especially of type std::system_error)?

For example, in case of methods like:

void MyClass::setVariable() {
    std::lock_guard<std::mutex> const guard(m_mutex);
    m_var = 42; // m_var is of type int
    m_conditionVariable.notify_all();
}

void MyClass::waitVariable() {
    std::unique_lock<std::mutex> lock(m_mutex);
    m_conditionVariable.wait(lock, [this]() noexcept { return m_var == 42; });
}

Is it safe to assume noexcept or should one write some try-catch blocks around the callsites? Or are there any caveats?

Please consider all types of mutexes and condition variables in C++11, C++14 and later.

Community
  • 1
  • 1
jotik
  • 17,044
  • 13
  • 58
  • 123
  • you could trace through the futex() implementation on linux to look for conditions where it fails: https://github.com/torvalds/linux/blob/master/kernel/futex.c#L3147 – Arvid May 13 '16 at 13:45
  • 1
    `std::condition_variable::wait()` is changed to `noexcept` in C++14. It now just calls `std::terminate()` when reacquiring the lock fails. You might want to consider that. – TFM May 16 '16 at 17:09
  • @TFM I don't agree. Can you quote some documentation? – Richard Hodges May 16 '16 at 21:17

2 Answers2

9

Short answer: No (sorry)

Any of these operations will throw std::system_error if the underlying synchronisation object fails to perform its operation.

This is because correct operation of synchronisation primitives depends on:

  1. available system resources.

  2. some other part of the program not invalidating the primitive

Although in fairness, if (1) is happening it's probably time to redesign the application or run it on a less-loaded machine.

And if (2) is happening, the program is not logically consistent.

That being said,

or should one write some try-catch blocks around the callsites?

Also no.

You should write try/catch blocks under the following conditions:

  1. Where the program is in a position to do something useful about the error condition (such as repairing it or asking the user if he wants to try again)

  2. You would like to add some information to the error and re-throw it in order to provide a diagnostic breadcrumb trail (nested exceptions, for example)

  3. You wish to log the failure and carry on.

Otherwise, the whole point of c++ exception handling is that you allow RAII to take care of resource reacquisition and allow the exception to flow up the call stack until is finds a handler that wants to handle it.

example of creating a breadcrumb trail:

void wait_for_object()
try
{
    _x.wait();  // let's say it throws a system_error on a loaded system
}
catch(...)
{
  std::throw_with_nested(std::runtime_error(__func__));
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • I think (2) was already considered by my assumptions 1 and 3. But can you give a real-life example of why (1) might happen, i.e. system resources run out? – jotik May 13 '16 at 10:06
  • 1
    @jotik I can see where you're going with this thinking. Essentially you're asking "are there any circumstances under which I can ignore the return codes of system synchronisation functions"? In reality almost everyone does almost all the time, because the circumstances under which they will fail are (very) rare in practice. Nevertheless, the standard says that these functions may throw an exception, and library implementors may therefore throw exceptions for reasons that are best understood by them. Therefore we must assume that an exception is possible. – Richard Hodges May 13 '16 at 10:12
  • 1
    @jotikThat's not to say that every exception needs to be handled. If it's very rare, and will only happen on a loaded system (in which other parts of the program may well be failing), it's almost always reasonable to catch the exception in main (or thread_main), report it and abort. – Richard Hodges May 13 '16 at 10:13
  • Maybe it were better to use `noexcept` and let `std::terminate()` or `std::unexpected()` report these instead of allowing these to propagate to `main()`? This might lose some context, but would allow to write more efficient code. – jotik May 16 '16 at 13:38
  • @jotik the actual performance cost of including exception handling is minimal when the non-exceptional code path is taken. The time cost of using a synchronisation primitive is orders of magnitude more. It really isn't worth worrying about. – Richard Hodges May 16 '16 at 14:14
  • @jotik @Richard Hodges Like I noted in the other comment, as of C++14 `std::condition_variable::wait()` is `noexcept` and calls `std::terminate()` on any problem. I image the rationale behind this is that those error conditions happen rarely and are difficult to recover from: You will need some kind of inter-thread communication to pass the exception while the exception being that inter-thread communication is failing. – TFM May 16 '16 at 20:59
  • @TFM are you sure about that? It's not what I see in the documentation. `std::condition_variable::notify()` is noexcept, but not `wait()` – Richard Hodges May 16 '16 at 21:14
  • @RichardHodges I got it from here http://en.cppreference.com/w/cpp/thread/condition_variable/wait Of course, I don't know if that's correct. – TFM May 16 '16 at 21:57
  • 1
    @TFM just checked the latest standard draft § 30.5.1 here http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4527.pdf You're right in the case of `wait()` with no predicate. `wait(cv, pred)` can throw if the predicate throws. – Richard Hodges May 16 '16 at 22:13
2

Thank's to the link T.C. provided now I'd say yes — your code should be safe. Since in the future standard device_or_resource_busy will be removed and as the author of the issue says that this situation can't occur in any reasonable way then there are only 2 possibilities for lock to throw:

(13.1) — operation_not_permitted — if the thread does not have the privilege to perform the operation.

(13.2) — resource_deadlock_would_occur — if the implementation detects that a deadlock would occur.

And both of these situations are excluded by your preconditions. So your code should be safe to use noexcept.

ixSci
  • 13,100
  • 5
  • 45
  • 79
  • Can you provide any real-life examples of why or where a `device_or_resource_busy` might be thrown during the operations under discussion? – jotik May 13 '16 at 09:56
  • @jotik, I can't but it doesn't mean that it is not possible. – ixSci May 13 '16 at 10:01