user1011201
user1011201

Reputation: 83

C++ Mutex for Windows

I'm working on a C++ project for Windows and need a good mutex implementation to synchronize b/w processes (i.e. a wrap for the winapi). I'm currently using boost::interprocess::named_mutex, however I noticed that if one of the processes crashes - the mutex remains locked forever and ever (even after reboot!). Googling this matter confirmed that this is in fact the behavior.

Quite frankly, I don't see how this behavior is acceptable under any project. Bugs exist, processes crash - this just means that a tiny bug may cause the system to never work again... Unacceptable!

So my questions are:

  1. Can anyone recommend of a different implementation that has the "normal" behavior (i.e. - mutex becomes abandoned on a crash, and after system reboot will "reset" to work as normal)? Cross platform is obviously better but definitely not a must.

  2. Out of curiosity - how's it possible that boost lets this behavior be??

Thanks!

Upvotes: 3

Views: 1581

Answers (3)

P. Saladin
P. Saladin

Reputation: 240

I rolled my own mutex for this, since I did not want to add a dependency to some heavyweight library just for this one functionality. It is used in production code and covered by our unit tests - please let me know if you find any issues with it.

It's a Windows-only solution though.

Usage example:

NamedSystemMutex m{ L"MyMutex" };
std::lock_guard<NamedSystemMutex> lock{ m };

Header NamedSystemMutex.h:

#pragma once

#include <string>
#include <windows.h> // Just needed for HANDLE

// Provides a system-wide, recursive, named lock. 
// This class satisfies requirements of C++11 concept "Lockable",  i.e. can be (and should be) used with unique_lock etc.
class NamedSystemMutex
{
public:
    explicit NamedSystemMutex(const std::wstring& name);
    ~NamedSystemMutex();

    // Moveable, not copyable
    NamedSystemMutex(const NamedSystemMutex& other) = delete;
    NamedSystemMutex(NamedSystemMutex&& other) = default;
    NamedSystemMutex& operator=(const NamedSystemMutex& other) = delete;
    NamedSystemMutex& operator=(NamedSystemMutex&& other) = default;

    void lock();
    void unlock();
    bool try_lock();

private:
    HANDLE handle_{};
};

Implementation:

#include "NamedSystemMutex.h"
#include <stdexcept>

NamedSystemMutex::NamedSystemMutex(const std::wstring& name) {
    handle_ = CreateMutexW(nullptr, FALSE, name.c_str());
    if (handle_ == NULL) {
        throw std::runtime_error("Creation of mutex failed");
    }
}

NamedSystemMutex::~NamedSystemMutex() {
    const BOOL result = CloseHandle(handle_);
    if (result == FALSE) {
        // Error: Failed to close mutex handle (Error ignored since we are in destructor)
    }
}

void NamedSystemMutex::lock() {
    const auto result = WaitForSingleObject(handle_, INFINITE);
    if (result == WAIT_ABANDONED) {
        // Warning: Lock obtained, but on an abandoned mutex (was not correctly released before, e.g. due to a crash)
    }
    else if (result != WAIT_OBJECT_0) {
        throw std::runtime_error("Failed to acquire lock");
    }
}

void NamedSystemMutex::unlock() {
    const BOOL result = ReleaseMutex(handle_);
    if (result == FALSE) {
        throw std::runtime_error("Failed to release lock: calling thread does not own the mutex");
    }
}

bool NamedSystemMutex::try_lock() {
    const auto result = WaitForSingleObject(handle_, 0);
    if (result == WAIT_TIMEOUT) {
        return false;
    }
    if (result == WAIT_OBJECT_0) {
        return true;
    }
    throw std::runtime_error("Failed to acquire lock");
}

Upvotes: 0

Pablo
Pablo

Reputation: 8644

Take a look at StlSoft's process_mutex. Crossplatform (use the platformstl component and not the OS-specifi winstl or unixstl versions) and header only.

Upvotes: 0

SteveL
SteveL

Reputation: 1821

The documentation for boost::interprocess explains the persistence of the different classes. In the case of named_mutex it is listed as Kernel or Filesystem. From this we can presume that the Windows implementation is uses the Filesystem which is why it persists after you reboot the system.

Depending on your situation you might be able to write your own file to disk for each process and then remove it if the process safely closes. When the process is restarted if the fie exists there must have been an unclean shutdown and you could use the boost::interprocess::named_mutex::remove static function to remove the mutex.

Upvotes: 4

Related Questions