14
14
// permissions and limitations under the License.
15
15
16
16
using System ;
17
- using System . Collections ;
18
17
using System . Collections . Generic ;
19
18
using System . Diagnostics ;
20
- using System . IO ;
21
- using System . Linq ;
22
19
using System . Threading ;
23
20
using System . Threading . Tasks ;
24
21
using Microsoft . Python . Analysis . Core . DependencyResolution ;
28
25
using Microsoft . Python . Analysis . Types ;
29
26
using Microsoft . Python . Core ;
30
27
using Microsoft . Python . Core . Collections ;
31
- using Microsoft . Python . Core . Diagnostics ;
32
28
using Microsoft . Python . Core . Disposables ;
33
29
using Microsoft . Python . Core . IO ;
34
30
using Microsoft . Python . Core . Logging ;
38
34
namespace Microsoft . Python . Analysis . Analyzer {
39
35
public sealed class PythonAnalyzer : IPythonAnalyzer , IDisposable {
40
36
private readonly IServiceManager _services ;
41
- private readonly IDependencyResolver < ModuleKey , AnalysisEntry > _dependencyResolver ;
42
- private readonly Dictionary < ModuleKey , AnalysisEntry > _analysisEntries = new Dictionary < ModuleKey , AnalysisEntry > ( ) ;
37
+ private readonly IDependencyResolver < ModuleKey , PythonAnalyzerEntry > _dependencyResolver ;
38
+ private readonly Dictionary < ModuleKey , PythonAnalyzerEntry > _analysisEntries = new Dictionary < ModuleKey , PythonAnalyzerEntry > ( ) ;
43
39
private readonly DisposeToken _disposeToken = DisposeToken . Create < PythonAnalyzer > ( ) ;
44
40
private readonly object _syncObj = new object ( ) ;
45
41
private readonly AsyncManualResetEvent _analysisCompleteEvent = new AsyncManualResetEvent ( ) ;
@@ -51,7 +47,7 @@ public sealed class PythonAnalyzer : IPythonAnalyzer, IDisposable {
51
47
public PythonAnalyzer ( IServiceManager services ) {
52
48
_services = services ;
53
49
_log = services . GetService < ILogger > ( ) ;
54
- _dependencyResolver = new DependencyResolver < ModuleKey , AnalysisEntry > ( new ModuleDependencyFinder ( services . GetService < IFileSystem > ( ) ) ) ;
50
+ _dependencyResolver = new DependencyResolver < ModuleKey , PythonAnalyzerEntry > ( new ModuleDependencyFinder ( services . GetService < IFileSystem > ( ) ) ) ;
55
51
_analysisCompleteEvent . Set ( ) ;
56
52
}
57
53
@@ -65,50 +61,54 @@ public void EnqueueDocumentForAnalysis(IPythonModule module, PythonAst ast, int
65
61
66
62
public async Task < IDocumentAnalysis > GetAnalysisAsync ( IPythonModule module , int waitTime , CancellationToken cancellationToken ) {
67
63
var key = new ModuleKey ( module ) ;
68
- AnalysisEntry entry ;
64
+ PythonAnalyzerEntry entry ;
69
65
lock ( _syncObj ) {
70
66
if ( ! _analysisEntries . TryGetValue ( key , out entry ) ) {
71
- return new EmptyAnalysis ( _services , ( IDocument ) module ) ;
67
+ var emptyAnalysis = new EmptyAnalysis ( _services , ( IDocument ) module ) ;
68
+ entry = new PythonAnalyzerEntry ( module , emptyAnalysis . Ast , emptyAnalysis , 0 ) ;
69
+ _analysisEntries [ key ] = entry ;
72
70
}
73
71
}
74
72
75
- using ( var timeoutCts = new CancellationTokenSource ( ) ) {
76
- if ( waitTime >= 0 && ! Debugger . IsAttached ) {
77
- timeoutCts . CancelAfter ( waitTime ) ;
78
- }
79
73
80
- using ( var cts = CancellationTokenSource . CreateLinkedTokenSource ( timeoutCts . Token , cancellationToken ) ) {
81
- var timeoutToken = timeoutCts . Token ;
82
- while ( ! timeoutToken . IsCancellationRequested ) {
83
- try {
84
- var analysis = await entry . GetAnalysisAsync ( cts . Token ) ;
85
- lock ( _syncObj ) {
86
- if ( entry . Version == analysis . Version ) {
87
- return analysis ;
88
- }
89
- }
90
- } catch ( OperationCanceledException ) when ( ! timeoutToken . IsCancellationRequested && ! cancellationToken . IsCancellationRequested ) {
91
- lock ( _syncObj ) {
92
- if ( ! _analysisEntries . TryGetValue ( key , out entry ) ) {
93
- return new EmptyAnalysis ( _services , ( IDocument ) module ) ;
94
- }
95
- }
74
+ if ( waitTime == 0 || Debugger . IsAttached ) {
75
+ return await GetAnalysisAsync ( entry , default , cancellationToken ) ;
76
+ }
77
+
78
+ using ( var timeoutCts = new CancellationTokenSource ( waitTime ) )
79
+ using ( var cts = CancellationTokenSource . CreateLinkedTokenSource ( timeoutCts . Token , cancellationToken ) ) {
80
+ cts . CancelAfter ( waitTime ) ;
81
+ var timeoutToken = timeoutCts . Token ;
82
+ return await GetAnalysisAsync ( entry , timeoutToken , cts . Token ) ;
83
+ }
84
+ }
85
+
86
+ private async Task < IDocumentAnalysis > GetAnalysisAsync ( PythonAnalyzerEntry entry , CancellationToken timeoutCt , CancellationToken cancellationToken ) {
87
+ while ( ! timeoutCt . IsCancellationRequested ) {
88
+ try {
89
+ var analysis = await entry . GetAnalysisAsync ( cancellationToken ) ;
90
+ lock ( _syncObj ) {
91
+ if ( entry . Version == analysis . Version ) {
92
+ return analysis ;
96
93
}
97
94
}
95
+ } catch ( OperationCanceledException ) when ( timeoutCt . IsCancellationRequested ) {
96
+ return entry . PreviousAnalysis ;
98
97
}
99
98
}
100
99
101
100
return entry . PreviousAnalysis ;
102
101
}
103
102
104
- private async Task AnalyzeDocumentAsync ( IPythonModule module , PythonAst ast , int version , CancellationToken cancellationToken ) {
103
+ private async Task AnalyzeDocumentAsync ( IPythonModule module , PythonAst ast , int bufferVersion , CancellationToken cancellationToken ) {
105
104
var key = new ModuleKey ( module ) ;
106
- AnalysisEntry entry ;
105
+ PythonAnalyzerEntry entry ;
107
106
lock ( _syncObj ) {
108
107
if ( _analysisEntries . TryGetValue ( key , out entry ) ) {
109
- entry . Invalidate ( _version ) ;
108
+ entry . Invalidate ( _version + 1 , ast ) ;
110
109
} else {
111
- _analysisEntries [ key ] = entry = new AnalysisEntry ( module , ast , new EmptyAnalysis ( _services , ( IDocument ) module ) , _version ) ;
110
+ entry = new PythonAnalyzerEntry ( module , ast , new EmptyAnalysis ( _services , ( IDocument ) module ) , _version ) ;
111
+ _analysisEntries [ key ] = entry ;
112
112
}
113
113
}
114
114
@@ -118,13 +118,13 @@ private async Task AnalyzeDocumentAsync(IPythonModule module, PythonAst ast, int
118
118
using ( var cts = CancellationTokenSource . CreateLinkedTokenSource ( _disposeToken . CancellationToken , cancellationToken ) ) {
119
119
var analysisToken = cts . Token ;
120
120
121
- var walker = await _dependencyResolver . AddChangesAsync ( new ModuleKey ( module ) , entry , version , cts . Token ) ;
121
+ var walker = await _dependencyResolver . AddChangesAsync ( new ModuleKey ( module ) , entry , bufferVersion , cts . Token ) ;
122
122
var abortAnalysisOnVersionChange = true ;
123
123
lock ( _syncObj ) {
124
124
if ( _version < walker . Version ) {
125
125
_version = walker . Version ;
126
126
foreach ( var affectedEntry in walker . AffectedValues ) {
127
- affectedEntry . Invalidate ( _version ) ;
127
+ affectedEntry . Invalidate ( _version , affectedEntry . Ast ) ;
128
128
if ( affectedEntry . UserNotAnalyzed ) {
129
129
abortAnalysisOnVersionChange = false ;
130
130
}
@@ -137,12 +137,13 @@ private async Task AnalyzeDocumentAsync(IPythonModule module, PythonAst ast, int
137
137
}
138
138
139
139
var stopWatch = Stopwatch . StartNew ( ) ;
140
- IDependencyChainNode < AnalysisEntry > node ;
140
+ IDependencyChainNode < PythonAnalyzerEntry > node ;
141
141
while ( ( node = await walker . GetNextAsync ( analysisToken ) ) != null ) {
142
142
lock ( _syncObj ) {
143
143
if ( _version > walker . Version ) {
144
144
if ( abortAnalysisOnVersionChange ) {
145
- break ;
145
+ stopWatch . Stop ( ) ;
146
+ return ;
146
147
}
147
148
148
149
if ( ! node . Value . UserNotAnalyzed ) {
@@ -159,7 +160,6 @@ private async Task AnalyzeDocumentAsync(IPythonModule module, PythonAst ast, int
159
160
}
160
161
}
161
162
162
- stopWatch . Stop ( ) ;
163
163
164
164
if ( walker . MissingKeys . Where ( k => ! k . IsTypeshed ) . Count == 0 ) {
165
165
Interlocked . Exchange ( ref _runningTasks , 0 ) ;
@@ -175,15 +175,15 @@ private static void LoadMissingDocuments(IPythonInterpreter interpreter, Immutab
175
175
}
176
176
}
177
177
178
- private void StartAnalysis ( IDependencyChainNode < AnalysisEntry > node , int version , Stopwatch stopWatch , CancellationToken cancellationToken )
178
+ private void StartAnalysis ( IDependencyChainNode < PythonAnalyzerEntry > node , int version , Stopwatch stopWatch , CancellationToken cancellationToken )
179
179
=> Task . Run ( ( ) => AnalyzeAsync ( node , version , stopWatch , cancellationToken ) , cancellationToken ) . DoNotWait ( ) ;
180
180
181
181
/// <summary>
182
182
/// Performs analysis of the document. Returns document global scope
183
183
/// with declared variables and inner scopes. Does not analyze chain
184
184
/// of dependencies, it is intended for the single file analysis.
185
185
/// </summary>
186
- private async Task AnalyzeAsync ( IDependencyChainNode < AnalysisEntry > node , int version , Stopwatch stopWatch , CancellationToken cancellationToken ) {
186
+ private async Task AnalyzeAsync ( IDependencyChainNode < PythonAnalyzerEntry > node , int version , Stopwatch stopWatch , CancellationToken cancellationToken ) {
187
187
try {
188
188
var startTime = stopWatch . ElapsedMilliseconds ;
189
189
var module = node . Value . Module ;
@@ -202,22 +202,16 @@ private async Task AnalyzeAsync(IDependencyChainNode<AnalysisEntry> node, int ve
202
202
var analysis = new DocumentAnalysis ( ( IDocument ) module , version , walker . GlobalScope , walker . Eval ) ;
203
203
204
204
( module as IAnalyzable ) ? . NotifyAnalysisComplete ( analysis ) ;
205
- lock ( _syncObj ) {
206
- node . Value . TrySetAnalysis ( analysis , version ) ;
207
- }
205
+ node . Value . TrySetAnalysis ( analysis , version , _syncObj ) ;
208
206
209
- node . MarkCompleted ( ) ;
210
207
_log ? . Log ( TraceEventType . Verbose , $ "Analysis of { module . Name } ({ module . ModuleType } ) complete in { stopWatch . ElapsedMilliseconds - startTime } ms.") ;
211
208
} catch ( OperationCanceledException oce ) {
212
- lock ( _syncObj ) {
213
- node . Value . TryCancel ( oce , version ) ;
214
- }
209
+ node . Value . TryCancel ( oce , version , _syncObj ) ;
215
210
} catch ( Exception exception ) {
216
- lock ( _syncObj ) {
217
- node . Value . TrySetException ( exception , version ) ;
218
- }
211
+ node . Value . TrySetException ( exception , version , _syncObj ) ;
219
212
} finally {
220
213
Interlocked . Decrement ( ref _runningTasks ) ;
214
+ node . MarkCompleted ( ) ;
221
215
}
222
216
}
223
217
@@ -230,7 +224,7 @@ private struct ModuleKey : IEquatable<ModuleKey> {
230
224
public ModuleKey ( IPythonModule module ) {
231
225
Name = module . Name ;
232
226
FilePath = module . ModuleType == ModuleType . CompiledBuiltin ? null : module . FilePath ;
233
- IsTypeshed = module . ModuleType == ModuleType . Stub ;
227
+ IsTypeshed = module is StubPythonModule stub && stub . IsTypeshed ;
234
228
}
235
229
236
230
public ModuleKey ( string name , string filePath , bool isTypeshed ) {
@@ -266,69 +260,17 @@ public void Deconstruct(out string moduleName, out string filePath, out bool isT
266
260
public override string ToString ( ) => $ "{ Name } ({ FilePath } )";
267
261
}
268
262
269
- private sealed class AnalysisEntry {
270
- private TaskCompletionSource < IDocumentAnalysis > _analysisTcs ;
271
-
272
- public IPythonModule Module { get ; }
273
- public PythonAst Ast { get ; }
274
- public IDocumentAnalysis PreviousAnalysis { get ; private set ; }
275
- public int Version { get ; private set ; }
276
- public bool UserNotAnalyzed => PreviousAnalysis is EmptyAnalysis && Module . ModuleType == ModuleType . User ;
277
-
278
- public AnalysisEntry ( IPythonModule module , PythonAst ast , IDocumentAnalysis previousAnalysis , int version ) {
279
- Module = module ;
280
- Ast = ast ;
281
- PreviousAnalysis = previousAnalysis ;
282
-
283
- Version = version ;
284
- _analysisTcs = new TaskCompletionSource < IDocumentAnalysis > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
285
- }
286
-
287
- public Task < IDocumentAnalysis > GetAnalysisAsync ( CancellationToken cancellationToken )
288
- => _analysisTcs . Task . ContinueWith ( t => t . GetAwaiter ( ) . GetResult ( ) , cancellationToken ) ;
289
-
290
- public void TrySetAnalysis ( IDocumentAnalysis analysis , int version ) {
291
- if ( version >= Version ) {
292
- _analysisTcs . TrySetResult ( analysis ) ;
293
- }
294
- }
295
-
296
- public void TrySetException ( Exception ex , int version ) {
297
- if ( version >= Version ) {
298
- _analysisTcs . TrySetException ( ex ) ;
299
- }
300
- }
301
-
302
- public void TryCancel ( OperationCanceledException oce , int version ) {
303
- if ( version >= Version ) {
304
- _analysisTcs . TrySetCanceled ( oce . CancellationToken ) ;
305
- }
306
- }
307
-
308
- public void Invalidate ( int version ) {
309
- if ( Version >= version ) {
310
- return ;
311
- }
312
- Version = version ;
313
-
314
- if ( ! _analysisTcs . TrySetCanceled ( ) && _analysisTcs . Task . Status == TaskStatus . RanToCompletion ) {
315
- PreviousAnalysis = _analysisTcs . Task . Result ;
316
- }
317
- _analysisTcs = new TaskCompletionSource < IDocumentAnalysis > ( ) ;
318
- }
319
- }
320
-
321
- private sealed class ModuleDependencyFinder : IDependencyFinder < ModuleKey , AnalysisEntry > {
263
+ private sealed class ModuleDependencyFinder : IDependencyFinder < ModuleKey , PythonAnalyzerEntry > {
322
264
private readonly IFileSystem _fileSystem ;
323
265
324
266
public ModuleDependencyFinder ( IFileSystem fileSystem ) {
325
267
_fileSystem = fileSystem ;
326
268
}
327
269
328
- public Task < ImmutableArray < ModuleKey > > FindDependenciesAsync ( AnalysisEntry value , CancellationToken cancellationToken ) {
270
+ public Task < ImmutableArray < ModuleKey > > FindDependenciesAsync ( PythonAnalyzerEntry value , CancellationToken cancellationToken ) {
329
271
var dependencies = new HashSet < ModuleKey > ( ) ;
330
272
var module = value . Module ;
331
- var isTypeshed = module . ModuleType == ModuleType . Stub ; // TODO: This is not the correct way to determine Typeshed.
273
+ var isTypeshed = module is StubPythonModule stub && stub . IsTypeshed ;
332
274
var moduleResolution = module . Interpreter . ModuleResolution ;
333
275
var pathResolver = isTypeshed
334
276
? module . Interpreter . TypeshedResolution . CurrentPathResolver
0 commit comments