-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Proposal: Hide Thread.VolatileRead and Thread.VolatileWrite #27997
Comments
cc: @kouvel |
Is there something I need to do to get someone to take a look at this? It ought to be a simple thumbs up or down. |
Cc @kouvel This does not seem a compelling reason on the face of it. It can cause more confusion eg if you are used to using these methods and no longer see them in Intellisense. I doubt we would change this. You might consider a Roslyn analyzer or simple grep script. |
Would a proposal to add If a developer discovers |
I would prefer to deprecate the |
We generally don't Obsolete things; there are a variety of problems with that. @terrajobst has been exploring an alternate analyzer-based mechanism; he can provide more details. |
Can this be ready-for-review or does something else need to happen first? |
|
I would love to see a volatile read with |
I don't see the benefit of hiding these. Why is it valuable, @terrajobst? |
I also lean against hiding these. It would be preferable to allow someone to reference them, followed by a diagnostic analyzer reporting the alternative and providing a code fix for it. |
Why not just add the bool overloads? Is it a simple oversight that there are all the others? |
@sharwell, I would mix both. The obsolete attribute could have an overload with the replacement API and a generic analyzer/fixer for this attribute would offer the proper fix. |
Moving back to ready for review to discuss again. |
I would just like to add some arguments in favor of hiding and obsoleting these methods. (The linked video is currently failing to play for me, so I am not sure if these have been brought up already.) There is an important bit of history that needs to be considered. Before .NET Core was a thing, these methods were implemented differently on .NET Framework. They used to just call So, why was that implementation problematic? In Ecma 335 §I.12.6.5, it is mentioned:
Next, §I.12.6.7 says:
As I mentioned above, the way these methods were implemented actually resulted in stronger ordering semantics. So users quite possibly came to rely on those semantics, even though they're not equivalent to what Ecma 335 claims. Let's address atomicity next. The above section says that volatile reads/writes are not necessarily atomic. It would make sense that this applies to these methods as well, considering that the .NET Framework implementation linked above is indeed not atomic. Only the 32-bit overloads are atomic as a result of §I.12.6.6:
But, unfortunately, things get murky when we start to look at MSDN documentation:
Consider the case of using the 64-bit overloads of these methods on a 32-bit system. Memory operations involving 64-bit quantities will often be implemented as two 32-bit operations on such platforms since, depending on architecture version, there simply might not be any instructions available to do 64-bit loads/stores. In this case, a simple non-atomic read of a 64-bit quantity could obtain a garbled value because another thread has just written the first half of the value to the location. I think it's safe to say that most people reading this text would conclude that these methods are atomic. The situation I just described could not reasonably be said to have obtained the "very latest value" in any useful sense. At the very least, if one wants to argue otherwise, I think one would have to concede that the documentation should have mentioned the 32-bit on 64-bit issue. The MSDN documentation actually commits another offense:
This is just flat out wrong. The C# In .NET Core today, these methods are forwarded to the In conclusion, the semantics of these methods were never clear. The documentation and implementation are all over the place. Between all the possible expectations when using them, no one can really say what a user can, will, and should expect. Therefore, to my mind, these methods have been sufficiently poisoned that it makes no sense to even passively encourage their use any longer. There are better replacements with clearer semantics, both in terms of documentation and actual implementation. These methods have to go. (And someone should probably consider what to do about the .NET Framework/.NET Core incompatibility.) Side note: I was trying to find out when/why .NET Core started forwarding these methods to |
History was not removed. a) All of the history is in this repo, and b) in the archived repos you just need to look at the right branch, e.g. release/3.1. |
Thanks, I hadn't noticed the release branches were still there. FWIW, the incompatibility seems to have been introduced in 5cfbd06 when Worth noting that a comment in
There's unfortunately not enough context in any of these commits to say why the change did happen. |
Approved as marking as obsolete and EditorBrowsable(Never), contingent upon first creating the fixer to rewrite the calls automatically. namespace System.Threading
{
public sealed class Thread : CriticalFinalizerObject
{
...
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static byte VolatileRead(ref byte address) => Volatile.Read(ref address);
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static double VolatileRead(ref double address) => Volatile.Read(ref address);
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static short VolatileRead(ref short address) => Volatile.Read(ref address);
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static int VolatileRead(ref int address) => Volatile.Read(ref address);
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static long VolatileRead(ref long address) => Volatile.Read(ref address);
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static IntPtr VolatileRead(ref IntPtr address) => Volatile.Read(ref address);
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static object VolatileRead(ref object address) => Volatile.Read(ref address);
[CLSCompliant(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static sbyte VolatileRead(ref sbyte address) => Volatile.Read(ref address);
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static float VolatileRead(ref float address) => Volatile.Read(ref address);
[CLSCompliant(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static ushort VolatileRead(ref ushort address) => Volatile.Read(ref address);
[CLSCompliant(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static uint VolatileRead(ref uint address) => Volatile.Read(ref address);
[CLSCompliant(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static ulong VolatileRead(ref ulong address) => Volatile.Read(ref address);
[CLSCompliant(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static UIntPtr VolatileRead(ref UIntPtr address) => Volatile.Read(ref address);
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static void VolatileWrite(ref byte address, byte value) => Volatile.Write(ref address, value);
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static void VolatileWrite(ref double address, double value) => Volatile.Write(ref address, value);
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static void VolatileWrite(ref short address, short value) => Volatile.Write(ref address, value);
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static void VolatileWrite(ref int address, int value) => Volatile.Write(ref address, value);
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static void VolatileWrite(ref long address, long value) => Volatile.Write(ref address, value);
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static void VolatileWrite(ref IntPtr address, IntPtr value) => Volatile.Write(ref address, value);
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static void VolatileWrite(ref object address, object value) => Volatile.Write(ref address, value);
[CLSCompliant(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static void VolatileWrite(ref sbyte address, sbyte value) => Volatile.Write(ref address, value);
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static void VolatileWrite(ref float address, float value) => Volatile.Write(ref address, value);
[CLSCompliant(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static void VolatileWrite(ref ushort address, ushort value) => Volatile.Write(ref address, value);
[CLSCompliant(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static void VolatileWrite(ref uint address, uint value) => Volatile.Write(ref address, value);
[CLSCompliant(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static void VolatileWrite(ref ulong address, ulong value) => Volatile.Write(ref address, value);
[CLSCompliant(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static void VolatileWrite(ref UIntPtr address, UIntPtr value) => Volatile.Write(ref address, value);
...
}
} |
Yep, looks as expected (for static and instance field) ; ConsoleApp2.Program.StaticField()
mov eax,[7FFC5EB13914]
ret
; Total bytes of code 7
; ConsoleApp2.Program.InstanceField()
add rcx,8
mov eax,[rcx]
ret
; Total bytes of code 7 As the code with reading the argument itself isn't realistic, I'd say there is no action to be taken on this. |
Yeah. I think that in June we didn't have a 100% solid plan on new obsoletions yet, so I didn't have anything to include in the ref code snippet. |
Yes, we're arguably too conservative now when we see some volatile accesses (the
This bit of code could also be checking that the base of the indirection is not on the stack. runtime/src/coreclr/src/jit/lclmorph.cpp Lines 487 to 492 in de6f911
cc @dotnet/jit-contrib |
Per #27997 (comment), this was approved to be marked as Obsolete. However, the labels says "code-analyzer" and "code-fixer". Are the labels incorrect? |
@Youssef1313 The approval is contingent on the implementation of an analyzer and fixer. |
I expect we'd just need a fixer for whatever diagnostic ID is included as part of the obsoletion attribute. |
I have implemented the fixer and now need to settle on a diagnostic ID. Can I confirm the ID "SYSLIB0054" for this obsoletion? |
Rationale
Thread.VolatileRead
andThread.VolatileWrite
delegate toVolatile.Read
andVolatile.Write
, but it is an incomplete delegation in thatVolatile
has support forbool
whereasThread
does not.I always reach for
Thread.VolatileRead
beforeVolatile.Read
. Hiding it would help break this habit.Proposal
The text was updated successfully, but these errors were encountered: