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

Remove usages of TaskQueue from interactive component #76455

Merged
merged 4 commits into from
Dec 17, 2024
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 @@ -14,7 +14,6 @@
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.InteractiveWindow;
using Microsoft.VisualStudio.InteractiveWindow.Commands;
Expand Down Expand Up @@ -70,7 +69,6 @@ internal CSharpInteractiveEvaluator(
IInteractiveWindowCommandsFactory commandsFactory,
ImmutableArray<IInteractiveWindowCommand> commands,
ITextDocumentFactoryService textDocumentFactoryService,
EditorOptionsService editorOptionsService,
InteractiveEvaluatorLanguageInfoProvider languageInfo,
string initialWorkingDirectory)
{
Expand All @@ -87,10 +85,8 @@ internal CSharpInteractiveEvaluator(

_session = new InteractiveSession(
_workspace,
threadingContext,
listener,
textDocumentFactoryService,
editorOptionsService,
languageInfo,
initialWorkingDirectory);

Expand Down
65 changes: 43 additions & 22 deletions src/EditorFeatures/Core/Interactive/InteractiveSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Scripting.Hosting;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.TestHooks;
Expand All @@ -27,17 +25,16 @@
namespace Microsoft.CodeAnalysis.Interactive;

using InteractiveHost::Microsoft.CodeAnalysis.Interactive;
using Microsoft.CodeAnalysis.Collections;
using RelativePathResolver = Scripting::Microsoft.CodeAnalysis.RelativePathResolver;

internal sealed class InteractiveSession : IDisposable
{
public InteractiveHost Host { get; }

private readonly IThreadingContext _threadingContext;
private readonly InteractiveEvaluatorLanguageInfoProvider _languageInfo;
private readonly InteractiveWorkspace _workspace;
private readonly ITextDocumentFactoryService _textDocumentFactoryService;
private readonly EditorOptionsService _editorOptionsService;

private readonly CancellationTokenSource _shutdownCancellationSource;

Expand All @@ -54,7 +51,7 @@ internal sealed class InteractiveSession : IDisposable
// At the same time a code submission might be in progress.
// If we left these operations run in parallel we might get into a state
// inconsistent with the state of the host.
private readonly TaskQueue _taskQueue;
private readonly AsyncBatchingWorkQueue<Func<Task>> _workQueue;

private ProjectId? _lastSuccessfulSubmissionProjectId;
private ProjectId? _currentSubmissionProjectId;
Expand All @@ -76,21 +73,21 @@ internal sealed class InteractiveSession : IDisposable

public InteractiveSession(
InteractiveWorkspace workspace,
IThreadingContext threadingContext,
IAsynchronousOperationListener listener,
ITextDocumentFactoryService documentFactory,
EditorOptionsService editorOptionsService,
InteractiveEvaluatorLanguageInfoProvider languageInfo,
string initialWorkingDirectory)
{
_workspace = workspace;
_threadingContext = threadingContext;
_languageInfo = languageInfo;
_textDocumentFactoryService = documentFactory;
_editorOptionsService = editorOptionsService;

_taskQueue = new TaskQueue(listener, TaskScheduler.Default);
_shutdownCancellationSource = new CancellationTokenSource();
_workQueue = new(
TimeSpan.Zero,
ProcessWorkQueueAsync,
listener,
_shutdownCancellationSource.Token);

// The following settings will apply when the REPL starts without .rsp file.
// They are discarded once the REPL is reset.
Expand All @@ -112,14 +109,29 @@ public void Dispose()
Host.Dispose();
}

private async ValueTask ProcessWorkQueueAsync(ImmutableSegmentedList<Func<Task>> list, CancellationToken cancellationToken)
{
foreach (var taskCreator in list)
{
cancellationToken.ThrowIfCancellationRequested();

// Kick off the task to run. This also ensures that if the taskCreator func throws synchronously, that we
// appropriately handle reporting it in ReportNonFatalErrorAsync. Also, ensure we always process the next
// piece of work, even if the current work fails.
var task = Task.Run(taskCreator, cancellationToken);
_ = task.ReportNonFatalErrorAsync();
await task.NoThrowAwaitableInternal(captureContext: false);
}
}

/// <summary>
/// Invoked by <see cref="InteractiveHost"/> when a new process initialization completes.
/// </summary>
private void ProcessInitialized(InteractiveHostPlatformInfo platformInfo, InteractiveHostOptions options, RemoteExecutionResult result)
{
Contract.ThrowIfFalse(result.InitializationResult != null);

_ = _taskQueue.ScheduleTask(nameof(ProcessInitialized), () =>
_workQueue.AddWork(() =>
{
_workspace.ResetSolution();

Expand All @@ -140,18 +152,21 @@ private void ProcessInitialized(InteractiveHostPlatformInfo platformInfo, Intera
}

_pendingBuffers.Clear();
}, _shutdownCancellationSource.Token);
return Task.CompletedTask;
});
}

/// <summary>
/// Invoked on UI thread when a new language buffer is created and before it is added to the projection.
/// </summary>
internal void AddSubmissionProject(ITextBuffer submissionBuffer)
{
_taskQueue.ScheduleTask(
nameof(AddSubmissionProject),
() => AddSubmissionProjectNoLock(submissionBuffer, _languageInfo.LanguageName),
_shutdownCancellationSource.Token);
_workQueue.AddWork(
() =>
{
AddSubmissionProjectNoLock(submissionBuffer, _languageInfo.LanguageName);
return Task.CompletedTask;
});
}

private void AddSubmissionProjectNoLock(ITextBuffer submissionBuffer, string languageName)
Expand Down Expand Up @@ -316,9 +331,10 @@ private Project CreateSubmissionProjectNoLock(Solution solution, ProjectId newSu
/// Called once a code snippet is submitted.
/// Followed by creation of a new language buffer and call to <see cref="AddSubmissionProject(ITextBuffer)"/>.
/// </summary>
internal Task<bool> ExecuteCodeAsync(string text)
internal async Task<bool> ExecuteCodeAsync(string text)
{
return _taskQueue.ScheduleTask(nameof(ExecuteCodeAsync), async () =>
var returnValue = false;
_workQueue.AddWork(async () =>
{
var result = await Host.ExecuteAsync(text).ConfigureAwait(false);
if (result.Success)
Expand All @@ -329,8 +345,11 @@ internal Task<bool> ExecuteCodeAsync(string text)
UpdatePathsNoLock(result);
}

return result.Success;
}, _shutdownCancellationSource.Token);
returnValue = result.Success;
});

await _workQueue.WaitUntilCurrentBatchCompletesAsync().ConfigureAwait(false);
return returnValue;
}

internal async Task<bool> ResetAsync(InteractiveHostOptions options)
Expand Down Expand Up @@ -377,11 +396,13 @@ private static SourceReferenceResolver CreateSourceReferenceResolver(ImmutableAr

public Task SetPathsAsync(ImmutableArray<string> referenceSearchPaths, ImmutableArray<string> sourceSearchPaths, string workingDirectory)
{
return _taskQueue.ScheduleTask(nameof(ExecuteCodeAsync), async () =>
_workQueue.AddWork(async () =>
{
var result = await Host.SetPathsAsync(referenceSearchPaths, sourceSearchPaths, workingDirectory).ConfigureAwait(false);
UpdatePathsNoLock(result);
}, _shutdownCancellationSource.Token);
});

return _workQueue.WaitUntilCurrentBatchCompletesAsync();
}

private void UpdatePathsNoLock(RemoteExecutionResult result)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Interactive;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.InteractiveWindow.Commands;
using Microsoft.VisualStudio.InteractiveWindow.Shell;
Expand All @@ -29,7 +28,6 @@ internal sealed class CSharpVsInteractiveWindowProvider : VsInteractiveWindowPro
private readonly IThreadingContext _threadingContext;
private readonly IAsynchronousOperationListener _listener;
private readonly ITextDocumentFactoryService _textDocumentFactoryService;
private readonly EditorOptionsService _editorOptionsService;

[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
Expand All @@ -43,14 +41,12 @@ public CSharpVsInteractiveWindowProvider(
IInteractiveWindowCommandsFactory commandsFactory,
[ImportMany] IInteractiveWindowCommand[] commands,
ITextDocumentFactoryService textDocumentFactoryService,
EditorOptionsService editorOptionsService,
VisualStudioWorkspace workspace)
: base(serviceProvider, interactiveWindowFactory, classifierAggregator, contentTypeRegistry, commandsFactory, commands, workspace)
{
_threadingContext = threadingContext;
_listener = listenerProvider.GetListener(FeatureAttribute.InteractiveEvaluator);
_textDocumentFactoryService = textDocumentFactoryService;
_editorOptionsService = editorOptionsService;
}

protected override Guid LanguageServiceGuid => LanguageServiceGuids.CSharpLanguageServiceId;
Expand All @@ -76,7 +72,6 @@ protected override CSharpInteractiveEvaluator CreateInteractiveEvaluator(
CommandsFactory,
Commands,
_textDocumentFactoryService,
_editorOptionsService,
CSharpInteractiveEvaluatorLanguageInfoProvider.Instance,
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile));
}
Expand Down
16 changes: 0 additions & 16 deletions src/Workspaces/Core/Portable/Utilities/TaskQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,6 @@ public Task<T> ScheduleTask<T>(string taskName, Func<T> operation, CancellationT
public Task ScheduleTask(string taskName, Func<Task> operation, CancellationToken cancellationToken)
=> EndOperation(BeginOperation(taskName), ScheduleTaskInProgress(operation, cancellationToken));

/// <inheritdoc cref="ScheduleTask(string, Action, CancellationToken)"/>
public Task<T> ScheduleTask<T>(string taskName, Func<Task<T>> operation, CancellationToken cancellationToken)
=> EndOperation(BeginOperation(taskName), ScheduleTaskInProgress(operation, cancellationToken));

/// <summary>
/// Enqueue specified <paramref name="operation"/>.
/// Assumes <see cref="Listener"/> has already been notified of its start and will be notified when it completes.
Expand Down Expand Up @@ -103,17 +99,5 @@ private Task ScheduleTaskInProgress(Func<Task> operation, CancellationToken canc
}
}

/// <inheritdoc cref="ScheduleTaskInProgress(Action, CancellationToken)"/>
[PerformanceSensitive("https://developercommunity.visualstudio.com/content/problem/854696/changing-target-framework-takes-10-minutes-with-10.html", AllowCaptures = false)]
private Task<T> ScheduleTaskInProgress<T>(Func<Task<T>> operation, CancellationToken cancellationToken)
{
lock (_gate)
{
var task = LastScheduledTask.SafeContinueWithFromAsync(_ => operation(), cancellationToken, TaskContinuationOptions.None, Scheduler);
LastScheduledTask = task;
return task;
}
}

#pragma warning restore
}
Loading