Skip to content

Conversation

davidwrighton
Copy link
Member

@davidwrighton davidwrighton commented Mar 7, 2025

  • Convert to the FCALL fast path/QCALL slow path approach
  • Make EnterHelperResult/LeaveHelperAction into enum class so that they can safely be silently marshaled between native and managed
  • Move the lockTaken flag handling to managed code so we can share more helpers
  • Benchmarking indicates that this makes uncontended locks somewhere around 10% faster. I believe this is primarily due to removing a small amount of handling around error checking and lock flag usage which isn't always needed.

- Convert to the FCALL fast path/QCALL slow path approach
- Make EnterHelperResult/LeaveHelperAction into enum class so that they can safely be silently marshaled between native and managed
- Move the lockTaken flag handling to managed code so we can share more helpers
Copy link
Contributor

Tagging subscribers to this area: @mangod9
See info in area-owners.md if you want to be subscribed.

@davidwrighton
Copy link
Member Author

@EgorBot -amd -arm -windows_intel

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Numerics;

BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);

public class Tests
{
    private obj _lockObj = new object();
    private int _field = 0;

    [Benchmark(Baseline = true)]
    public string Lock_OnLockObj()
    {
        _field++;
        lock(_lockObj)
        {
            _field++;
        }
    }

    [Benchmark]
    public bool LockOnThis_ThisKnownToBeNonNull()
    {
        _field++;
        lock(this)
        {
            _field++;
        }
    }

    [Benchmark]
    public bool LockOnThis_ThisKnownToBeNonNull()
    {
        lock(this)
        {
            _field++;
        }
    }
}

@davidwrighton davidwrighton marked this pull request as ready for review March 7, 2025 18:19
@Copilot Copilot AI review requested due to automatic review settings March 7, 2025 18:19
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Overview

This PR refactors the Monitor implementation to remove helper method frames by introducing a fast path/slow path approach for lock acquisition and release. Key changes include:

  • Introducing new fast path methods (TryEnter_FastPath and TryEnter_FastPath_WithTimeout) and enumerated types (EnterHelperResult, LeaveHelperAction) for better handling of lock acquisition.
  • Replacing the ReliableEnter/Exit methods with new logic for handling both immediate and timeout-based lock acquisition.
  • Migrating lockTaken flag handling to managed code and updating related methods (Enter, TryEnter, and Exit) accordingly.

Reviewed Changes

File Description
src/coreclr/System.Private.CoreLib/src/System/Threading/Monitor.CoreCLR.cs Refactors lock acquisition and release by adding fast path/slow path methods and replacing old helper methods, while updating argument validation and exception handling

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

TryEnter(obj, millisecondsTimeout, ref lockTaken);
return lockTaken;
ArgumentNullException.ThrowIfNull(obj);

Copy link

Copilot AI Mar 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TryEnter(object, int) method does not explicitly validate that millisecondsTimeout is not less than -1. To maintain the documented contract, consider adding a check to throw ArgumentException for invalid timeout values.

Suggested change
if (millisecondsTimeout < -1)
{
throw new ArgumentException("Timeout must be greater than or equal to -1.", nameof(millisecondsTimeout));
}

Copilot uses AI. Check for mistakes.

}

private static void TryEnter_Timeout_WithLockTaken(object obj, int timeout, ref bool lockTaken)
{
Copy link

Copilot AI Mar 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TryEnter_Timeout_WithLockTaken method does not handle cases where timeout is less than -1. It is recommended to add an explicit check and throw an ArgumentException when timeout is invalid.

Suggested change
{
{
if (timeout < -1)
{
throw new ArgumentException("Timeout must be greater than or equal to -1.", nameof(timeout));
}

Copilot uses AI. Check for mistakes.

@davidwrighton
Copy link
Member Author

Sigh. Looks like something is busted. I'd like to finish looking into that before anyone reviews this.

return;
}

Exit_Slowpath(obj, exitBehavior);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can Exit_FastPath do anything when returning exitBehavior != LeaveHelperAction.None (note NOT EQUAL) that would break the subsequent fall-through to Exit_Slowpath (e.g. we did the fast-path exit, but it needs some other LeaveHelperAction... but we've done the Exit already)?

(same question on line 154)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No.

if (exitBehavior == LeaveHelperAction.None)
return;

Exit_Slowpath(obj, exitBehavior);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can Exit_FastPath do anything when returning exitBehavior != LeaveHelperAction.None (note NOT EQUAL) that would break the subsequent fall-through to Exit_Slowpath (e.g. we did the fast-path exit, but it needs some other LeaveHelperAction... but we've done the Exit already)?

(same question on line 174)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No.

}
}

if (TryEnter_Slowpath(obj, timeout))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this result in up to two timeout periods elapsing if the fast-path tried and returned anything other than Entered or Contention?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the timeout input to the fast path is only used to check against 0 (which has special meaning).

return AwareLock::EnterHelperResult::Contention;
}

result = obj->EnterObjMonitorHelperSpin(pCurThread);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should a timeOut of non-zero be handled, should it be passed down to/honored by the EnterObjMonitorHelperSpin?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is the fast set of spinning that waits for fractions of a millisecond or so, if you're willing to wait at all, we run the same amount of spinning for all possible timeouts.

…ectly. Also hard-code the exact values for the Enter/Leave results on the native side so that the values are well defined instead of implicit.
@davidwrighton davidwrighton marked this pull request as ready for review March 24, 2025 18:09
Yield = 2,
Contention = 3,
Error = 4,
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
};
}

Comment on lines +229 to +236
if (obj->TryEnterObjMonitorSpinHelper())
{
FC_RETURN_BOOL(TRUE);
}
else
{
FC_RETURN_BOOL(FALSE);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (obj->TryEnterObjMonitorSpinHelper())
{
FC_RETURN_BOOL(TRUE);
}
else
{
FC_RETURN_BOOL(FALSE);
}
FC_RETURN_BOOL(obj->TryEnterObjMonitorSpinHelper());

Comment on lines +279 to +280
if (timeOut < -1)
COMPlusThrow(kArgumentOutOfRangeException);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should assert this here and do the error handling in managed code. The Copilot for the methods also called this out.

Comment on lines +173 to +180
if (exitBehavior == LeaveHelperAction.None)
{
lockTaken = false;
return;
}

Exit_Slowpath(exitBehavior, obj);
lockTaken = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (exitBehavior == LeaveHelperAction.None)
{
lockTaken = false;
return;
}
Exit_Slowpath(exitBehavior, obj);
lockTaken = false;
if (exitBehavior != LeaveHelperAction.None)
{
Exit_Slowpath(exitBehavior, obj);
}
lockTaken = false;

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants