Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use IExpeditableDelaySource and TimeSpan in additional locations #54344

Merged
merged 5 commits into from
Jun 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.Workspaces
[Shared]
internal partial class ProjectCacheHostServiceFactory : IWorkspaceServiceFactory
{
private const int ImplicitCacheTimeoutInMS = 10000;
private static readonly TimeSpan s_implicitCacheTimeout = TimeSpan.FromMilliseconds(10000);

[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
Expand All @@ -30,7 +30,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
return new ProjectCacheService(workspaceServices.Workspace);
}

var service = new ProjectCacheService(workspaceServices.Workspace, ImplicitCacheTimeoutInMS);
var service = new ProjectCacheService(workspaceServices.Workspace, s_implicitCacheTimeout);

// Also clear the cache when the solution is cleared or removed.
workspaceServices.Workspace.WorkspaceChanged += (s, e) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public void Register(Workspace workspace)

private async Task AnalyzeAsync()
{
var workerBackOffTimeSpanInMS = _workspace.Options.GetOption(InternalSolutionCrawlerOptions.PreviewBackOffTimeSpanInMS);
var workerBackOffTimeSpan = InternalSolutionCrawlerOptions.PreviewBackOffTimeSpan;
var incrementalAnalyzer = _owner._analyzerService.CreateIncrementalAnalyzer(_workspace);

var solution = _workspace.CurrentSolution;
Expand All @@ -89,7 +89,7 @@ private async Task AnalyzeAsync()
}

// delay analyzing
await Task.Delay(workerBackOffTimeSpanInMS, _source.Token).ConfigureAwait(false);
await _owner._listener.Delay(workerBackOffTimeSpan, _source.Token).ConfigureAwait(false);

// do actual analysis
if (textDocument is Document document)
Expand Down
42 changes: 27 additions & 15 deletions src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -788,14 +788,23 @@ internal async Task Document_Cancellation(BackgroundAnalysisScope analysisScope,
var expectedDocumentSyntaxEvents = 1;
var expectedDocumentSemanticEvents = 5;

var listenerProvider = GetListenerProvider(workspace.ExportProvider);

// start an operation that allows an expedited wait to cover the remainder of the delayed operations in the test
var token = listenerProvider.GetListener(FeatureAttribute.SolutionCrawler).BeginAsyncOperation("Test operation");
var expeditedWait = listenerProvider.GetWaiter(FeatureAttribute.SolutionCrawler).ExpeditedWaitAsync();

workspace.ChangeDocument(document.Id, SourceText.From("//"));
if (expectedDocumentSyntaxEvents > 0 || expectedDocumentSemanticEvents > 0)
{
analyzer.RunningEvent.Wait();
}

token.Dispose();

workspace.ChangeDocument(document.Id, SourceText.From("// "));
await WaitAsync(service, workspace);
await expeditedWait;

service.Unregister(workspace);

Expand Down Expand Up @@ -833,6 +842,12 @@ internal async Task Document_Cancellation_MultipleTimes(BackgroundAnalysisScope

service.Register(workspace);

var listenerProvider = GetListenerProvider(workspace.ExportProvider);

// start an operation that allows an expedited wait to cover the remainder of the delayed operations in the test
var token = listenerProvider.GetListener(FeatureAttribute.SolutionCrawler).BeginAsyncOperation("Test operation");
var expeditedWait = listenerProvider.GetWaiter(FeatureAttribute.SolutionCrawler).ExpeditedWaitAsync();

workspace.ChangeDocument(document.Id, SourceText.From("//"));
if (expectedDocumentSyntaxEvents > 0 || expectedDocumentSemanticEvents > 0)
{
Expand All @@ -846,8 +861,11 @@ internal async Task Document_Cancellation_MultipleTimes(BackgroundAnalysisScope
analyzer.RunningEvent.Wait();
}

token.Dispose();

workspace.ChangeDocument(document.Id, SourceText.From("// "));
await WaitAsync(service, workspace);
await expeditedWait;

service.Unregister(workspace);

Expand Down Expand Up @@ -1273,6 +1291,12 @@ public async Task FileFromSameProjectTogetherTest()

await WaitWaiterAsync(workspace.ExportProvider);

var listenerProvider = GetListenerProvider(workspace.ExportProvider);

// start an operation that allows an expedited wait to cover the remainder of the delayed operations in the test
var token = listenerProvider.GetListener(FeatureAttribute.SolutionCrawler).BeginAsyncOperation("Test operation");
var expeditedWait = listenerProvider.GetWaiter(FeatureAttribute.SolutionCrawler).ExpeditedWaitAsync();

// we want to test order items processed by solution crawler.
// but since everything async, lazy and cancellable, order is not 100% deterministic. an item might
// start to be processed, and get cancelled due to newly enqueued item requiring current work to be re-processed
Expand Down Expand Up @@ -1319,8 +1343,11 @@ await crawlerListener.WaitUntilConditionIsMetAsync(
operation.Done();
}

token.Dispose();

// wait analyzers to finish process
await WaitAsync(service, workspace);
await expeditedWait;

Assert.Equal(1, worker.DocumentIds.Take(5).Select(d => d.ProjectId).Distinct().Count());
Assert.Equal(1, worker.DocumentIds.Skip(5).Take(5).Select(d => d.ProjectId).Distinct().Count());
Expand All @@ -1339,7 +1366,6 @@ private static async Task InsertText(string code, string text, bool expectDocume
composition: EditorTestCompositions.EditorFeatures.AddExcludedPartTypes(typeof(IIncrementalAnalyzerProvider)).AddParts(typeof(AnalyzerProviderNoWaitNoBlock)),
workspaceKind: SolutionCrawlerWorkspaceKind);

SetOptions(workspace);
var testDocument = workspace.Documents.First();
var textBuffer = testDocument.GetTextBuffer();

Expand Down Expand Up @@ -1476,18 +1502,6 @@ private static IEnumerable<DocumentInfo> GetDocuments(ProjectId projectId, int c
private static AsynchronousOperationListenerProvider GetListenerProvider(ExportProvider provider)
=> provider.GetExportedValue<AsynchronousOperationListenerProvider>();

private static void SetOptions(Workspace workspace)
{
// override default timespan to make test run faster
workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(workspace.Options
.WithChangedOption(InternalSolutionCrawlerOptions.ActiveFileWorkerBackOffTimeSpanInMS, 0)
.WithChangedOption(InternalSolutionCrawlerOptions.AllFilesWorkerBackOffTimeSpanInMS, 0)
.WithChangedOption(InternalSolutionCrawlerOptions.PreviewBackOffTimeSpanInMS, 0)
.WithChangedOption(InternalSolutionCrawlerOptions.ProjectPropagationBackOffTimeSpanInMS, 0)
.WithChangedOption(InternalSolutionCrawlerOptions.SemanticChangeBackOffTimeSpanInMS, 0)
.WithChangedOption(InternalSolutionCrawlerOptions.EntireProjectWorkerBackOffTimeSpanInMS, 100)));
}

private static void MakeFirstDocumentActive(Project project)
=> MakeDocumentActive(project.Documents.First());

Expand Down Expand Up @@ -1518,8 +1532,6 @@ public WorkCoordinatorWorkspace(string workspaceKind = null, bool disablePartial

Assert.False(_workspaceWaiter.HasPendingWork);
Assert.False(_solutionCrawlerWaiter.HasPendingWork);

WorkCoordinatorTests.SetOptions(this);
}

public static WorkCoordinatorWorkspace CreateWithAnalysisScope(BackgroundAnalysisScope analysisScope, string workspaceKind = null, bool disablePartialSolutions = true, Type incrementalAnalyzer = null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ private static void Test(Action<IProjectCacheHostService, ProjectId, ICachedObje
// Putting cacheService.CreateStrongReference in a using statement
// creates a temporary local that isn't collected in Debug builds
// Wrapping it in a lambda allows it to get collected.
var cacheService = new ProjectCacheService(null, int.MaxValue);
var cacheService = new ProjectCacheService(null, TimeSpan.MaxValue);
var projectId = ProjectId.CreateNewId();
var owner = new Owner();
var instance = ObjectReference.CreateFromFactory(() => new object());
Expand Down Expand Up @@ -113,7 +113,7 @@ public void TestCacheDoesNotKeepObjectsAliveAfterOwnerIsCollected2()
public void TestImplicitCacheKeepsObjectAlive1()
{
var workspace = new AdhocWorkspace(MockHostServices.Instance, workspaceKind: WorkspaceKind.Host);
var cacheService = new ProjectCacheService(workspace, int.MaxValue);
var cacheService = new ProjectCacheService(workspace, TimeSpan.MaxValue);
var reference = ObjectReference.CreateFromFactory(() => new object());
reference.UseReference(r => cacheService.CacheObjectIfCachingEnabledForKey(ProjectId.CreateNewId(), (object)null, r));
reference.AssertHeld();
Expand All @@ -125,7 +125,7 @@ public void TestImplicitCacheKeepsObjectAlive1()
public void TestImplicitCacheMonitoring()
{
var workspace = new AdhocWorkspace(MockHostServices.Instance, workspaceKind: WorkspaceKind.Host);
var cacheService = new ProjectCacheService(workspace, 10);
var cacheService = new ProjectCacheService(workspace, TimeSpan.FromMilliseconds(10));
var weak = PutObjectInImplicitCache(cacheService);

weak.AssertReleased();
Expand Down Expand Up @@ -156,7 +156,7 @@ public void TestP2PReference()

var instanceTracker = ObjectReference.CreateFromFactory(() => new object());

var cacheService = new ProjectCacheService(workspace, int.MaxValue);
var cacheService = new ProjectCacheService(workspace, TimeSpan.MaxValue);
using (var cache = cacheService.EnableCaching(project2.Id))
{
instanceTracker.UseReference(r => cacheService.CacheObjectIfCachingEnabledForKey(project1.Id, (object)null, r));
Expand Down Expand Up @@ -184,7 +184,7 @@ public void TestEjectFromImplicitCache()
var weakLast = ObjectReference.Create(compilations[compilations.Count - 1]);

var workspace = new AdhocWorkspace(MockHostServices.Instance, workspaceKind: WorkspaceKind.Host);
var cache = new ProjectCacheService(workspace, int.MaxValue);
var cache = new ProjectCacheService(workspace, TimeSpan.MaxValue);
for (var i = 0; i < ProjectCacheService.ImplicitCacheSize + 1; i++)
{
cache.CacheObjectIfCachingEnabledForKey(ProjectId.CreateNewId(), (object)null, compilations[i]);
Expand Down Expand Up @@ -212,7 +212,7 @@ public void TestCacheCompilationTwice()
var weak1 = ObjectReference.Create(comp1);

var workspace = new AdhocWorkspace(MockHostServices.Instance, workspaceKind: WorkspaceKind.Host);
var cache = new ProjectCacheService(workspace, int.MaxValue);
var cache = new ProjectCacheService(workspace, TimeSpan.MaxValue);
var key = ProjectId.CreateNewId();
var owner = new object();
cache.CacheObjectIfCachingEnabledForKey(key, owner, comp1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ internal abstract class GlobalOperationAwareIdleProcessor : IdleProcessor
public GlobalOperationAwareIdleProcessor(
IAsynchronousOperationListener listener,
IGlobalOperationNotificationService globalOperationNotificationService,
int backOffTimeSpanInMs,
TimeSpan backOffTimeSpan,
CancellationToken shutdownToken)
: base(listener, backOffTimeSpanInMs, shutdownToken)
: base(listener, backOffTimeSpan, shutdownToken)
{
_globalOperation = null;
_globalOperationTask = Task.CompletedTask;
Expand Down
32 changes: 16 additions & 16 deletions src/Features/Core/Portable/SolutionCrawler/IdleProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,28 @@ namespace Microsoft.CodeAnalysis.SolutionCrawler
{
internal abstract class IdleProcessor
{
private const int MinimumDelayInMS = 50;
private static readonly TimeSpan s_minimumDelay = TimeSpan.FromMilliseconds(50);

protected readonly IAsynchronousOperationListener Listener;
protected readonly CancellationToken CancellationToken;
protected readonly int BackOffTimeSpanInMS;
protected readonly TimeSpan BackOffTimeSpan;

// points to processor task
private Task? _processorTask;

// there is one thread that writes to it and one thread reads from it
private int _lastAccessTimeInMS;
private SharedStopwatch _timeSinceLastAccess;

public IdleProcessor(
IAsynchronousOperationListener listener,
int backOffTimeSpanInMS,
TimeSpan backOffTimeSpan,
CancellationToken cancellationToken)
{
Listener = listener;
CancellationToken = cancellationToken;

BackOffTimeSpanInMS = backOffTimeSpanInMS;
_lastAccessTimeInMS = Environment.TickCount;
BackOffTimeSpan = backOffTimeSpan;
_timeSinceLastAccess = SharedStopwatch.StartNew();
}

protected abstract Task WaitAsync(CancellationToken cancellationToken);
Expand All @@ -46,7 +46,7 @@ protected void Start()
}

protected void UpdateLastAccessTime()
=> _lastAccessTimeInMS = Environment.TickCount;
=> _timeSinceLastAccess = SharedStopwatch.StartNew();

protected async Task WaitForIdleAsync(IExpeditableDelaySource expeditableDelaySource)
{
Expand All @@ -57,22 +57,22 @@ protected async Task WaitForIdleAsync(IExpeditableDelaySource expeditableDelaySo
return;
}

var diffInMS = Environment.TickCount - _lastAccessTimeInMS;
if (diffInMS >= BackOffTimeSpanInMS)
var diff = _timeSinceLastAccess.Elapsed;
if (diff >= BackOffTimeSpan)
{
return;
}

// TODO: will safestart/unwarp capture cancellation exception?
var timeLeft = BackOffTimeSpanInMS - diffInMS;
if (!await expeditableDelaySource.Delay(TimeSpan.FromMilliseconds(Math.Max(MinimumDelayInMS, timeLeft)), CancellationToken).ConfigureAwait(false))
var timeLeft = BackOffTimeSpan - diff;
if (!await expeditableDelaySource.Delay(TimeSpan.FromMilliseconds(Math.Max(s_minimumDelay.TotalMilliseconds, timeLeft.TotalMilliseconds)), CancellationToken).ConfigureAwait(false))
{
// The delay terminated early to accommodate a blocking operation. Make sure to delay long
// enough that low priority (on idle) operations get a chance to be triggered.
// The delay terminated early to accommodate a blocking operation. Make sure to yield so low
// priority (on idle) operations get a chance to be triggered.
//
// 📝 At the time this was discovered, it was not clear exactly why the delay was needed in order
// to avoid live-lock scenarios.
await Task.Delay(TimeSpan.FromMilliseconds(10), CancellationToken).ConfigureAwait(false);
// 📝 At the time this was discovered, it was not clear exactly why the yield (previously delay)
// was needed in order to avoid live-lock scenarios.
await Task.Yield().ConfigureAwait(false);
return;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using Microsoft.CodeAnalysis.Options;

namespace Microsoft.CodeAnalysis.SolutionCrawler
Expand All @@ -16,22 +17,11 @@ internal static class InternalSolutionCrawlerOptions
public static readonly Option2<bool> DirectDependencyPropagationOnly = new(nameof(InternalSolutionCrawlerOptions), "Project propagation only on direct dependency", defaultValue: true,
storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "Project propagation only on direct dependency"));

public static readonly Option2<int> ActiveFileWorkerBackOffTimeSpanInMS = new(nameof(InternalSolutionCrawlerOptions), "Active file worker backoff timespan in ms", defaultValue: 100,
storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "Active file worker backoff timespan in ms"));

public static readonly Option2<int> AllFilesWorkerBackOffTimeSpanInMS = new(nameof(InternalSolutionCrawlerOptions), "All files worker backoff timespan in ms", defaultValue: 1500,
storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "All files worker backoff timespan in ms"));

public static readonly Option2<int> EntireProjectWorkerBackOffTimeSpanInMS = new(nameof(InternalSolutionCrawlerOptions), "Entire project analysis worker backoff timespan in ms", defaultValue: 5000,
storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "Entire project analysis worker backoff timespan in ms"));

public static readonly Option2<int> SemanticChangeBackOffTimeSpanInMS = new(nameof(InternalSolutionCrawlerOptions), "Semantic change backoff timespan in ms", defaultValue: 100,
storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "Semantic change backoff timespan in ms"));

public static readonly Option2<int> ProjectPropagationBackOffTimeSpanInMS = new(nameof(InternalSolutionCrawlerOptions), "Project propagation backoff timespan in ms", defaultValue: 500,
storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "Project propagation backoff timespan in ms"));

public static readonly Option2<int> PreviewBackOffTimeSpanInMS = new(nameof(InternalSolutionCrawlerOptions), "Preview backoff timespan in ms", defaultValue: 500,
storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "Preview backoff timespan in ms"));
public static readonly TimeSpan ActiveFileWorkerBackOffTimeSpan = TimeSpan.FromMilliseconds(100);
public static readonly TimeSpan AllFilesWorkerBackOffTimeSpan = TimeSpan.FromMilliseconds(1500);
public static readonly TimeSpan EntireProjectWorkerBackOffTimeSpan = TimeSpan.FromMilliseconds(5000);
public static readonly TimeSpan SemanticChangeBackOffTimeSpan = TimeSpan.FromMilliseconds(100);
public static readonly TimeSpan ProjectPropagationBackOffTimeSpan = TimeSpan.FromMilliseconds(500);
public static readonly TimeSpan PreviewBackOffTimeSpan = TimeSpan.FromMilliseconds(500);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@ public InternalSolutionCrawlerOptionsProvider()

public ImmutableArray<IOption> Options { get; } = ImmutableArray.Create<IOption>(
InternalSolutionCrawlerOptions.SolutionCrawler,
InternalSolutionCrawlerOptions.ActiveFileWorkerBackOffTimeSpanInMS,
InternalSolutionCrawlerOptions.AllFilesWorkerBackOffTimeSpanInMS,
InternalSolutionCrawlerOptions.EntireProjectWorkerBackOffTimeSpanInMS,
InternalSolutionCrawlerOptions.SemanticChangeBackOffTimeSpanInMS,
InternalSolutionCrawlerOptions.ProjectPropagationBackOffTimeSpanInMS,
InternalSolutionCrawlerOptions.PreviewBackOffTimeSpanInMS);
InternalSolutionCrawlerOptions.DirectDependencyPropagationOnly);
}
}
Loading