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

Commit ddc2060

Browse files
Stability improvements (#749)
- Reanalyze user module out of the order if required - Skip walker if it is outdated and doesn't have a lot of unanalyzed modules. - Initialize PriorityProducerConsumer<IDependencyChainNode<TValue>> only when GetNextAsync is called for the first time - Fix ProcessServices so that error stream doesn't hang the process
1 parent d63a646 commit ddc2060

14 files changed

+365
-277
lines changed

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

Lines changed: 162 additions & 187 deletions
Large diffs are not rendered by default.

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

Lines changed: 136 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,29 @@
1414
// permissions and limitations under the License.
1515

1616
using System;
17+
using System.Collections.Generic;
18+
using System.Diagnostics;
19+
using System.Linq;
1720
using System.Threading;
1821
using System.Threading.Tasks;
22+
using Microsoft.Python.Analysis.Core.DependencyResolution;
1923
using Microsoft.Python.Analysis.Modules;
2024
using Microsoft.Python.Analysis.Types;
25+
using Microsoft.Python.Core;
2126
using Microsoft.Python.Core.Collections;
2227
using Microsoft.Python.Parsing.Ast;
2328

2429
namespace Microsoft.Python.Analysis.Analyzer {
30+
[DebuggerDisplay("{_module.Name}({_module.ModuleType})")]
2531
internal sealed class PythonAnalyzerEntry {
2632
private readonly object _syncObj = new object();
2733
private TaskCompletionSource<IDocumentAnalysis> _analysisTcs;
2834
private IPythonModule _module;
35+
private bool _isUserModule;
2936
private PythonAst _ast;
3037
private IDocumentAnalysis _previousAnalysis;
31-
private ImmutableArray<IPythonModule> _analysisDependencies;
38+
private HashSet<AnalysisModuleKey> _parserDependencies;
39+
private HashSet<AnalysisModuleKey> _analysisDependencies;
3240
private int _bufferVersion;
3341
private int _analysisVersion;
3442

@@ -40,29 +48,23 @@ public IPythonModule Module {
4048
}
4149
}
4250

43-
public IDocumentAnalysis PreviousAnalysis {
44-
get {
45-
lock (_syncObj) {
46-
return _previousAnalysis;
47-
}
48-
}
49-
}
50-
51-
public ImmutableArray<IPythonModule> AnalysisDependencies {
51+
public bool IsUserModule {
5252
get {
5353
lock (_syncObj) {
54-
return _analysisDependencies;
54+
return _isUserModule;
5555
}
5656
}
5757
}
5858

59-
public int BufferVersion {
59+
public IDocumentAnalysis PreviousAnalysis {
6060
get {
6161
lock (_syncObj) {
62-
return _bufferVersion;
62+
return _previousAnalysis;
6363
}
6464
}
6565
}
66+
67+
public int BufferVersion => _bufferVersion;
6668

6769
public int AnalysisVersion {
6870
get {
@@ -73,15 +75,13 @@ public int AnalysisVersion {
7375
}
7476

7577
public bool NotAnalyzed => PreviousAnalysis is EmptyAnalysis;
78+
79+
public PythonAnalyzerEntry(EmptyAnalysis emptyAnalysis) {
80+
_previousAnalysis = emptyAnalysis;
81+
_isUserModule = emptyAnalysis.Document.ModuleType == ModuleType.User;
7682

77-
public PythonAnalyzerEntry(IPythonModule module, PythonAst ast, IDocumentAnalysis previousAnalysis, int bufferVersion, int analysisVersion) {
78-
_module = module;
79-
_ast = ast;
80-
_previousAnalysis = previousAnalysis;
81-
_analysisDependencies = ImmutableArray<IPythonModule>.Empty;
82-
83-
_bufferVersion = bufferVersion;
84-
_analysisVersion = analysisVersion;
83+
_bufferVersion = -1;
84+
_analysisVersion = 0;
8585
_analysisTcs = new TaskCompletionSource<IDocumentAnalysis>(TaskCreationOptions.RunContinuationsAsynchronously);
8686
}
8787

@@ -92,7 +92,7 @@ public bool IsValidVersion(int version, out IPythonModule module, out PythonAst
9292
lock (_syncObj) {
9393
module = _module;
9494
ast = _ast;
95-
return _previousAnalysis is EmptyAnalysis || _analysisVersion <= version;
95+
return _previousAnalysis is EmptyAnalysis || _isUserModule || _analysisVersion <= version;
9696
}
9797
}
9898

@@ -106,7 +106,7 @@ public void TrySetAnalysis(IDocumentAnalysis analysis, int version) {
106106
return;
107107
}
108108

109-
_analysisDependencies = ImmutableArray<IPythonModule>.Empty;
109+
_analysisDependencies = null;
110110
UpdateAnalysisTcs(version);
111111
}
112112

@@ -147,36 +147,142 @@ public void Invalidate(int analysisVersion) {
147147
}
148148
}
149149

150-
public void Invalidate(ImmutableArray<IPythonModule> dependencies, int analysisVersion) {
150+
public bool Invalidate(ImmutableArray<IPythonModule> analysisDependencies, int analysisVersion, out ImmutableArray<AnalysisModuleKey> dependencies) {
151+
dependencies = ImmutableArray<AnalysisModuleKey>.Empty;
152+
IPythonModule module;
153+
int version;
151154
lock (_syncObj) {
152155
if (_analysisVersion >= analysisVersion) {
153-
return;
156+
return false;
157+
}
158+
159+
version = _analysisVersion;
160+
module = _module;
161+
}
162+
163+
var dependenciesHashSet = new HashSet<AnalysisModuleKey>();
164+
foreach (var dependency in analysisDependencies) {
165+
if (dependency != module && (dependency.ModuleType == ModuleType.User && dependency.Analysis.Version < version || dependency.Analysis is EmptyAnalysis)) {
166+
dependenciesHashSet.Add(new AnalysisModuleKey(dependency));
167+
}
168+
}
169+
170+
if (dependenciesHashSet.Count == 0) {
171+
return false;
172+
}
173+
174+
lock (_syncObj) {
175+
if (_analysisVersion >= analysisVersion) {
176+
return false;
154177
}
155178

156179
UpdateAnalysisTcs(analysisVersion);
157-
_analysisDependencies = _analysisDependencies.AddRange(dependencies);
180+
if (_analysisDependencies == null) {
181+
_analysisDependencies = dependenciesHashSet;
182+
} else {
183+
var countBefore = _analysisDependencies.Count;
184+
_analysisDependencies.UnionWith(dependenciesHashSet);
185+
if (countBefore == _analysisDependencies.Count) {
186+
return false;
187+
}
188+
}
189+
190+
dependencies = _parserDependencies != null
191+
? ImmutableArray<AnalysisModuleKey>.Create(_parserDependencies.Union(_analysisDependencies).ToArray())
192+
: ImmutableArray<AnalysisModuleKey>.Create(_analysisDependencies);
193+
return true;
158194
}
159195
}
160196

161-
public void Invalidate(IPythonModule module, PythonAst ast, int bufferVersion, int analysisVersion) {
197+
public bool Invalidate(IPythonModule module, PythonAst ast, int bufferVersion, int analysisVersion, out ImmutableArray<AnalysisModuleKey> dependencies) {
198+
dependencies = ImmutableArray<AnalysisModuleKey>.Empty;
199+
if (_bufferVersion >= bufferVersion) {
200+
return false;
201+
}
202+
203+
var dependenciesHashSet = FindDependencies(module, ast, bufferVersion);
162204
lock (_syncObj) {
163205
if (_analysisVersion >= analysisVersion && _bufferVersion >= bufferVersion) {
164-
return;
206+
return false;
165207
}
166208

167209
_ast = ast;
168210
_module = module;
169-
_bufferVersion = bufferVersion;
211+
_isUserModule = module.ModuleType == ModuleType.User;
212+
_parserDependencies = dependenciesHashSet;
170213

214+
Interlocked.Exchange(ref _bufferVersion, bufferVersion);
171215
UpdateAnalysisTcs(analysisVersion);
216+
dependencies = _analysisDependencies != null
217+
? ImmutableArray<AnalysisModuleKey>.Create(_parserDependencies.Union(_analysisDependencies).ToArray())
218+
: ImmutableArray<AnalysisModuleKey>.Create(_parserDependencies);
219+
return true;
220+
}
221+
}
222+
223+
private HashSet<AnalysisModuleKey> FindDependencies(IPythonModule module, PythonAst ast, int bufferVersion) {
224+
var isTypeshed = module is StubPythonModule stub && stub.IsTypeshed;
225+
var moduleResolution = module.Interpreter.ModuleResolution;
226+
var pathResolver = isTypeshed
227+
? module.Interpreter.TypeshedResolution.CurrentPathResolver
228+
: moduleResolution.CurrentPathResolver;
229+
230+
var dependencies = new HashSet<AnalysisModuleKey>();
231+
232+
if (module.Stub != null) {
233+
dependencies.Add(new AnalysisModuleKey(module.Stub));
234+
}
235+
236+
foreach (var node in ast.TraverseDepthFirst<Node>(n => n.GetChildNodes())) {
237+
if (_bufferVersion > bufferVersion) {
238+
return dependencies;
239+
}
240+
241+
switch (node) {
242+
case ImportStatement import:
243+
foreach (var moduleName in import.Names) {
244+
HandleSearchResults(isTypeshed, dependencies, moduleResolution, pathResolver.FindImports(module.FilePath, moduleName, import.ForceAbsolute));
245+
}
246+
break;
247+
case FromImportStatement fromImport:
248+
var imports = pathResolver.FindImports(module.FilePath, fromImport);
249+
HandleSearchResults(isTypeshed, dependencies, moduleResolution, imports);
250+
if (imports is IImportChildrenSource childrenSource) {
251+
foreach (var name in fromImport.Names) {
252+
if (childrenSource.TryGetChildImport(name.Name, out var childImport)) {
253+
HandleSearchResults(isTypeshed, dependencies, moduleResolution, childImport);
254+
}
255+
}
256+
}
257+
break;
258+
}
259+
}
260+
261+
dependencies.Remove(new AnalysisModuleKey(module));
262+
return dependencies;
263+
}
264+
265+
private static void HandleSearchResults(bool isTypeshed, HashSet<AnalysisModuleKey> dependencies, IModuleManagement moduleResolution, IImportSearchResult searchResult) {
266+
switch (searchResult) {
267+
case ModuleImport moduleImport when !Ignore(moduleResolution, moduleImport.FullName):
268+
dependencies.Add(new AnalysisModuleKey(moduleImport.FullName, moduleImport.ModulePath, isTypeshed));
269+
return;
270+
case PossibleModuleImport possibleModuleImport when !Ignore(moduleResolution, possibleModuleImport.PrecedingModuleFullName):
271+
dependencies.Add(new AnalysisModuleKey(possibleModuleImport.PrecedingModuleFullName, possibleModuleImport.PrecedingModulePath, isTypeshed));
272+
return;
273+
default:
274+
return;
172275
}
173276
}
174277

278+
private static bool Ignore(IModuleManagement moduleResolution, string name)
279+
=> moduleResolution.BuiltinModuleName.EqualsOrdinal(name) || moduleResolution.GetSpecializedModule(name) != null;
280+
175281
private void UpdateAnalysisTcs(int analysisVersion) {
176282
_analysisVersion = analysisVersion;
177283
if (_analysisTcs.Task.Status == TaskStatus.RanToCompletion) {
178284
_previousAnalysis = _analysisTcs.Task.Result;
179-
_analysisDependencies = ImmutableArray<IPythonModule>.Empty;
285+
_analysisDependencies = null;
180286
}
181287

182288
if (_analysisTcs.Task.IsCompleted) {

src/Analysis/Ast/Impl/Dependencies/DependencyResolver.cs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,10 @@ private static void RemoveLoopEdges(WalkingVertex<TKey, TValue> vertex, ref int
217217

218218
private sealed class DependencyChainWalker : IDependencyChainWalker<TKey, TValue> {
219219
private readonly DependencyResolver<TKey, TValue> _dependencyResolver;
220-
private readonly PriorityProducerConsumer<IDependencyChainNode<TValue>> _ppc;
220+
private readonly ImmutableArray<WalkingVertex<TKey, TValue>> _startingVertices;
221221
private readonly object _syncObj;
222222
private int _remaining;
223+
private PriorityProducerConsumer<IDependencyChainNode<TValue>> _ppc;
223224

224225
public ImmutableArray<TKey> MissingKeys { get; }
225226
public ImmutableArray<TValue> AffectedValues { get; }
@@ -243,19 +244,30 @@ public DependencyChainWalker(
243244

244245
_syncObj = new object();
245246
_dependencyResolver = dependencyResolver;
246-
_ppc = new PriorityProducerConsumer<IDependencyChainNode<TValue>>();
247+
_startingVertices = startingVertices;
247248
AffectedValues = affectedValues;
248249
Version = version;
249250
MissingKeys = missingKeys;
250251

251252
_remaining = totalNodesCount;
252-
foreach (var vertex in startingVertices) {
253-
_ppc.Produce(new DependencyChainNode(this, vertex));
254-
}
255253
}
256254

257-
public Task<IDependencyChainNode<TValue>> GetNextAsync(CancellationToken cancellationToken) =>
258-
_ppc.ConsumeAsync(cancellationToken);
255+
public Task<IDependencyChainNode<TValue>> GetNextAsync(CancellationToken cancellationToken) {
256+
PriorityProducerConsumer<IDependencyChainNode<TValue>> ppc;
257+
lock (_syncObj) {
258+
if (_ppc == null) {
259+
_ppc = new PriorityProducerConsumer<IDependencyChainNode<TValue>>();
260+
261+
foreach (var vertex in _startingVertices) {
262+
_ppc.Produce(new DependencyChainNode(this, vertex));
263+
}
264+
}
265+
266+
ppc = _ppc;
267+
}
268+
269+
return ppc.ConsumeAsync(cancellationToken);
270+
}
259271

260272
public void MarkCompleted(WalkingVertex<TKey, TValue> vertex, bool commitChanges) {
261273
var verticesToProduce = new List<WalkingVertex<TKey, TValue>>();

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,15 @@ private string ScrapeModule() {
8585
CreateNoWindow = true,
8686
StandardOutputEncoding = Encoding.UTF8,
8787
RedirectStandardInput = true,
88-
RedirectStandardOutput = true,
89-
RedirectStandardError = true
88+
RedirectStandardOutput = true
9089
};
9190
var ps = Services.GetService<IProcessServices>();
9291

9392
Log?.Log(TraceEventType.Verbose, "Scrape", startInfo.FileName, startInfo.Arguments);
9493

9594
try {
9695
var token = new CancellationTokenSource(30000).Token;
97-
var lines = ps.ExecuteAndCaptureOutputAsync(startInfo, token).GetAwaiter().GetResult();
98-
return string.Join(Environment.NewLine, lines);
96+
return ps.ExecuteAndCaptureOutputAsync(startInfo, token).GetAwaiter().GetResult();
9997
} catch (Exception ex) when (!ex.IsCriticalException()) { }
10098

10199
return string.Empty;

src/Analysis/Ast/Impl/Modules/Definitions/IModuleCache.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
namespace Microsoft.Python.Analysis.Modules {
2121
public interface IModuleCache {
22-
Task<IDocument> ImportFromCacheAsync(string name, CancellationToken cancellationToken);
2322
string GetCacheFilePath(string filePath);
2423
string ReadCachedModule(string filePath);
2524
void WriteCachedModule(string filePath, string code);

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

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -43,35 +43,7 @@ public ModuleCache(IPythonInterpreter interpreter, IServiceContainer services) {
4343
_log = services.GetService<ILogger>();
4444
_skipCache = string.IsNullOrEmpty(_interpreter.Configuration.DatabasePath);
4545
}
46-
47-
public async Task<IDocument> ImportFromCacheAsync(string name, CancellationToken cancellationToken) {
48-
if (string.IsNullOrEmpty(ModuleCachePath)) {
49-
return null;
50-
}
51-
52-
var cache = GetCacheFilePath("python.{0}.pyi".FormatInvariant(name));
53-
if (!_fs.FileExists(cache)) {
54-
cache = GetCacheFilePath("python._{0}.pyi".FormatInvariant(name));
55-
if (!_fs.FileExists(cache)) {
56-
cache = GetCacheFilePath("{0}.pyi".FormatInvariant(name));
57-
if (!_fs.FileExists(cache)) {
58-
return null;
59-
}
60-
}
61-
}
62-
63-
var rdt = _services.GetService<IRunningDocumentTable>();
64-
var mco = new ModuleCreationOptions {
65-
ModuleName = name,
66-
ModuleType = ModuleType.Compiled,
67-
FilePath = cache
68-
};
69-
var module = rdt.AddModule(mco);
70-
71-
await module.LoadAndAnalyzeAsync(cancellationToken);
72-
return module;
73-
}
74-
46+
7547
public string GetCacheFilePath(string filePath) {
7648
if (string.IsNullOrEmpty(filePath) || !PathEqualityComparer.IsValidPath(ModuleCachePath)) {
7749
if (!_loggedBadDbPath) {

src/Analysis/Ast/Impl/Values/Collections/PythonDictionary.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ public IReadOnlyList<IPythonCollection> Items
6161
public IMember this[IMember key] =>
6262
_contents.TryGetValue(key, out var value) ? value : UnknownType;
6363

64-
public override IPythonIterator GetIterator() => Call(@"iterkeys", ArgumentSet.Empty) as IPythonIterator;
64+
public override IPythonIterator GetIterator() =>
65+
Call(@"iterkeys", ArgumentSet.Empty) as IPythonIterator ?? new EmptyIterator(Type.DeclaringModule.Interpreter.UnknownType);
6566

6667
public override IMember Index(object key) => key is IMember m ? this[m] : UnknownType;
6768

0 commit comments

Comments
 (0)