1
0
Fork 0
mirror of https://github.com/VSadov/Satori.git synced 2025-06-10 01:50:53 +09:00

Remove class constraint from Interlocked.{Compare}Exchange (#104558)

* Remove class constraint from Interlocked.{Compare}Exchange

Today `Interlocked.CompareExchange<T>` and `Interlocked.Exchange<T>` support only reference type `T`s. Now that we have corresponding {Compare}Exchange methods that support types of size 1, 2, 4, and 8, we can remove the constraint and support any `T` that's either a reference type, a primitive type, or an enum type, making the generic overload more useful and avoiding consumers needing to choose less-than-ideal types just because of the need for atomicity with Interlocked.{Compare}Exchange.

---------

Co-authored-by: Michal Strehovský <MichalStrehovsky@users.noreply.github.com>
This commit is contained in:
Stephen Toub 2024-07-19 12:44:27 -04:00 committed by GitHub
parent 6aa28625e3
commit 41e02e5549
Signed by: github
GPG key ID: B5690EEEBB952194
62 changed files with 975 additions and 581 deletions

View file

@ -102,21 +102,6 @@ namespace System.Threading
[return: NotNullIfNotNull(nameof(location1))]
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern object? ExchangeObject([NotNullIfNotNull(nameof(value))] ref object? location1, object? value);
// The below whole method reduces to a single call to Exchange(ref object, object) but
// the JIT thinks that it will generate more native code than it actually does.
/// <summary>Sets a variable of the specified type <typeparamref name="T"/> to a specified value and returns the original value, as an atomic operation.</summary>
/// <param name="location1">The variable to set to the specified value.</param>
/// <param name="value">The value to which the <paramref name="location1"/> parameter is set.</param>
/// <returns>The original value of <paramref name="location1"/>.</returns>
/// <exception cref="NullReferenceException">The address of location1 is a null pointer.</exception>
/// <typeparam name="T">The type to be used for <paramref name="location1"/> and <paramref name="value"/>. This type must be a reference type.</typeparam>
[Intrinsic]
[return: NotNullIfNotNull(nameof(location1))]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Exchange<T>([NotNullIfNotNull(nameof(value))] ref T location1, T value) where T : class? =>
Unsafe.As<T>(Exchange(ref Unsafe.As<T, object?>(ref location1), value));
#endregion
#region CompareExchange
@ -183,29 +168,6 @@ namespace System.Threading
[MethodImpl(MethodImplOptions.InternalCall)]
[return: NotNullIfNotNull(nameof(location1))]
private static extern object? CompareExchangeObject(ref object? location1, object? value, object? comparand);
// Note that getILIntrinsicImplementationForInterlocked() in vm\jitinterface.cpp replaces
// the body of the following method with the following IL:
// ldarg.0
// ldarg.1
// ldarg.2
// call System.Threading.Interlocked::CompareExchange(ref Object, Object, Object)
// ret
// The workaround is no longer strictly necessary now that we have Unsafe.As but it does
// have the advantage of being less sensitive to JIT's inliner decisions.
/// <summary>Compares two instances of the specified reference type <typeparamref name="T"/> for reference equality and, if they are equal, replaces the first one.</summary>
/// <param name="location1">The destination, whose value is compared by reference with <paramref name="comparand"/> and possibly replaced.</param>
/// <param name="value">The value that replaces the destination value if the comparison by reference results in equality.</param>
/// <param name="comparand">The object that is compared by reference to the value at <paramref name="location1"/>.</param>
/// <returns>The original value in <paramref name="location1"/>.</returns>
/// <exception cref="NullReferenceException">The address of <paramref name="location1"/> is a null pointer.</exception>
/// <typeparam name="T">The type to be used for <paramref name="location1"/>, <paramref name="value"/>, and <paramref name="comparand"/>. This type must be a reference type.</typeparam>
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[return: NotNullIfNotNull(nameof(location1))]
public static T CompareExchange<T>(ref T location1, T value, T comparand) where T : class? =>
Unsafe.As<T>(CompareExchange(ref Unsafe.As<T, object?>(ref location1), value, comparand));
#endregion
#region Add

View file

@ -4130,11 +4130,7 @@ GenTree* Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd,
case NI_System_Threading_Interlocked_Exchange:
case NI_System_Threading_Interlocked_ExchangeAdd:
{
assert(callType != TYP_STRUCT);
assert(sig->numArgs == 2);
var_types retType = JITtype2varType(sig->retType);
assert((genTypeSize(retType) >= 4) || (ni == NI_System_Threading_Interlocked_Exchange));
if (genTypeSize(retType) > TARGET_POINTER_SIZE)
{
@ -4159,6 +4155,10 @@ GenTree* Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd,
break;
}
assert(callType != TYP_STRUCT);
assert(sig->numArgs == 2);
assert((genTypeSize(retType) >= 4) || (ni == NI_System_Threading_Interlocked_Exchange));
GenTree* op2 = impPopStack().val;
GenTree* op1 = impPopStack().val;

View file

@ -36,14 +36,6 @@ namespace System.Threading
#endif
}
[Intrinsic]
[return: NotNullIfNotNull(nameof(location1))]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T CompareExchange<T>(ref T location1, T value, T comparand) where T : class?
{
return Unsafe.As<T>(CompareExchange(ref Unsafe.As<T, object?>(ref location1), value, comparand));
}
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[return: NotNullIfNotNull(nameof(location1))]
@ -92,16 +84,6 @@ namespace System.Threading
#endif
}
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[return: NotNullIfNotNull(nameof(location1))]
public static T Exchange<T>([NotNullIfNotNull(nameof(value))] ref T location1, T value) where T : class?
{
if (Unsafe.IsNullRef(ref location1))
ThrowHelper.ThrowNullReferenceException();
return Unsafe.As<T>(RuntimeImports.InterlockedExchange(ref Unsafe.As<T, object?>(ref location1), value));
}
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[return: NotNullIfNotNull(nameof(location1))]

View file

@ -46,12 +46,6 @@ namespace Internal.IL
switch (owningType.Name)
{
case "Interlocked":
{
if (owningType.Namespace == "System.Threading")
return InterlockedIntrinsics.EmitIL(method);
}
break;
case "Unsafe":
{
if (owningType.Namespace == "System.Runtime.CompilerServices")
@ -108,6 +102,12 @@ namespace Internal.IL
switch (owningType.Name)
{
case "Interlocked":
{
if (owningType.Namespace == "System.Threading")
return InterlockedIntrinsics.EmitIL(method);
}
break;
case "Activator":
{
TypeSystemContext context = owningType.Context;

View file

@ -19,6 +19,7 @@ namespace Internal.IL.Stubs
MethodDesc method)
{
Debug.Assert(((MetadataType)method.OwningType).Name == "Interlocked");
Debug.Assert(!method.IsGenericMethodDefinition);
if (method.HasInstantiation && method.Name == "CompareExchange")
{
@ -30,22 +31,45 @@ namespace Internal.IL.Stubs
if (compilationModuleGroup.ContainsType(method.OwningType))
#endif // READYTORUN
{
TypeDesc objectType = method.Context.GetWellKnownType(WellKnownType.Object);
MethodDesc compareExchangeObject = method.OwningType.GetKnownMethod("CompareExchange",
new MethodSignature(
MethodSignatureFlags.Static,
genericParameterCount: 0,
returnType: objectType,
parameters: new TypeDesc[] { objectType.MakeByRefType(), objectType, objectType }));
// Rewrite the generic Interlocked.CompareExchange<T> to be a call to one of the non-generic overloads.
TypeDesc ceArgType = null;
ILEmitter emit = new ILEmitter();
ILCodeStream codeStream = emit.NewCodeStream();
codeStream.EmitLdArg(0);
codeStream.EmitLdArg(1);
codeStream.EmitLdArg(2);
codeStream.Emit(ILOpcode.call, emit.NewToken(compareExchangeObject));
codeStream.Emit(ILOpcode.ret);
return emit.Link(method);
TypeDesc tType = method.Instantiation[0];
if (!tType.IsValueType)
{
ceArgType = method.Context.GetWellKnownType(WellKnownType.Object);
}
else if ((tType.IsPrimitive || tType.IsEnum) && (tType.UnderlyingType.Category is not (TypeFlags.Single or TypeFlags.Double)))
{
int size = tType.GetElementSize().AsInt;
Debug.Assert(size is 1 or 2 or 4 or 8);
ceArgType = size switch
{
1 => method.Context.GetWellKnownType(WellKnownType.Byte),
2 => method.Context.GetWellKnownType(WellKnownType.UInt16),
4 => method.Context.GetWellKnownType(WellKnownType.Int32),
_ => method.Context.GetWellKnownType(WellKnownType.Int64),
};
}
if (ceArgType is not null)
{
MethodDesc compareExchangeNonGeneric = method.OwningType.GetKnownMethod("CompareExchange",
new MethodSignature(
MethodSignatureFlags.Static,
genericParameterCount: 0,
returnType: ceArgType,
parameters: [ceArgType.MakeByRefType(), ceArgType, ceArgType]));
ILEmitter emit = new ILEmitter();
ILCodeStream codeStream = emit.NewCodeStream();
codeStream.EmitLdArg(0);
codeStream.EmitLdArg(1);
codeStream.EmitLdArg(2);
codeStream.Emit(ILOpcode.call, emit.NewToken(compareExchangeNonGeneric));
codeStream.Emit(ILOpcode.ret);
return emit.Link(method);
}
}
}

View file

@ -85,11 +85,6 @@ namespace Internal.IL
return UnsafeIntrinsics.EmitIL(method);
}
if (mdType.Name == "Interlocked" && mdType.Namespace == "System.Threading")
{
return InterlockedIntrinsics.EmitIL(_compilationModuleGroup, method);
}
return null;
}
@ -114,6 +109,11 @@ namespace Internal.IL
return TryGetIntrinsicMethodILForActivator(method);
}
if (mdType.Name == "Interlocked" && mdType.Namespace == "System.Threading")
{
return InterlockedIntrinsics.EmitIL(_compilationModuleGroup, method);
}
return null;
}

View file

@ -694,6 +694,10 @@ DEFINE_METHOD(MEMORY_MARSHAL, GET_ARRAY_DATA_REFERENCE_MDARRAY, GetArrayDa
DEFINE_CLASS(INTERLOCKED, Threading, Interlocked)
DEFINE_METHOD(INTERLOCKED, COMPARE_EXCHANGE_T, CompareExchange, GM_RefT_T_T_RetT)
DEFINE_METHOD(INTERLOCKED, COMPARE_EXCHANGE_OBJECT,CompareExchange, SM_RefObject_Object_Object_RetObject)
DEFINE_METHOD(INTERLOCKED, COMPARE_EXCHANGE_BYTE, CompareExchange, SM_RefByte_Byte_Byte_RetByte)
DEFINE_METHOD(INTERLOCKED, COMPARE_EXCHANGE_USHRT, CompareExchange, SM_RefUShrt_UShrt_UShrt_RetUShrt)
DEFINE_METHOD(INTERLOCKED, COMPARE_EXCHANGE_INT, CompareExchange, SM_RefInt_Int_Int_RetInt)
DEFINE_METHOD(INTERLOCKED, COMPARE_EXCHANGE_LONG, CompareExchange, SM_RefLong_Long_Long_RetLong)
DEFINE_CLASS(RAW_DATA, CompilerServices, RawData)
DEFINE_FIELD(RAW_DATA, DATA, Data)

View file

@ -7154,28 +7154,79 @@ bool getILIntrinsicImplementationForInterlocked(MethodDesc * ftn,
if (ftn->GetMemberDef() != CoreLibBinder::GetMethod(METHOD__INTERLOCKED__COMPARE_EXCHANGE_T)->GetMemberDef())
return false;
// Get MethodDesc for non-generic System.Threading.Interlocked.CompareExchange()
MethodDesc* cmpxchgObject = CoreLibBinder::GetMethod(METHOD__INTERLOCKED__COMPARE_EXCHANGE_OBJECT);
// Determine the type of the generic T method parameter
_ASSERTE(ftn->HasMethodInstantiation());
_ASSERTE(ftn->GetNumGenericMethodArgs() == 1);
TypeHandle typeHandle = ftn->GetMethodInstantiation()[0];
// Setup up the body of the method
static BYTE il[] = {
CEE_LDARG_0,
CEE_LDARG_1,
CEE_LDARG_2,
CEE_CALL,0,0,0,0,
CEE_RET
};
// Setup up the body of the CompareExchange methods; the method token will be patched on first use.
static BYTE il[5][9] =
{
{ CEE_LDARG_0, CEE_LDARG_1, CEE_LDARG_2, CEE_CALL, 0, 0, 0, 0, CEE_RET }, // object
{ CEE_LDARG_0, CEE_LDARG_1, CEE_LDARG_2, CEE_CALL, 0, 0, 0, 0, CEE_RET }, // byte
{ CEE_LDARG_0, CEE_LDARG_1, CEE_LDARG_2, CEE_CALL, 0, 0, 0, 0, CEE_RET }, // ushort
{ CEE_LDARG_0, CEE_LDARG_1, CEE_LDARG_2, CEE_CALL, 0, 0, 0, 0, CEE_RET }, // int
{ CEE_LDARG_0, CEE_LDARG_1, CEE_LDARG_2, CEE_CALL, 0, 0, 0, 0, CEE_RET }, // long
};
// Get the token for non-generic System.Threading.Interlocked.CompareExchange(), and patch [target]
mdMethodDef cmpxchgObjectToken = cmpxchgObject->GetMemberDef();
il[4] = (BYTE)((int)cmpxchgObjectToken >> 0);
il[5] = (BYTE)((int)cmpxchgObjectToken >> 8);
il[6] = (BYTE)((int)cmpxchgObjectToken >> 16);
il[7] = (BYTE)((int)cmpxchgObjectToken >> 24);
// Based on the generic method parameter, determine which overload of CompareExchange
// to delegate to, or if we can't handle the type at all.
int ilIndex;
MethodDesc* cmpxchgMethod;
if (!typeHandle.IsValueType())
{
ilIndex = 0;
cmpxchgMethod = CoreLibBinder::GetMethod(METHOD__INTERLOCKED__COMPARE_EXCHANGE_OBJECT);
}
else
{
CorElementType elementType = typeHandle.GetVerifierCorElementType();
if (!CorTypeInfo::IsPrimitiveType(elementType) ||
elementType == ELEMENT_TYPE_R4 ||
elementType == ELEMENT_TYPE_R8)
{
return false;
}
else
{
switch (typeHandle.GetSize())
{
case 1:
ilIndex = 1;
cmpxchgMethod = CoreLibBinder::GetMethod(METHOD__INTERLOCKED__COMPARE_EXCHANGE_BYTE);
break;
case 2:
ilIndex = 2;
cmpxchgMethod = CoreLibBinder::GetMethod(METHOD__INTERLOCKED__COMPARE_EXCHANGE_USHRT);
break;
case 4:
ilIndex = 3;
cmpxchgMethod = CoreLibBinder::GetMethod(METHOD__INTERLOCKED__COMPARE_EXCHANGE_INT);
break;
case 8:
ilIndex = 4;
cmpxchgMethod = CoreLibBinder::GetMethod(METHOD__INTERLOCKED__COMPARE_EXCHANGE_LONG);
break;
default:
_ASSERT(!"Unexpected primitive type size");
return false;
}
}
}
mdMethodDef cmpxchgToken = cmpxchgMethod->GetMemberDef();
il[ilIndex][4] = (BYTE)((int)cmpxchgToken >> 0);
il[ilIndex][5] = (BYTE)((int)cmpxchgToken >> 8);
il[ilIndex][6] = (BYTE)((int)cmpxchgToken >> 16);
il[ilIndex][7] = (BYTE)((int)cmpxchgToken >> 24);
// Initialize methInfo
methInfo->ILCode = const_cast<BYTE*>(il);
methInfo->ILCodeSize = sizeof(il);
methInfo->ILCode = const_cast<BYTE*>(il[ilIndex]);
methInfo->ILCodeSize = sizeof(il[ilIndex]);
methInfo->maxStack = 3;
methInfo->EHcount = 0;
methInfo->options = (CorInfoOptions)0;

View file

@ -586,6 +586,8 @@ DEFINE_METASIG_T(SM(RefDec_RetVoid, r(g(DECIMAL)), v))
DEFINE_METASIG(GM(RefT_T_T_RetT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, r(M(0)) M(0) M(0), M(0)))
DEFINE_METASIG(SM(RefObject_Object_Object_RetObject, r(j) j j, j))
DEFINE_METASIG(SM(RefByte_Byte_Byte_RetByte, r(b) b b, b))
DEFINE_METASIG(SM(RefUShrt_UShrt_UShrt_RetUShrt, r(H) H H, H))
DEFINE_METASIG_T(SM(RefCleanupWorkListElement_RetVoid, r(C(CLEANUP_WORK_LIST_ELEMENT)), v))
DEFINE_METASIG_T(SM(RefCleanupWorkListElement_SafeHandle_RetIntPtr, r(C(CLEANUP_WORK_LIST_ELEMENT)) C(SAFE_HANDLE), I))

View file

@ -292,7 +292,7 @@ namespace System.IO
private ManualResetValueTaskSourceCore<bool> _waitSource; // mutable struct, do not make this readonly
private CancellationTokenRegistration _waitSourceCancellation;
private int _hasWaiter;
private bool _hasWaiter;
ValueTaskSourceStatus IValueTaskSource.GetStatus(short token) => _waitSource.GetStatus(token);
@ -300,7 +300,7 @@ namespace System.IO
void IValueTaskSource.GetResult(short token)
{
Debug.Assert(_hasWaiter == 0);
Debug.Assert(!_hasWaiter);
// Clean up the registration. This will wait for any in-flight cancellation to complete.
_waitSourceCancellation.Dispose();
@ -312,7 +312,7 @@ namespace System.IO
public void SignalWaiter()
{
if (Interlocked.Exchange(ref _hasWaiter, 0) == 1)
if (Interlocked.Exchange(ref _hasWaiter, false))
{
_waitSource.SetResult(true);
}
@ -322,7 +322,7 @@ namespace System.IO
{
Debug.Assert(cancellationToken.IsCancellationRequested);
if (Interlocked.Exchange(ref _hasWaiter, 0) == 1)
if (Interlocked.Exchange(ref _hasWaiter, false))
{
_waitSource.SetException(ExceptionDispatchInfo.SetCurrentStackTrace(new OperationCanceledException(cancellationToken)));
}
@ -330,13 +330,13 @@ namespace System.IO
public void Reset()
{
if (_hasWaiter != 0)
if (_hasWaiter)
{
throw new InvalidOperationException("Concurrent use is not supported");
}
_waitSource.Reset();
Volatile.Write(ref _hasWaiter, 1);
Volatile.Write(ref _hasWaiter, true);
}
public void Wait()

View file

@ -21,8 +21,8 @@ namespace System.Net.WebSockets
// Used by the class to indicate that the stream is writable.
private bool _writeable;
// Whether Dispose has been called. 0 == false, 1 == true
private int _disposed;
// Whether Dispose has been called.
private bool _disposed;
public WebSocketStream(WebSocket socket)
: this(socket, FileAccess.ReadWrite, ownsSocket: false)
@ -140,7 +140,7 @@ namespace System.Net.WebSockets
protected override void Dispose(bool disposing)
{
if (Interlocked.Exchange(ref _disposed, 1) != 0)
if (Interlocked.Exchange(ref _disposed, true))
{
return;
}
@ -269,7 +269,7 @@ namespace System.Net.WebSockets
private void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed != 0, this);
ObjectDisposedException.ThrowIf(_disposed, this);
}
private static IOException WrapException(string resourceFormatString, Exception innerException)

View file

@ -628,11 +628,11 @@ namespace System.Collections.Concurrent
// Finally, wait for all unsynchronized operations on each queue to be done.
for (WorkStealingQueue? queue = head; queue != null; queue = queue._nextQueue)
{
if (queue._currentOp != (int)Operation.None)
if (queue._currentOp != Operation.None)
{
SpinWait spinner = default;
do { spinner.SpinOnce(); }
while (queue._currentOp != (int)Operation.None);
while (queue._currentOp != Operation.None);
}
}
}
@ -684,7 +684,7 @@ namespace System.Collections.Concurrent
/// <summary>Number of steals; needs to be combined with <see cref="_addTakeCount"/> to get an actual Count.</summary>
private int _stealCount;
/// <summary>The current queue operation. Used to quiesce before performing operations from one thread onto another.</summary>
internal volatile int _currentOp;
internal volatile Operation _currentOp;
/// <summary>true if this queue's lock is held as part of a global freeze.</summary>
internal bool _frozen;
/// <summary>Next queue in the <see cref="ConcurrentBag{T}"/>'s set of thread-local queues.</summary>
@ -726,14 +726,14 @@ namespace System.Collections.Concurrent
try
{
// Full fence to ensure subsequent reads don't get reordered before this
Interlocked.Exchange(ref _currentOp, (int)Operation.Add);
Interlocked.Exchange(ref _currentOp, Operation.Add);
int tail = _tailIndex;
// Rare corner case (at most once every 2 billion pushes on this thread):
// We're going to increment the tail; if we'll overflow, then we need to reset our counts
if (tail == int.MaxValue)
{
_currentOp = (int)Operation.None; // set back to None temporarily to avoid a deadlock
_currentOp = Operation.None; // set back to None temporarily to avoid a deadlock
lock (this)
{
Debug.Assert(_tailIndex == tail, "No other thread should be changing _tailIndex");
@ -749,7 +749,7 @@ namespace System.Collections.Concurrent
_tailIndex = tail &= _mask;
Debug.Assert(_headIndex - _tailIndex <= 0);
Interlocked.Exchange(ref _currentOp, (int)Operation.Add); // ensure subsequent reads aren't reordered before this
Interlocked.Exchange(ref _currentOp, Operation.Add); // ensure subsequent reads aren't reordered before this
}
}
@ -778,7 +778,7 @@ namespace System.Collections.Concurrent
else
{
// We need to contend with foreign operations (e.g. steals, enumeration, etc.), so we lock.
_currentOp = (int)Operation.None; // set back to None to avoid a deadlock
_currentOp = Operation.None; // set back to None to avoid a deadlock
Monitor.Enter(this, ref lockTaken);
head = _headIndex;
@ -830,7 +830,7 @@ namespace System.Collections.Concurrent
}
finally
{
_currentOp = (int)Operation.None;
_currentOp = Operation.None;
if (lockTaken)
{
Monitor.Exit(this);
@ -874,7 +874,7 @@ namespace System.Collections.Concurrent
// If the read of _headIndex moved before this write to _tailIndex, we could erroneously end up
// popping an element that's concurrently being stolen, leading to the same element being
// dequeued from the bag twice.
_currentOp = (int)Operation.Take;
_currentOp = Operation.Take;
Interlocked.Exchange(ref _tailIndex, --tail);
// If there is no interaction with a steal, we can head down the fast path.
@ -895,7 +895,7 @@ namespace System.Collections.Concurrent
else
{
// Interaction with steals: 0 or 1 elements left.
_currentOp = (int)Operation.None; // set back to None to avoid a deadlock
_currentOp = Operation.None; // set back to None to avoid a deadlock
Monitor.Enter(this, ref lockTaken);
if (_headIndex - tail <= 0)
{
@ -920,7 +920,7 @@ namespace System.Collections.Concurrent
}
finally
{
_currentOp = (int)Operation.None;
_currentOp = Operation.None;
if (lockTaken)
{
Monitor.Exit(this);
@ -975,14 +975,14 @@ namespace System.Collections.Concurrent
// is in progress, as add operations need to accurately count transitions
// from empty to non-empty, and they can only do that if there are no concurrent
// steal operations happening at the time.
if ((head - (_tailIndex - 2) >= 0) && _currentOp == (int)Operation.Add)
if ((head - (_tailIndex - 2) >= 0) && _currentOp == Operation.Add)
{
SpinWait spinner = default;
do
{
spinner.SpinOnce();
}
while (_currentOp == (int)Operation.Add);
while (_currentOp == Operation.Add);
}
// Increment head to tentatively take an element: a full fence is used to ensure the read

View file

@ -1276,11 +1276,11 @@ namespace System.ComponentModel.Tests
{
}
private long _concurrentError = 0;
private volatile bool _concurrentError;
private bool ConcurrentError
{
get => Interlocked.Read(ref _concurrentError) == 1;
set => Interlocked.Exchange(ref _concurrentError, value ? 1 : 0);
get => _concurrentError;
set => Interlocked.Exchange(ref _concurrentError, value);
}
private void ConcurrentTest(TypeWithProperty instance)

View file

@ -128,7 +128,7 @@ namespace System.IO.Compression
if (buffer != null)
{
_buffer = null!;
if (!AsyncOperationIsActive)
if (!_activeAsyncOperation)
{
ArrayPool<byte>.Shared.Return(buffer);
}
@ -170,18 +170,19 @@ namespace System.IO.Compression
/// <param name="value">The length of the stream.</param>
public override void SetLength(long value) => throw new NotSupportedException();
private int _activeAsyncOperation; // 1 == true, 0 == false
private bool AsyncOperationIsActive => _activeAsyncOperation != 0;
private volatile bool _activeAsyncOperation;
private void EnsureNoActiveAsyncOperation()
{
if (AsyncOperationIsActive)
if (_activeAsyncOperation)
{
ThrowInvalidBeginCall();
}
}
private void AsyncOperationStarting()
{
if (Interlocked.Exchange(ref _activeAsyncOperation, 1) != 0)
if (Interlocked.Exchange(ref _activeAsyncOperation, true))
{
ThrowInvalidBeginCall();
}
@ -189,8 +190,8 @@ namespace System.IO.Compression
private void AsyncOperationCompleting()
{
Debug.Assert(_activeAsyncOperation == 1);
Volatile.Write(ref _activeAsyncOperation, 0);
Debug.Assert(_activeAsyncOperation);
_activeAsyncOperation = false;
}
private static void ThrowInvalidBeginCall() =>

View file

@ -20,7 +20,7 @@ namespace System.IO.Compression
private Inflater? _inflater;
private Deflater? _deflater;
private byte[]? _buffer;
private int _activeAsyncOperation; // 1 == true, 0 == false
private volatile bool _activeAsyncOperation;
private bool _wroteBytes;
internal DeflateStream(Stream stream, CompressionMode mode, long uncompressedSize) : this(stream, mode, leaveOpen: false, ZLibNative.Deflate_DefaultWindowBits, uncompressedSize)
@ -698,7 +698,7 @@ namespace System.IO.Compression
if (buffer != null)
{
_buffer = null;
if (!AsyncOperationIsActive)
if (!_activeAsyncOperation)
{
ArrayPool<byte>.Shared.Return(buffer);
}
@ -751,7 +751,7 @@ namespace System.IO.Compression
if (buffer != null)
{
_buffer = null;
if (!AsyncOperationIsActive)
if (!_activeAsyncOperation)
{
ArrayPool<byte>.Shared.Return(buffer);
}
@ -1059,24 +1059,27 @@ namespace System.IO.Compression
public override void SetLength(long value) { throw new NotSupportedException(); }
}
private bool AsyncOperationIsActive => _activeAsyncOperation != 0;
private void EnsureNoActiveAsyncOperation()
{
if (AsyncOperationIsActive)
ThrowInvalidBeginCall();
}
private void AsyncOperationStarting()
{
if (Interlocked.Exchange(ref _activeAsyncOperation, 1) != 0)
if (_activeAsyncOperation)
{
ThrowInvalidBeginCall();
}
}
private void AsyncOperationCompleting() =>
Volatile.Write(ref _activeAsyncOperation, 0);
private void AsyncOperationStarting()
{
if (Interlocked.Exchange(ref _activeAsyncOperation, true))
{
ThrowInvalidBeginCall();
}
}
private void AsyncOperationCompleting()
{
Debug.Assert(_activeAsyncOperation);
_activeAsyncOperation = false;
}
private static void ThrowInvalidBeginCall() =>
throw new InvalidOperationException(SR.InvalidBeginCall);

View file

@ -86,10 +86,10 @@ namespace System.Linq.Parallel
private ManualResetEventSlim? _producerEvent;
private IntValueEvent? _consumerEvent;
// These two-valued ints track whether a producer or consumer _might_ be waiting. They are marked
// These bools track whether a producer or consumer _might_ be waiting. They are marked
// volatile because they are used in synchronization critical regions of code (see usage below).
private volatile int _producerIsWaiting;
private volatile int _consumerIsWaiting;
private volatile bool _producerIsWaiting;
private volatile bool _consumerIsWaiting;
private readonly CancellationToken _cancellationToken;
//-----------------------------------------------------------------------------------
@ -309,11 +309,11 @@ namespace System.Linq.Parallel
// our producer index doesn't pass the read of the consumer waiting flags; the CLR memory
// model unfortunately permits this reordering. That is handled by using a CAS above.)
if (_consumerIsWaiting == 1 && !IsChunkBufferEmpty)
if (_consumerIsWaiting && !IsChunkBufferEmpty)
{
TraceHelpers.TraceInfo("AsynchronousChannel::EnqueueChunk - producer waking consumer");
Debug.Assert(_consumerEvent != null);
_consumerIsWaiting = 0;
_consumerIsWaiting = false;
_consumerEvent.Set(_index);
}
}
@ -341,7 +341,7 @@ namespace System.Linq.Parallel
// very quickly, suddenly seeing an empty queue. This would lead to deadlock
// if we aren't careful. Therefore we check the empty/full state AGAIN after
// setting our flag to see if a real wait is warranted.
Interlocked.Exchange(ref _producerIsWaiting, 1);
Interlocked.Exchange(ref _producerIsWaiting, true);
// (We have to prevent the reads that go into determining whether the buffer
// is full from moving before the write to the producer-wait flag. Hence the CAS.)
@ -359,7 +359,7 @@ namespace System.Linq.Parallel
else
{
// Reset the flags, we don't actually have to wait after all.
_producerIsWaiting = 0;
_producerIsWaiting = false;
}
}
while (IsFull);
@ -558,7 +558,7 @@ namespace System.Linq.Parallel
// very quickly, suddenly seeing a full queue. This would lead to deadlock
// if we aren't careful. Therefore we check the empty/full state AGAIN after
// setting our flag to see if a real wait is warranted.
Interlocked.Exchange(ref _consumerIsWaiting, 1);
Interlocked.Exchange(ref _consumerIsWaiting, true);
// (We have to prevent the reads that go into determining whether the buffer
// is full from moving before the write to the producer-wait flag. Hence the CAS.)
@ -580,7 +580,7 @@ namespace System.Linq.Parallel
{
// Reset the wait flags, we don't need to wait after all. We loop back around
// and recheck that the queue isn't empty, done, etc.
_consumerIsWaiting = 0;
_consumerIsWaiting = false;
}
}
@ -624,11 +624,11 @@ namespace System.Linq.Parallel
// that the write to _consumerBufferIndex doesn't pass the read of the wait-flags; the CLR memory
// model sadly permits this reordering. Hence the CAS above.)
if (_producerIsWaiting == 1 && !IsFull)
if (_producerIsWaiting && !IsFull)
{
TraceHelpers.TraceInfo("BoundedSingleLockFreeChannel::DequeueChunk - consumer waking producer");
Debug.Assert(_producerEvent != null);
_producerIsWaiting = 0;
_producerIsWaiting = false;
_producerEvent.Set();
}
@ -643,7 +643,7 @@ namespace System.Linq.Parallel
internal void DoneWithDequeueWait()
{
// On our way out, be sure to reset the flags.
_consumerIsWaiting = 0;
_consumerIsWaiting = false;
}
//-----------------------------------------------------------------------------------

View file

@ -38,10 +38,10 @@ namespace System.Net.Http
// https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.4.1-2.2.1
private uint _maxHeaderListSize = uint.MaxValue; // Defaults to infinite
// Once the server's streams are received, these are set to 1. Further receipt of these streams results in a connection error.
private int _haveServerControlStream;
private int _haveServerQpackDecodeStream;
private int _haveServerQpackEncodeStream;
// Once the server's streams are received, these are set to true. Further receipt of these streams results in a connection error.
private bool _haveServerControlStream;
private bool _haveServerQpackDecodeStream;
private bool _haveServerQpackEncodeStream;
// A connection-level error will abort any future operations.
private Exception? _abortException;
@ -598,7 +598,7 @@ namespace System.Net.Http
switch (buffer.ActiveSpan[0])
{
case (byte)Http3StreamType.Control:
if (Interlocked.Exchange(ref _haveServerControlStream, 1) != 0)
if (Interlocked.Exchange(ref _haveServerControlStream, true))
{
// A second control stream has been received.
throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.StreamCreationError);
@ -614,7 +614,7 @@ namespace System.Net.Http
await ProcessServerControlStreamAsync(stream, bufferCopy).ConfigureAwait(false);
return;
case (byte)Http3StreamType.QPackDecoder:
if (Interlocked.Exchange(ref _haveServerQpackDecodeStream, 1) != 0)
if (Interlocked.Exchange(ref _haveServerQpackDecodeStream, true))
{
// A second QPack decode stream has been received.
throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.StreamCreationError);
@ -625,7 +625,7 @@ namespace System.Net.Http
await stream.CopyToAsync(Stream.Null).ConfigureAwait(false);
return;
case (byte)Http3StreamType.QPackEncoder:
if (Interlocked.Exchange(ref _haveServerQpackEncodeStream, 1) != 0)
if (Interlocked.Exchange(ref _haveServerQpackEncodeStream, true))
{
// A second QPack encode stream has been received.
throw HttpProtocolException.CreateHttp3ConnectionException(Http3ErrorCode.StreamCreationError);

View file

@ -67,8 +67,7 @@ namespace System.Net.Http
private bool _canRetry;
private bool _connectionClose; // Connection: close was seen on last response
private const int Status_Disposed = 1;
private int _disposed;
private volatile bool _disposed;
public HttpConnection(
HttpConnectionPool pool,
@ -98,7 +97,7 @@ namespace System.Net.Http
{
// Ensure we're only disposed once. Dispose could be called concurrently, for example,
// if the request and the response were running concurrently and both incurred an exception.
if (Interlocked.Exchange(ref _disposed, Status_Disposed) != Status_Disposed)
if (!Interlocked.Exchange(ref _disposed, true))
{
if (NetEventSource.Log.IsEnabled()) Trace("Connection closing.");
@ -873,7 +872,7 @@ namespace System.Net.Http
// In case the connection is disposed, it's most probable that
// expect100Continue timer expired and request content sending failed.
// We're awaiting the task to propagate the exception in this case.
if (Volatile.Read(ref _disposed) == Status_Disposed)
if (_disposed)
{
try
{

View file

@ -11,13 +11,13 @@ namespace System.Net.Http
{
internal abstract class HttpContentReadStream : HttpContentStream
{
private int _disposed; // 0==no, 1==yes
private bool _disposed;
public HttpContentReadStream(HttpConnection connection) : base(connection)
{
}
public sealed override bool CanRead => _disposed == 0;
public sealed override bool CanRead => !_disposed;
public sealed override bool CanWrite => false;
public sealed override void Write(ReadOnlySpan<byte> buffer) => throw new NotSupportedException(SR.net_http_content_readonly_stream);
@ -26,7 +26,7 @@ namespace System.Net.Http
public virtual bool NeedsDrain => false;
protected bool IsDisposed => _disposed == 1;
protected bool IsDisposed => _disposed;
protected bool CanReadFromConnection
{
@ -35,7 +35,7 @@ namespace System.Net.Http
// _connection == null typically means that we have finished reading the response.
// Cancellation may lead to a state where a disposed _connection is not null.
HttpConnection? connection = _connection;
return connection != null && connection._disposed != Status_Disposed;
return connection != null && !connection._disposed;
}
}
@ -52,7 +52,7 @@ namespace System.Net.Http
// response stream and response content) will kick off multiple concurrent draining
// operations. Also don't delegate to the base if Dispose has already been called,
// as doing so will end up disposing of the connection before we're done draining.
if (Interlocked.Exchange(ref _disposed, 1) != 0)
if (Interlocked.Exchange(ref _disposed, true))
{
return;
}

View file

@ -1535,7 +1535,15 @@ namespace System.Net
private readonly ulong _connectionId;
private readonly HttpListenerSession _listenerSession;
private readonly NativeOverlapped* _nativeOverlapped;
private int _ownershipState; // 0 = normal, 1 = in HandleAuthentication(), 2 = disconnected, 3 = cleaned up
private OwnershipState _ownershipState;
private enum OwnershipState
{
Normal,
InHandleAuthentication,
Disconnected,
CleanedUp
}
internal NativeOverlapped* NativeOverlapped
{
@ -1577,7 +1585,7 @@ namespace System.Net
internal unsafe DisconnectAsyncResult(HttpListenerSession session, ulong connectionId)
{
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"HttpListener: {session.Listener}, ConnectionId: {connectionId}");
_ownershipState = 1;
_ownershipState = OwnershipState.InHandleAuthentication;
_listenerSession = session;
_connectionId = connectionId;
@ -1588,23 +1596,23 @@ namespace System.Net
internal bool StartOwningDisconnectHandling()
{
int oldValue;
OwnershipState oldValue;
SpinWait spin = default;
while ((oldValue = Interlocked.CompareExchange(ref _ownershipState, 1, 0)) == 2)
while ((oldValue = Interlocked.CompareExchange(ref _ownershipState, OwnershipState.InHandleAuthentication, OwnershipState.Normal)) == OwnershipState.Disconnected)
{
// Must block until it equals 3 - we must be in the callback right now.
spin.SpinOnce();
}
Debug.Assert(oldValue != 1, "StartOwningDisconnectHandling called twice.");
return oldValue < 2;
Debug.Assert(oldValue != OwnershipState.InHandleAuthentication, "StartOwningDisconnectHandling called twice.");
return oldValue < OwnershipState.Disconnected;
}
internal void FinishOwningDisconnectHandling()
{
// If it got disconnected, run the disconnect code.
if (Interlocked.CompareExchange(ref _ownershipState, 0, 1) == 2)
if (Interlocked.CompareExchange(ref _ownershipState, OwnershipState.Normal, OwnershipState.InHandleAuthentication) == OwnershipState.Disconnected)
{
HandleDisconnect();
}
@ -1620,7 +1628,7 @@ namespace System.Net
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, "_connectionId:" + asyncResult._connectionId);
asyncResult._listenerSession.RequestQueueBoundHandle.FreeNativeOverlapped(nativeOverlapped);
if (Interlocked.Exchange(ref asyncResult._ownershipState, 2) == 0)
if (Interlocked.Exchange(ref asyncResult._ownershipState, OwnershipState.Disconnected) == OwnershipState.Normal)
{
asyncResult.HandleDisconnect();
}
@ -1655,8 +1663,8 @@ namespace System.Net
identity.Dispose();
}
int oldValue = Interlocked.Exchange(ref _ownershipState, 3);
Debug.Assert(oldValue == 2, $"Expected OwnershipState of 2, saw {oldValue}.");
OwnershipState oldValue = Interlocked.Exchange(ref _ownershipState, OwnershipState.CleanedUp);
Debug.Assert(oldValue == OwnershipState.Disconnected, $"Expected OwnershipState of Disconnected, saw {oldValue}.");
}
internal WindowsPrincipal? AuthenticatedConnection { get; set; }

View file

@ -50,7 +50,7 @@ namespace System.Net.WebSockets
private volatile WebSocketOperation.CloseOutputOperation? _closeOutputOperation;
private Nullable<WebSocketCloseStatus> _closeStatus;
private string? _closeStatusDescription;
private int _receiveState;
private ReceiveState _receiveState;
private Exception? _pendingException;
protected WebSocketBase(Stream innerStream,
@ -1199,9 +1199,9 @@ namespace System.Net.WebSockets
ObjectDisposedException.ThrowIf(_isDisposed, this);
}
private void UpdateReceiveState(int newReceiveState, int expectedReceiveState)
private void UpdateReceiveState(ReceiveState newReceiveState, ReceiveState expectedReceiveState)
{
int receiveState;
ReceiveState receiveState;
if ((receiveState = Interlocked.Exchange(ref _receiveState, newReceiveState)) != expectedReceiveState)
{
Debug.Fail($"'_receiveState' had an invalid value '{receiveState}'. The expected value was '{expectedReceiveState}'.");
@ -1588,7 +1588,7 @@ namespace System.Net.WebSockets
public sealed class ReceiveOperation : WebSocketOperation
{
private int _receiveState;
private ReceiveState _receiveState;
private bool _pongReceived;
private bool _receiveCompleted;
@ -1614,8 +1614,7 @@ namespace System.Net.WebSockets
_receiveCompleted = false;
_webSocket.ThrowIfDisposed();
int originalReceiveState = Interlocked.CompareExchange(ref _webSocket._receiveState,
ReceiveState.Application, ReceiveState.Idle);
ReceiveState originalReceiveState = Interlocked.CompareExchange(ref _webSocket._receiveState, ReceiveState.Application, ReceiveState.Idle);
switch (originalReceiveState)
{
@ -1709,7 +1708,7 @@ namespace System.Net.WebSockets
{
ArraySegment<byte> payload;
WebSocketMessageType messageType = GetMessageType(bufferType);
int newReceiveState = ReceiveState.Idle;
ReceiveState newReceiveState = ReceiveState.Idle;
if (bufferType == WebSocketProtocolComponent.BufferType.Close)
{
@ -2156,12 +2155,12 @@ namespace System.Net.WebSockets
Task CloseNetworkConnectionAsync(CancellationToken cancellationToken);
}
private static class ReceiveState
private enum ReceiveState
{
internal const int SendOperation = -1;
internal const int Idle = 0;
internal const int Application = 1;
internal const int PayloadAvailable = 2;
SendOperation = -1,
Idle = 0,
Application = 1,
PayloadAvailable = 2,
}
}
}

View file

@ -44,7 +44,7 @@ namespace System.Net.WebSockets
private ArraySegment<byte> _pinnedSendBuffer;
private GCHandle _pinnedSendBufferHandle;
private int _stateWhenDisposing = int.MinValue;
private int _sendBufferState;
private SendBufferState _sendBufferState;
private WebSocketBuffer(ArraySegment<byte> internalBuffer, int receiveBufferSize, int sendBufferSize)
{
@ -170,7 +170,7 @@ namespace System.Net.WebSockets
{
bufferHasBeenPinned = false;
WebSocketValidate.ValidateBuffer(payload.Array!, payload.Offset, payload.Count);
int previousState = Interlocked.Exchange(ref _sendBufferState, SendBufferState.SendPayloadSpecified);
SendBufferState previousState = Interlocked.Exchange(ref _sendBufferState, SendBufferState.SendPayloadSpecified);
if (previousState != SendBufferState.None)
{
@ -274,7 +274,7 @@ namespace System.Net.WebSockets
// This method is only thread safe for races between Abort and at most 1 uncompleted send operation
internal void ReleasePinnedSendBuffer()
{
int previousState = Interlocked.Exchange(ref _sendBufferState, SendBufferState.None);
SendBufferState previousState = Interlocked.Exchange(ref _sendBufferState, SendBufferState.None);
if (previousState != SendBufferState.SendPayloadSpecified)
{
@ -662,10 +662,10 @@ namespace System.Net.WebSockets
return 2 * receiveBufferSize + nativeSendBufferSize + NativeOverheadBufferSize + s_PropertyBufferSize;
}
private static class SendBufferState
private enum SendBufferState
{
public const int None = 0;
public const int SendPayloadSpecified = 1;
None = 0,
SendPayloadSpecified = 1,
}
private sealed class PayloadReceiveResult

View file

@ -30,7 +30,7 @@ namespace System.Net.WebSockets
private HttpListenerAsyncEventArgs? _readEventArgs;
private TaskCompletionSource? _writeTaskCompletionSource;
private TaskCompletionSource<int>? _readTaskCompletionSource;
private int _cleanedUp;
private bool _cleanedUp;
#if DEBUG
private sealed class OutstandingOperations
@ -595,7 +595,7 @@ namespace System.Net.WebSockets
protected override void Dispose(bool disposing)
{
if (disposing && Interlocked.Exchange(ref _cleanedUp, 1) == 0)
if (disposing && !Interlocked.Exchange(ref _cleanedUp, true))
{
_readTaskCompletionSource?.TrySetCanceled();
@ -716,10 +716,14 @@ namespace System.Net.WebSockets
internal sealed class HttpListenerAsyncEventArgs : EventArgs, IDisposable
{
private const int Free = 0;
private const int InProgress = 1;
private const int Disposed = 2;
private int _operating;
private OperatingState _operating;
private enum OperatingState
{
Free = 0,
InProgress = 1,
Disposed = 2,
}
private bool _disposeCalled;
private unsafe NativeOverlapped* _ptrNativeOverlapped;
@ -783,7 +787,7 @@ namespace System.Net.WebSockets
Debug.Assert(!_shouldCloseOutput, "'m_ShouldCloseOutput' MUST be 'false' at this point.");
Debug.Assert(value == null || _buffer == null,
"Either 'm_Buffer' or 'm_BufferList' MUST be NULL.");
Debug.Assert(_operating == Free,
Debug.Assert(_operating == OperatingState.Free,
"This property can only be modified if no IO operation is outstanding.");
Debug.Assert(value == null || value.Count == 2,
"This list can only be 'NULL' or MUST have exactly '2' items.");
@ -883,7 +887,7 @@ namespace System.Net.WebSockets
_disposeCalled = true;
// Check if this object is in-use for an async socket operation.
if (Interlocked.CompareExchange(ref _operating, Disposed, Free) != Free)
if (Interlocked.CompareExchange(ref _operating, OperatingState.Disposed, OperatingState.Free) != OperatingState.Free)
{
// Either already disposed or will be disposed when current operation completes.
return;
@ -930,7 +934,7 @@ namespace System.Net.WebSockets
internal void StartOperationCommon(ThreadPoolBoundHandle boundHandle)
{
// Change status to "in-use".
if (Interlocked.CompareExchange(ref _operating, InProgress, Free) != Free)
if (Interlocked.CompareExchange(ref _operating, OperatingState.InProgress, OperatingState.Free) != OperatingState.Free)
{
// If it was already "in-use" check if Dispose was called.
ObjectDisposedException.ThrowIf(_disposeCalled, this);
@ -1039,7 +1043,7 @@ namespace System.Net.WebSockets
{
FreeOverlapped(false);
// Mark as not in-use
Interlocked.Exchange(ref _operating, Free);
Interlocked.Exchange(ref _operating, OperatingState.Free);
// Check for deferred Dispose().
// The deferred Dispose is not guaranteed if Dispose is called while an operation is in progress.

View file

@ -739,7 +739,7 @@ namespace System.Net.Mail
CancellationTokenRegistration ctr = default;
// Indicates whether the CTR has been set - captured in handler
int state = 0;
bool ctrSet = false;
// Register a handler that will transfer completion results to the TCS Task
SendCompletedEventHandler? handler = null;
@ -750,7 +750,7 @@ namespace System.Net.Mail
try
{
((SmtpClient)sender).SendCompleted -= handler;
if (Interlocked.Exchange(ref state, 1) != 0)
if (Interlocked.Exchange(ref ctrSet, true))
{
// A CTR has been set, we have to wait until it completes before completing the task
ctr.Dispose();
@ -783,7 +783,7 @@ namespace System.Net.Mail
((SmtpClient)s!).SendAsyncCancel();
}, this);
if (Interlocked.Exchange(ref state, 1) != 0)
if (Interlocked.Exchange(ref ctrSet, true))
{
// SendCompleted was already invoked, ensure the CTR completes before returning the task
ctr.Dispose();

View file

@ -109,9 +109,9 @@ public sealed partial class QuicConnection : IAsyncDisposable
private readonly MsQuicContextSafeHandle _handle;
/// <summary>
/// Set to non-zero once disposed. Prevents double and/or concurrent disposal.
/// Set to true once disposed. Prevents double and/or concurrent disposal.
/// </summary>
private int _disposed;
private bool _disposed;
private readonly ValueTaskSource _connectedTcs = new ValueTaskSource();
private readonly ResettableValueTaskSource _shutdownTcs = new ResettableValueTaskSource()
@ -502,7 +502,7 @@ public sealed partial class QuicConnection : IAsyncDisposable
/// <returns>An asynchronous task that completes with the opened <see cref="QuicStream" />.</returns>
public async ValueTask<QuicStream> OpenOutboundStreamAsync(QuicStreamType type, CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(_disposed == 1, this);
ObjectDisposedException.ThrowIf(_disposed, this);
QuicStream? stream = null;
try
@ -524,7 +524,7 @@ public sealed partial class QuicConnection : IAsyncDisposable
}
// Propagate ODE if disposed in the meantime.
ObjectDisposedException.ThrowIf(_disposed == 1, this);
ObjectDisposedException.ThrowIf(_disposed, this);
// Propagate connection error when the connection was closed (remotely = ABORTED / locally = INVALID_STATE).
if (ex is QuicException qex && qex.QuicError == QuicError.InternalError &&
@ -544,7 +544,7 @@ public sealed partial class QuicConnection : IAsyncDisposable
/// <returns>An asynchronous task that completes with the accepted <see cref="QuicStream" />.</returns>
public async ValueTask<QuicStream> AcceptInboundStreamAsync(CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(_disposed == 1, this);
ObjectDisposedException.ThrowIf(_disposed, this);
if (!_canAccept)
{
@ -583,7 +583,7 @@ public sealed partial class QuicConnection : IAsyncDisposable
/// <returns>An asynchronous task that completes when the connection is closed.</returns>
public ValueTask CloseAsync(long errorCode, CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(_disposed == 1, this);
ObjectDisposedException.ThrowIf(_disposed, this);
ThrowHelper.ValidateErrorCode(nameof(errorCode), errorCode, $"{nameof(CloseAsync)}.{nameof(errorCode)}");
if (_shutdownTcs.TryGetValueTask(out ValueTask valueTask, this, cancellationToken))
@ -645,7 +645,7 @@ public sealed partial class QuicConnection : IAsyncDisposable
// make sure we log at least some secrets in case of shutdown before handshake completes.
_tlsSecret?.WriteSecret();
Exception exception = ExceptionDispatchInfo.SetCurrentStackTrace(_disposed == 1 ? new ObjectDisposedException(GetType().FullName) : ThrowHelper.GetOperationAbortedException());
Exception exception = ExceptionDispatchInfo.SetCurrentStackTrace(_disposed ? new ObjectDisposedException(GetType().FullName) : ThrowHelper.GetOperationAbortedException());
_connectionCloseTcs.TrySetException(exception);
_acceptQueue.Writer.TryComplete(exception);
_connectedTcs.TrySetException(exception);
@ -783,7 +783,7 @@ public sealed partial class QuicConnection : IAsyncDisposable
/// <returns>A task that represents the asynchronous dispose operation.</returns>
public async ValueTask DisposeAsync()
{
if (Interlocked.Exchange(ref _disposed, 1) != 0)
if (Interlocked.Exchange(ref _disposed, true))
{
return;
}

View file

@ -74,9 +74,9 @@ public sealed partial class QuicListener : IAsyncDisposable
private readonly MsQuicContextSafeHandle _handle;
/// <summary>
/// Set to non-zero once disposed. Prevents double and/or concurrent disposal.
/// Set to true once disposed. Prevents double and/or concurrent disposal.
/// </summary>
private int _disposed;
private bool _disposed;
/// <summary>
/// Completed when SHUTDOWN_COMPLETE arrives.
@ -175,7 +175,7 @@ public sealed partial class QuicListener : IAsyncDisposable
/// <returns>A task that will contain a fully connected <see cref="QuicConnection" /> which successfully finished the handshake and is ready to be used.</returns>
public async ValueTask<QuicConnection> AcceptConnectionAsync(CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(_disposed == 1, this);
ObjectDisposedException.ThrowIf(_disposed, this);
GCHandle keepObject = GCHandle.Alloc(this);
try
@ -405,7 +405,7 @@ public sealed partial class QuicListener : IAsyncDisposable
/// <returns>A task that represents the asynchronous dispose operation.</returns>
public async ValueTask DisposeAsync()
{
if (Interlocked.Exchange(ref _disposed, 1) != 0)
if (Interlocked.Exchange(ref _disposed, true))
{
return;
}

View file

@ -50,12 +50,12 @@ public partial class QuicStream : Stream
{
get
{
ObjectDisposedException.ThrowIf(_disposed == 1, this);
ObjectDisposedException.ThrowIf(_disposed, this);
return (int)_readTimeout.TotalMilliseconds;
}
set
{
ObjectDisposedException.ThrowIf(_disposed == 1, this);
ObjectDisposedException.ThrowIf(_disposed, this);
if (value <= 0 && value != Timeout.Infinite)
{
throw new ArgumentOutOfRangeException(nameof(value), SR.net_quic_timeout_use_gt_zero);
@ -69,12 +69,12 @@ public partial class QuicStream : Stream
{
get
{
ObjectDisposedException.ThrowIf(_disposed == 1, this);
ObjectDisposedException.ThrowIf(_disposed, this);
return (int)_writeTimeout.TotalMilliseconds;
}
set
{
ObjectDisposedException.ThrowIf(_disposed == 1, this);
ObjectDisposedException.ThrowIf(_disposed, this);
if (value <= 0 && value != Timeout.Infinite)
{
throw new ArgumentOutOfRangeException(nameof(value), SR.net_quic_timeout_use_gt_zero);
@ -86,7 +86,7 @@ public partial class QuicStream : Stream
// Read boilerplate.
/// <inheritdoc />
/// <summary>Gets a value indicating whether the <see cref="QuicStream" /> supports reading.</summary>
public override bool CanRead => Volatile.Read(ref _disposed) == 0 && _canRead;
public override bool CanRead => !Volatile.Read(ref _disposed) && _canRead;
/// <inheritdoc />
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state)
@ -113,7 +113,7 @@ public partial class QuicStream : Stream
/// <inheritdoc />
public override int Read(Span<byte> buffer)
{
ObjectDisposedException.ThrowIf(_disposed == 1, this);
ObjectDisposedException.ThrowIf(_disposed, this);
byte[] rentedBuffer = ArrayPool<byte>.Shared.Rent(buffer.Length);
CancellationTokenSource? cts = null;
@ -149,7 +149,7 @@ public partial class QuicStream : Stream
// Write boilerplate.
/// <inheritdoc />
/// <summary>Gets a value indicating whether the <see cref="QuicStream" /> supports writing.</summary>
public override bool CanWrite => Volatile.Read(ref _disposed) == 0 && _canWrite;
public override bool CanWrite => !Volatile.Read(ref _disposed) && _canWrite;
/// <inheritdoc />
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state)
@ -175,7 +175,7 @@ public partial class QuicStream : Stream
/// <inheritdoc />
public override void Write(ReadOnlySpan<byte> buffer)
{
ObjectDisposedException.ThrowIf(_disposed == 1, this);
ObjectDisposedException.ThrowIf(_disposed, this);
CancellationTokenSource? cts = null;
if (_writeTimeout > TimeSpan.Zero)

View file

@ -60,9 +60,9 @@ public sealed partial class QuicStream
private readonly MsQuicContextSafeHandle _handle;
/// <summary>
/// Set to non-zero once disposed. Prevents double and/or concurrent disposal.
/// Set to true once disposed. Prevents double and/or concurrent disposal.
/// </summary>
private int _disposed;
private bool _disposed;
private readonly ValueTaskSource _startedTcs = new ValueTaskSource();
private readonly ValueTaskSource _shutdownTcs = new ValueTaskSource();
@ -272,7 +272,7 @@ public sealed partial class QuicStream
/// <inheritdoc />
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(_disposed == 1, this);
ObjectDisposedException.ThrowIf(_disposed, this);
if (!_canRead)
{
@ -359,7 +359,7 @@ public sealed partial class QuicStream
/// <param name="completeWrites">Notifies the peer about gracefully closing the write side, i.e.: sends FIN flag with the data.</param>
public ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, bool completeWrites, CancellationToken cancellationToken = default)
{
if (_disposed == 1)
if (_disposed)
{
return ValueTask.FromException(ExceptionDispatchInfo.SetCurrentStackTrace(new ObjectDisposedException(nameof(QuicStream))));
}
@ -451,7 +451,7 @@ public sealed partial class QuicStream
/// <param name="errorCode">The error code with which to abort the stream, this value is application protocol (layer above QUIC) dependent.</param>
public void Abort(QuicAbortDirection abortDirection, long errorCode)
{
if (_disposed == 1)
if (_disposed)
{
return;
}
@ -510,7 +510,7 @@ public sealed partial class QuicStream
/// </remarks>
public void CompleteWrites()
{
ObjectDisposedException.ThrowIf(_disposed == 1, this);
ObjectDisposedException.ThrowIf(_disposed, this);
// Nothing to complete, the writing side is already closed.
if (_sendTcs.IsCompleted)
@ -712,7 +712,7 @@ public sealed partial class QuicStream
/// <returns>A task that represents the asynchronous dispose operation.</returns>
public override async ValueTask DisposeAsync()
{
if (Interlocked.Exchange(ref _disposed, 1) != 0)
if (Interlocked.Exchange(ref _disposed, true))
{
return;
}

View file

@ -51,10 +51,10 @@ namespace System.Net
private static int _defaultMaxResponseHeadersLength = HttpHandlerDefaults.DefaultMaxResponseHeadersLength;
private static int _defaultMaximumErrorResponseLength = -1;
private int _beginGetRequestStreamCalled;
private int _beginGetResponseCalled;
private int _endGetRequestStreamCalled;
private int _endGetResponseCalled;
private bool _beginGetRequestStreamCalled;
private bool _beginGetResponseCalled;
private bool _endGetRequestStreamCalled;
private bool _endGetResponseCalled;
private int _maximumAllowedRedirections = HttpHandlerDefaults.DefaultMaxAutomaticRedirections;
private int _maximumResponseHeadersLen = _defaultMaxResponseHeadersLength;
@ -74,7 +74,7 @@ namespace System.Net
private TaskCompletionSource<WebResponse>? _responseOperation;
private AsyncCallback? _requestStreamCallback;
private AsyncCallback? _responseCallback;
private int _abortCalled;
private volatile bool _abortCalled;
private CancellationTokenSource? _sendRequestCts;
private X509CertificateCollection? _clientCertificates;
private Booleans _booleans = Booleans.Default;
@ -995,7 +995,7 @@ namespace System.Net
public override void Abort()
{
if (Interlocked.Exchange(ref _abortCalled, 1) != 0)
if (Interlocked.Exchange(ref _abortCalled, true))
{
return;
}
@ -1125,7 +1125,7 @@ namespace System.Net
{
CheckAbort();
if (Interlocked.Exchange(ref _beginGetRequestStreamCalled, 1) != 0)
if (Interlocked.Exchange(ref _beginGetRequestStreamCalled, true))
{
throw new InvalidOperationException(SR.net_repcall);
}
@ -1147,7 +1147,7 @@ namespace System.Net
throw new ArgumentException(SR.net_io_invalidasyncresult, nameof(asyncResult));
}
if (Interlocked.Exchange(ref _endGetRequestStreamCalled, 1) != 0)
if (Interlocked.Exchange(ref _endGetRequestStreamCalled, true))
{
throw new InvalidOperationException(SR.Format(SR.net_io_invalidendcall, "EndGetRequestStream"));
}
@ -1398,7 +1398,7 @@ namespace System.Net
{
CheckAbort();
if (Interlocked.Exchange(ref _beginGetResponseCalled, 1) != 0)
if (Interlocked.Exchange(ref _beginGetResponseCalled, true))
{
throw new InvalidOperationException(SR.net_repcall);
}
@ -1418,7 +1418,7 @@ namespace System.Net
throw new ArgumentException(SR.net_io_invalidasyncresult, nameof(asyncResult));
}
if (Interlocked.Exchange(ref _endGetResponseCalled, 1) != 0)
if (Interlocked.Exchange(ref _endGetResponseCalled, true))
{
throw new InvalidOperationException(SR.Format(SR.net_io_invalidendcall, "EndGetResponse"));
}
@ -1565,7 +1565,7 @@ namespace System.Net
private void CheckAbort()
{
if (Volatile.Read(ref _abortCalled) == 1)
if (_abortCalled)
{
throw new WebException(SR.net_reqaborted, WebExceptionStatus.RequestCanceled);
}

View file

@ -445,14 +445,14 @@ namespace System.Net
{
internal InfiniteTimer() : base(Timeout.Infinite) { }
private int _cancelled;
private bool _canceled;
internal override bool HasExpired => false;
/// <summary>
/// <para>Cancels the timer. Returns true the first time, false after that.</para>
/// </summary>
internal override bool Cancel() => Interlocked.Exchange(ref _cancelled, 1) == 0;
internal override bool Cancel() => !Interlocked.Exchange(ref _canceled, true);
}
/// <summary>

View file

@ -42,9 +42,9 @@ namespace System.Net.Security
private int _readBufferCount;
private ArrayBufferWriter<byte>? _writeBuffer;
private volatile int _writeInProgress;
private volatile int _readInProgress;
private volatile int _authInProgress;
private volatile bool _writeInProgress;
private volatile bool _readInProgress;
private volatile bool _authInProgress;
private ExceptionDispatchInfo? _exception;
private StreamFramer? _framer;
@ -340,7 +340,7 @@ namespace System.Net.Security
{
Debug.Assert(_context is not null);
if (Interlocked.Exchange(ref _readInProgress, 1) == 1)
if (Interlocked.Exchange(ref _readInProgress, true))
{
throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, "read"));
}
@ -441,7 +441,7 @@ namespace System.Net.Security
}
finally
{
_readInProgress = 0;
_readInProgress = false;
}
static async ValueTask<int> ReadAllAsync(Stream stream, Memory<byte> buffer, bool allowZeroRead, CancellationToken cancellationToken)
@ -506,7 +506,7 @@ namespace System.Net.Security
Debug.Assert(_context is not null);
Debug.Assert(_writeBuffer is not null);
if (Interlocked.Exchange(ref _writeInProgress, 1) == 1)
if (Interlocked.Exchange(ref _writeInProgress, true))
{
throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, "write"));
}
@ -556,7 +556,7 @@ namespace System.Net.Security
finally
{
_writeBuffer.Clear();
_writeInProgress = 0;
_writeInProgress = false;
}
}
@ -724,7 +724,7 @@ namespace System.Net.Security
Debug.Assert(_context != null);
ThrowIfFailed(authSuccessCheck: false);
if (Interlocked.Exchange(ref _authInProgress, 1) == 1)
if (Interlocked.Exchange(ref _authInProgress, true))
{
throw new InvalidOperationException(SR.Format(SR.net_io_invalidnestedcall, "authenticate"));
}
@ -742,7 +742,7 @@ namespace System.Net.Security
}
finally
{
_authInProgress = 0;
_authInProgress = false;
}
}

View file

@ -16,7 +16,7 @@ namespace System.Net.Security
public partial class SslStream
{
private readonly SslAuthenticationOptions _sslAuthenticationOptions = new SslAuthenticationOptions();
private int _nestedAuth;
private NestedState _nestedAuth;
private bool _isRenego;
private TlsFrameHelper.TlsFrameInfo _lastFrame;
@ -32,10 +32,14 @@ namespace System.Net.Security
private bool _receivedEOF;
// Used by Telemetry to ensure we log connection close exactly once
// 0 = no handshake
// 1 = handshake completed, connection opened
// 2 = SslStream disposed, connection closed
private int _connectionOpenedStatus;
private enum ConnectionStatus
{
NoHandshake = 0,
HandshakeCompleted = 1, // connection opened
Disposed = 2, // connection closed
}
private ConnectionStatus _connectionOpenedStatus;
private void SetException(Exception e)
{
@ -56,10 +60,10 @@ namespace System.Net.Security
// Ensure a Read or Auth operation is not in progress,
// block potential future read and auth operations since SslStream is disposing.
// This leaves the _nestedRead = 2 and _nestedAuth = 2, but that's ok, since
// This leaves the _nestedRead = StreamDisposed and _nestedAuth = StreamDisposed, but that's ok, since
// subsequent operations check the _exception sentinel first
if (Interlocked.Exchange(ref _nestedRead, StreamDisposed) == StreamNotInUse &&
Interlocked.Exchange(ref _nestedAuth, StreamDisposed) == StreamNotInUse)
if (Interlocked.Exchange(ref _nestedRead, NestedState.StreamDisposed) == NestedState.StreamNotInUse &&
Interlocked.Exchange(ref _nestedAuth, NestedState.StreamDisposed) == NestedState.StreamNotInUse)
{
_buffer.ReturnBuffer();
}
@ -73,7 +77,7 @@ namespace System.Net.Security
if (NetSecurityTelemetry.Log.IsEnabled())
{
// Set the status to disposed. If it was opened before, log ConnectionClosed
if (Interlocked.Exchange(ref _connectionOpenedStatus, 2) == 1)
if (Interlocked.Exchange(ref _connectionOpenedStatus, ConnectionStatus.Disposed) == ConnectionStatus.HandshakeCompleted)
{
NetSecurityTelemetry.Log.ConnectionClosed(GetSslProtocolInternal());
}
@ -143,9 +147,9 @@ namespace System.Net.Security
if (startingTimestamp is not 0)
{
// SslStream could already have been disposed at this point, in which case _connectionOpenedStatus == 2
// SslStream could already have been disposed at this point, in which case _connectionOpenedStatus == ConnectionStatus.Disposed
// Make sure that we increment the open connection counter only if it is guaranteed to be decremented in dispose/finalize
bool connectionOpen = Interlocked.CompareExchange(ref _connectionOpenedStatus, 1, 0) == 0;
bool connectionOpen = Interlocked.CompareExchange(ref _connectionOpenedStatus, ConnectionStatus.HandshakeCompleted, ConnectionStatus.NoHandshake) == ConnectionStatus.NoHandshake;
SslProtocols protocol = GetSslProtocolInternal();
NetSecurityTelemetry.Log.HandshakeCompleted(protocol, startingTimestamp, connectionOpen);
}
@ -187,22 +191,22 @@ namespace System.Net.Security
private async Task RenegotiateAsync<TIOAdapter>(CancellationToken cancellationToken)
where TIOAdapter : IReadWriteAdapter
{
if (Interlocked.CompareExchange(ref _nestedAuth, StreamInUse, StreamNotInUse) != StreamNotInUse)
if (Interlocked.CompareExchange(ref _nestedAuth, NestedState.StreamInUse, NestedState.StreamNotInUse) != NestedState.StreamNotInUse)
{
ObjectDisposedException.ThrowIf(_nestedAuth == StreamDisposed, this);
ObjectDisposedException.ThrowIf(_nestedAuth == NestedState.StreamDisposed, this);
throw new InvalidOperationException(SR.Format(SR.net_io_invalidnestedcall, "authenticate"));
}
if (Interlocked.CompareExchange(ref _nestedRead, StreamInUse, StreamNotInUse) != StreamNotInUse)
if (Interlocked.CompareExchange(ref _nestedRead, NestedState.StreamInUse, NestedState.StreamNotInUse) != NestedState.StreamNotInUse)
{
ObjectDisposedException.ThrowIf(_nestedRead == StreamDisposed, this);
ObjectDisposedException.ThrowIf(_nestedRead == NestedState.StreamDisposed, this);
throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, "read"));
}
// Write is different since we do not do anything special in Dispose
if (Interlocked.Exchange(ref _nestedWrite, StreamInUse) != StreamNotInUse)
if (Interlocked.Exchange(ref _nestedWrite, NestedState.StreamInUse) != NestedState.StreamNotInUse)
{
_nestedRead = StreamNotInUse;
_nestedRead = NestedState.StreamNotInUse;
throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, "write"));
}
@ -265,8 +269,8 @@ namespace System.Net.Security
token.ReleasePayload();
_nestedRead = StreamNotInUse;
_nestedWrite = StreamNotInUse;
_nestedRead = NestedState.StreamNotInUse;
_nestedWrite = NestedState.StreamNotInUse;
_isRenego = false;
// We will not release _nestedAuth at this point to prevent another renegotiation attempt.
}
@ -284,7 +288,7 @@ namespace System.Net.Security
if (reAuthenticationData == null)
{
// prevent nesting only when authentication functions are called explicitly. e.g. handle renegotiation transparently.
if (Interlocked.Exchange(ref _nestedAuth, StreamInUse) == StreamInUse)
if (Interlocked.Exchange(ref _nestedAuth, NestedState.StreamInUse) == NestedState.StreamInUse)
{
throw new InvalidOperationException(SR.Format(SR.net_io_invalidnestedcall, "authenticate"));
}
@ -378,7 +382,7 @@ namespace System.Net.Security
{
if (reAuthenticationData == null)
{
_nestedAuth = StreamNotInUse;
_nestedAuth = NestedState.StreamNotInUse;
_isRenego = false;
}
@ -547,7 +551,7 @@ namespace System.Net.Security
{
ProcessHandshakeSuccess();
if (_nestedAuth != StreamInUse)
if (_nestedAuth != NestedState.StreamInUse)
{
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, $"Ignoring unsolicited renegotiated certificate.");
// ignore certificates received outside of handshake or requested renegotiation.
@ -797,7 +801,7 @@ namespace System.Net.Security
// If that happen before EncryptData() runs, _handshakeWaiter will be set to null
// and EncryptData() will work normally e.g. no waiting, just exclusion with DecryptData()
if (_sslAuthenticationOptions.AllowRenegotiation || SslProtocol == SslProtocols.Tls13 || _nestedAuth != 0)
if (_sslAuthenticationOptions.AllowRenegotiation || SslProtocol == SslProtocols.Tls13 || _nestedAuth != NestedState.StreamNotInUse)
{
// create TCS only if we plan to proceed. If not, we will throw later outside of the lock.
// Tls1.3 does not have renegotiation. However on Windows this error code is used
@ -820,9 +824,9 @@ namespace System.Net.Security
// Check for disposal is not atomic so we will check again below.
ThrowIfExceptionalOrNotAuthenticated();
if (Interlocked.CompareExchange(ref _nestedRead, StreamInUse, StreamNotInUse) != StreamNotInUse)
if (Interlocked.CompareExchange(ref _nestedRead, NestedState.StreamInUse, NestedState.StreamNotInUse) != NestedState.StreamNotInUse)
{
ObjectDisposedException.ThrowIf(_nestedRead == StreamDisposed, this);
ObjectDisposedException.ThrowIf(_nestedRead == NestedState.StreamDisposed, this);
throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, "read"));
}
@ -947,7 +951,7 @@ namespace System.Net.Security
finally
{
ReturnReadBufferIfEmpty();
_nestedRead = StreamNotInUse;
_nestedRead = NestedState.StreamNotInUse;
}
}
@ -962,7 +966,7 @@ namespace System.Net.Security
return;
}
if (Interlocked.Exchange(ref _nestedWrite, StreamInUse) == StreamInUse)
if (Interlocked.Exchange(ref _nestedWrite, NestedState.StreamInUse) == NestedState.StreamInUse)
{
throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, "write"));
}
@ -985,7 +989,7 @@ namespace System.Net.Security
}
finally
{
_nestedWrite = StreamNotInUse;
_nestedWrite = NestedState.StreamNotInUse;
}
}

View file

@ -167,13 +167,15 @@ namespace System.Net.Security
}
}
// used to track ussage in _nested* variables bellow
private const int StreamNotInUse = 0;
private const int StreamInUse = 1;
private const int StreamDisposed = 2;
private enum NestedState
{
StreamNotInUse = 0,
StreamInUse = 1,
StreamDisposed = 2,
}
private int _nestedWrite;
private int _nestedRead;
private NestedState _nestedWrite;
private NestedState _nestedRead;
private PoolingPointerMemoryManager? _readPointerMemoryManager;
private PoolingPointerMemoryManager? _writePointerMemoryManager;
@ -735,7 +737,7 @@ namespace System.Net.Security
public override int ReadByte()
{
ThrowIfExceptionalOrNotAuthenticated();
if (Interlocked.Exchange(ref _nestedRead, StreamInUse) == StreamInUse)
if (Interlocked.Exchange(ref _nestedRead, NestedState.StreamInUse) == NestedState.StreamInUse)
{
throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, "read"));
}
@ -756,7 +758,7 @@ namespace System.Net.Security
// Regardless of whether we were able to read a byte from the buffer,
// reset the read tracking. If we weren't able to read a byte, the
// subsequent call to Read will set the flag again.
_nestedRead = StreamNotInUse;
_nestedRead = NestedState.StreamNotInUse;
}
// Otherwise, fall back to reading a byte via Read, the same way Stream.ReadByte does.

View file

@ -54,12 +54,12 @@ namespace System.Net.Security
// Without setting (or using) these members you will get a build exception in the unit test project.
// The code that normally uses these in the main solution is in the implementation of SslStream.
if (_nestedWrite == 0)
if (_nestedWrite == NestedState.StreamNotInUse)
{
}
_exception = null;
_nestedWrite = 0;
_nestedWrite = NestedState.StreamNotInUse;
_handshakeCompleted = false;
}

View file

@ -23,8 +23,8 @@ namespace System.Net.Sockets
// Used by the class to indicate that the stream is writable.
private bool _writeable;
// Whether Dispose has been called. 0 == false, 1 == true
private int _disposed;
// Whether Dispose has been called.
private bool _disposed;
// Creates a new instance of the System.Net.Sockets.NetworkStream class for the specified System.Net.Sockets.Socket.
public NetworkStream(Socket socket)
@ -369,7 +369,7 @@ namespace System.Net.Sockets
protected override void Dispose(bool disposing)
{
if (Interlocked.Exchange(ref _disposed, 1) != 0)
if (Interlocked.Exchange(ref _disposed, true))
{
return;
}
@ -685,7 +685,7 @@ namespace System.Net.Sockets
private void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed != 0, this);
ObjectDisposedException.ThrowIf(_disposed, this);
}
private static IOException WrapException(string resourceFormatString, Exception innerException)

View file

@ -28,7 +28,7 @@ namespace System.Net.Sockets
private int _closeSocketThread;
private int _closeSocketTick;
#endif
private int _ownClose;
private bool _ownClose;
/// <summary>
/// Creates a <see cref="T:System.Net.Sockets.SafeSocketHandle" />.
@ -54,8 +54,7 @@ namespace System.Net.Sockets
internal bool HasShutdownSend => _hasShutdownSend;
private bool TryOwnClose()
=> Interlocked.CompareExchange(ref _ownClose, 1, 0) == 0;
private bool TryOwnClose() => !Interlocked.Exchange(ref _ownClose, true);
private volatile bool _released;
private bool _hasShutdownSend;

View file

@ -148,7 +148,7 @@ namespace System.Net.Sockets
return errorCode;
}
if (Volatile.Read(ref _disposed) != 0)
if (Volatile.Read(ref _disposed))
{
_handle.Dispose();
throw new ObjectDisposedException(GetType().FullName);

View file

@ -67,7 +67,7 @@ namespace System.Net.Sockets
private bool _receivingPacketInformation;
private int _closeTimeout = Socket.DefaultCloseTimeout;
private int _disposed; // 0 == false, anything else == true
private bool _disposed;
public Socket(SocketType socketType, ProtocolType protocolType)
: this(OSSupportsIPv6 ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork, socketType, protocolType)
@ -3148,7 +3148,7 @@ namespace System.Net.Sockets
// Internal and private properties
//
internal bool Disposed => _disposed != 0;
internal bool Disposed => _disposed;
//
// Internal and private methods
@ -3240,7 +3240,7 @@ namespace System.Net.Sockets
}
// Make sure we're the first call to Dispose
if (Interlocked.CompareExchange(ref _disposed, 1, 0) == 1)
if (Interlocked.Exchange(ref _disposed, true))
{
return;
}

View file

@ -119,10 +119,10 @@ namespace System.Net.Sockets
Canceled
}
private int _state; // Actually AsyncOperation.State.
private volatile AsyncOperation.State _state;
#if DEBUG
private int _callbackQueued; // When non-zero, the callback has been queued.
private bool _callbackQueued; // When true, the callback has been queued.
#endif
public readonly SocketAsyncContext AssociatedContext;
@ -141,11 +141,11 @@ namespace System.Net.Sockets
public void Reset()
{
_state = (int)State.Waiting;
_state = State.Waiting;
Event = null;
Next = this;
#if DEBUG
_callbackQueued = 0;
_callbackQueued = false;
#endif
}
@ -154,34 +154,34 @@ namespace System.Net.Sockets
TraceWithContext(context, "Enter");
// Set state to Running, unless we've been canceled
int oldState = Interlocked.CompareExchange(ref _state, (int)State.Running, (int)State.Waiting);
if (oldState == (int)State.Canceled)
State oldState = Interlocked.CompareExchange(ref _state, State.Running, State.Waiting);
if (oldState == State.Canceled)
{
TraceWithContext(context, "Exit, Previously canceled");
return OperationResult.Cancelled;
}
Debug.Assert(oldState == (int)State.Waiting, $"Unexpected operation state: {(State)oldState}");
Debug.Assert(oldState == State.Waiting, $"Unexpected operation state: {(State)oldState}");
// Try to perform the IO
if (DoTryComplete(context))
{
Debug.Assert((State)Volatile.Read(ref _state) is State.Running or State.RunningWithPendingCancellation, "Unexpected operation state");
Debug.Assert(_state is State.Running or State.RunningWithPendingCancellation, "Unexpected operation state");
Volatile.Write(ref _state, (int)State.Complete);
_state = State.Complete;
TraceWithContext(context, "Exit, Completed");
return OperationResult.Completed;
}
// Set state back to Waiting, unless we were canceled, in which case we have to process cancellation now
int newState;
State newState;
while (true)
{
int state = Volatile.Read(ref _state);
Debug.Assert(state is (int)State.Running or (int)State.RunningWithPendingCancellation, $"Unexpected operation state: {(State)state}");
State state = _state;
Debug.Assert(state is State.Running or State.RunningWithPendingCancellation, $"Unexpected operation state: {(State)state}");
newState = (state == (int)State.Running ? (int)State.Waiting : (int)State.Canceled);
newState = (state == State.Running ? State.Waiting : State.Canceled);
if (state == Interlocked.CompareExchange(ref _state, newState, state))
{
break;
@ -190,7 +190,7 @@ namespace System.Net.Sockets
// Race to update the state. Loop and try again.
}
if (newState == (int)State.Canceled)
if (newState == State.Canceled)
{
ProcessCancellation();
TraceWithContext(context, "Exit, Newly cancelled");
@ -208,16 +208,16 @@ namespace System.Net.Sockets
// Note we could be cancelling because of socket close. Regardless, we don't need the registration anymore.
CancellationRegistration.Dispose();
int newState;
State newState;
while (true)
{
int state = Volatile.Read(ref _state);
if (state is (int)State.Complete or (int)State.Canceled or (int)State.RunningWithPendingCancellation)
State state = _state;
if (state is State.Complete or State.Canceled or State.RunningWithPendingCancellation)
{
return false;
}
newState = (state == (int)State.Waiting ? (int)State.Canceled : (int)State.RunningWithPendingCancellation);
newState = (state == State.Waiting ? State.Canceled : State.RunningWithPendingCancellation);
if (state == Interlocked.CompareExchange(ref _state, newState, state))
{
break;
@ -226,7 +226,7 @@ namespace System.Net.Sockets
// Race to update the state. Loop and try again.
}
if (newState == (int)State.RunningWithPendingCancellation)
if (newState == State.RunningWithPendingCancellation)
{
// TryComplete will either succeed, or it will see the pending cancellation and deal with it.
return false;
@ -243,7 +243,7 @@ namespace System.Net.Sockets
{
Trace("Enter");
Debug.Assert(_state == (int)State.Canceled);
Debug.Assert(_state == State.Canceled);
ErrorCode = SocketError.OperationAborted;
@ -255,7 +255,7 @@ namespace System.Net.Sockets
else
{
#if DEBUG
Debug.Assert(Interlocked.CompareExchange(ref _callbackQueued, 1, 0) == 0, $"Unexpected _callbackQueued: {_callbackQueued}");
Debug.Assert(!Interlocked.Exchange(ref _callbackQueued, true), $"Unexpected _callbackQueued: {_callbackQueued}");
#endif
// We've marked the operation as canceled, and so should invoke the callback, but
// we can't pool the object, as ProcessQueue may still have a reference to it, due to

View file

@ -103,7 +103,7 @@ namespace System.Net.Sockets
Scheduled
}
private int _eventQueueProcessingStage;
private EventQueueProcessingStage _eventQueueProcessingStage;
//
// Registers the Socket with a SocketAsyncEngine, and returns the associated engine.
@ -206,7 +206,7 @@ namespace System.Net.Sockets
if (handler.HandleSocketEvents(numEvents) &&
Interlocked.Exchange(
ref _eventQueueProcessingStage,
(int)EventQueueProcessingStage.Scheduled) == (int)EventQueueProcessingStage.NotScheduled)
EventQueueProcessingStage.Scheduled) == EventQueueProcessingStage.NotScheduled)
{
ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false);
}
@ -223,20 +223,20 @@ namespace System.Net.Sockets
if (!isEventQueueEmpty)
{
// There are more events to process, set stage to Scheduled and enqueue a work item.
_eventQueueProcessingStage = (int)EventQueueProcessingStage.Scheduled;
_eventQueueProcessingStage = EventQueueProcessingStage.Scheduled;
}
else
{
// The stage here would be Scheduled if an enqueuer has enqueued work and changed the stage, or Determining
// otherwise. If the stage is Determining, there's no more work to do. If the stage is Scheduled, the enqueuer
// would not have scheduled a work item to process the work, so schedule one now.
int stageBeforeUpdate =
EventQueueProcessingStage stageBeforeUpdate =
Interlocked.CompareExchange(
ref _eventQueueProcessingStage,
(int)EventQueueProcessingStage.NotScheduled,
(int)EventQueueProcessingStage.Determining);
Debug.Assert(stageBeforeUpdate != (int)EventQueueProcessingStage.NotScheduled);
if (stageBeforeUpdate == (int)EventQueueProcessingStage.Determining)
EventQueueProcessingStage.NotScheduled,
EventQueueProcessingStage.Determining);
Debug.Assert(stageBeforeUpdate != EventQueueProcessingStage.NotScheduled);
if (stageBeforeUpdate == EventQueueProcessingStage.Determining)
{
return;
}
@ -251,14 +251,14 @@ namespace System.Net.Sockets
SocketIOEvent ev;
while (true)
{
Debug.Assert(_eventQueueProcessingStage == (int)EventQueueProcessingStage.Scheduled);
Debug.Assert(_eventQueueProcessingStage == EventQueueProcessingStage.Scheduled);
// The change needs to be visible to other threads that may request a worker thread before a work item is attempted
// to be dequeued by the current thread. In particular, if an enqueuer queues a work item and does not request a
// thread because it sees a Determining or Scheduled stage, and the current thread is the last thread processing
// work items, the current thread must either see the work item queued by the enqueuer, or it must see a stage of
// Scheduled, and try to dequeue again or request another thread.
_eventQueueProcessingStage = (int)EventQueueProcessingStage.Determining;
_eventQueueProcessingStage = EventQueueProcessingStage.Determining;
Interlocked.MemoryBarrier();
if (eventQueue.TryDequeue(out ev))
@ -269,13 +269,13 @@ namespace System.Net.Sockets
// The stage here would be Scheduled if an enqueuer has enqueued work and changed the stage, or Determining
// otherwise. If the stage is Determining, there's no more work to do. If the stage is Scheduled, the enqueuer
// would not have scheduled a work item to process the work, so try to dequeue a work item again.
int stageBeforeUpdate =
EventQueueProcessingStage stageBeforeUpdate =
Interlocked.CompareExchange(
ref _eventQueueProcessingStage,
(int)EventQueueProcessingStage.NotScheduled,
(int)EventQueueProcessingStage.Determining);
Debug.Assert(stageBeforeUpdate != (int)EventQueueProcessingStage.NotScheduled);
if (stageBeforeUpdate == (int)EventQueueProcessingStage.Determining)
EventQueueProcessingStage.NotScheduled,
EventQueueProcessingStage.Determining);
Debug.Assert(stageBeforeUpdate != EventQueueProcessingStage.NotScheduled);
if (stageBeforeUpdate == EventQueueProcessingStage.Determining)
{
return;
}

View file

@ -100,7 +100,7 @@ namespace System.Net.Sockets
private unsafe NativeOverlapped* AllocateNativeOverlapped()
{
Debug.Assert(OperatingSystem.IsWindows());
Debug.Assert(_operating == InProgress, $"Expected {nameof(_operating)} == {nameof(InProgress)}, got {_operating}");
Debug.Assert(_operating == OperationState.InProgress, $"Expected {nameof(_operating)} == {nameof(OperationState.InProgress)}, got {_operating}");
Debug.Assert(_currentSocket != null, "_currentSocket is null");
Debug.Assert(_currentSocket.SafeHandle != null, "_currentSocket.SafeHandle is null");
Debug.Assert(_preAllocatedOverlapped != null, "_preAllocatedOverlapped is null");
@ -113,7 +113,7 @@ namespace System.Net.Sockets
{
Debug.Assert(OperatingSystem.IsWindows());
Debug.Assert(overlapped != null, "overlapped is null");
Debug.Assert(_operating == InProgress, $"Expected _operating == InProgress, got {_operating}");
Debug.Assert(_operating == OperationState.InProgress, $"Expected _operating == OperationState.InProgress, got {_operating}");
Debug.Assert(_currentSocket != null, "_currentSocket is null");
Debug.Assert(_currentSocket.SafeHandle != null, "_currentSocket.SafeHandle is null");
Debug.Assert(_currentSocket.SafeHandle.IOCPBoundHandle != null, "_currentSocket.SafeHandle.IOCPBoundHandle is null");

View file

@ -77,12 +77,14 @@ namespace System.Net.Sockets
private bool _userSocket; // if false when performing Connect, _currentSocket should be disposed
private bool _disposeCalled;
// Controls thread safety via Interlocked.
private const int Configuring = -1;
private const int Free = 0;
private const int InProgress = 1;
private const int Disposed = 2;
private int _operating;
private enum OperationState
{
Configuring = -1,
Free = 0,
InProgress = 1,
Disposed = 2,
}
private OperationState _operating;
private CancellationTokenSource? _multipleConnectCancellation;
@ -480,7 +482,7 @@ namespace System.Net.Sockets
_context = null;
// Mark as not in-use.
_operating = Free;
_operating = OperationState.Free;
// Check for deferred Dispose().
// The deferred Dispose is not guaranteed if Dispose is called while an operation is in progress.
@ -498,7 +500,7 @@ namespace System.Net.Sockets
_disposeCalled = true;
// Check if this object is in-use for an async socket operation.
if (Interlocked.CompareExchange(ref _operating, Disposed, Free) != Free)
if (Interlocked.CompareExchange(ref _operating, OperationState.Disposed, OperationState.Free) != OperationState.Free)
{
// Either already disposed or will be disposed when current operation completes.
return;
@ -525,17 +527,17 @@ namespace System.Net.Sockets
// NOTE: Use a try/finally to make sure Complete is called when you're done
private void StartConfiguring()
{
int status = Interlocked.CompareExchange(ref _operating, Configuring, Free);
if (status != Free)
OperationState status = Interlocked.CompareExchange(ref _operating, OperationState.Configuring, OperationState.Free);
if (status != OperationState.Free)
{
ThrowForNonFreeStatus(status);
}
}
private void ThrowForNonFreeStatus(int status)
private void ThrowForNonFreeStatus(OperationState status)
{
Debug.Assert(status == InProgress || status == Configuring || status == Disposed, $"Unexpected status: {status}");
ObjectDisposedException.ThrowIf(status == Disposed, this);
Debug.Assert(status == OperationState.InProgress || status == OperationState.Configuring || status == OperationState.Disposed, $"Unexpected status: {status}");
ObjectDisposedException.ThrowIf(status == OperationState.Disposed, this);
throw new InvalidOperationException(SR.net_socketopinprogress);
}
@ -544,8 +546,8 @@ namespace System.Net.Sockets
internal void StartOperationCommon(Socket? socket, SocketAsyncOperation operation)
{
// Change status to "in-use".
int status = Interlocked.CompareExchange(ref _operating, InProgress, Free);
if (status != Free)
OperationState status = Interlocked.CompareExchange(ref _operating, OperationState.InProgress, OperationState.Free);
if (status != OperationState.Free)
{
ThrowForNonFreeStatus(status);
}
@ -606,7 +608,7 @@ namespace System.Net.Sockets
internal void CancelConnectAsync()
{
if (_operating == InProgress && _completedOperation == SocketAsyncOperation.Connect)
if (_operating == OperationState.InProgress && _completedOperation == SocketAsyncOperation.Connect)
{
CancellationTokenSource? multipleConnectCancellation = _multipleConnectCancellation;
if (multipleConnectCancellation != null)
@ -865,7 +867,7 @@ namespace System.Net.Sockets
private sealed class MultiConnectSocketAsyncEventArgs : SocketAsyncEventArgs, IValueTaskSource
{
private ManualResetValueTaskSourceCore<bool> _mrvtsc;
private int _isCompleted;
private bool _isCompleted;
public MultiConnectSocketAsyncEventArgs() : base(unsafeSuppressExecutionContextFlow: false) { }
@ -878,7 +880,7 @@ namespace System.Net.Sockets
protected override void OnCompleted(SocketAsyncEventArgs e) => _mrvtsc.SetResult(true);
public bool ReachedCoordinationPointFirst() => Interlocked.Exchange(ref _isCompleted, 1) == 0;
public bool ReachedCoordinationPointFirst() => !Interlocked.Exchange(ref _isCompleted, true);
}
internal void FinishOperationSyncSuccess(int bytesTransferred, SocketFlags flags)

View file

@ -17,10 +17,10 @@ namespace System.Net.Sockets
private AddressFamily _family;
private Socket _clientSocket = null!; // initialized by helper called from ctor
private NetworkStream? _dataStream;
private volatile int _disposed;
private volatile bool _disposed;
private bool _active;
private bool Disposed => _disposed != 0;
private bool Disposed => _disposed;
// Initializes a new instance of the System.Net.Sockets.TcpClient class.
public TcpClient() : this(AddressFamily.Unknown)
@ -252,7 +252,7 @@ namespace System.Net.Sockets
// Disposes the Tcp connection.
protected virtual void Dispose(bool disposing)
{
if (Interlocked.CompareExchange(ref _disposed, 1, 0) == 0)
if (!Interlocked.Exchange(ref _disposed, true))
{
if (disposing)
{

View file

@ -11,7 +11,7 @@ namespace System.Net
{
private static volatile string? s_domainName;
private static volatile IPAddress[]? s_localAddresses;
private static int s_networkChangeRegistered;
private static bool s_networkChangeRegistered;
private static bool IsLocal(Uri host)
{
@ -47,13 +47,13 @@ namespace System.Net
/// <summary>Ensures we've registered with NetworkChange to clear out statically-cached state upon a network change notification.</summary>
private static void EnsureNetworkChangeRegistration()
{
if (s_networkChangeRegistered == 0)
if (!s_networkChangeRegistered)
{
Register();
static void Register()
{
if (Interlocked.Exchange(ref s_networkChangeRegistered, 1) != 0)
if (Interlocked.Exchange(ref s_networkChangeRegistered, true))
{
return;
}

View file

@ -12,12 +12,12 @@ namespace System.Net.WebSockets
public sealed partial class ClientWebSocket : WebSocket
{
/// <summary>This is really an InternalState value, but Interlocked doesn't support operations on values of enum types.</summary>
private int _state;
private InternalState _state;
private WebSocketHandle? _innerWebSocket;
public ClientWebSocket()
{
_state = (int)InternalState.Created;
_state = InternalState.Created;
Options = WebSocketHandle.CreateDefaultOptions();
}
@ -39,14 +39,14 @@ namespace System.Net.WebSockets
return _innerWebSocket.State;
}
switch ((InternalState)_state)
switch (_state)
{
case InternalState.Created:
return WebSocketState.None;
case InternalState.Connecting:
return WebSocketState.Connecting;
default: // We only get here if disposed before connecting
Debug.Assert((InternalState)_state == InternalState.Disposed);
Debug.Assert(_state == InternalState.Disposed);
return WebSocketState.Closed;
}
}
@ -105,7 +105,7 @@ namespace System.Net.WebSockets
}
// Check that we have not started already.
switch ((InternalState)Interlocked.CompareExchange(ref _state, (int)InternalState.Connecting, (int)InternalState.Created))
switch (Interlocked.CompareExchange(ref _state, InternalState.Connecting, InternalState.Created))
{
case InternalState.Disposed:
throw new ObjectDisposedException(GetType().FullName);
@ -135,9 +135,9 @@ namespace System.Net.WebSockets
throw;
}
if ((InternalState)Interlocked.CompareExchange(ref _state, (int)InternalState.Connected, (int)InternalState.Connecting) != InternalState.Connecting)
if (Interlocked.CompareExchange(ref _state, InternalState.Connected, InternalState.Connecting) != InternalState.Connecting)
{
Debug.Assert(_state == (int)InternalState.Disposed);
Debug.Assert(_state == InternalState.Disposed);
throw new ObjectDisposedException(GetType().FullName);
}
}
@ -167,9 +167,9 @@ namespace System.Net.WebSockets
{
get
{
ObjectDisposedException.ThrowIf((InternalState)_state == InternalState.Disposed, this);
ObjectDisposedException.ThrowIf(_state == InternalState.Disposed, this);
if ((InternalState)_state != InternalState.Connected)
if (_state != InternalState.Connected)
{
throw new InvalidOperationException(SR.net_WebSockets_NotConnected);
}
@ -183,7 +183,7 @@ namespace System.Net.WebSockets
public override void Abort()
{
if ((InternalState)_state != InternalState.Disposed)
if (_state != InternalState.Disposed)
{
_innerWebSocket?.Abort();
Dispose();
@ -192,7 +192,7 @@ namespace System.Net.WebSockets
public override void Dispose()
{
if ((InternalState)Interlocked.Exchange(ref _state, (int)InternalState.Disposed) != InternalState.Disposed)
if (Interlocked.Exchange(ref _state, InternalState.Disposed) != InternalState.Disposed)
{
_innerWebSocket?.Dispose();
}

View file

@ -4328,10 +4328,13 @@
<data name="NotSupported_EmitDebugInfo" xml:space="preserve">
<value>Emitting debug info is not supported for this member.</value>
</data>
<data name="NotSupported_ReferenceEnumOrPrimitiveTypeRequired" xml:space="preserve">
<value>The specified type must be a reference type, a primitive type, or an enum type.</value>
</data>
<data name="Argument_BadFieldForInitializeArray" xml:space="preserve">
<value>The field is invalid for initializing array or span.</value>
</data>
<data name="Argument_BadArrayForInitializeArray" xml:space="preserve">
<value>Only array or span of primitive or enum types can be initialized from static data.</value>
</data>
</root>
</root>

View file

@ -35,7 +35,7 @@ namespace System.Buffers
/// </summary>
private readonly SharedArrayPoolPartitions?[] _buckets = new SharedArrayPoolPartitions[NumBuckets];
/// <summary>Whether the callback to trim arrays in response to memory pressure has been created.</summary>
private int _trimCallbackCreated;
private bool _trimCallbackCreated;
/// <summary>Allocate a new <see cref="SharedArrayPoolPartitions"/> and try to store it into the <see cref="_buckets"/> array.</summary>
private unsafe SharedArrayPoolPartitions CreatePerCorePartitions(int bucketIndex)
@ -283,7 +283,7 @@ namespace System.Buffers
t_tlsBuckets = tlsBuckets;
_allTlsBuckets.Add(tlsBuckets, null);
if (Interlocked.Exchange(ref _trimCallbackCreated, 1) == 0)
if (!Interlocked.Exchange(ref _trimCallbackCreated, true))
{
Gen2GcCallback.Register(s => ((SharedArrayPool<T>)s).Trim(), this);
}

View file

@ -165,7 +165,7 @@ namespace System.Diagnostics.Tracing
ActivityInfo? orphan = currentActivity;
while (orphan != activityToStop && orphan != null)
{
if (orphan.m_stopped != 0) // Skip dead activities.
if (orphan.m_stopped) // Skip dead activities.
{
orphan = orphan.m_creator;
continue;
@ -177,14 +177,13 @@ namespace System.Diagnostics.Tracing
}
else
{
orphan.m_stopped = 1;
Debug.Assert(orphan.m_stopped != 0);
orphan.m_stopped = true;
}
orphan = orphan.m_creator;
}
// try to Stop the activity atomically. Other threads may be trying to do this as well.
if (Interlocked.CompareExchange(ref activityToStop.m_stopped, 1, 0) == 0)
if (!Interlocked.Exchange(ref activityToStop.m_stopped, true))
{
// I succeeded stopping this activity. Now we update our m_current pointer
@ -239,7 +238,7 @@ namespace System.Diagnostics.Tracing
ActivityInfo? activity = startLocation;
while (activity != null)
{
if (name == activity.m_name && activity.m_stopped == 0)
if (name == activity.m_name && !activity.m_stopped)
return activity;
activity = activity.m_creator;
}
@ -309,7 +308,7 @@ namespace System.Diagnostics.Tracing
public override string ToString()
{
return m_name + "(" + Path(this) + (m_stopped != 0 ? ",DEAD)" : ")");
return m_name + "(" + Path(this) + (m_stopped ? ",DEAD)" : ")");
}
public static string LiveActivities(ActivityInfo? list)
@ -520,7 +519,7 @@ namespace System.Diagnostics.Tracing
internal readonly int m_level; // current depth of the Path() of the activity (used to keep recursion under control)
internal readonly EventActivityOptions m_eventOptions; // Options passed to start.
internal long m_lastChildID; // used to create a unique ID for my children activities
internal int m_stopped; // This work item has stopped
internal bool m_stopped; // This work item has stopped
internal readonly ActivityInfo? m_creator; // My parent (creator). Forms the Path() for the activity.
internal readonly Guid m_activityIdToRestore; // The Guid to restore after a stop.
#endregion
@ -578,7 +577,7 @@ namespace System.Diagnostics.Tracing
while (cur != null)
{
// We found a live activity (typically the first time), set it to that.
if (cur.m_stopped == 0)
if (!cur.m_stopped)
{
EventSource.SetCurrentThreadActivityId(cur.ActivityId);
return;

View file

@ -25,7 +25,7 @@ namespace System.Threading
public class CancellationTokenSource : IDisposable
{
/// <summary>A <see cref="CancellationTokenSource"/> that's already canceled.</summary>
internal static readonly CancellationTokenSource s_canceledSource = new CancellationTokenSource() { _state = NotifyingCompleteState };
internal static readonly CancellationTokenSource s_canceledSource = new CancellationTokenSource() { _state = States.NotifyingCompleteState };
/// <summary>A <see cref="CancellationTokenSource"/> that's never canceled. This isn't enforced programmatically, only by usage. Do not cancel!</summary>
internal static readonly CancellationTokenSource s_neverCanceledSource = new CancellationTokenSource();
@ -35,7 +35,7 @@ namespace System.Threading
((CancellationTokenSource)state!).NotifyCancellation(throwOnFirstException: false); // skip ThrowIfDisposed() check in Cancel()
/// <summary>The current state of the CancellationTokenSource.</summary>
private volatile int _state;
private volatile States _state;
/// <summary>Whether this <see cref="CancellationTokenSource"/> has been disposed.</summary>
private bool _disposed;
/// <summary>ITimer used by CancelAfter and Timer-related ctors. Used instead of Timer to avoid extra allocations and because the rooted behavior is desired.</summary>
@ -46,10 +46,13 @@ namespace System.Threading
/// <remarks>Lazily-initialized, also serving as the lock to protect its contained state.</remarks>
private Registrations? _registrations;
// legal values for _state
private const int NotCanceledState = 0; // default value of _state
private const int NotifyingState = 1;
private const int NotifyingCompleteState = 2;
/// <summary>Legal values for <see cref="_state"/>.</summary>
private enum States
{
NotCanceledState = 0, // default value of _state
NotifyingState = 1,
NotifyingCompleteState = 2,
}
/// <summary>Gets whether cancellation has been requested for this <see cref="CancellationTokenSource" />.</summary>
/// <value>Whether cancellation has been requested for this <see cref="CancellationTokenSource" />.</value>
@ -66,10 +69,10 @@ namespace System.Threading
/// canceled concurrently.
/// </para>
/// </remarks>
public bool IsCancellationRequested => _state != NotCanceledState;
public bool IsCancellationRequested => _state != States.NotCanceledState;
/// <summary>A simple helper to determine whether cancellation has finished.</summary>
internal bool IsCancellationCompleted => _state == NotifyingCompleteState;
internal bool IsCancellationCompleted => _state == States.NotifyingCompleteState;
/// <summary>Gets the <see cref="CancellationToken"/> associated with this <see cref="CancellationTokenSource"/>.</summary>
/// <value>The <see cref="CancellationToken"/> associated with this <see cref="CancellationTokenSource"/>.</value>
@ -201,7 +204,7 @@ namespace System.Threading
{
if (millisecondsDelay == TimeSpan.Zero)
{
_state = NotifyingCompleteState;
_state = States.NotifyingCompleteState;
}
else
{
@ -480,7 +483,7 @@ namespace System.Threading
// We can only reset if cancellation has not yet been requested: we never want to allow a CancellationToken
// to transition from canceled to non-canceled.
if (_state == NotCanceledState)
if (_state == States.NotCanceledState)
{
// If there is no timer, then we're free to reset. If there is a timer, then we need to first try
// to reset it to be infinite so that it won't fire, and then recognize that it could have already
@ -556,7 +559,7 @@ namespace System.Threading
if (_kernelEvent != null)
{
ManualResetEvent? mre = Interlocked.Exchange<ManualResetEvent?>(ref _kernelEvent!, null);
if (mre != null && _state != NotifyingState)
if (mre != null && _state != States.NotifyingState)
{
mre.Dispose();
}
@ -698,13 +701,13 @@ namespace System.Threading
}
}
/// <summary>Transitions from <see cref="NotCanceledState"/> to <see cref="NotifyingState"/>.</summary>
/// <summary>Transitions from <see cref="States.NotCanceledState"/> to <see cref="States.NotifyingState"/>.</summary>
/// <returns>true if it successfully transitioned; otherwise, false.</returns>
/// <remarks>If it successfully transitions, it will also have disposed of <see cref="_timer"/> and set <see cref="_kernelEvent"/>.</remarks>
private bool TransitionToCancellationRequested()
{
if (!IsCancellationRequested &&
Interlocked.CompareExchange(ref _state, NotifyingState, NotCanceledState) == NotCanceledState)
Interlocked.CompareExchange(ref _state, States.NotifyingState, States.NotCanceledState) == States.NotCanceledState)
{
// Dispose of the timer, if any. Dispose may be running concurrently here, but ITimer.Dispose is thread-safe.
ITimer? timer = _timer;
@ -737,7 +740,7 @@ namespace System.Threading
Registrations? registrations = Interlocked.Exchange(ref _registrations, null);
if (registrations is null)
{
Interlocked.Exchange(ref _state, NotifyingCompleteState);
Interlocked.Exchange(ref _state, States.NotifyingCompleteState);
return;
}
@ -825,7 +828,7 @@ namespace System.Threading
}
finally
{
_state = NotifyingCompleteState;
_state = States.NotifyingCompleteState;
Interlocked.Exchange(ref registrations.ExecutingCallbackId, 0); // for safety, prevent reorderings crossing this point and seeing inconsistent state.
}
@ -1021,7 +1024,7 @@ namespace System.Threading
/// </remarks>
public volatile int ThreadIDExecutingCallbacks = -1;
/// <summary>Spin lock that protects state in the instance.</summary>
private int _lock;
private volatile bool _locked;
/// <summary>Initializes the instance.</summary>
/// <param name="source">The associated source.</param>
@ -1030,7 +1033,7 @@ namespace System.Threading
[MethodImpl(MethodImplOptions.AggressiveInlining)] // used in only two places, one of which is a hot path
private void Recycle(CallbackNode node)
{
Debug.Assert(_lock == 1);
Debug.Assert(_locked);
// Clear out the unused node and put it on the singly-linked free list.
// The only field we don't clear out is the associated Registrations, as that's fixed
@ -1163,16 +1166,16 @@ namespace System.Threading
/// <summary>Enters the lock for this instance. The current thread must not be holding the lock, but that is not validated.</summary>
public void EnterLock()
{
ref int value = ref _lock;
if (Interlocked.Exchange(ref value, 1) != 0)
ref bool value = ref _locked;
if (Interlocked.Exchange(ref value, true))
{
Contention(ref value);
[MethodImpl(MethodImplOptions.NoInlining)]
static void Contention(ref int value)
static void Contention(ref bool value)
{
SpinWait sw = default;
do { sw.SpinOnce(); } while (Interlocked.Exchange(ref value, 1) == 1);
do { sw.SpinOnce(); } while (Interlocked.Exchange(ref value, true));
}
}
}
@ -1180,8 +1183,8 @@ namespace System.Threading
/// <summary>Exits the lock for this instance. The current thread must be holding the lock, but that is not validated.</summary>
public void ExitLock()
{
Debug.Assert(_lock == 1);
Volatile.Write(ref _lock, 0);
Debug.Assert(_locked);
_locked = false;
}
}

View file

@ -2,7 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#pragma warning disable CS8500 // takes the address of, gets the size of, or declares a pointer to a managed type ('T')
namespace System.Threading
{
@ -222,6 +226,66 @@ namespace System.Threading
return (UIntPtr)Exchange(ref Unsafe.As<UIntPtr, int>(ref location1), (int)value);
#endif
}
/// <summary>Sets a variable of the specified type <typeparamref name="T"/> to a specified value and returns the original value, as an atomic operation.</summary>
/// <param name="location1">The variable to set to the specified value.</param>
/// <param name="value">The value to which the <paramref name="location1"/> parameter is set.</param>
/// <returns>The original value of <paramref name="location1"/>.</returns>
/// <exception cref="NullReferenceException">The address of location1 is a null pointer.</exception>
/// <exception cref="NotSupportedException">An unsupported <typeparamref name="T"/> is specified.</exception>
/// <typeparam name="T">
/// The type to be used for <paramref name="location1"/> and <paramref name="value"/>.
/// This type must be a reference type, an enum type (i.e. typeof(T).IsEnum is true), or a primitive type (i.e. typeof(T).IsPrimitive is true).
/// </typeparam>
[Intrinsic]
[return: NotNullIfNotNull(nameof(location1))]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe T Exchange<T>([NotNullIfNotNull(nameof(value))] ref T location1, T value)
{
// Handle all reference types with CompareExchange(ref object, ...).
if (!typeof(T).IsValueType)
{
object? result = Exchange(ref Unsafe.As<T, object?>(ref location1), value);
return Unsafe.As<object?, T>(ref result);
}
// Handle everything else with a CompareExchange overload for the unsigned integral type of the corresponding size.
// Only primitive types and enum types (which are backed by primitive types) are supported.
if (!typeof(T).IsPrimitive && !typeof(T).IsEnum)
{
throw new NotSupportedException(SR.NotSupported_ReferenceEnumOrPrimitiveTypeRequired);
}
if (sizeof(T) == 1)
{
return Unsafe.BitCast<byte, T>(
Exchange(
ref Unsafe.As<T, byte>(ref location1),
Unsafe.BitCast<T, byte>(value)));
}
if (sizeof(T) == 2)
{
return Unsafe.BitCast<ushort, T>(
Exchange(
ref Unsafe.As<T, ushort>(ref location1),
Unsafe.BitCast<T, ushort>(value)));
}
if (sizeof(T) == 4)
{
return Unsafe.BitCast<int, T>(
Exchange(
ref Unsafe.As<T, int>(ref location1),
Unsafe.BitCast<T, int>(value)));
}
Debug.Assert(sizeof(T) == 8);
return Unsafe.BitCast<long, T>(
Exchange(
ref Unsafe.As<T, long>(ref location1),
Unsafe.BitCast<T, long>(value)));
}
#endregion
#region CompareExchange
@ -413,6 +477,71 @@ namespace System.Threading
return (UIntPtr)CompareExchange(ref Unsafe.As<UIntPtr, int>(ref location1), (int)value, (int)comparand);
#endif
}
/// <summary>Compares two instances of the specified type <typeparamref name="T"/> for equality and, if they are equal, replaces the first one.</summary>
/// <param name="location1">The destination, whose value is compared with <paramref name="comparand"/> and possibly replaced.</param>
/// <param name="value">The value that replaces the destination value if the comparison results in equality.</param>
/// <param name="comparand">The object that is compared to the value at <paramref name="location1"/>.</param>
/// <returns>The original value in <paramref name="location1"/>.</returns>
/// <exception cref="NullReferenceException">The address of <paramref name="location1"/> is a null pointer.</exception>
/// <exception cref="NotSupportedException">An unsupported <typeparamref name="T"/> is specified.</exception>
/// <typeparam name="T">
/// The type to be used for <paramref name="location1"/>, <paramref name="value"/>, and <paramref name="comparand"/>.
/// This type must be a reference type, an enum type (i.e. typeof(T).IsEnum is true), or a primitive type (i.e. typeof(T).IsPrimitive is true).
/// </typeparam>
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[return: NotNullIfNotNull(nameof(location1))]
public static unsafe T CompareExchange<T>(ref T location1, T value, T comparand)
{
// Handle all reference types with CompareExchange(ref object, ...).
if (!typeof(T).IsValueType)
{
object? result = CompareExchange(ref Unsafe.As<T, object?>(ref location1), value, comparand);
return Unsafe.As<object?, T>(ref result);
}
// Handle everything else with a CompareExchange overload for the unsigned integral type of the corresponding size.
// Only primitive types and enum types (which are backed by primitive types) are supported.
if (!typeof(T).IsPrimitive && !typeof(T).IsEnum)
{
throw new NotSupportedException(SR.NotSupported_ReferenceEnumOrPrimitiveTypeRequired);
}
if (sizeof(T) == 1)
{
return Unsafe.BitCast<byte, T>(
CompareExchange(
ref Unsafe.As<T, byte>(ref location1),
Unsafe.BitCast<T, byte>(value),
Unsafe.BitCast<T, byte>(comparand)));
}
if (sizeof(T) == 2)
{
return Unsafe.BitCast<ushort, T>(
CompareExchange(
ref Unsafe.As<T, ushort>(ref location1),
Unsafe.BitCast<T, ushort>(value),
Unsafe.BitCast<T, ushort>(comparand)));
}
if (sizeof(T) == 4)
{
return Unsafe.BitCast<int, T>(
CompareExchange(
ref Unsafe.As<T, int>(ref location1),
Unsafe.BitCast<T, int>(value),
Unsafe.BitCast<T, int>(comparand)));
}
Debug.Assert(sizeof(T) == 8);
return Unsafe.BitCast<long, T>(
CompareExchange(
ref Unsafe.As<T, long>(ref location1),
Unsafe.BitCast<T, long>(value),
Unsafe.BitCast<T, long>(comparand)));
}
#endregion
#region Add

View file

@ -1416,7 +1416,7 @@ namespace System.Threading
private struct SpinLock
{
private int _isLocked;
private bool _isLocked;
/// <summary>
/// Used to deprioritize threads attempting to enter the lock when they would not make progress after doing so.
@ -1535,7 +1535,7 @@ namespace System.Threading
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool TryEnter()
{
return Interlocked.CompareExchange(ref _isLocked, 1, 0) == 0;
return !Interlocked.Exchange(ref _isLocked, true);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -1577,7 +1577,7 @@ namespace System.Threading
if (!IsEnterDeprioritized(reason))
{
if (_isLocked == 0 && TryEnter())
if (!_isLocked && TryEnter())
{
if (deprioritizationStateChange != 0)
{
@ -1606,12 +1606,12 @@ namespace System.Threading
public void Exit()
{
Debug.Assert(_isLocked != 0, "Exiting spin lock that is not held");
Volatile.Write(ref _isLocked, 0);
Debug.Assert(_isLocked, "Exiting spin lock that is not held");
Volatile.Write(ref _isLocked, false);
}
#if DEBUG
public bool IsHeld => _isLocked != 0;
public bool IsHeld => _isLocked;
#endif
}

View file

@ -399,7 +399,7 @@ namespace System.Threading
private bool _loggingEnabled;
private bool _dispatchNormalPriorityWorkFirst;
private int _mayHaveHighPriorityWorkItems;
private bool _mayHaveHighPriorityWorkItems;
// SOS's ThreadPool command depends on the following names
internal readonly ConcurrentQueue<object> workItems = new ConcurrentQueue<object>();
@ -443,7 +443,7 @@ namespace System.Threading
{
private readonly Internal.PaddingFor32 pad1;
public int queueProcessingStage;
public QueueProcessingStage queueProcessingStage;
private readonly Internal.PaddingFor32 pad2;
}
@ -611,7 +611,7 @@ namespace System.Threading
// Otherwise let the current requested thread handle parallelization.
if (Interlocked.Exchange(
ref _separated.queueProcessingStage,
(int)QueueProcessingStage.Scheduled) == (int)QueueProcessingStage.NotScheduled)
QueueProcessingStage.Scheduled) == QueueProcessingStage.NotScheduled)
{
ThreadPool.RequestWorkerThread();
}
@ -696,7 +696,7 @@ namespace System.Threading
highPriorityWorkItems.Enqueue(workItem);
// If the change below is seen by another thread, ensure that the enqueued work item will also be visible
Volatile.Write(ref _mayHaveHighPriorityWorkItems, 1);
Volatile.Write(ref _mayHaveHighPriorityWorkItems, true);
EnsureThreadRequested();
}
@ -736,8 +736,8 @@ namespace System.Threading
tl.isProcessingHighPriorityWorkItems = false;
}
else if (
_mayHaveHighPriorityWorkItems != 0 &&
Interlocked.CompareExchange(ref _mayHaveHighPriorityWorkItems, 0, 1) != 0 &&
_mayHaveHighPriorityWorkItems &&
Interlocked.CompareExchange(ref _mayHaveHighPriorityWorkItems, false, true) &&
TryStartProcessingHighPriorityWorkItemsAndDequeue(tl, out workItem))
{
return workItem;
@ -816,7 +816,7 @@ namespace System.Threading
}
tl.isProcessingHighPriorityWorkItems = true;
_mayHaveHighPriorityWorkItems = 1;
_mayHaveHighPriorityWorkItems = true;
return true;
}
@ -906,8 +906,8 @@ namespace System.Threading
// thread because it sees a Determining or Scheduled stage, and the current thread is the last thread processing
// work items, the current thread must either see the work item queued by the enqueuer, or it must see a stage of
// Scheduled, and try to dequeue again or request another thread.
Debug.Assert(workQueue._separated.queueProcessingStage == (int)QueueProcessingStage.Scheduled);
workQueue._separated.queueProcessingStage = (int)QueueProcessingStage.Determining;
Debug.Assert(workQueue._separated.queueProcessingStage == QueueProcessingStage.Scheduled);
workQueue._separated.queueProcessingStage = QueueProcessingStage.Determining;
Interlocked.MemoryBarrier();
object? workItem = null;
@ -935,8 +935,8 @@ namespace System.Threading
workQueue.UnassignWorkItemQueue(tl);
}
Debug.Assert(workQueue._separated.queueProcessingStage != (int)QueueProcessingStage.NotScheduled);
workQueue._separated.queueProcessingStage = (int)QueueProcessingStage.Scheduled;
Debug.Assert(workQueue._separated.queueProcessingStage != QueueProcessingStage.NotScheduled);
workQueue._separated.queueProcessingStage = QueueProcessingStage.Scheduled;
ThreadPool.RequestWorkerThread();
return true;
}
@ -944,13 +944,13 @@ namespace System.Threading
// The stage here would be Scheduled if an enqueuer has enqueued work and changed the stage, or Determining
// otherwise. If the stage is Determining, there's no more work to do. If the stage is Scheduled, the enqueuer
// would not have scheduled a work item to process the work, so try to dequeue a work item again.
int stageBeforeUpdate =
QueueProcessingStage stageBeforeUpdate =
Interlocked.CompareExchange(
ref workQueue._separated.queueProcessingStage,
(int)QueueProcessingStage.NotScheduled,
(int)QueueProcessingStage.Determining);
Debug.Assert(stageBeforeUpdate != (int)QueueProcessingStage.NotScheduled);
if (stageBeforeUpdate == (int)QueueProcessingStage.Determining)
QueueProcessingStage.NotScheduled,
QueueProcessingStage.Determining);
Debug.Assert(stageBeforeUpdate != QueueProcessingStage.NotScheduled);
if (stageBeforeUpdate == QueueProcessingStage.Determining)
{
if (s_assignableWorkItemQueueCount > 0)
{
@ -964,7 +964,7 @@ namespace System.Threading
// by the enqueuer. Set the stage back to Determining and try to dequeue a work item again.
//
// See the first similarly used memory barrier in the method for why it's necessary.
workQueue._separated.queueProcessingStage = (int)QueueProcessingStage.Determining;
workQueue._separated.queueProcessingStage = QueueProcessingStage.Determining;
Interlocked.MemoryBarrier();
}
}
@ -976,7 +976,7 @@ namespace System.Threading
// be able to detect if an enqueue races with the dequeue below.
//
// See the first similarly used memory barrier in the method for why it's necessary.
workQueue._separated.queueProcessingStage = (int)QueueProcessingStage.Determining;
workQueue._separated.queueProcessingStage = QueueProcessingStage.Determining;
Interlocked.MemoryBarrier();
object? secondWorkItem = DequeueWithPriorityAlternation(workQueue, tl, out bool missedSteal);
@ -993,8 +993,8 @@ namespace System.Threading
// responsibility of the new thread and other enqueuers to request more threads as necessary. The
// parallelization may be necessary here for correctness (aside from perf) if the work item blocks for some
// reason that may have a dependency on other queued work items.
Debug.Assert(workQueue._separated.queueProcessingStage != (int)QueueProcessingStage.NotScheduled);
workQueue._separated.queueProcessingStage = (int)QueueProcessingStage.Scheduled;
Debug.Assert(workQueue._separated.queueProcessingStage != QueueProcessingStage.NotScheduled);
workQueue._separated.queueProcessingStage = QueueProcessingStage.Scheduled;
ThreadPool.RequestWorkerThread();
}
else
@ -1002,13 +1002,13 @@ namespace System.Threading
// The stage here would be Scheduled if an enqueuer has enqueued work and changed the stage, or Determining
// otherwise. If the stage is Determining, there's no more work to do. If the stage is Scheduled, the enqueuer
// would not have requested a thread, so request one now.
int stageBeforeUpdate =
QueueProcessingStage stageBeforeUpdate =
Interlocked.CompareExchange(
ref workQueue._separated.queueProcessingStage,
(int)QueueProcessingStage.NotScheduled,
(int)QueueProcessingStage.Determining);
Debug.Assert(stageBeforeUpdate != (int)QueueProcessingStage.NotScheduled);
if (stageBeforeUpdate == (int)QueueProcessingStage.Scheduled)
QueueProcessingStage.NotScheduled,
QueueProcessingStage.Determining);
Debug.Assert(stageBeforeUpdate != QueueProcessingStage.NotScheduled);
if (stageBeforeUpdate == QueueProcessingStage.Scheduled)
{
// A work item was enqueued after the stage was set to Determining earlier, and a thread was not
// requested by the enqueuer, so request a thread now. An alternate is to retry dequeuing, as requesting
@ -1269,7 +1269,7 @@ namespace System.Threading
Scheduled
}
private int _queueProcessingStage;
private QueueProcessingStage _queueProcessingStage;
private readonly ConcurrentQueue<T> _workItems = new ConcurrentQueue<T>();
public int Count => _workItems.Count;
@ -1287,7 +1287,7 @@ namespace System.Threading
// Otherwise there must be a work item already queued or another thread already handling parallelization.
if (Interlocked.Exchange(
ref _queueProcessingStage,
(int)QueueProcessingStage.Scheduled) == (int)QueueProcessingStage.NotScheduled)
QueueProcessingStage.Scheduled) == QueueProcessingStage.NotScheduled)
{
ThreadPool.UnsafeQueueHighPriorityWorkItemInternal(this);
}
@ -1298,20 +1298,20 @@ namespace System.Threading
if (!isQueueEmpty)
{
// There are more items to process, set stage to Scheduled and enqueue a TP work item.
_queueProcessingStage = (int)QueueProcessingStage.Scheduled;
_queueProcessingStage = QueueProcessingStage.Scheduled;
}
else
{
// The stage here would be Scheduled if an enqueuer has enqueued work and changed the stage, or Determining
// otherwise. If the stage is Determining, there's no more work to do. If the stage is Scheduled, the enqueuer
// would not have scheduled a work item to process the work, so schedule one one.
int stageBeforeUpdate =
QueueProcessingStage stageBeforeUpdate =
Interlocked.CompareExchange(
ref _queueProcessingStage,
(int)QueueProcessingStage.NotScheduled,
(int)QueueProcessingStage.Determining);
Debug.Assert(stageBeforeUpdate != (int)QueueProcessingStage.NotScheduled);
if (stageBeforeUpdate == (int)QueueProcessingStage.Determining)
QueueProcessingStage.NotScheduled,
QueueProcessingStage.Determining);
Debug.Assert(stageBeforeUpdate != QueueProcessingStage.NotScheduled);
if (stageBeforeUpdate == QueueProcessingStage.Determining)
{
return;
}
@ -1325,14 +1325,14 @@ namespace System.Threading
T workItem;
while (true)
{
Debug.Assert(_queueProcessingStage == (int)QueueProcessingStage.Scheduled);
Debug.Assert(_queueProcessingStage == QueueProcessingStage.Scheduled);
// The change needs to be visible to other threads that may request a worker thread before a work item is attempted
// to be dequeued by the current thread. In particular, if an enqueuer queues a work item and does not request a
// thread because it sees a Determining or Scheduled stage, and the current thread is the last thread processing
// work items, the current thread must either see the work item queued by the enqueuer, or it must see a stage of
// Scheduled, and try to dequeue again or request another thread.
_queueProcessingStage = (int)QueueProcessingStage.Determining;
_queueProcessingStage = QueueProcessingStage.Determining;
Interlocked.MemoryBarrier();
if (_workItems.TryDequeue(out workItem))
@ -1343,13 +1343,13 @@ namespace System.Threading
// The stage here would be Scheduled if an enqueuer has enqueued work and changed the stage, or Determining
// otherwise. If the stage is Determining, there's no more work to do. If the stage is Scheduled, the enqueuer
// would not have scheduled a work item to process the work, so try to dequeue a work item again.
int stageBeforeUpdate =
QueueProcessingStage stageBeforeUpdate =
Interlocked.CompareExchange(
ref _queueProcessingStage,
(int)QueueProcessingStage.NotScheduled,
(int)QueueProcessingStage.Determining);
Debug.Assert(stageBeforeUpdate != (int)QueueProcessingStage.NotScheduled);
if (stageBeforeUpdate == (int)QueueProcessingStage.Determining)
QueueProcessingStage.NotScheduled,
QueueProcessingStage.Determining);
Debug.Assert(stageBeforeUpdate != QueueProcessingStage.NotScheduled);
if (stageBeforeUpdate == QueueProcessingStage.Determining)
{
return;
}
@ -1401,13 +1401,12 @@ namespace System.Threading
internal abstract class QueueUserWorkItemCallbackBase : IThreadPoolWorkItem
{
#if DEBUG
private int executed;
private bool _executed;
~QueueUserWorkItemCallbackBase()
{
Interlocked.MemoryBarrier(); // ensure that an old cached value is not read below
Debug.Assert(
executed != 0, "A QueueUserWorkItemCallback was never called!");
Debug.Assert(_executed, "A QueueUserWorkItemCallback was never called!");
}
#endif
@ -1415,9 +1414,7 @@ namespace System.Threading
{
#if DEBUG
GC.SuppressFinalize(this);
Debug.Assert(
0 == Interlocked.Exchange(ref executed, 1),
"A QueueUserWorkItemCallback was called twice!");
Debug.Assert(!Interlocked.Exchange(ref _executed, true), "A QueueUserWorkItemCallback was called twice!");
#endif
}
}

View file

@ -75,7 +75,7 @@ namespace System.Threading
/// - Sleep(0) intentionally does not acquire any lock, so it uses an interlocked compare-exchange for the read and
/// reset, see <see cref="CheckAndResetPendingInterrupt_NotLocked"/>
/// </summary>
private int _isPendingInterrupt;
private bool _isPendingInterrupt;
////////////////////////////////////////////////////////////////
@ -508,7 +508,7 @@ namespace System.Threading
s_lock.VerifyIsLocked();
_waitMonitor.VerifyIsLocked();
_isPendingInterrupt = 1;
_isPendingInterrupt = true;
}
public bool CheckAndResetPendingInterrupt
@ -519,11 +519,11 @@ namespace System.Threading
Debug.Assert(s_lock.IsLocked || _waitMonitor.IsLocked);
#endif
if (_isPendingInterrupt == 0)
if (!_isPendingInterrupt)
{
return false;
}
_isPendingInterrupt = 0;
_isPendingInterrupt = false;
return true;
}
}
@ -535,7 +535,7 @@ namespace System.Threading
s_lock.VerifyIsNotLocked();
_waitMonitor.VerifyIsNotLocked();
return Interlocked.CompareExchange(ref _isPendingInterrupt, 0, 1) != 0;
return Interlocked.CompareExchange(ref _isPendingInterrupt, false, true);
}
}

View file

@ -2396,16 +2396,13 @@ namespace System
Done:
cF |= Flags.MinimalUriInfoSet;
Debug.Assert(sizeof(Flags) == sizeof(ulong));
Interlocked.CompareExchange(ref _info, info, null!);
Flags current = _flags;
while ((current & Flags.MinimalUriInfoSet) == 0)
{
Flags newValue = (current & ~Flags.IndexMask) | cF;
ulong oldValue = Interlocked.CompareExchange(ref Unsafe.As<Flags, ulong>(ref _flags), (ulong)newValue, (ulong)current);
if (oldValue == (ulong)current)
Flags oldValue = Interlocked.CompareExchange(ref _flags, (current & ~Flags.IndexMask) | cF, current);
if (oldValue == current)
{
return;
}

View file

@ -23,8 +23,7 @@ namespace System.Runtime.InteropServices.Marshalling
private readonly object? _runtimeCallableWrapper;
// This is an int so we can use the Interlocked APIs to update it.
private volatile int _released;
private volatile bool _released;
/// <summary>
/// Initialize ComObject instance.
@ -81,7 +80,7 @@ namespace System.Runtime.InteropServices.Marshalling
/// </remarks>
public void FinalRelease()
{
if (UniqueInstance && Interlocked.CompareExchange(ref _released, 1, 0) == 0)
if (UniqueInstance && !Interlocked.Exchange(ref _released, true))
{
GC.SuppressFinalize(this);
CacheStrategy.Clear(IUnknownStrategy);
@ -115,7 +114,7 @@ namespace System.Runtime.InteropServices.Marshalling
private bool LookUpVTableInfo(RuntimeTypeHandle handle, out IIUnknownCacheStrategy.TableInfo result, out int qiHResult)
{
ObjectDisposedException.ThrowIf(_released != 0, this);
ObjectDisposedException.ThrowIf(_released, this);
qiHResult = 0;
if (!CacheStrategy.TryGetTableInfo(handle, out result))

View file

@ -92,32 +92,10 @@ namespace System.Threading.Tasks
return Task.CompletedTask;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool Interlockable() =>
typeof(T) == typeof(sbyte) ||
typeof(T) == typeof(byte) ||
typeof(T) == typeof(short) ||
typeof(T) == typeof(ushort) ||
typeof(T) == typeof(char) ||
typeof(T) == typeof(int) ||
typeof(T) == typeof(uint) ||
typeof(T) == typeof(long) ||
typeof(T) == typeof(ulong) ||
typeof(T) == typeof(nint) ||
typeof(T) == typeof(nuint);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static unsafe bool CompareExchange(ref T location, T value, T comparand) =>
sizeof(T) == sizeof(byte) ? Interlocked.CompareExchange(ref Unsafe.As<T, byte>(ref location), Unsafe.As<T, byte>(ref value), Unsafe.As<T, byte>(ref comparand)) == Unsafe.As<T, byte>(ref comparand) :
sizeof(T) == sizeof(ushort) ? Interlocked.CompareExchange(ref Unsafe.As<T, ushort>(ref location), Unsafe.As<T, ushort>(ref value), Unsafe.As<T, ushort>(ref comparand)) == Unsafe.As<T, ushort>(ref comparand) :
sizeof(T) == sizeof(uint) ? Interlocked.CompareExchange(ref Unsafe.As<T, uint>(ref location), Unsafe.As<T, uint>(ref value), Unsafe.As<T, uint>(ref comparand)) == Unsafe.As<T, uint>(ref comparand) :
sizeof(T) == sizeof(ulong) ? Interlocked.CompareExchange(ref Unsafe.As<T, ulong>(ref location), Unsafe.As<T, ulong>(ref value), Unsafe.As<T, ulong>(ref comparand)) == Unsafe.As<T, ulong>(ref comparand) :
throw new UnreachableException();
// The worker body. Each worker will execute this same body.
Func<object, Task> taskBody = static async o =>
{
var state = (ForEachState<T>)o;
var state = (ForAsyncState<T>)o;
bool launchedNext = false;
#pragma warning disable CA2007 // Explicitly don't use ConfigureAwait, as we want to perform all work on the specified scheduler that's now current
@ -126,10 +104,10 @@ namespace System.Threading.Tasks
// Continue to loop while there are more elements to be processed.
while (!state.Cancellation.IsCancellationRequested)
{
// Get the next element from the enumerator. For some types, we can get the next element with just
// Get the next element from the enumerator. For primitive types, we can get the next element with just
// interlocked operations, avoiding the need to take a lock. For other types, we need to take a lock.
T element;
if (Interlockable())
if (typeof(T).IsPrimitive)
{
TryAgain:
element = state.NextAvailable;
@ -138,7 +116,7 @@ namespace System.Threading.Tasks
break;
}
if (!CompareExchange(ref state.NextAvailable, element + T.One, element))
if (Interlocked.CompareExchange(ref state.NextAvailable, element + T.One, element) != element)
{
goto TryAgain;
}
@ -200,7 +178,7 @@ namespace System.Threading.Tasks
{
// Construct a state object that encapsulates all state to be passed and shared between
// the workers, and queues the first worker.
var state = new ForEachState<T>(fromInclusive, toExclusive, taskBody, !Interlockable(), dop, scheduler, cancellationToken, body);
var state = new ForAsyncState<T>(fromInclusive, toExclusive, taskBody, dop, scheduler, cancellationToken, body);
state.QueueWorkerIfDopAvailable();
return state.Task;
}
@ -747,18 +725,18 @@ namespace System.Threading.Tasks
}
}
/// <summary>Stores the state associated with an IAsyncEnumerable ForEachAsync operation, shared between all its workers.</summary>
/// <summary>Stores the state associated with an IAsyncEnumerable ForAsyncState operation, shared between all its workers.</summary>
/// <typeparam name="T">Specifies the type of data being enumerated.</typeparam>
private sealed class ForEachState<T> : ForEachAsyncState<T>, IDisposable
private sealed class ForAsyncState<T> : ForEachAsyncState<T>, IDisposable
{
public T NextAvailable;
public readonly T ToExclusive;
public ForEachState(
public ForAsyncState(
T fromExclusive, T toExclusive, Func<object, Task> taskBody,
bool needsLock, int dop, TaskScheduler scheduler, CancellationToken cancellationToken,
int dop, TaskScheduler scheduler, CancellationToken cancellationToken,
Func<T, CancellationToken, ValueTask> body) :
base(taskBody, needsLock, dop, scheduler, cancellationToken, body)
base(taskBody, !typeof(T).IsPrimitive, dop, scheduler, cancellationToken, body)
{
NextAvailable = fromExclusive;
ToExclusive = toExclusive;

View file

@ -211,9 +211,7 @@ namespace System.Threading.Tasks
if (iteration < oldLBI)
{
SpinWait wait = default;
while (typeof(TInt) == typeof(int) ?
Interlocked.CompareExchange(ref Unsafe.As<TInt, int>(ref pflags._lowestBreakIteration), Unsafe.As<TInt, int>(ref iteration), Unsafe.As<TInt, int>(ref oldLBI)) != Unsafe.As<TInt, int>(ref oldLBI) :
Interlocked.CompareExchange(ref Unsafe.As<TInt, long>(ref pflags._lowestBreakIteration), Unsafe.As<TInt, long>(ref iteration), Unsafe.As<TInt, long>(ref oldLBI)) != Unsafe.As<TInt, long>(ref oldLBI))
while (Interlocked.CompareExchange(ref pflags._lowestBreakIteration, iteration, oldLBI) != oldLBI)
{
wait.SpinOnce();
oldLBI = pflags.LowestBreakIteration;

View file

@ -33,8 +33,8 @@ namespace System.Threading.Tasks
// that range, minimizing the chances it'll be near the objects from other threads.
internal volatile StrongBox<long>? _nSharedCurrentIndexOffset;
// to be set to 1 by the worker that finishes this range. It's OK to do a non-interlocked write here.
internal int _bRangeFinished;
// to be set to true by the worker that finishes this range. It's OK to do a non-interlocked write here.
internal bool _bRangeFinished;
}
@ -101,7 +101,7 @@ namespace System.Threading.Tasks
// local snap to save array access bounds checks in places where we only read fields
IndexRange currentRange = _indexRanges[_nCurrentIndexRange];
if (currentRange._bRangeFinished == 0)
if (!currentRange._bRangeFinished)
{
StrongBox<long>? sharedCurrentIndexOffset = _indexRanges[_nCurrentIndexRange]._nSharedCurrentIndexOffset;
if (sharedCurrentIndexOffset == null)
@ -157,7 +157,7 @@ namespace System.Threading.Tasks
else
{
// this index range is completed, mark it so that others can skip it quickly
Interlocked.Exchange(ref _indexRanges[_nCurrentIndexRange]._bRangeFinished, 1);
Interlocked.Exchange(ref _indexRanges[_nCurrentIndexRange]._bRangeFinished, true);
}
}
@ -262,7 +262,7 @@ namespace System.Threading.Tasks
// the fromInclusive of the new index range is always on nCurrentIndex
_indexRanges[i]._nFromInclusive = nCurrentIndex;
_indexRanges[i]._nSharedCurrentIndexOffset = null;
_indexRanges[i]._bRangeFinished = 0;
_indexRanges[i]._bRangeFinished = false;
// now increment it to find the toExclusive value for our range
nCurrentIndex = unchecked(nCurrentIndex + nRangeSize);

View file

@ -452,7 +452,7 @@ namespace System.Threading.ThreadPools.Tests
bool waitForWorkStart = false;
var workStarted = new AutoResetEvent(false);
var localWorkScheduled = new AutoResetEvent(false);
int completeWork = 0;
bool completeWork = false;
int queuedWorkCount = 0;
var allWorkCompleted = new ManualResetEvent(false);
Exception backgroundEx = null;
@ -467,7 +467,7 @@ namespace System.Threading.ThreadPools.Tests
// Blocking can affect thread pool thread injection heuristics, so don't block, pretend like a
// long-running CPU-bound work item
ThreadTestHelpers.WaitForConditionWithoutRelinquishingTimeSlice(
() => Interlocked.CompareExchange(ref completeWork, 0, 0) != 0);
() => Interlocked.CompareExchange(ref completeWork, false, false));
}
catch (Exception ex)
{
@ -557,7 +557,7 @@ namespace System.Threading.ThreadPools.Tests
finally
{
// Complete the work
Interlocked.Exchange(ref completeWork, 1);
Interlocked.Exchange(ref completeWork, true);
}
// Wait for work items to exit, for counting

View file

@ -269,7 +269,7 @@ namespace System.Threading
[System.CLSCompliantAttribute(false)]
public static ulong CompareExchange(ref ulong location1, ulong value, ulong comparand) { throw null; }
[return: System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("location1")]
public static T CompareExchange<T>(ref T location1, T value, T comparand) where T : class? { throw null; }
public static T CompareExchange<T>(ref T location1, T value, T comparand) { throw null; }
public static int Decrement(ref int location) { throw null; }
public static long Decrement(ref long location) { throw null; }
[System.CLSCompliantAttribute(false)]
@ -296,7 +296,7 @@ namespace System.Threading
[System.CLSCompliantAttribute(false)]
public static ulong Exchange(ref ulong location1, ulong value) { throw null; }
[return: System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("location1")]
public static T Exchange<T>([System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("value")] ref T location1, T value) where T : class? { throw null; }
public static T Exchange<T>([System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("value")] ref T location1, T value) { throw null; }
public static int Increment(ref int location) { throw null; }
public static long Increment(ref long location) { throw null; }
[System.CLSCompliantAttribute(false)]

View file

@ -5,6 +5,7 @@ using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Xunit;
@ -145,9 +146,15 @@ namespace System.Threading.Tests
{
using BoundedMemory<sbyte> memory = BoundedMemory.Allocate<sbyte>(1);
ref sbyte value = ref memory.Span[0];
value = 42;
Assert.Equal(42, Interlocked.Exchange(ref value, 123));
Assert.Equal(123, value);
value = 42;
Assert.Equal(42, Interlocked.Exchange<sbyte>(ref value, 123));
Assert.Equal(123, value);
Assert.Throws<NullReferenceException>(() => Interlocked.Exchange(ref Unsafe.NullRef<byte>(), 123));
}
@ -156,9 +163,15 @@ namespace System.Threading.Tests
{
using BoundedMemory<byte> memory = BoundedMemory.Allocate<byte>(1);
ref byte value = ref memory.Span[0];
value = 42;
Assert.Equal(42u, Interlocked.Exchange(ref value, 123));
Assert.Equal(123u, value);
value = 42;
Assert.Equal(42u, Interlocked.Exchange<byte>(ref value, 123));
Assert.Equal(123u, value);
Assert.Throws<NullReferenceException>(() => Interlocked.Exchange(ref Unsafe.NullRef<sbyte>(), 123));
}
@ -167,9 +180,15 @@ namespace System.Threading.Tests
{
using BoundedMemory<short> memory = BoundedMemory.Allocate<short>(1);
ref short value = ref memory.Span[0];
value = 42;
Assert.Equal(42, Interlocked.Exchange(ref value, 12345));
Assert.Equal(12345, value);
value = 42;
Assert.Equal(42, Interlocked.Exchange<short>(ref value, 12345));
Assert.Equal(12345, value);
Assert.Throws<NullReferenceException>(() => Interlocked.Exchange(ref Unsafe.NullRef<short>(), 12345));
}
@ -178,9 +197,15 @@ namespace System.Threading.Tests
{
using BoundedMemory<ushort> memory = BoundedMemory.Allocate<ushort>(1);
ref ushort value = ref memory.Span[0];
value = 42;
Assert.Equal(42u, Interlocked.Exchange(ref value, 12345));
Assert.Equal(12345u, value);
value = 42;
Assert.Equal(42u, Interlocked.Exchange<ushort>(ref value, 12345));
Assert.Equal(12345u, value);
Assert.Throws<NullReferenceException>(() => Interlocked.Exchange(ref Unsafe.NullRef<ushort>(), 12345));
}
@ -189,9 +214,15 @@ namespace System.Threading.Tests
{
using BoundedMemory<int> memory = BoundedMemory.Allocate<int>(1);
ref int value = ref memory.Span[0];
value = 42;
Assert.Equal(42, Interlocked.Exchange(ref value, 12345));
Assert.Equal(12345, value);
value = 42;
Assert.Equal(42, Interlocked.Exchange<int>(ref value, 12345));
Assert.Equal(12345, value);
Assert.Throws<NullReferenceException>(() => Interlocked.Exchange(ref Unsafe.NullRef<int>(), 12345));
}
@ -200,9 +231,15 @@ namespace System.Threading.Tests
{
using BoundedMemory<uint> memory = BoundedMemory.Allocate<uint>(1);
ref uint value = ref memory.Span[0];
value = 42;
Assert.Equal(42u, Interlocked.Exchange(ref value, 12345u));
Assert.Equal(12345u, value);
value = 42;
Assert.Equal(42u, Interlocked.Exchange<uint>(ref value, 12345u));
Assert.Equal(12345u, value);
Assert.Throws<NullReferenceException>(() => Interlocked.Exchange(ref Unsafe.NullRef<uint>(), 12345));
}
@ -211,9 +248,15 @@ namespace System.Threading.Tests
{
using BoundedMemory<long> memory = BoundedMemory.Allocate<long>(1);
ref long value = ref memory.Span[0];
value = 42;
Assert.Equal(42, Interlocked.Exchange(ref value, 12345));
Assert.Equal(12345, value);
value = 42;
Assert.Equal(42, Interlocked.Exchange<long>(ref value, 12345));
Assert.Equal(12345, value);
Assert.Throws<NullReferenceException>(() => Interlocked.Exchange(ref Unsafe.NullRef<long>(), 12345));
}
@ -222,14 +265,22 @@ namespace System.Threading.Tests
{
using BoundedMemory<nint> memory = BoundedMemory.Allocate<nint>(1);
ref nint value = ref memory.Span[0];
value = 42;
Assert.Equal(42, (nint)Interlocked.Exchange(ref value, (nint)12345));
Assert.Equal(12345, value);
value = 42;
Assert.Equal(42, (nint)Interlocked.Exchange<nint>(ref value, (nint)12345));
Assert.Equal(12345, value);
if (Environment.Is64BitProcess)
{
Assert.Equal(12345, (nint)Interlocked.Exchange(ref value, unchecked((nint)1 + int.MaxValue)));
Assert.Equal(unchecked((nint)1 + int.MaxValue), value);
Assert.Equal(unchecked((nint)1 + int.MaxValue), (nint)Interlocked.Exchange(ref value, unchecked((nint)2 + int.MaxValue)));
Assert.Equal(unchecked((nint)2 + int.MaxValue), value);
}
Assert.Throws<NullReferenceException>(() => Interlocked.Exchange(ref Unsafe.NullRef<nint>(), 12345));
}
@ -239,9 +290,15 @@ namespace System.Threading.Tests
{
using BoundedMemory<ulong> memory = BoundedMemory.Allocate<ulong>(1);
ref ulong value = ref memory.Span[0];
value = 42;
Assert.Equal(42u, Interlocked.Exchange(ref value, 12345u));
Assert.Equal(12345u, value);
value = 42;
Assert.Equal(42u, Interlocked.Exchange<ulong>(ref value, 12345u));
Assert.Equal(12345u, value);
Assert.Throws<NullReferenceException>(() => Interlocked.Exchange(ref Unsafe.NullRef<ulong>(), 12345));
}
@ -250,14 +307,22 @@ namespace System.Threading.Tests
{
using BoundedMemory<nuint> memory = BoundedMemory.Allocate<nuint>(1);
ref nuint value = ref memory.Span[0];
value = 42;
Assert.Equal(42u, (nuint)Interlocked.Exchange(ref value, (nuint)12345u));
Assert.Equal(12345u, value);
value = 42;
Assert.Equal(42u, (nuint)Interlocked.Exchange<nuint>(ref value, (nuint)12345u));
Assert.Equal(12345u, value);
if (Environment.Is64BitProcess)
{
Assert.Equal(12345u, (nuint)Interlocked.Exchange(ref value, unchecked((nuint)1 + uint.MaxValue)));
Assert.Equal(unchecked((nuint)1 + uint.MaxValue), value);
Assert.Equal(unchecked((nuint)1 + uint.MaxValue), (nuint)Interlocked.Exchange(ref value, unchecked((nuint)2 + uint.MaxValue)));
Assert.Equal(unchecked((nuint)2 + uint.MaxValue), value);
}
Assert.Throws<NullReferenceException>(() => Interlocked.Exchange(ref Unsafe.NullRef<nuint>(), 12345));
}
@ -267,9 +332,15 @@ namespace System.Threading.Tests
{
using BoundedMemory<float> memory = BoundedMemory.Allocate<float>(1);
ref float value = ref memory.Span[0];
value = 42.1f;
Assert.Equal(42.1f, Interlocked.Exchange(ref value, 12345.1f));
Assert.Equal(12345.1f, value);
value = 42.1f;
Assert.Equal(42.1f, Interlocked.Exchange<float>(ref value, 12345.1f));
Assert.Equal(12345.1f, value);
Assert.Throws<NullReferenceException>(() => Interlocked.Exchange(ref Unsafe.NullRef<float>(), 12345.1f));
}
@ -278,9 +349,15 @@ namespace System.Threading.Tests
{
using BoundedMemory<double> memory = BoundedMemory.Allocate<double>(1);
ref double value = ref memory.Span[0];
value = 42.1;
Assert.Equal(42.1, Interlocked.Exchange(ref value, 12345.1));
Assert.Equal(12345.1, value);
value = 42.1;
Assert.Equal(42.1, Interlocked.Exchange<double>(ref value, 12345.1));
Assert.Equal(12345.1, value);
Assert.Throws<NullReferenceException>(() => Interlocked.Exchange(ref Unsafe.NullRef<double>(), 12345.1));
}
@ -289,10 +366,15 @@ namespace System.Threading.Tests
{
var oldValue = new object();
var newValue = new object();
object value = oldValue;
object value = oldValue;
Assert.Same(oldValue, Interlocked.Exchange(ref value, newValue));
Assert.Same(newValue, value);
value = oldValue;
Assert.Same(oldValue, Interlocked.Exchange<object>(ref value, newValue));
Assert.Same(newValue, value);
Assert.Throws<NullReferenceException>(() => Interlocked.Exchange(ref Unsafe.NullRef<object>(), null));
Assert.Throws<NullReferenceException>(() => Interlocked.Exchange(ref Unsafe.NullRef<object>(), newValue));
}
@ -311,6 +393,20 @@ namespace System.Threading.Tests
Assert.Equal(12345, (int)value);
}
[Fact]
public void InterlockedExchange_Unsupported()
{
DateTime value1 = default;
TimeSpan value2 = default;
Rune value3 = default;
ValueTask value4 = default;
Assert.Throws<NotSupportedException>(() => Interlocked.Exchange(ref value1, default));
Assert.Throws<NotSupportedException>(() => Interlocked.Exchange(ref value2, default));
Assert.Throws<NotSupportedException>(() => Interlocked.Exchange(ref value3, default));
Assert.Throws<NotSupportedException>(() => Interlocked.Exchange(ref value4, default));
}
[Fact]
public void InterlockedCompareExchange_Int8()
{
@ -324,7 +420,16 @@ namespace System.Threading.Tests
Assert.Equal(42, Interlocked.CompareExchange(ref value, 123, 42));
Assert.Equal(123, value);
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange(ref Unsafe.NullRef<byte>(), 123, 41));
value = 42;
Assert.Equal(42, Interlocked.CompareExchange<sbyte>(ref value, 123, 41));
Assert.Equal(42, value);
Assert.Equal(42, Interlocked.CompareExchange<sbyte>(ref value, 123, 42));
Assert.Equal(123, value);
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange(ref Unsafe.NullRef<sbyte>(), 123, 41));
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange<sbyte>(ref Unsafe.NullRef<sbyte>(), 123, 41));
}
[Fact]
@ -340,7 +445,32 @@ namespace System.Threading.Tests
Assert.Equal(42u, Interlocked.CompareExchange(ref value, 123, 42));
Assert.Equal(123u, value);
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange(ref Unsafe.NullRef<sbyte>(), 123, 41));
value = 42;
Assert.Equal(42u, Interlocked.CompareExchange<byte>(ref value, 123, 41));
Assert.Equal(42u, value);
Assert.Equal(42u, Interlocked.CompareExchange<byte>(ref value, 123, 42));
Assert.Equal(123u, value);
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange(ref Unsafe.NullRef<byte>(), 123, 41));
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange<byte>(ref Unsafe.NullRef<byte>(), 123, 41));
}
[Fact]
public void InterlockedCompareExchange_Bool()
{
using BoundedMemory<bool> memory = BoundedMemory.Allocate<bool>(1);
ref bool value = ref memory.Span[0];
value = false;
Assert.False(Interlocked.CompareExchange<bool>(ref value, true, false));
Assert.True(value);
Assert.True(Interlocked.CompareExchange<bool>(ref value, false, false));
Assert.True(value);
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange<bool>(ref Unsafe.NullRef<bool>(), false, false));
}
[Fact]
@ -356,7 +486,16 @@ namespace System.Threading.Tests
Assert.Equal(42, Interlocked.CompareExchange(ref value, 12345, 42));
Assert.Equal(12345, value);
value = 42;
Assert.Equal(42, Interlocked.CompareExchange<short>(ref value, 12345, 41));
Assert.Equal(42, value);
Assert.Equal(42, Interlocked.CompareExchange<short>(ref value, 12345, 42));
Assert.Equal(12345, value);
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange(ref Unsafe.NullRef<short>(), 12345, 41));
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange<short>(ref Unsafe.NullRef<short>(), 12345, 41));
}
[Fact]
@ -372,7 +511,32 @@ namespace System.Threading.Tests
Assert.Equal(42u, Interlocked.CompareExchange(ref value, 12345, 42));
Assert.Equal(12345u, value);
value = 42;
Assert.Equal(42u, Interlocked.CompareExchange<ushort>(ref value, 12345, 41));
Assert.Equal(42u, value);
Assert.Equal(42u, Interlocked.CompareExchange<ushort>(ref value, 12345, 42));
Assert.Equal(12345u, value);
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange(ref Unsafe.NullRef<ushort>(), 12345, 41));
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange<ushort>(ref Unsafe.NullRef<ushort>(), 12345, 41));
}
[Fact]
public void InterlockedCompareExchange_Char()
{
using BoundedMemory<char> memory = BoundedMemory.Allocate<char>(1);
ref char value = ref memory.Span[0];
value = (char)42;
Assert.Equal(42u, Interlocked.CompareExchange<char>(ref value, (char)12345, (char)41));
Assert.Equal(42u, value);
Assert.Equal(42u, Interlocked.CompareExchange<char>(ref value, (char)12345, (char)42));
Assert.Equal(12345u, value);
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange<char>(ref Unsafe.NullRef<char>(), (char)12345, (char)41));
}
[Fact]
@ -388,7 +552,16 @@ namespace System.Threading.Tests
Assert.Equal(42, Interlocked.CompareExchange(ref value, 12345, 42));
Assert.Equal(12345, value);
value = 42;
Assert.Equal(42, Interlocked.CompareExchange<int>(ref value, 12345, 41));
Assert.Equal(42, value);
Assert.Equal(42, Interlocked.CompareExchange<int>(ref value, 12345, 42));
Assert.Equal(12345, value);
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange(ref Unsafe.NullRef<int>(), 12345, 41));
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange<int>(ref Unsafe.NullRef<int>(), 12345, 41));
}
[Fact]
@ -404,7 +577,32 @@ namespace System.Threading.Tests
Assert.Equal(42u, Interlocked.CompareExchange(ref value, 12345u, 42u));
Assert.Equal(12345u, value);
value = 42;
Assert.Equal(42u, Interlocked.CompareExchange<uint>(ref value, 12345u, 41u));
Assert.Equal(42u, value);
Assert.Equal(42u, Interlocked.CompareExchange<uint>(ref value, 12345u, 42u));
Assert.Equal(12345u, value);
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange(ref Unsafe.NullRef<uint>(), 12345, 41));
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange<uint>(ref Unsafe.NullRef<uint>(), 12345, 41));
}
[Fact]
public void InterlockedCompareExchange_Enum()
{
using BoundedMemory<DayOfWeek> memory = BoundedMemory.Allocate<DayOfWeek>(1);
ref DayOfWeek value = ref memory.Span[0];
value = DayOfWeek.Monday;
Assert.Equal(DayOfWeek.Monday, Interlocked.CompareExchange<DayOfWeek>(ref value, DayOfWeek.Tuesday, DayOfWeek.Monday));
Assert.Equal(DayOfWeek.Tuesday, value);
Assert.Equal(DayOfWeek.Tuesday, Interlocked.CompareExchange<DayOfWeek>(ref value, DayOfWeek.Wednesday, DayOfWeek.Monday));
Assert.Equal(DayOfWeek.Tuesday, value);
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange<DayOfWeek>(ref Unsafe.NullRef<DayOfWeek>(), DayOfWeek.Monday, DayOfWeek.Tuesday));
}
[Fact]
@ -420,7 +618,16 @@ namespace System.Threading.Tests
Assert.Equal(42, Interlocked.CompareExchange(ref value, 12345, 42));
Assert.Equal(12345, value);
value = 42;
Assert.Equal(42, Interlocked.CompareExchange<long>(ref value, 12345, 41));
Assert.Equal(42, value);
Assert.Equal(42, Interlocked.CompareExchange<long>(ref value, 12345, 42));
Assert.Equal(12345, value);
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange(ref Unsafe.NullRef<long>(), 12345, 41));
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange<long>(ref Unsafe.NullRef<long>(), 12345, 41));
}
[Fact]
@ -430,19 +637,31 @@ namespace System.Threading.Tests
ref nint value = ref memory.Span[0];
value = 42;
Assert.Equal(42, (nint)Interlocked.CompareExchange(ref value, (nint)12345, (nint)41));
Assert.Equal(42, Interlocked.CompareExchange(ref value, (nint)12345, (nint)41));
Assert.Equal(42, value);
Assert.Equal(42, (nint)Interlocked.CompareExchange(ref value, (nint)12345, (nint)42));
Assert.Equal(42, Interlocked.CompareExchange(ref value, (nint)12345, (nint)42));
Assert.Equal(12345, value);
value = 42;
Assert.Equal(42, Interlocked.CompareExchange<nint>(ref value, (nint)12345, (nint)41));
Assert.Equal(42, value);
Assert.Equal(42, Interlocked.CompareExchange<nint>(ref value, (nint)12345, (nint)42));
Assert.Equal(12345, value);
if (Environment.Is64BitProcess)
{
Assert.Equal(12345, (nint)Interlocked.CompareExchange(ref value, unchecked((nint)1 + int.MaxValue), (nint)12345u));
Assert.Equal(12345, Interlocked.CompareExchange(ref value, unchecked((nint)1 + int.MaxValue), (nint)12345));
Assert.Equal(unchecked((nint)1 + int.MaxValue), value);
Assert.Equal(unchecked((nint)1 + int.MaxValue), Interlocked.CompareExchange<nint>(ref value, unchecked((nint)2 + int.MaxValue), unchecked((nint)1 + int.MaxValue)));
Assert.Equal(unchecked((nint)2 + int.MaxValue), value);
}
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange(ref Unsafe.NullRef<nint>(), 12345, 41));
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange<nint>(ref Unsafe.NullRef<nint>(), 12345, 41));
}
[Fact]
@ -458,7 +677,16 @@ namespace System.Threading.Tests
Assert.Equal(42u, Interlocked.CompareExchange(ref value, 12345u, 42u));
Assert.Equal(12345u, value);
value = 42;
Assert.Equal(42u, Interlocked.CompareExchange<ulong>(ref value, 12345u, 41u));
Assert.Equal(42u, value);
Assert.Equal(42u, Interlocked.CompareExchange<ulong>(ref value, 12345u, 42u));
Assert.Equal(12345u, value);
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange(ref Unsafe.NullRef<ulong>(), 12345, 41));
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange<ulong>(ref Unsafe.NullRef<ulong>(), 12345, 41));
}
[Fact]
@ -468,19 +696,31 @@ namespace System.Threading.Tests
ref nuint value = ref memory.Span[0];
value = 42;
Assert.Equal(42u, (nuint)Interlocked.CompareExchange(ref value, (nuint)12345u, (nuint)41u));
Assert.Equal(42u, Interlocked.CompareExchange(ref value, (nuint)12345u, (nuint)41u));
Assert.Equal(42u, value);
Assert.Equal(42u, (nuint)Interlocked.CompareExchange(ref value, (nuint)12345u, (nuint)42u));
Assert.Equal(42u, Interlocked.CompareExchange(ref value, (nuint)12345u, (nuint)42u));
Assert.Equal(12345u, value);
value = 42;
Assert.Equal(42u, Interlocked.CompareExchange<nuint>(ref value, (nuint)12345u, (nuint)41u));
Assert.Equal(42u, value);
Assert.Equal(42u, Interlocked.CompareExchange<nuint>(ref value, (nuint)12345u, (nuint)42u));
Assert.Equal(12345u, value);
if (Environment.Is64BitProcess)
{
Assert.Equal(12345u, (nuint)Interlocked.CompareExchange(ref value, unchecked((nuint)1 + uint.MaxValue), (nuint)12345u));
Assert.Equal(unchecked((nuint)1 + uint.MaxValue), value);
Assert.Equal(unchecked((nuint)1 + uint.MaxValue), Interlocked.CompareExchange<nuint>(ref value, unchecked((nuint)2 + uint.MaxValue), unchecked((nuint)1 + uint.MaxValue)));
Assert.Equal(unchecked((nuint)2 + uint.MaxValue), value);
}
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange(ref Unsafe.NullRef<nuint>(), 12345, 41));
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange<nuint>(ref Unsafe.NullRef<nuint>(), 12345, 41));
}
[Fact]
@ -496,7 +736,16 @@ namespace System.Threading.Tests
Assert.Equal(42.1f, Interlocked.CompareExchange(ref value, 12345.1f, 42.1f));
Assert.Equal(12345.1f, value);
value = 42.1f;
Assert.Equal(42.1f, Interlocked.CompareExchange<float>(ref value, 12345.1f, 41.1f));
Assert.Equal(42.1f, value);
Assert.Equal(42.1f, Interlocked.CompareExchange<float>(ref value, 12345.1f, 42.1f));
Assert.Equal(12345.1f, value);
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange(ref Unsafe.NullRef<float>(), 12345.1f, 41.1f));
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange<float>(ref Unsafe.NullRef<float>(), 12345.1f, 41.1f));
}
[Fact]
@ -512,7 +761,16 @@ namespace System.Threading.Tests
Assert.Equal(42.1, Interlocked.CompareExchange(ref value, 12345.1, 42.1));
Assert.Equal(12345.1, value);
value = 42.1;
Assert.Equal(42.1, Interlocked.CompareExchange<double>(ref value, 12345.1, 41.1));
Assert.Equal(42.1, value);
Assert.Equal(42.1, Interlocked.CompareExchange<double>(ref value, 12345.1, 42.1));
Assert.Equal(12345.1, value);
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange(ref Unsafe.NullRef<double>(), 12345.1, 41.1));
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange<double>(ref Unsafe.NullRef<double>(), 12345.1, 41.1));
}
[Fact]
@ -528,8 +786,18 @@ namespace System.Threading.Tests
Assert.Same(oldValue, Interlocked.CompareExchange(ref value, newValue, oldValue));
Assert.Same(newValue, value);
value = oldValue;
Assert.Same(oldValue, Interlocked.CompareExchange<object>(ref value, newValue, new object()));
Assert.Same(oldValue, value);
Assert.Same(oldValue, Interlocked.CompareExchange<object>(ref value, newValue, oldValue));
Assert.Same(newValue, value);
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange(ref Unsafe.NullRef<object>(), null, null));
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange(ref Unsafe.NullRef<object>(), newValue, oldValue));
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange<object>(ref Unsafe.NullRef<object>(), null, null));
Assert.Throws<NullReferenceException>(() => Interlocked.CompareExchange<object>(ref Unsafe.NullRef<object>(), newValue, oldValue));
}
[Fact]
@ -552,6 +820,20 @@ namespace System.Threading.Tests
Assert.Equal(12345, (int)value);
}
[Fact]
public void InterlockedCompareExchange_Unsupported()
{
DateTime value1 = default;
TimeSpan value2 = default;
Rune value3 = default;
ValueTask value4 = default;
Assert.Throws<NotSupportedException>(() => Interlocked.CompareExchange(ref value1, default, default));
Assert.Throws<NotSupportedException>(() => Interlocked.CompareExchange(ref value2, default, default));
Assert.Throws<NotSupportedException>(() => Interlocked.CompareExchange(ref value3, default, default));
Assert.Throws<NotSupportedException>(() => Interlocked.CompareExchange(ref value4, default, default));
}
[Fact]
public void InterlockedRead_Int64()
{

View file

@ -72,47 +72,10 @@ namespace System.Threading
[MethodImplAttribute(MethodImplOptions.InternalCall)]
public static extern long CompareExchange(ref long location1, long value, long comparand);
[return: NotNullIfNotNull(nameof(location1))]
[Intrinsic]
public static T CompareExchange<T>(ref T location1, T value, T comparand) where T : class?
{
if (Unsafe.IsNullRef(ref location1))
throw new NullReferenceException();
// Besides avoiding coop handles for efficiency,
// and correctness, this also appears needed to
// avoid an assertion failure in the runtime, related to
// coop handles over generics.
//
// See CompareExchange(object) for comments.
//
// This is not entirely convincing due to lack of volatile.
//
T? result = null;
// T : class so call the object overload.
CompareExchange(ref Unsafe.As<T, object?>(ref location1), ref Unsafe.As<T, object?>(ref value), ref Unsafe.As<T, object?>(ref comparand), ref Unsafe.As<T, object?>(ref result!));
return result;
}
[Intrinsic]
[MethodImplAttribute(MethodImplOptions.InternalCall)]
public static extern long Exchange(ref long location1, long value);
[return: NotNullIfNotNull(nameof(location1))]
[Intrinsic]
public static T Exchange<T>([NotNullIfNotNull(nameof(value))] ref T location1, T value) where T : class?
{
if (Unsafe.IsNullRef(ref location1))
throw new NullReferenceException();
// See CompareExchange(T) for comments.
//
// This is not entirely convincing due to lack of volatile.
//
T? result = null;
// T : class so call the object overload.
Exchange(ref Unsafe.As<T, object?>(ref location1), ref Unsafe.As<T, object?>(ref value), ref Unsafe.As<T, object?>(ref result!));
return result;
}
[Intrinsic]
[MethodImplAttribute(MethodImplOptions.InternalCall)]
public static extern long Read(ref long location);