-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Make DependentHandle public #54246
Merged
stephentoub
merged 30 commits into
dotnet:main
from
Sergio0694:sergio0694/public-dependent-handle
Jun 26, 2021
Merged
Make DependentHandle public #54246
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
57fe91c
Move DependentHandle to System.Runtime
Sergio0694 b1f54b5
Update DependentHandle APIs to follow review
Sergio0694 b331b8f
Make DependentHandle type public
Sergio0694 a96fa3f
Update DependentHandle on Mono runtime
Sergio0694 16fdf0e
Add allocation checks to DependentHandle APIs
Sergio0694 748d88e
Add more unit tests for new public DependentHandle APIs
Sergio0694 247fa5a
Add faster, unsafe internal APIs versions to DependentHandle
Sergio0694 66d2ac5
Naming improvements to Ephemeron type
Sergio0694 4067ac3
Code style tweaks, improved nullability annotations
Sergio0694 1670339
Remove incorrect DependentHandle comment on Mono
Sergio0694 96cfc91
Add default Dispose test for DependentHandle
Sergio0694 6a8db56
Fix race condition in DependentHandle on Mono
Sergio0694 1601d88
Optimize DependentHandle.nGetPrimary on CoreCLR
Sergio0694 359938b
Small IL codegen improvement in DependentHandle.nGetPrimary
Sergio0694 312851a
Simplify comments, add #ifdef for using directive
Sergio0694 0145a76
Minor code style tweaks
Sergio0694 4925877
Change nGetPrimaryAndSecondary to nGetSecondary
Sergio0694 1664a95
Minor code refactoring to DependentHandle on Mono
Sergio0694 ca515b6
Rename DependentHandle FCalls
Sergio0694 08df598
Remove DependentHandle.UnsafeGetTargetAndDependent
Sergio0694 4e2b624
Remove DependentHandle.GetTargetAndDependent
Sergio0694 4e03297
Fix FCall path for internal DependentHandle APIs
Sergio0694 25b34c2
Add more DependentHandle unit tests
Sergio0694 01f32a3
Reintroduce DependentHandle.GetTargetAndDependent()
Sergio0694 b3963f2
Minor IL tweaks to produce smaller IR in the JIT
Sergio0694 34e1bcb
Add DependentHandle.StopTracking() API
Sergio0694 9fd1da4
Rename InternalSetTarget to StopTracking, remove redundant param
Sergio0694 d7146e0
Remove FCUnique from InternalStopTracking
Sergio0694 c9c6325
Update API surface to match approved specs from API review
Sergio0694 c463d54
Update DependentHandle XML docs
Sergio0694 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Update API surface to match approved specs from API review
- Loading branch information
commit c9c632552ccfb9c9322435735bbe8a029321d27e
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -70,9 +70,12 @@ public DependentHandle(object? target, object? dependent) | |
public bool IsAllocated => (nint)_handle != 0; | ||
|
||
/// <summary> | ||
/// Gets the target object instance for the current handle. | ||
/// Gets or sets the target object instance for the current handle. The target can only be set to a <see langword="null"/> value | ||
/// once the <see cref="DependentHandle"/> instance has been created. Doing so will cause <see cref="Dependent"/> to start | ||
/// returning <see langword="null"/> as well, and to become eligible for collection even if the previous target is still alive. | ||
/// </summary> | ||
/// <exception cref="InvalidOperationException">Thrown if <see cref="IsAllocated"/> is <see langword="false"/>.</exception> | ||
/// <exception cref="InvalidOperationException"> | ||
/// Thrown if <see cref="IsAllocated"/> is <see langword="false"/> or if the input value is not <see langword="null"/>.</exception> | ||
/// <remarks>This property is thread-safe.</remarks> | ||
public object? Target | ||
{ | ||
|
@@ -87,6 +90,17 @@ public object? Target | |
|
||
return InternalGetTarget(handle); | ||
} | ||
set | ||
{ | ||
IntPtr handle = _handle; | ||
|
||
if ((nint)handle == 0 || value is not null) | ||
{ | ||
ThrowHelper.ThrowInvalidOperationException(); | ||
} | ||
|
||
InternalSetTargetToNull(handle); | ||
} | ||
} | ||
|
||
/// <summary> | ||
|
@@ -127,49 +141,29 @@ public object? Dependent | |
} | ||
|
||
/// <summary> | ||
/// Atomically retrieves the values of both <see cref="Target"/> and <see cref="Dependent"/>, if available. | ||
/// Gets the values of both <see cref="Target"/> and <see cref="Dependent"/> (if available) as an atomic operation. | ||
/// That is, even if <see cref="Target"/> is concurrently set to <see langword="null"/>, calling this method | ||
/// will either return <see langword="null"/> for both target and dependent, or return both previous values. | ||
/// If <see cref="Target"/> and <see cref="Dependent"/> were used sequentially in this scenario instead, it's | ||
/// could be possible to sometimes successfully retrieve the previous target, but then fail to get the dependent. | ||
/// </summary> | ||
/// <returns>The values of <see cref="Target"/> and <see cref="Dependent"/>.</returns> | ||
/// <exception cref="InvalidOperationException">Thrown if <see cref="IsAllocated"/> is <see langword="false"/>.</exception> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thread safety remark? |
||
public (object? Target, object? Dependent) GetTargetAndDependent() | ||
public (object? Target, object? Dependent) TargetAndDependent | ||
{ | ||
IntPtr handle = _handle; | ||
|
||
if ((nint)handle == 0) | ||
get | ||
{ | ||
ThrowHelper.ThrowInvalidOperationException(); | ||
} | ||
|
||
object? target = InternalGetTargetAndDependent(handle, out object? dependent); | ||
IntPtr handle = _handle; | ||
|
||
return (target, dependent); | ||
} | ||
if ((nint)handle == 0) | ||
{ | ||
ThrowHelper.ThrowInvalidOperationException(); | ||
} | ||
|
||
/// <summary> | ||
/// Stops tracking the target and dependent objects in the current <see cref="DependentHandle"/> instance. Once this method | ||
/// is invoked, calling other APIs is still allowed, but <see cref="Target"/> and <see cref="Dependent"/> will always just | ||
/// return <see langword="null"/>. Additionally, since the dependent instance will no longer be tracked, it will also | ||
/// immediately become eligible for collection if there are no other roots for it, even if the original target is still alive. | ||
/// </summary> | ||
/// <exception cref="InvalidOperationException">Thrown if <see cref="IsAllocated"/> is <see langword="false"/>.</exception> | ||
/// <remarks> | ||
/// This method does not dispose the current <see cref="DependentHandle"/> instance, which means that after calling it will not | ||
/// affect the value of <see cref="IsAllocated"/>, and <see cref="Dispose"/> will still need to be called to release resources. | ||
/// </remarks> | ||
public void StopTracking() | ||
{ | ||
IntPtr handle = _handle; | ||
object? target = InternalGetTargetAndDependent(handle, out object? dependent); | ||
|
||
if ((nint)handle == 0) | ||
{ | ||
ThrowHelper.ThrowInvalidOperationException(); | ||
return (target, dependent); | ||
} | ||
|
||
InternalStopTracking(handle); | ||
} | ||
|
||
/// <summary> | ||
|
@@ -188,7 +182,7 @@ public void StopTracking() | |
/// <param name="dependent">The dependent instance, if available.</param> | ||
/// <returns>The values of <see cref="Target"/> and <see cref="Dependent"/>.</returns> | ||
/// <remarks> | ||
/// This method mirrors <see cref="GetTargetAndDependent"/>, but without the allocation check. | ||
/// This method mirrors the <see cref="TargetAndDependent"/> property, but without the allocation check. | ||
/// The signature is also kept the same as the one for the internal call, to improve the codegen. | ||
/// Note that <paramref name="dependent"/> is required to be on the stack (or it might not be tracked). | ||
/// </remarks> | ||
|
@@ -198,22 +192,21 @@ public void StopTracking() | |
} | ||
|
||
/// <summary> | ||
/// Sets the dependent object instance for the current handle. | ||
/// Sets the dependent object instance for the current handle to <see langword="null"/>. | ||
/// </summary> | ||
/// <remarks>This method mirrors <see cref="Dependent"/>, but without the allocation check.</remarks> | ||
internal void UnsafeSetDependent(object? dependent) | ||
/// <remarks>This method mirrors the <see cref="Target"/> setter, but without allocation and input checks.</remarks> | ||
internal void UnsafeSetTargetToNull() | ||
{ | ||
InternalSetDependent(_handle, dependent); | ||
InternalSetTargetToNull(_handle); | ||
} | ||
|
||
/// <summary> | ||
/// Stops tracking the target and dependent objects in the current <see cref="DependentHandle"/> instance. | ||
/// Sets the dependent object instance for the current handle. | ||
/// </summary> | ||
/// <exception cref="InvalidOperationException">Thrown if <see cref="IsAllocated"/> is <see langword="false"/>.</exception> | ||
/// <remarks>This method mirrors <see cref="StopTracking"/>, but without the allocation check.</remarks> | ||
internal void UnsafeStopTracking() | ||
/// <remarks>This method mirrors <see cref="Dependent"/>, but without the allocation check.</remarks> | ||
internal void UnsafeSetDependent(object? dependent) | ||
{ | ||
InternalStopTracking(_handle); | ||
InternalSetDependent(_handle, dependent); | ||
} | ||
|
||
/// <inheritdoc cref="IDisposable.Dispose"/> | ||
|
@@ -258,7 +251,7 @@ public void Dispose() | |
private static extern void InternalSetDependent(IntPtr dependentHandle, object? dependent); | ||
|
||
[MethodImpl(MethodImplOptions.InternalCall)] | ||
private static extern void InternalStopTracking(IntPtr dependentHandle); | ||
private static extern void InternalSetTargetToNull(IntPtr dependentHandle); | ||
|
||
[MethodImpl(MethodImplOptions.InternalCall)] | ||
private static extern void InternalFree(IntPtr dependentHandle); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -37,6 +37,48 @@ public void GetNotNullTarget() | |||||||||||||
handle.Dispose(); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
[Fact] | ||||||||||||||
public void SetTargetToNull_StateIsConsistent() | ||||||||||||||
{ | ||||||||||||||
object target = new(), dependent = new(); | ||||||||||||||
DependentHandle handle = new(target, dependent); | ||||||||||||||
|
||||||||||||||
Assert.True(handle.IsAllocated); | ||||||||||||||
Assert.Same(handle.Target, target); | ||||||||||||||
Assert.Same(handle.Dependent, dependent); | ||||||||||||||
|
||||||||||||||
handle.Target = null; | ||||||||||||||
|
||||||||||||||
Assert.True(handle.IsAllocated); | ||||||||||||||
Assert.Null(handle.Target); | ||||||||||||||
Assert.Null(handle.Dependent); | ||||||||||||||
|
||||||||||||||
handle.Dispose(); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
[Fact] | ||||||||||||||
public void SetTargetToNull_RepeatedCallsAreFine() | ||||||||||||||
{ | ||||||||||||||
object target = new(), dependent = new(); | ||||||||||||||
DependentHandle handle = new(target, dependent); | ||||||||||||||
|
||||||||||||||
handle.Target = null; | ||||||||||||||
|
||||||||||||||
Assert.True(handle.IsAllocated); | ||||||||||||||
Assert.Null(handle.Target); | ||||||||||||||
Assert.Null(handle.Dependent); | ||||||||||||||
|
||||||||||||||
handle.Target = null; | ||||||||||||||
handle.Target = null; | ||||||||||||||
handle.Target = null; | ||||||||||||||
|
||||||||||||||
Assert.True(handle.IsAllocated); | ||||||||||||||
Assert.Null(handle.Target); | ||||||||||||||
Assert.Null(handle.Dependent); | ||||||||||||||
|
||||||||||||||
handle.Dispose(); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
[Fact] | ||||||||||||||
public void GetSetDependent() | ||||||||||||||
{ | ||||||||||||||
|
@@ -175,50 +217,8 @@ private sealed class ObjectWithReference | |||||||||||||
public object Reference; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
[Fact] | ||||||||||||||
public void StopTracking_RepeatedCallsAreFine() | ||||||||||||||
{ | ||||||||||||||
object target = new(), dependent = new(); | ||||||||||||||
DependentHandle handle = new(target, dependent); | ||||||||||||||
|
||||||||||||||
handle.StopTracking(); | ||||||||||||||
|
||||||||||||||
Assert.True(handle.IsAllocated); | ||||||||||||||
Assert.Null(handle.Target); | ||||||||||||||
Assert.Null(handle.Dependent); | ||||||||||||||
|
||||||||||||||
handle.StopTracking(); | ||||||||||||||
handle.StopTracking(); | ||||||||||||||
handle.StopTracking(); | ||||||||||||||
|
||||||||||||||
Assert.True(handle.IsAllocated); | ||||||||||||||
Assert.Null(handle.Target); | ||||||||||||||
Assert.Null(handle.Dependent); | ||||||||||||||
|
||||||||||||||
handle.Dispose(); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
[Fact] | ||||||||||||||
public void StopTracking_StateIsConsistent() | ||||||||||||||
{ | ||||||||||||||
object target = new(), dependent = new(); | ||||||||||||||
DependentHandle handle = new(target, dependent); | ||||||||||||||
|
||||||||||||||
Assert.True(handle.IsAllocated); | ||||||||||||||
Assert.Same(handle.Target, target); | ||||||||||||||
Assert.Same(handle.Dependent, dependent); | ||||||||||||||
|
||||||||||||||
handle.StopTracking(); | ||||||||||||||
|
||||||||||||||
Assert.True(handle.IsAllocated); | ||||||||||||||
Assert.Null(handle.Target); | ||||||||||||||
Assert.Null(handle.Dependent); | ||||||||||||||
|
||||||||||||||
handle.Dispose(); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsPreciseGcSupported))] | ||||||||||||||
public void DependentIsCollectedAfterStopTracking() | ||||||||||||||
public void DependentIsCollectedAfterTargetIsSetToNull() | ||||||||||||||
{ | ||||||||||||||
[MethodImpl(MethodImplOptions.NoInlining)] | ||||||||||||||
static DependentHandle Initialize(out object target, out WeakReference weakDependent) | ||||||||||||||
|
@@ -234,7 +234,7 @@ static DependentHandle Initialize(out object target, out WeakReference weakDepen | |||||||||||||
|
||||||||||||||
DependentHandle handle = Initialize(out object target, out WeakReference dependent); | ||||||||||||||
|
||||||||||||||
handle.StopTracking(); | ||||||||||||||
handle.Target = null; | ||||||||||||||
|
||||||||||||||
GC.Collect(); | ||||||||||||||
|
||||||||||||||
|
@@ -263,19 +263,41 @@ public void GetDependent_ThrowsInvalidOperationException() | |||||||||||||
} | ||||||||||||||
|
||||||||||||||
[Fact] | ||||||||||||||
public void SetDependent_ThrowsInvalidOperationException() | ||||||||||||||
public void SetTarget_NotAllocated_ThrowsInvalidOperationException() | ||||||||||||||
{ | ||||||||||||||
Assert.Throws<InvalidOperationException>(() => | ||||||||||||||
{ | ||||||||||||||
DependentHandle handle = default; | ||||||||||||||
handle.Dependent = new(); | ||||||||||||||
handle.Target = new(); | ||||||||||||||
}); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
[Fact] | ||||||||||||||
public void StopTracking_ThrowsInvalidOperationException() | ||||||||||||||
public void SetTarget_NotNullObject_ThrowsInvalidOperationException() | ||||||||||||||
{ | ||||||||||||||
Assert.Throws<InvalidOperationException>(() => default(DependentHandle).StopTracking()); | ||||||||||||||
Assert.Throws<InvalidOperationException>(() => | ||||||||||||||
{ | ||||||||||||||
DependentHandle handle = default; | ||||||||||||||
|
||||||||||||||
try | ||||||||||||||
{ | ||||||||||||||
handle.Target = new(); | ||||||||||||||
} | ||||||||||||||
finally | ||||||||||||||
{ | ||||||||||||||
handle.Dispose(); | ||||||||||||||
} | ||||||||||||||
}); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
[Fact] | ||||||||||||||
public void SetDependent_ThrowsInvalidOperationException() | ||||||||||||||
{ | ||||||||||||||
Assert.Throws<InvalidOperationException>(() => | ||||||||||||||
{ | ||||||||||||||
DependentHandle handle = default; | ||||||||||||||
handle.Dependent = new(); | ||||||||||||||
}); | ||||||||||||||
Comment on lines
+296
to
+300
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto:
Suggested change
|
||||||||||||||
} | ||||||||||||||
|
||||||||||||||
[Fact] | ||||||||||||||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
IsAllocated is missing a similar thread-safety remark.