0X00000120

ERROR_NOT_OWNER (0x120): Release Mutex Not Owned Fix

Windows Errors Intermediate 👁 1 views 📅 May 31, 2026

This error means your program tried to release a mutex it doesn't own. It's a threading bug. Here's how to find and fix the culprit.

Fix 1: The Thread Doesn't Actually Own the Mutex

This is the most common cause. The thread calling ReleaseMutex was never the one that called WaitForSingleObject (or WaitForMultipleObjects) on that mutex. I've seen this trip up devs who pass a mutex handle between threads without checking ownership. A mutex in Windows is owned by the thread that acquired it — no other thread can release it, period.

How to verify ownership

  1. In C++, use GetLastError() after ReleaseMutex to catch this error.
  2. In .NET, wrap Mutex.ReleaseMutex() in a try-catch for AbandonedMutexException or ApplicationException — but the error shows up as an UnauthorizedAccessException on some frameworks.
  3. If using Win32 directly, check that the thread ID calling ReleaseMutex matches the thread ID that acquired it. You can store the owning thread ID when you first acquire the mutex.

I once spent two hours debugging a server process where a background thread was releasing a mutex that the main thread held. The fix: make sure only the acquiring thread calls release, and if you need cross-thread signaling, use an event or semaphore instead.

Fix 2: You're Releasing the Same Mutex Twice

This one's sneaky. Your code calls ReleaseMutex once correctly, then accidentally calls it again — maybe in a cleanup routine or error handler. The second call fails with ERROR_NOT_OWNER because after the first release, the mutex is no longer owned by any thread. This is especially common in C++ destructors or .NET Dispose methods that aren't guarded by a check.

How to prevent double release

  1. Set the mutex handle to NULL or INVALID_HANDLE_VALUE after releasing it.
  2. In .NET, use a bool _isOwned flag. Only call ReleaseMutex() if _isOwned is true, then set it false.
  3. Check the return value of ReleaseMutex — if it returns FALSE and GetLastError() is ERROR_NOT_OWNER, log the call stack. I've seen this happen in complex callback chains where multiple paths call release.

Here's a C++ snippet that avoids double release:

if (hMutex != NULL) {
    if (!ReleaseMutex(hMutex)) {
        DWORD err = GetLastError();
        if (err == ERROR_NOT_OWNER) {
            // Log: mutex already released or not owned
        }
    }
    hMutex = NULL; // critical: prevent reuse
}

Fix 3: Mutex Abandonment — The Thread Exited Without Releasing

Less common but nasty: a thread acquires a mutex and then crashes or terminates without releasing it. Windows marks the mutex as abandoned. The next thread that waits on it gets WAIT_ABANDONED — but if that new thread tries to release the mutex, it gets ERROR_NOT_OWNER. Why? Because the mutex's ownership was broken by the crash. The new thread can now hold the mutex, but the system expects you to handle the abandoned state explicitly.

The fix

  1. Check the return value of WaitForSingleObject. If it's WAIT_ABANDONED, don't call ReleaseMutex on that handle — instead, close it and create a new mutex, or reset your shared state.
  2. In .NET, catch AbandonedMutexException and decide whether to continue or abort the operation.
  3. Use structured exception handling (SEH) or try-finally blocks to guarantee release under normal conditions, but accept that crashes will orphan the mutex.

I've seen this most often in Windows services that spawn worker threads. A worker throws an unhandled exception, the mutex is abandoned, and the next request fails with ERROR_NOT_OWNER. The fix: wrap thread work in a top-level try-catch that releases the mutex before exiting.

Quick-Reference Summary Table

Cause Symptom Fix
Thread doesn't own mutex Release called by wrong thread Store owning thread ID, validate before release
Double release Two ReleaseMutex calls for one acquire Null or flag the handle after release
Mutex abandonment Thread crashed while owning mutex Check for WAIT_ABANDONED, handle separately

Was this solution helpful?