-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Remove helper method frames from Monitors #113242
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
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.
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); | ||
|
Copilot
AI
Mar 7, 2025
There was a problem hiding this comment.
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.
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) | ||
{ |
Copilot
AI
Mar 7, 2025
There was a problem hiding this comment.
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.
{ | |
{ | |
if (timeout < -1) | |
{ | |
throw new ArgumentException("Timeout must be greater than or equal to -1.", nameof(timeout)); | |
} |
Copilot uses AI. Check for mistakes.
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.
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)
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
}; | |
} |
if (obj->TryEnterObjMonitorSpinHelper()) | ||
{ | ||
FC_RETURN_BOOL(TRUE); | ||
} | ||
else | ||
{ | ||
FC_RETURN_BOOL(FALSE); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
if (exitBehavior == LeaveHelperAction.None) | ||
{ | ||
lockTaken = false; | ||
return; | ||
} | ||
|
||
Exit_Slowpath(exitBehavior, obj); | ||
lockTaken = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.