Skip to content

Monitor.TryEnter should fail fast for timeout 0 #6573

@benaadams

Description

@benaadams

Monitor.TryEnter(o) and variants with timeout 0 are as special case that is purely opportunistic and should fail fast and not do any spinning.

1M TryEnter(o)s on a locked object will currently take over 3 minutes.

Remarks from MSDN Monitor.TryEnter Method (Object)

If successful, this method acquires an exclusive lock on the obj parameter. This method returns immediately, whether or not the lock is available.

This method is similar to Enter, but it will never block the current thread. If the thread cannot enter without blocking, the method returns false,.

Emphasis mine 😉 Technically it doesn't block/suspend; but its not as opportunistic with resources as it should be.

Consider the following code (Server GC, 4 core machine)

public class Program
{
    private const int iters = 1000 * 1000;
    public static void Main(string[] args)
    {
        var o = new object();
        var s = new SemaphoreSlim(0);

        Task.Run(() =>
        {
            Monitor.Enter(o);

            s.Release();

            Thread.Sleep(240000);

            Monitor.Exit(o);
        });

        s.Wait();

        var sw = Stopwatch.StartNew();

        for (var i = 0; i < 1000; i++)
        {
            Monitor.TryEnter(o, 0);
        }

        sw.Stop();
        Console.WriteLine($"Warmup 1000 iters {sw.Elapsed.TotalMilliseconds}ms");

        GC.Collect();

        sw.Restart();

        for (var i = 0; i < iters; i++)
        {
            Monitor.TryEnter(o);
        }

        sw.Stop();

        Console.WriteLine($"Main {iters} iters {sw.Elapsed.TotalMilliseconds}ms");

        Console.ReadLine();
    }

This will output something similar to the following and consume a full cpu core during the execution

Warmup 1000 iters 220.6732ms
Main 1000000 iters 219587.2351ms

Which means 1M iterations of a 0 timeout TryEnter when blocked will take 3 mins 39 secs to complete and burn an entire cpu during the full time.

Contrast to
SpinLock TryEnter(0, ref) which currently consumes 24ms (down to 5.95ms after dotnet/coreclr#6944)
SemaphoreSlim where Wait(0) and WaitAsync(0) take 12ms and 24ms respectively dotnet/coreclr#6898 (comment)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions