Wrong Lock Usage

ID

csharp.wrong_lock_usage

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

CSharp

Tags

concurrency, reliability

Description

Reports calls to Monitor.Enter(x) or Monitor.TryEnter(x) that are not paired with a Monitor.Exit(x) call inside a finally block. Without that pairing, if the protected body throws, the lock is never released and the next thread that tries to acquire it deadlocks.

Rationale

Monitor.Enter acquires a synchronization lock that must be released via Monitor.Exit. The lock statement does this automatically by emitting the equivalent of try { Monitor.Enter(x); …​ } finally { Monitor.Exit(x); }. When code calls Monitor.Enter directly it must reproduce that pattern, otherwise an exception in the critical section leaks the lock for the lifetime of the process. This rule reports any Monitor.Enter/TryEnter whose lock target is not released by a matching Monitor.Exit inside the same scope’s finally clause.

public void Bad(object sync)
{
    Monitor.Enter(sync);                // FLAW — no try/finally guarding the release
    DoWork();
    Monitor.Exit(sync);                 // skipped if DoWork() throws
}

public void Good(object sync)
{
    Monitor.Enter(sync);                // OK — paired with Monitor.Exit in finally
    try
    {
        DoWork();
    }
    finally
    {
        Monitor.Exit(sync);
    }
}

public void Best(object sync)
{
    lock (sync)                         // OK — compiler emits the try/finally pair
    {
        DoWork();
    }
}

Remediation

Prefer the lock statement whenever possible — the compiler generates the correct try/finally automatically. If Monitor.Enter must be called directly (for example to use TryEnter with a timeout), wrap the critical section in try { …​ } finally { Monitor.Exit(sync); } so the lock is released on every code path, exceptional or not.