in src/Microsoft.VisualStudio.Threading/AsyncReaderWriterLock.cs [1068:1185]
private bool TryIssueLock(Awaiter awaiter, bool previouslyQueued, bool skipPendingWriteLockCheck = false)
{
lock (this.syncObject)
{
if (this.completeInvoked && !previouslyQueued)
{
// If this is a new top-level lock request, reject it completely.
if (awaiter.NestingLock is null)
{
awaiter.SetFault(new InvalidOperationException(Strings.LockCompletionAlreadyRequested));
return false;
}
}
bool issued = false;
if (this.reenterConcurrencyPrepRunning is null)
{
if (this.issuedWriteLocks.Count == 0 && this.issuedUpgradeableReadLocks.Count == 0 && this.issuedReadLocks.Count == 0)
{
issued = true;
}
else
{
this.AggregateLockStackKinds(awaiter, out bool hasRead, out bool hasUpgradeableRead, out bool hasWrite);
switch (awaiter.Kind)
{
case LockKind.Read:
if (this.issuedWriteLocks.Count == 0 && (skipPendingWriteLockCheck || this.waitingWriters.Count == 0))
{
issued = true;
}
else if (hasWrite)
{
// We allow STA threads to not have the sync context applied because it never has it applied,
// and a write lock holder is allowed to transition to an STA tread.
// But if an MTA thread has the write lock but not the sync context, then they're likely
// an accidental execution fork that is exposing concurrency inappropriately.
if (this.CanCurrentThreadHoldActiveLock && !(SynchronizationContext.Current is NonConcurrentSynchronizationContext))
{
#if NETFRAMEWORK || NETCOREAPP // Assertion failures crash on .NET Core < 3.0
Report.Fail("Dangerous request for read lock from fork of write lock.");
#endif
Verify.FailOperation(Strings.DangerousReadLockRequestFromWriteLockFork);
}
issued = true;
}
else if (hasRead || hasUpgradeableRead)
{
issued = true;
}
break;
case LockKind.UpgradeableRead:
if (hasUpgradeableRead || hasWrite)
{
issued = true;
}
else if (hasRead)
{
// We cannot issue an upgradeable read lock to folks who have (only) a read lock.
throw new InvalidOperationException(Strings.CannotUpgradeNonUpgradeableLock);
}
#pragma warning disable CA1508 // Avoid dead conditional code
else if (this.issuedUpgradeableReadLocks.Count == 0 && this.issuedWriteLocks.Count == 0)
#pragma warning restore CA1508 // Avoid dead conditional code
{
issued = true;
}
break;
case LockKind.Write:
if (hasWrite)
{
issued = true;
}
else if (hasRead && !hasUpgradeableRead)
{
// We cannot issue a write lock when the caller already holds a read lock.
throw new InvalidOperationException(Strings.CannotUpgradeNonUpgradeableLock);
}
else if (this.AllHeldLocksAreByThisStack(awaiter.NestingLock))
{
issued = true;
Awaiter? stickyWriteAwaiter = this.FindRootUpgradeableReadWithStickyWrite(awaiter);
if (stickyWriteAwaiter is object)
{
// Add the upgradeable reader as a write lock as well.
this.issuedWriteLocks.Add(stickyWriteAwaiter);
}
}
break;
default:
throw Assumes.NotReachable();
}
}
}
if (issued)
{
this.GetActiveLockSet(awaiter.Kind).Add(awaiter);
this.etw.Issued(awaiter);
}
if (!issued)
{
this.etw.WaitStart(awaiter);
// If the lock is immediately available, we don't need to coordinate with other threads.
// But if it is NOT available, we'd have to wait potentially for other threads to do more work.
Debugger.NotifyOfCrossThreadDependency();
}
return issued;
}
}