Skip to content

Commit 472f41c

Browse files
committed
Throttle "geterr" requests for better performance
This change causes the "geterr" request to be throttled to the specified millisecond delay argument. This prevents the language service from getting overloaded by many successive calls into Script Analyzer.
1 parent f17f91c commit 472f41c

File tree

1 file changed

+77
-1
lines changed

1 file changed

+77
-1
lines changed

src/PowerShellEditorServices.Transport.Stdio/Request/ErrorRequest.cs

+77-1
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,19 @@
66
using Microsoft.PowerShell.EditorServices.Session;
77
using Microsoft.PowerShell.EditorServices.Transport.Stdio.Event;
88
using Microsoft.PowerShell.EditorServices.Transport.Stdio.Message;
9+
using Microsoft.PowerShell.EditorServices.Utility;
10+
using System;
911
using System.Collections.Generic;
12+
using System.Threading;
13+
using System.Threading.Tasks;
1014

1115
namespace Microsoft.PowerShell.EditorServices.Transport.Stdio.Request
1216
{
1317
[MessageTypeName("geterr")]
1418
public class ErrorRequest : RequestBase<ErrorRequestArguments>
1519
{
20+
private static CancellationTokenSource existingRequestCancellation;
21+
1622
public static ErrorRequest Create(params string[] filePaths)
1723
{
1824
return new ErrorRequest
@@ -30,8 +36,78 @@ public override void ProcessMessage(
3036
{
3137
List<ScriptFile> fileList = new List<ScriptFile>();
3238

39+
// If there's an existing task, attempt to cancel it
40+
try
41+
{
42+
if (existingRequestCancellation != null)
43+
{
44+
// Try to cancel the request
45+
existingRequestCancellation.Cancel();
46+
47+
// If cancellation didn't throw an exception,
48+
// clean up the existing token
49+
existingRequestCancellation.Dispose();
50+
existingRequestCancellation = null;
51+
}
52+
}
53+
catch (Exception e)
54+
{
55+
// TODO: Catch a more specific exception!
56+
Logger.Write(
57+
LogLevel.Error,
58+
string.Format(
59+
"Exception while cancelling analysis task:\n\n{0}",
60+
e.ToString()));
61+
62+
return;
63+
}
64+
65+
// Create a fresh cancellation token and then start the task.
66+
// We create this on a different TaskScheduler so that we
67+
// don't block the main message loop thread.
68+
// TODO: Is there a better way to do this?
69+
existingRequestCancellation = new CancellationTokenSource();
70+
Task.Factory.StartNew(
71+
() =>
72+
DelayThenInvokeDiagnostics(
73+
this.Arguments.Delay,
74+
this.Arguments.Files,
75+
editorSession,
76+
messageWriter,
77+
existingRequestCancellation.Token),
78+
CancellationToken.None,
79+
TaskCreationOptions.None,
80+
TaskScheduler.Default);
81+
}
82+
83+
private static async Task DelayThenInvokeDiagnostics(
84+
int delayMilliseconds,
85+
string[] filesToAnalyze,
86+
EditorSession editorSession,
87+
MessageWriter messageWriter,
88+
CancellationToken cancellationToken)
89+
{
90+
// First of all, wait for the desired delay period before
91+
// analyzing the provided list of files
92+
try
93+
{
94+
await Task.Delay(delayMilliseconds, cancellationToken);
95+
}
96+
catch (TaskCanceledException)
97+
{
98+
// If the task is cancelled, exit directly
99+
return;
100+
}
101+
102+
// If we've made it past the delay period then we don't care
103+
// about the cancellation token anymore. This could happen
104+
// when the user stops typing for long enough that the delay
105+
// period ends but then starts typing while analysis is going
106+
// on. It makes sense to send back the results from the first
107+
// delay period while the second one is ticking away.
108+
33109
// Get the requested files
34-
foreach (string filePath in this.Arguments.Files)
110+
foreach (string filePath in filesToAnalyze)
35111
{
36112
ScriptFile scriptFile =
37113
editorSession.Workspace.GetFile(

0 commit comments

Comments
 (0)