In the following example, the method foo() gets called, where it acquires ownership of a mutex, and locks it. It then calls check(), which acquires ownership, but assumes that the mutex is already locked, and so simply adopts it using std::adopt_lock.
But when check() finishes, the mutex gets unlocked. So when foo() continues, the section I was trying to guard is actually no longer guarded.
#include <mutex>
static std::mutex sessionLock;
bool check();
void foo() {
std::lock_guard<std::mutex> guard(sessionLock);
if (check()) {
// Do transaction
// Wait... the mutex is unlocked here!
}
}
bool check() {
std::lock_guard<std::mutex> guard(sessionLock, std::adopt_lock);
// Critical section
return true;
}
int main() {
foo();
return 0;
}
I find this behaviour very unintuitive. If a sub-method decides to take ownership of a lock using std::adopt_lock (ie. it doesn't call lock()), shouldn't it also release ownership without calling unlock()? The standard says otherwise, but I'm curious if this was an oversight or if there is a particular reason this is expected.
This could be rewritten using std::recursive_mutex, though in this case where a regular std::mutex is used, is there a proper way inside check() to ensure its critical section is guarded?