Remove helper method frames from Monitors#113242
Conversation
- 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
|
Tagging subscribers to this area: @mangod9 |
|
@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++;
}
}
} |
There was a problem hiding this comment.
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); | ||
|
|
There was a problem hiding this comment.
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.
| if (millisecondsTimeout < -1) | |
| { | |
| throw new ArgumentException("Timeout must be greater than or equal to -1.", nameof(millisecondsTimeout)); | |
| } |
| } | ||
|
|
||
| private static void TryEnter_Timeout_WithLockTaken(object obj, int timeout, ref bool lockTaken) | ||
| { |
There was a problem hiding this comment.
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.
| { | |
| { | |
| if (timeout < -1) | |
| { | |
| throw new ArgumentException("Timeout must be greater than or equal to -1.", nameof(timeout)); | |
| } |
|
Sigh. Looks like something is busted. I'd like to finish looking into that before anyone reviews this. |
| return; | ||
| } | ||
|
|
||
| Exit_Slowpath(obj, exitBehavior); |
There was a problem hiding this comment.
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)
| if (exitBehavior == LeaveHelperAction.None) | ||
| return; | ||
|
|
||
| Exit_Slowpath(obj, exitBehavior); |
There was a problem hiding this comment.
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)
| } | ||
| } | ||
|
|
||
| if (TryEnter_Slowpath(obj, timeout)) |
There was a problem hiding this comment.
Will this result in up to two timeout periods elapsing if the fast-path tried and returned anything other than Entered or Contention?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
Should a timeOut of non-zero be handled, should it be passed down to/honored by the EnterObjMonitorHelperSpin?
There was a problem hiding this comment.
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.
src/coreclr/System.Private.CoreLib/src/System/Threading/Monitor.CoreCLR.cs
Outdated
Show resolved
Hide resolved
| Yield = 2, | ||
| Contention = 3, | ||
| Error = 4, | ||
| }; |
There was a problem hiding this comment.
| }; | |
| } |
| if (obj->TryEnterObjMonitorSpinHelper()) | ||
| { | ||
| FC_RETURN_BOOL(TRUE); | ||
| } | ||
| else | ||
| { | ||
| FC_RETURN_BOOL(FALSE); | ||
| } |
There was a problem hiding this comment.
| if (obj->TryEnterObjMonitorSpinHelper()) | |
| { | |
| FC_RETURN_BOOL(TRUE); | |
| } | |
| else | |
| { | |
| FC_RETURN_BOOL(FALSE); | |
| } | |
| FC_RETURN_BOOL(obj->TryEnterObjMonitorSpinHelper()); |
| if (timeOut < -1) | ||
| COMPlusThrow(kArgumentOutOfRangeException); |
There was a problem hiding this comment.
I think we should assert this here and do the error handling in managed code. The Copilot for the methods also called this out.
| if (exitBehavior == LeaveHelperAction.None) | ||
| { | ||
| lockTaken = false; | ||
| return; | ||
| } | ||
|
|
||
| Exit_Slowpath(exitBehavior, obj); | ||
| lockTaken = false; |
There was a problem hiding this comment.
| if (exitBehavior == LeaveHelperAction.None) | |
| { | |
| lockTaken = false; | |
| return; | |
| } | |
| Exit_Slowpath(exitBehavior, obj); | |
| lockTaken = false; | |
| if (exitBehavior != LeaveHelperAction.None) | |
| { | |
| Exit_Slowpath(exitBehavior, obj); | |
| } | |
| lockTaken = false; |
Uh oh!
There was an error while loading. Please reload this page.