in ReSharper.FSharp/src/FSharp/FSharp.ProjectModelBase/src/FSharpAsyncUtil.cs [56:149]
public static void UsingReadLockInsideFcs(IShellLocks locks, Action action)
{
var logger = Logger.GetLogger(typeof(FSharpAsyncUtil));
// Capture FCS cancellation token so we can propagate cancellation in cases like F#->C#->F#,
// where the last FCS request is initiated from inside reading the C# metadata,
// and the initial request may get cancelled
var fcsToken = Cancellable.HasCancellationToken ? Cancellable.Token : CancellationToken.None;
// Try to acquire read lock on the current thread.
// It should be possible, unless there's a write lock request that prevents it.
try
{
// FCS token is the source for the cancellation
using var _ = Interruption.Current.Add(new LifetimeInterruptionSource(fcsToken));
if (locks.TryExecuteWithReadLock(action))
{
logger.Trace("UsingReadLockInsideFcs: executed on the initial thread, returning");
return;
}
}
catch (Exception e) when (e.IsOperationCanceled())
{
logger.Trace("UsingReadLockInsideFcs: exception: the operation cancelled on the initial thread");
if (locks.IsReadAccessAllowed())
{
// The FCS request has originated from a R# thread and was cancelled. We don't want to requeue this request.
// If the request is coming from FCS, it's cancelled below in the loop.
// We could also check `Cancellable.HasCancellationToken` instead.
logger.Trace("UsingReadLockInsideFcs: read lock was acquired before the request, rethrowing");
throw;
}
}
catch (Exception e)
{
logger.Trace("UsingReadLockInsideFcs: exception: rethrowing");
Logger.LogException(e);
throw;
}
// Could not finish task under a read lock. Queue a request to be processed by a thread calling FCS.
// To ensure FCS metadata consistency, we retry cancelled requests in this loop.
// This may happen when R# read lock is cancelled, but the corresponding FCS request has not seen it yet.
// We check the FCS request cancellation separately via its cancellation token.
logger.Trace("UsingReadLockInsideFcs: before the loop");
while (true)
{
CheckAndThrow();
logger.Trace("UsingReadLockInsideFcs: enqueueing a new request");
var tcs = new TaskCompletionSource<Unit>();
ReadRequests.Enqueue(() =>
{
try
{
logger.Trace("UsingReadLockInsideFcs: before action");
action();
logger.Trace("UsingReadLockInsideFcs: after action");
tcs.SetResult(Unit.Instance);
}
catch (Exception e) when (e.IsOperationCanceled())
{
tcs.SetCanceled();
logger.Trace("UsingReadLockInsideFcs: exception: cancelled");
}
catch (Exception e)
{
tcs.SetException(e);
logger.Trace("UsingReadLockInsideFcs: exception");
Logger.LogException(e);
}
});
try
{
logger.Trace("UsingReadLockInsideFcs: before tcs.Task.Wait");
tcs.Task.Wait();
logger.Trace("UsingReadLockInsideFcs: after tcs.Task.Wait, breaking");
break;
}
catch (Exception e) when (e.IsOperationCanceled())
{
logger.Trace("UsingReadLockInsideFcs: exception: cancelled, checking FCS token");
CheckAndThrow();
}
catch (Exception)
{
logger.Trace("UsingReadLockInsideFcs: exception");
throw;
}
}
}