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

Commit fc77d42

Browse files
authored
Reenable file path watcher, watching interpreter paths (#1306)
* first working path classfier, breaks existing path code for now * inherit Comparer instead of implementing both * don't do path depth comparison, preserve user ordering as before * switch to using new path classification code in main module resolution * fix typo * clean up comment about paths in Server.cs * use String.Split instead of regex when parsing script output * test ordering of user provided paths * add new normalize and trim helper, check preconditions as debug asserts rather than commenting on them * Bring back search path watcher for interpreter paths * make watchSearchPaths change code more obvious, remove stray semicolon * perform check only once * filter files to python-ish ones, up buffer size to maximum * avoid reanalysis of non-existent files on reload by removing all unopened files from the table * fix build * check parsed path instead of unparsed line * move RDT reloading into RDT
1 parent 157886e commit fc77d42

File tree

7 files changed

+114
-60
lines changed

7 files changed

+114
-60
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ public interface IRunningDocumentTable {
7070
/// <returns>New lock count or -1 if document was not found.</returns>
7171
int UnlockDocument(Uri uri);
7272

73+
/// <summary>
74+
/// Reloads the table by removing all unopened files (which would have been loaded from disk),
75+
/// and resetting the content of all other files to trigger reanalysis.
76+
/// </summary>
77+
void ReloadAll();
78+
7379
/// <summary>
7480
/// Fires when document is opened.
7581
/// </summary>

src/Analysis/Ast/Impl/Documents/RunningDocumentTable.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
using Microsoft.Python.Analysis.Analyzer;
2222
using Microsoft.Python.Analysis.Modules;
2323
using Microsoft.Python.Core;
24+
using Microsoft.Python.Core.Collections;
2425
using Microsoft.Python.Core.Logging;
2526

2627
namespace Microsoft.Python.Analysis.Documents {
@@ -189,6 +190,29 @@ public void CloseDocument(Uri documentUri) {
189190
}
190191
}
191192

193+
public void ReloadAll() {
194+
ImmutableArray<KeyValuePair<Uri, DocumentEntry>> opened;
195+
ImmutableArray<KeyValuePair<Uri, DocumentEntry>> closed;
196+
197+
lock (_lock) {
198+
_documentsByUri.Split(kvp => kvp.Value.Document.IsOpen, out opened, out closed);
199+
200+
foreach (var (uri, entry) in closed) {
201+
_documentsByUri.Remove(uri);
202+
entry.Document.Dispose();
203+
}
204+
}
205+
206+
foreach (var (_, entry) in closed) {
207+
Closed?.Invoke(this, new DocumentEventArgs(entry.Document));
208+
Removed?.Invoke(this, new DocumentEventArgs(entry.Document));
209+
}
210+
211+
foreach (var (_, entry) in opened) {
212+
entry.Document.Reset(null);
213+
}
214+
}
215+
192216
public void Dispose() {
193217
lock (_lock) {
194218
foreach (var d in _documentsByUri.Values.OfType<IDisposable>()) {

src/Analysis/Ast/Impl/Modules/Resolution/MainModuleResolution.cs

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ internal sealed class MainModuleResolution : ModuleResolutionBase, IModuleManage
3838
private readonly ConcurrentDictionary<string, IPythonModule> _specialized = new ConcurrentDictionary<string, IPythonModule>();
3939
private IRunningDocumentTable _rdt;
4040

41+
private IEnumerable<string> _userPaths = Enumerable.Empty<string>();
42+
4143
public MainModuleResolution(string root, IServiceContainer services)
4244
: base(root, services) { }
4345

@@ -75,7 +77,7 @@ protected override IPythonModule CreateModule(string name) {
7577
return module;
7678
}
7779
}
78-
80+
7981
// If there is a stub, make sure it is loaded and attached
8082
// First check stub next to the module.
8183
if (!TryCreateModuleStub(name, moduleImport.ModulePath, out var stub)) {
@@ -169,14 +171,34 @@ internal async Task LoadBuiltinTypesAsync(CancellationToken cancellationToken =
169171
}
170172
}
171173

174+
internal async Task ReloadSearchPaths(CancellationToken cancellationToken = default) {
175+
var ps = _services.GetService<IProcessServices>();
176+
177+
var paths = await GetInterpreterSearchPathsAsync(cancellationToken);
178+
var (interpreterPaths, userPaths) = PythonLibraryPath.ClassifyPaths(Root, _fs, paths, Configuration.SearchPaths);
179+
180+
InterpreterPaths = interpreterPaths.Select(p => p.Path);
181+
_userPaths = userPaths.Select(p => p.Path);
182+
183+
_log?.Log(TraceEventType.Information, "Interpreter search paths:");
184+
foreach (var s in InterpreterPaths) {
185+
_log?.Log(TraceEventType.Information, $" {s}");
186+
}
187+
188+
_log?.Log(TraceEventType.Information, "User search paths:");
189+
foreach (var s in _userPaths) {
190+
_log?.Log(TraceEventType.Information, $" {s}");
191+
}
192+
}
193+
172194
public async Task ReloadAsync(CancellationToken cancellationToken = default) {
173195
foreach (var uri in Modules
174196
.Where(m => m.Value.Value?.Name != BuiltinModuleName)
175197
.Select(m => m.Value.Value?.Uri)
176198
.ExcludeDefault()) {
177199
GetRdt()?.UnlockDocument(uri);
178200
}
179-
201+
180202
// Preserve builtins, they don't need to be reloaded since interpreter does not change.
181203
var builtins = Modules[BuiltinModuleName];
182204
Modules.Clear();
@@ -187,26 +209,10 @@ public async Task ReloadAsync(CancellationToken cancellationToken = default) {
187209
var addedRoots = new HashSet<string>();
188210
addedRoots.UnionWith(PathResolver.SetRoot(Root));
189211

190-
var ps = _services.GetService<IProcessServices>();
191-
192-
var paths = await GetInterpreterSearchPathsAsync(cancellationToken);
193-
var (interpreterPaths, userPaths) = PythonLibraryPath.ClassifyPaths(Root, _fs, paths, Configuration.SearchPaths);
194-
195-
InterpreterPaths = interpreterPaths.Select(p => p.Path);
196-
var userSearchPaths = userPaths.Select(p => p.Path);
197-
198-
_log?.Log(TraceEventType.Information, "Interpreter search paths:");
199-
foreach (var s in InterpreterPaths) {
200-
_log?.Log(TraceEventType.Information, $" {s}");
201-
}
202-
203-
_log?.Log(TraceEventType.Information, "User search paths:");
204-
foreach (var s in userSearchPaths) {
205-
_log?.Log(TraceEventType.Information, $" {s}");
206-
}
212+
await ReloadSearchPaths(cancellationToken);
207213

208214
addedRoots.UnionWith(PathResolver.SetInterpreterSearchPaths(InterpreterPaths));
209-
addedRoots.UnionWith(PathResolver.SetUserSearchPaths(userSearchPaths));
215+
addedRoots.UnionWith(PathResolver.SetUserSearchPaths(_userPaths));
210216
ReloadModulePaths(addedRoots);
211217
}
212218

src/LanguageServer/Impl/Implementation/Server.cs

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ public sealed partial class Server : IDisposable {
4949
private IIndexManager _indexManager;
5050
private string _rootDir;
5151

52+
private bool _watchSearchPaths;
53+
private PathsWatcher _pathsWatcher;
54+
private string[] _searchPaths;
55+
5256
public Server(IServiceManager services) {
5357
_services = services;
5458

@@ -58,6 +62,7 @@ public Server(IServiceManager services) {
5862
ext.Dispose();
5963
}
6064
})
65+
.Add(() => _pathsWatcher?.Dispose())
6166
.Add(() => _shutdownCts.Cancel());
6267
}
6368

@@ -173,11 +178,11 @@ public void DidChangeConfiguration(DidChangeConfigurationParams @params, Cancell
173178
_disposableBag.ThrowIfDisposed();
174179
switch (@params.settings) {
175180
case ServerSettings settings: {
176-
if (HandleConfigurationChanges(settings)) {
177-
RestartAnalysis();
181+
if (HandleConfigurationChanges(settings)) {
182+
RestartAnalysis();
183+
}
184+
break;
178185
}
179-
break;
180-
}
181186
default:
182187
_log?.Log(TraceEventType.Error, "change configuration notification sent unsupported settings");
183188
break;
@@ -233,7 +238,34 @@ private IDocumentationSource ChooseDocumentationSource(string[] kinds) {
233238
}
234239
#endregion
235240

236-
public void NotifyPackagesChanged(CancellationToken cancellationToken) {
241+
public void HandleWatchPathsChange(bool watchSearchPaths) {
242+
if (watchSearchPaths == _watchSearchPaths) {
243+
return;
244+
}
245+
246+
_watchSearchPaths = watchSearchPaths;
247+
248+
if (!_watchSearchPaths) {
249+
_searchPaths = null;
250+
_pathsWatcher?.Dispose();
251+
_pathsWatcher = null;
252+
return;
253+
}
254+
255+
ResetPathWatcher();
256+
}
257+
258+
private void ResetPathWatcher() {
259+
var paths = _interpreter.ModuleResolution.InterpreterPaths.ToArray();
260+
261+
if (_searchPaths == null || !_searchPaths.SequenceEqual(paths)) {
262+
_searchPaths = paths;
263+
_pathsWatcher?.Dispose();
264+
_pathsWatcher = new PathsWatcher(_searchPaths, () => NotifyPackagesChanged(), _log);
265+
}
266+
}
267+
268+
public void NotifyPackagesChanged(CancellationToken cancellationToken = default) {
237269
var interpreter = _services.GetService<IPythonInterpreter>();
238270
_log?.Log(TraceEventType.Information, Resources.ReloadingModules);
239271
// No need to reload typeshed resolution since it is a static storage.
@@ -242,17 +274,20 @@ public void NotifyPackagesChanged(CancellationToken cancellationToken) {
242274
interpreter.ModuleResolution.ReloadAsync(cancellationToken).ContinueWith(t => {
243275
_log?.Log(TraceEventType.Information, Resources.Done);
244276
_log?.Log(TraceEventType.Information, Resources.AnalysisRestarted);
277+
245278
RestartAnalysis();
279+
280+
if (_watchSearchPaths) {
281+
ResetPathWatcher();
282+
}
246283
}, cancellationToken).DoNotWait();
247284

248285
}
249286

250287
private void RestartAnalysis() {
251-
var analyzer = Services.GetService<IPythonAnalyzer>();;
288+
var analyzer = Services.GetService<IPythonAnalyzer>();
252289
analyzer.ResetAnalyzer();
253-
foreach (var doc in _rdt.GetDocuments()) {
254-
doc.Reset(null);
255-
}
290+
_rdt.ReloadAll();
256291
}
257292
}
258293
}

src/LanguageServer/Impl/LanguageServer.Configuration.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public async Task DidChangeConfiguration(JToken token, CancellationToken cancell
5151
settings.symbolsHierarchyMaxSymbols = GetSetting(analysis, "symbolsHierarchyMaxSymbols", 1000);
5252

5353
_logger.LogLevel = GetLogLevel(analysis).ToTraceEventType();
54-
HandlePathWatchChanges(token, cancellationToken);
54+
HandlePathWatchChanges(token);
5555
HandleDiagnosticsChanges(pythonSection, settings);
5656

5757
_server.DidChangeConfiguration(new DidChangeConfigurationParams { settings = settings }, cancellationToken);

src/LanguageServer/Impl/LanguageServer.cs

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,8 @@ public sealed partial class LanguageServer : IDisposable {
5555

5656
private JsonRpc _rpc;
5757
private JsonSerializer _jsonSerializer;
58-
private PathsWatcher _pathsWatcher;
5958
private IIdleTimeTracker _idleTimeTracker;
6059

61-
private bool _watchSearchPaths;
62-
private string[] _searchPaths = Array.Empty<string>();
63-
6460
public CancellationToken Start(IServiceManager services, JsonRpc rpc) {
6561
_server = new Server(services);
6662
_services = services;
@@ -78,7 +74,6 @@ public CancellationToken Start(IServiceManager services, JsonRpc rpc) {
7874
_disposables
7975
.Add(() => _shutdownCts.Cancel())
8076
.Add(_prioritizer)
81-
.Add(() => _pathsWatcher?.Dispose())
8277
.Add(() => _rpc.TraceSource.Listeners.Remove(rpcTraceListener));
8378

8479
services.AddService(_optionsProvider);
@@ -357,30 +352,8 @@ private MessageType GetLogLevel(JToken analysisKey) {
357352
return MessageType.Error;
358353
}
359354

360-
private void HandlePathWatchChanges(JToken section, CancellationToken cancellationToken) {
361-
var watchSearchPaths = GetSetting(section, "watchSearchPaths", true);
362-
if (!watchSearchPaths) {
363-
// No longer watching.
364-
_pathsWatcher?.Dispose();
365-
_searchPaths = Array.Empty<string>();
366-
_watchSearchPaths = false;
367-
return;
368-
}
369-
370-
// Now watching.
371-
if (!_watchSearchPaths || (_watchSearchPaths && _searchPaths.SetEquals(_initParams.initializationOptions.searchPaths))) {
372-
// Were not watching OR were watching but paths have changed. Recreate the watcher.
373-
_pathsWatcher?.Dispose();
374-
_pathsWatcher = new PathsWatcher(
375-
_initParams.initializationOptions.searchPaths,
376-
() =>_server.NotifyPackagesChanged(cancellationToken),
377-
_services.GetService<ILogger>()
378-
);
379-
380-
_watchSearchPaths = true;
381-
_searchPaths = _initParams.initializationOptions.searchPaths;
382-
}
383-
}
355+
private void HandlePathWatchChanges(JToken section)
356+
=> _server.HandleWatchPathsChange(GetSetting(section, "watchSearchPaths", true));
384357

385358
private static CancellationToken GetToken(CancellationToken original)
386359
=> Debugger.IsAttached ? CancellationToken.None : original;

src/LanguageServer/Impl/PathsWatcher.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public PathsWatcher(string[] paths, Action onChanged, ILogger log) {
4343
_onChanged = onChanged;
4444

4545
var reduced = ReduceToCommonRoots(paths);
46+
4647
foreach (var p in reduced) {
4748
try {
4849
if (!Directory.Exists(p)) {
@@ -53,20 +54,29 @@ public PathsWatcher(string[] paths, Action onChanged, ILogger log) {
5354
continue;
5455
}
5556

57+
_log.Log(TraceEventType.Verbose, $"Watching {p}");
58+
5659
try {
5760
var fsw = new System.IO.FileSystemWatcher(p) {
5861
IncludeSubdirectories = true,
5962
EnableRaisingEvents = true,
60-
NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName
63+
NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite,
64+
InternalBufferSize = 1 << 16, // Max buffer size of 64 KB
6165
};
6266

67+
fsw.Changed += OnChanged;
6368
fsw.Created += OnChanged;
6469
fsw.Deleted += OnChanged;
70+
fsw.Renamed += OnChanged;
71+
72+
fsw.Filter = "*.p*"; // .py, .pyc, .pth - TODO: Use Filters in .NET Core 3.0.
6573

6674
_disposableBag
6775
.Add(() => _throttleTimer?.Dispose())
76+
.Add(() => fsw.Changed -= OnChanged)
6877
.Add(() => fsw.Created -= OnChanged)
6978
.Add(() => fsw.Deleted -= OnChanged)
79+
.Add(() => fsw.Renamed -= OnChanged)
7080
.Add(() => fsw.EnableRaisingEvents = false)
7181
.Add(fsw);
7282
} catch (ArgumentException ex) {

0 commit comments

Comments
 (0)