Skip to content
This repository was archived by the owner on Apr 14, 2022. It is now read-only.

Commit 888d2f7

Browse files
Fix #914: Multiple fast opening of files causes GetAnalysisAsync to return old analysis (#919)
* Fix #914: Multiple fast opening of files causes GetAnalysisAsync to return old analysis * Disable out of order analysis for the cases when current session is small
1 parent 569d24d commit 888d2f7

File tree

7 files changed

+58
-42
lines changed

7 files changed

+58
-42
lines changed

src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,14 +217,16 @@ private void AnalyzeDocument(AnalysisModuleKey key, PythonAnalyzerEntry entry, I
217217
}
218218

219219
private bool TryCreateSession(DependencyGraphSnapshot<AnalysisModuleKey, PythonAnalyzerEntry> snapshot, PythonAnalyzerEntry entry, CancellationToken cancellationToken, out PythonAnalyzerSession session) {
220+
var analyzeUserModuleOutOfOrder = false;
220221
lock (_syncObj) {
221222
if (_currentSession != null) {
222223
if (_currentSession.Version > snapshot.Version || _nextSession != null && _nextSession.Version > snapshot.Version) {
223224
session = null;
224225
return false;
225226
}
226227

227-
if (_version > snapshot.Version && !_currentSession.IsCompleted && entry.IsUserModule) {
228+
analyzeUserModuleOutOfOrder = !_currentSession.IsCompleted && entry.IsUserModule && _currentSession.AffectedEntriesCount >= _maxTaskRunning;
229+
if (_version > snapshot.Version && analyzeUserModuleOutOfOrder) {
228230
session = CreateSession(null, entry, cancellationToken);
229231
return true;
230232
}
@@ -253,8 +255,8 @@ private bool TryCreateSession(DependencyGraphSnapshot<AnalysisModuleKey, PythonA
253255
}
254256

255257
_currentSession.Cancel();
256-
_nextSession = session = CreateSession(walker, entry.IsUserModule ? entry : null, cancellationToken);
257-
return entry.IsUserModule;
258+
_nextSession = session = CreateSession(walker, analyzeUserModuleOutOfOrder ? entry : null, cancellationToken);
259+
return analyzeUserModuleOutOfOrder;
258260
}
259261
}
260262

src/Analysis/Ast/Impl/Analyzer/PythonAnalyzerSession.cs

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public bool IsCompleted {
5858
}
5959

6060
public int Version { get; }
61+
public int AffectedEntriesCount { get; }
6162

6263
public PythonAnalyzerSession(IServiceManager services,
6364
IProgressReporter progress,
@@ -73,6 +74,7 @@ public PythonAnalyzerSession(IServiceManager services,
7374
_analysisCompleteEvent = analysisCompleteEvent;
7475
_startNextSession = startNextSession;
7576
Version = version;
77+
AffectedEntriesCount = walker.AffectedValues.Count;
7678
_walker = walker;
7779
_entry = entry;
7880
_state = State.NotStarted;
@@ -99,7 +101,7 @@ public void Start(bool analyzeEntry) {
99101
if (analyzeEntry && _entry != null) {
100102
Task.Run(() => Analyze(_entry, Version, _cts.Token), _cts.Token).DoNotWait();
101103
} else {
102-
StartAsync(_walker).ContinueWith(_startNextSession).DoNotWait();
104+
StartAsync().ContinueWith(_startNextSession).DoNotWait();
103105
}
104106
}
105107

@@ -109,11 +111,11 @@ public void Cancel() {
109111
}
110112
}
111113

112-
private async Task StartAsync(IDependencyChainWalker<AnalysisModuleKey, PythonAnalyzerEntry> walker) {
113-
_progress.ReportRemaining(walker.Remaining);
114+
private async Task StartAsync() {
115+
_progress.ReportRemaining(_walker.Remaining);
114116

115117
lock (_syncObj) {
116-
var notAnalyzed = walker.AffectedValues.Count(e => e.NotAnalyzed);
118+
var notAnalyzed = _walker.AffectedValues.Count(e => e.NotAnalyzed);
117119

118120
if (_isCanceled && notAnalyzed < _maxTaskRunning) {
119121
_state = State.Completed;
@@ -124,15 +126,15 @@ private async Task StartAsync(IDependencyChainWalker<AnalysisModuleKey, PythonAn
124126

125127
var cancellationToken = _cts.Token;
126128
var stopWatch = Stopwatch.StartNew();
127-
foreach (var affectedEntry in walker.AffectedValues) {
129+
foreach (var affectedEntry in _walker.AffectedValues) {
128130
affectedEntry.Invalidate(Version);
129131
}
130132

131-
var originalRemaining = walker.Remaining;
133+
var originalRemaining = _walker.Remaining;
132134
var remaining = originalRemaining;
133135
try {
134-
_log?.Log(TraceEventType.Verbose, $"Analysis version {walker.Version} of {originalRemaining} entries has started.");
135-
remaining = await AnalyzeAffectedEntriesAsync(walker, stopWatch, cancellationToken);
136+
_log?.Log(TraceEventType.Verbose, $"Analysis version {_walker.Version} of {originalRemaining} entries has started.");
137+
remaining = await AnalyzeAffectedEntriesAsync(stopWatch, cancellationToken);
136138
} finally {
137139
_cts.Dispose();
138140
stopWatch.Stop();
@@ -150,8 +152,8 @@ private async Task StartAsync(IDependencyChainWalker<AnalysisModuleKey, PythonAn
150152

151153
var elapsed = stopWatch.Elapsed.TotalMilliseconds;
152154

153-
SendTelemetry(elapsed, originalRemaining, remaining, walker.Version);
154-
LogResults(elapsed, originalRemaining, remaining, walker.Version);
155+
SendTelemetry(elapsed, originalRemaining, remaining, _walker.Version);
156+
LogResults(elapsed, originalRemaining, remaining, _walker.Version);
155157
}
156158

157159
private void SendTelemetry(double elapsed, int originalRemaining, int remaining, int version) {
@@ -201,10 +203,12 @@ private void LogResults(double elapsed, int originalRemaining, int remaining, in
201203
}
202204
}
203205

204-
private async Task<int> AnalyzeAffectedEntriesAsync(IDependencyChainWalker<AnalysisModuleKey, PythonAnalyzerEntry> walker, Stopwatch stopWatch, CancellationToken cancellationToken) {
206+
private async Task<int> AnalyzeAffectedEntriesAsync(Stopwatch stopWatch, CancellationToken cancellationToken) {
205207
IDependencyChainNode<PythonAnalyzerEntry> node;
206208
var remaining = 0;
207-
while ((node = await walker.GetNextAsync(cancellationToken)) != null) {
209+
var ace = new AsyncCountdownEvent(0);
210+
211+
while ((node = await _walker.GetNextAsync(cancellationToken)) != null) {
208212
bool isCanceled;
209213
lock (_syncObj) {
210214
isCanceled = _isCanceled;
@@ -216,14 +220,16 @@ private async Task<int> AnalyzeAffectedEntriesAsync(IDependencyChainWalker<Analy
216220
continue;
217221
}
218222

219-
if (Interlocked.Increment(ref _runningTasks) >= _maxTaskRunning || walker.Remaining == 1) {
220-
Analyze(walker, node, stopWatch, cancellationToken);
223+
if (Interlocked.Increment(ref _runningTasks) >= _maxTaskRunning || _walker.Remaining == 1) {
224+
Analyze(node, null, stopWatch, cancellationToken);
221225
} else {
222-
StartAnalysis(walker, node, stopWatch, cancellationToken).DoNotWait();
226+
StartAnalysis(node, ace, stopWatch, cancellationToken).DoNotWait();
223227
}
224228
}
225229

226-
if (walker.MissingKeys.All(k => k.IsTypeshed)) {
230+
await ace.WaitAsync(cancellationToken);
231+
232+
if (_walker.MissingKeys.All(k => k.IsTypeshed)) {
227233
Interlocked.Exchange(ref _runningTasks, 0);
228234
bool isCanceled;
229235
lock (_syncObj) {
@@ -239,36 +245,37 @@ private async Task<int> AnalyzeAffectedEntriesAsync(IDependencyChainWalker<Analy
239245
}
240246

241247

242-
private Task StartAnalysis(IDependencyChainWalker<AnalysisModuleKey, PythonAnalyzerEntry> walker, IDependencyChainNode<PythonAnalyzerEntry> node, Stopwatch stopWatch, CancellationToken cancellationToken)
243-
=> Task.Run(() => Analyze(walker, node, stopWatch, cancellationToken), cancellationToken);
248+
private Task StartAnalysis(IDependencyChainNode<PythonAnalyzerEntry> node, AsyncCountdownEvent ace, Stopwatch stopWatch, CancellationToken cancellationToken)
249+
=> Task.Run(() => Analyze(node, ace, stopWatch, cancellationToken), cancellationToken);
244250

245251
/// <summary>
246252
/// Performs analysis of the document. Returns document global scope
247253
/// with declared variables and inner scopes. Does not analyze chain
248254
/// of dependencies, it is intended for the single file analysis.
249255
/// </summary>
250-
private void Analyze(IDependencyChainWalker<AnalysisModuleKey, PythonAnalyzerEntry> walker, IDependencyChainNode<PythonAnalyzerEntry> node, Stopwatch stopWatch, CancellationToken cancellationToken) {
256+
private void Analyze(IDependencyChainNode<PythonAnalyzerEntry> node, AsyncCountdownEvent ace, Stopwatch stopWatch, CancellationToken cancellationToken) {
251257
IPythonModule module;
252258
try {
259+
ace?.AddOne();
253260
var entry = node.Value;
254-
if (!entry.IsValidVersion(walker.Version, out module, out var ast)) {
261+
if (!entry.IsValidVersion(_walker.Version, out module, out var ast)) {
255262
_log?.Log(TraceEventType.Verbose, $"Analysis of {module.Name}({module.ModuleType}) canceled.");
256263
node.Skip();
257264
return;
258265
}
259266
var startTime = stopWatch.Elapsed;
260-
AnalyzeEntry(entry, module, ast, walker.Version, cancellationToken);
267+
AnalyzeEntry(entry, module, ast, _walker.Version, cancellationToken);
261268
node.Commit();
262269

263270
_log?.Log(TraceEventType.Verbose, $"Analysis of {module.Name}({module.ModuleType}) completed in {(stopWatch.Elapsed - startTime).TotalMilliseconds} ms.");
264271
} catch (OperationCanceledException oce) {
265-
node.Value.TryCancel(oce, walker.Version);
272+
node.Value.TryCancel(oce, _walker.Version);
266273
node.Skip();
267274
module = node.Value.Module;
268275
_log?.Log(TraceEventType.Verbose, $"Analysis of {module.Name}({module.ModuleType}) canceled.");
269276
} catch (Exception exception) {
270277
module = node.Value.Module;
271-
node.Value.TrySetException(exception, walker.Version);
278+
node.Value.TrySetException(exception, _walker.Version);
272279
node.Commit();
273280
_log?.Log(TraceEventType.Verbose, $"Analysis of {module.Name}({module.ModuleType}) failed.");
274281
} finally {
@@ -278,8 +285,9 @@ private void Analyze(IDependencyChainWalker<AnalysisModuleKey, PythonAnalyzerEnt
278285
}
279286

280287
if (!isCanceled) {
281-
_progress.ReportRemaining(walker.Remaining);
288+
_progress.ReportRemaining(_walker.Remaining);
282289
}
290+
ace?.Signal();
283291
Interlocked.Decrement(ref _runningTasks);
284292
}
285293
}

src/Analysis/Ast/Impl/Documents/Definitions/IDocument.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public interface IDocument: IPythonModule, IDisposable {
4949
/// <summary>
5050
/// Returns document analysis.
5151
/// </summary>
52-
Task<IDocumentAnalysis> GetAnalysisAsync(int waitTime, CancellationToken cancellationToken = default);
52+
Task<IDocumentAnalysis> GetAnalysisAsync(int waitTime = 200, CancellationToken cancellationToken = default);
5353

5454
/// <summary>
5555
/// Returns last known document AST. The AST may be out of date or null.

src/Analysis/Ast/Impl/Modules/PythonModule.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ private void InitializeContent(string content, int version) {
212212
lock (AnalysisLock) {
213213
LoadContent(content, version);
214214

215-
var startParse = ContentState < State.Parsing && _parsingTask == null;
215+
var startParse = ContentState < State.Parsing && (_parsingTask == null || version > 0);
216216
if (startParse) {
217217
Parse();
218218
}

src/Analysis/Ast/Impl/Types/LocatedMember.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using System.Collections.Generic;
1818
using System.Linq;
1919
using Microsoft.Python.Analysis.Modules;
20+
using Microsoft.Python.Core;
2021

2122
namespace Microsoft.Python.Analysis.Types {
2223
internal abstract class LocatedMember : ILocatedMember {
@@ -56,8 +57,9 @@ public virtual IReadOnlyList<LocationInfo> References {
5657
}
5758

5859
var refs = _references
59-
.GroupBy(x => x.LocationInfo.DocumentUri)
60-
.SelectMany(g => g.Select(x => x.LocationInfo).OrderBy(x => x.Span));
60+
.Select(x => x.LocationInfo)
61+
.OrderBy(x => x.DocumentUri.AbsolutePath, StringExtensions.PathsStringComparer)
62+
.ThenBy(x => x.Span);
6163
return Enumerable.Repeat(Definition, 1).Concat(refs).ToArray();
6264
}
6365
}

src/LanguageServer/Impl/Sources/ReferenceSource.cs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,16 +142,18 @@ private IEnumerable<string> ScanFiles(IDictionary<string, PythonAst> closedFiles
142142

143143
private async Task AnalyzeFiles(IEnumerable<Uri> files, CancellationToken cancellationToken) {
144144
var rdt = _services.GetService<IRunningDocumentTable>();
145+
var analysisTasks = new List<Task>();
145146
foreach (var f in files) {
146-
Analyze(f, rdt);
147+
analysisTasks.Add(GetOrOpenModule(f, rdt).GetAnalysisAsync(cancellationToken: cancellationToken));
147148
}
148-
var analyzer = _services.GetService<IPythonAnalyzer>();
149-
await analyzer.WaitForCompleteAnalysisAsync(cancellationToken);
149+
150+
await Task.WhenAll(analysisTasks);
150151
}
151152

152-
private static void Analyze(Uri uri, IRunningDocumentTable rdt) {
153-
if (rdt.GetDocument(uri) != null) {
154-
return; // Already opened by another analysis.
153+
private static IDocument GetOrOpenModule(Uri uri, IRunningDocumentTable rdt) {
154+
var document = rdt.GetDocument(uri);
155+
if (document != null) {
156+
return document; // Already opened by another analysis.
155157
}
156158

157159
var filePath = uri.ToAbsolutePath();
@@ -161,7 +163,8 @@ private static void Analyze(Uri uri, IRunningDocumentTable rdt) {
161163
Uri = uri,
162164
ModuleType = ModuleType.User
163165
};
164-
rdt.AddModule(mco);
166+
167+
return rdt.AddModule(mco);
165168
}
166169

167170
private ILocatedMember GetRootDefinition(ILocatedMember lm) {

src/LanguageServer/Test/ReferencesTests.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,14 @@ def func(x):
7070
y = func(x)
7171
x = 2
7272
";
73-
var uri1 = TestData.GetDefaultModuleUri();
74-
var uri2 = TestData.GetNextModuleUri();
7573

7674
var code2 = $@"
77-
from {Path.GetFileNameWithoutExtension(uri1.AbsolutePath)} import x
75+
from module1 import x
7876
y = x
7977
";
78+
var uri1 = await TestData.CreateTestSpecificFileAsync("module1.py", code1);
79+
var uri2 = await TestData.CreateTestSpecificFileAsync("module2.py", code2);
80+
8081
await CreateServicesAsync(PythonVersions.LatestAvailable3X, uri1.AbsolutePath);
8182

8283
var rdt = Services.GetService<IRunningDocumentTable>();
@@ -98,7 +99,7 @@ def func(x):
9899
refs[2].range.Should().Be(7, 0, 7, 1);
99100
refs[2].uri.Should().Be(uri1);
100101

101-
refs[3].range.Should().Be(1, 19, 1, 20);
102+
refs[3].range.Should().Be(1, 20, 1, 21);
102103
refs[3].uri.Should().Be(uri2);
103104
refs[4].range.Should().Be(2, 4, 2, 5);
104105
refs[4].uri.Should().Be(uri2);

0 commit comments

Comments
 (0)