Skip to content

Commit 237dc0e

Browse files
committed
Merge pull request #20 from PowerShell/daviwil/throttle-analysis
Throttle semantic analysis requests for better performance
2 parents 26077c0 + e59cbbf commit 237dc0e

File tree

4 files changed

+125
-13
lines changed

4 files changed

+125
-13
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(

src/PowerShellEditorServices.Transport.Stdio/StdioHost.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ async Task ListenForMessages()
142142
MessageReader messageReader =
143143
new MessageReader(
144144
System.Console.In,
145-
MessageFormat.WithoutContentLength,
145+
MessageFormat.WithContentLength,
146146
messageTypeResolver);
147147

148148
MessageWriter messageWriter =

src/PowerShellEditorServices/Utility/Logger.cs

+46-10
Original file line numberDiff line numberDiff line change
@@ -126,15 +126,16 @@ public LogWriter(LogLevel minimumLogLevel, string logFilePath, bool deleteExisti
126126
logFilePath);
127127
}
128128

129-
// Open the log file for writing with UTF8 encoding
130-
this.textWriter =
131-
new StreamWriter(
132-
new FileStream(
133-
logFilePath,
134-
deleteExisting ?
135-
FileMode.Create :
136-
FileMode.Append),
137-
Encoding.UTF8);
129+
if (!this.TryOpenLogFile(logFilePath, deleteExisting))
130+
{
131+
// If the log file couldn't be opened at this location,
132+
// try opening it in a more reliable path
133+
this.TryOpenLogFile(
134+
Path.Combine(
135+
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
136+
Path.GetFileName(logFilePath)),
137+
deleteExisting);
138+
}
138139
}
139140

140141
public void Write(
@@ -144,7 +145,8 @@ public void Write(
144145
string callerSourceFile = null,
145146
int callerLineNumber = 0)
146147
{
147-
if (logLevel >= this.minimumLogLevel)
148+
if (this.textWriter != null &&
149+
logLevel >= this.minimumLogLevel)
148150
{
149151
// Print the timestamp and log level
150152
this.textWriter.WriteLine(
@@ -176,5 +178,39 @@ public void Dispose()
176178
this.textWriter = null;
177179
}
178180
}
181+
182+
private bool TryOpenLogFile(
183+
string logFilePath,
184+
bool deleteExisting)
185+
{
186+
try
187+
{
188+
// Open the log file for writing with UTF8 encoding
189+
this.textWriter =
190+
new StreamWriter(
191+
new FileStream(
192+
logFilePath,
193+
deleteExisting ?
194+
FileMode.Create :
195+
FileMode.Append),
196+
Encoding.UTF8);
197+
198+
return true;
199+
}
200+
catch (Exception e)
201+
{
202+
if (e is UnauthorizedAccessException ||
203+
e is IOException)
204+
{
205+
// This exception is thrown when we can't open the file
206+
// at the path in logFilePath. Return false to indicate
207+
// that the log file couldn't be created.
208+
return false;
209+
}
210+
211+
// Unexpected exception, rethrow it
212+
throw;
213+
}
214+
}
179215
}
180216
}

test/PowerShellEditorServices.Test.Host/LanguageServiceManager.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public void Start()
7373
this.MessageWriter =
7474
new MessageWriter(
7575
this.languageServiceProcess.StandardInput,
76-
MessageFormat.WithoutContentLength,
76+
MessageFormat.WithContentLength,
7777
messageTypeResolver);
7878

7979
// Wait for the 'started' event

0 commit comments

Comments
 (0)