Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/monodevelop.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortherzok <marius.ungureanu@xamarin.com>2019-06-18 03:22:42 +0300
committertherzok <marius.ungureanu@xamarin.com>2019-06-20 05:09:11 +0300
commit47742270cbf8bc51864658484a7fdc378f6f53a9 (patch)
tree49a6a2703d741d610b414598dca496e1a4767566 /main/src/core
parenta711ab2bb6e778873bea579fd3b27e2cdd666611 (diff)
Initial work towards optimizing FileService handlers
Fixes VSTS #853896 - [Feedback] Visual Studio freezes when i run mono Fixes VSTS #902435 - Investigate performance issue with FileSystemWatcher
Diffstat (limited to 'main/src/core')
-rw-r--r--main/src/core/MonoDevelop.Core/CoreExtensions.cs77
-rw-r--r--main/src/core/MonoDevelop.Core/MonoDevelop.Core/FileService.cs12
-rw-r--r--main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/DefaultMSBuildEngine.cs5
-rw-r--r--main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildProjectService.cs4
-rw-r--r--main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Project.cs112
-rw-r--r--main/src/core/MonoDevelop.Core/MonoDevelop.Projects/ProjectFileCollection.cs5
6 files changed, 156 insertions, 59 deletions
diff --git a/main/src/core/MonoDevelop.Core/CoreExtensions.cs b/main/src/core/MonoDevelop.Core/CoreExtensions.cs
index 3d90766d6b..0818ad2c72 100644
--- a/main/src/core/MonoDevelop.Core/CoreExtensions.cs
+++ b/main/src/core/MonoDevelop.Core/CoreExtensions.cs
@@ -26,6 +26,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
+using System.Reflection;
using System.Threading.Tasks;
using MonoDevelop.Core;
@@ -296,6 +297,82 @@ namespace System
}
}
}
+
+ internal static void TimeInvoke<T> (this EventHandler<T> events, object sender, T args)
+ {
+ var sw = new Diagnostics.Stopwatch ();
+ foreach (var ev in events.GetInvocationList ()) {
+ try {
+ sw.Restart ();
+ ((EventHandler<T>)ev) (sender, args);
+ sw.Stop ();
+
+ lock (timings) {
+ if (!timings.TryGetValue (sender, out var timingPair)) {
+ timings [sender] = timingPair = new Dictionary<Reflection.MethodInfo, TimeSpan> ();
+ }
+
+ if (!timingPair.TryGetValue (ev.Method, out var previousTime))
+ previousTime = TimeSpan.Zero;
+
+ timingPair [ev.Method] = previousTime.Add (sw.Elapsed);
+ }
+ } catch (Exception ex) {
+ LoggingService.LogInternalError (ex);
+ }
+ }
+ }
+
+ static readonly Dictionary<object, Dictionary<MethodInfo, TimeSpan>> timings = new Dictionary<object, Dictionary<MethodInfo, TimeSpan>> ();
+
+ internal static void TimingsReport ()
+ {
+ foreach (var kvp in timings) {
+ var source = kvp.Key;
+ var values = kvp.Value;
+
+ Console.WriteLine ("Source {0}", source);
+ foreach (var timeReport in values.OrderByDescending (x => x.Value)) {
+ Console.WriteLine ("{0} - {1} {2}", timeReport.Value.ToString (), timeReport.Key.DeclaringType, timeReport.Key);
+ }
+ }
+ }
+
+ internal static void TimeInvoke (this EventHandler events, object sender, EventArgs args)
+ {
+ var sw = new Diagnostics.Stopwatch ();
+ foreach (var ev in events.GetInvocationList ()) {
+ try {
+ sw.Restart ();
+ ((EventHandler)ev) (sender, args);
+ sw.Stop ();
+
+ lock (timings) {
+ if (!timings.TryGetValue (sender, out var timingPair)) {
+ timings [sender] = timingPair = new Dictionary<MethodInfo, TimeSpan> (MethodInfoEqualityComparer.Instance);
+ }
+
+ if (!timingPair.TryGetValue (ev.Method, out var previousTime))
+ previousTime = TimeSpan.Zero;
+
+ timingPair [ev.Method] = previousTime.Add (sw.Elapsed);
+ }
+ } catch (Exception ex) {
+ LoggingService.LogInternalError (ex);
+ }
+ }
+ }
+
+ sealed class MethodInfoEqualityComparer : IEqualityComparer<MethodInfo>
+ {
+ public static MethodInfoEqualityComparer Instance = new MethodInfoEqualityComparer ();
+ public bool Equals (MethodInfo x, MethodInfo y)
+ => x.Name == y.Name
+ && x.DeclaringType == y.DeclaringType;
+
+ public int GetHashCode (MethodInfo obj)
+ => obj.Name.GetHashCode () ^ obj.DeclaringType.GetHashCode ();
+ }
}
}
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core/FileService.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core/FileService.cs
index 81221d827b..cab986eacc 100644
--- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core/FileService.cs
+++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core/FileService.cs
@@ -909,6 +909,8 @@ namespace MonoDevelop.Core
class EventQueue
{
+ static readonly EventQueue q = new EventQueue ();
+
static void RaiseSync (FileService.EventDataKind kind, FileEventArgs args)
{
var handler = FileService.GetHandler (kind);
@@ -918,14 +920,14 @@ namespace MonoDevelop.Core
// Ugly, but it saves us the problem of having to deal with generic event handlers without covariance.
if (args is FileCopyEventArgs copyArgs) {
if (handler is EventHandler<FileCopyEventArgs> copyHandler) {
- copyHandler.Invoke (null, copyArgs);
+ copyHandler?.TimeInvoke (q, copyArgs);
return;
}
throw new InvalidOperationException ();
}
if (handler is EventHandler<FileEventArgs> fileHandler)
- fileHandler.Invoke (null, args);
+ fileHandler?.TimeInvoke (q, args);
else
throw new InvalidOperationException ();
}
@@ -1345,17 +1347,17 @@ namespace MonoDevelop.Core
internal void OnFileCreated (FileEventArgs args)
{
- FileCreated?.Invoke (this, Clone (args));
+ FileCreated?.TimeInvoke (this, Clone (args));
}
internal void OnFileRemoved (FileEventArgs args)
{
- FileRemoved?.Invoke (this, Clone (args));
+ FileRemoved?.TimeInvoke (this, Clone (args));
}
internal void OnFileRenamed (FileCopyEventArgs args)
{
- FileRenamed?.Invoke (this, Clone (args));
+ FileRenamed?.TimeInvoke (this, Clone (args));
}
static T Clone<T> (T args) where T : FileEventArgs, new()
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/DefaultMSBuildEngine.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/DefaultMSBuildEngine.cs
index 0cc36a1f0e..49f790ef94 100644
--- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/DefaultMSBuildEngine.cs
+++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/DefaultMSBuildEngine.cs
@@ -1435,7 +1435,10 @@ namespace MonoDevelop.Projects.MSBuild
{
var pi = (ProjectInfo)projectInstance;
string filePath = MSBuildProjectService.FromMSBuildPath (pi.Project.BaseDirectory, include);
- foreach (var g in pi.GlobIncludes.Where (g => g.Condition)) {
+ foreach (var g in pi.GlobIncludes) {
+ if (!g.Condition)
+ continue;
+
if (g.ExcludeRegex != null) {
if (g.ExcludeRegex.IsMatch (include))
continue;
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildProjectService.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildProjectService.cs
index d2874912c7..703bbcf97d 100644
--- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildProjectService.cs
+++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildProjectService.cs
@@ -843,7 +843,7 @@ namespace MonoDevelop.Projects.MSBuild
{
int i = str.IndexOfAny (specialCharacters);
if (i != -1) {
- var sb = new System.Text.StringBuilder ();
+ var sb = StringBuilderCache.Allocate ();
int start = 0;
while (i != -1) {
sb.Append (str, start, i - start);
@@ -855,7 +855,7 @@ namespace MonoDevelop.Projects.MSBuild
}
if (start < str.Length)
sb.Append (str, start, str.Length - start);
- return sb.ToString ();
+ return StringBuilderCache.ReturnAndFree (sb);
}
return str;
}
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Project.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Project.cs
index 7caab35662..a7f140539b 100644
--- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Project.cs
+++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Project.cs
@@ -48,6 +48,7 @@ using Microsoft.CodeAnalysis;
using MonoDevelop.Core.Collections;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
using System.Runtime.CompilerServices;
+using Microsoft.Extensions.ObjectPool;
namespace MonoDevelop.Projects
{
@@ -2406,19 +2407,23 @@ namespace MonoDevelop.Projects
internal virtual void OnFileChanged (object source, FileEventArgs e)
{
+ var args = new ProjectFileEventArgs ();
+
foreach (FileEventInfo fi in e) {
ProjectFile file = GetProjectFile (fi.FileName);
if (file != null) {
SetFastBuildCheckDirty ();
- try {
- NotifyFileChangedInProject (file);
- } catch {
- // Workaround Mono bug. The watcher seems to
- // stop watching if an exception is thrown in
- // the event handler
- }
+ args.Add (new ProjectFileEventInfo (this, file));
}
}
+
+ try {
+ OnFileChangedInProject (args);
+ } catch {
+ // Workaround Mono bug. The watcher seems to
+ // stop watching if an exception is thrown in
+ // the event handler
+ }
}
protected override IEnumerable<FilePath> OnGetItemFiles (bool includeReferencedFiles)
@@ -4488,17 +4493,17 @@ namespace MonoDevelop.Projects
void OnFileRenamed (object sender, FileCopyEventArgs e)
{
foreach (FileEventInfo info in e) {
- OnFileRenamed (info.SourceFile, info.TargetFile);
+ OnFileRenamed (info.SourceFile, info.TargetFile, info.IsDirectory);
}
}
- void OnFileRenamed (FilePath sourceFile, FilePath targetFile)
+ void OnFileRenamed (FilePath sourceFile, FilePath targetFile, bool isDirectory)
{
if (Runtime.IsMainThread)
return;
try {
- if (Directory.Exists (targetFile)) {
+ if (isDirectory) {
OnDirectoryRenamedExternally (sourceFile, targetFile);
return;
}
@@ -4508,7 +4513,7 @@ namespace MonoDevelop.Projects
Runtime.RunInMainThread (() => {
OnFileCreatedExternally (targetFile);
- OnFileDeletedExternally (sourceFile);
+ OnFileDeletedExternally (sourceFile, isDirectory);
});
}
@@ -4518,25 +4523,30 @@ namespace MonoDevelop.Projects
return;
foreach (FileEventInfo info in e) {
- OnFileCreated (info.FileName);
+ OnFileCreated (info.FileName, info.IsDirectory);
}
}
- void OnFileCreated (FilePath filePath)
+ void OnFileCreated (FilePath filePath, bool isDirectory)
{
if (Runtime.IsMainThread)
return;
try {
- if (Directory.Exists (filePath))
+ if (isDirectory)
return;
- if (filePath.FileName == ".DS_Store")
- return;
+ var fileName = ((string)filePath).AsSpan ();
+ fileName = fileName.Slice (fileName.LastIndexOf (Path.DirectorySeparatorChar) + 1);
- // Ignore temporary files created when saving a file in the editor.
- if (filePath.FileName.StartsWith (".#", StringComparison.OrdinalIgnoreCase))
- return;
+ if (fileName[0] == '.') {
+ // Ignore temporary files created when saving a file in the editor.
+ if (fileName [1] == '#')
+ return;
+
+ if (fileName.SequenceEqual (".DS_Store".AsSpan ()))
+ return;
+ }
OnFileCreatedExternally (filePath);
} catch (Exception ex) {
@@ -4551,7 +4561,7 @@ namespace MonoDevelop.Projects
Runtime.RunInMainThread (() => {
foreach (FileEventInfo info in e) {
- OnFileDeletedExternally (info.FileName);
+ OnFileDeletedExternally (info.FileName, info.IsDirectory);
}
});
}
@@ -4594,36 +4604,33 @@ namespace MonoDevelop.Projects
void OnDirectoryMovedOutOfProject (FilePath oldDirectory)
{
// Directory moved outside project directory. Remove files from project.
- var projectFilesInDirectory = Files.GetFilesInPath (oldDirectory);
- if (!projectFilesInDirectory.Any ())
- return;
-
Runtime.RunInMainThread (() => {
- foreach (ProjectFile file in projectFilesInDirectory)
- Files.Remove (file);
+ Files.RemoveFilesInPath (oldDirectory);
});
}
+ static readonly ObjectPool<List<ProjectItem>> projectItemListPool = ObjectPool.Create<List<ProjectItem>> ();
+
void OnFileCreatedExternally (FilePath fileName)
{
if (sourceProject == null) {
- // sometimes this method is called after disposing this class.
+ // sometimes this method is called after disposing this class.
// (i.e. when quitting MD or creating a new project.)
LoggingService.LogWarning ("File created externally not processed. {0}", fileName);
return;
}
- // Check file is inside the project directory. The file globs would exclude the file anyway
- // if the relative path starts with "..\" but checking here avoids checking the file globs.
- if (!fileName.IsChildPathOf (BaseDirectory))
- return;
-
- if (Files.Any (file => file.FilePath == fileName)) {
+ if (Files.GetFile (fileName) != null) {
// File exists in project. This can happen if the file was added
// in the IDE and not externally.
return;
}
+ // Check file is inside the project directory. The file globs would exclude the file anyway
+ // if the relative path starts with "..\" but checking here avoids checking the file globs.
+ if (!fileName.IsChildPathOf (BaseDirectory))
+ return;
+
string include = MSBuildProjectService.ToMSBuildPath (ItemDirectory, fileName);
var globItems = sourceProject.FindGlobItemsIncludingFile (include);
if (globItems == null) {
@@ -4633,34 +4640,37 @@ namespace MonoDevelop.Projects
}
if (!UseAdvancedGlobSupport)
- globItems = globItems.Where (it => it.Metadata.GetProperties ().Count () == 0);
+ globItems = globItems.Where (it => !it.Metadata.GetProperties ().Any ());
+ var list = projectItemListPool.Get ();
foreach (var it in globItems) {
var eit = CreateFakeEvaluatedItem (sourceProject, it, include, null);
var pi = CreateProjectItem (eit);
pi.Read (this, eit);
- if (Runtime.IsMainThread) {
- Items.Add (pi);
- } else {
- Runtime.RunInMainThread (() => {
- // Double check the file has not been added on the UI thread by the IDE.
- if (!Files.Any (file => file.FilePath == fileName)) {
- Items.Add (pi);
- }
- }).Ignore ();
- }
+
+ list.Add (pi);
}
+
+ Runtime.RunInMainThread (() => {
+ // Double check the file has not been added on the UI thread by the IDE.
+ list.RemoveAll (x => Files.GetFile (fileName) != null);
+ Items.AddRange (list);
+ projectItemListPool.Return (list);
+ }).Ignore ();
}
- void OnFileDeletedExternally (string fileName)
+ void OnFileDeletedExternally (string fileName, bool isDirectory)
{
- if (File.Exists (fileName) || Directory.Exists (fileName)) {
- // File has not been deleted. The delete event could have been due to
- // the file being saved. Saving with TextFileUtility will result in
- // FileService.SystemRename being called to move a temporary file
- // to the file being saved which deletes and then creates the file.
+ // File has not been deleted. The delete event could have been due to
+ // the file being saved. Saving with TextFileUtility will result in
+ // FileService.SystemRename being called to move a temporary file
+ // to the file being saved which deletes and then creates the file.
+ bool notDeleted = isDirectory
+ ? Directory.Exists (fileName)
+ : File.Exists (fileName);
+
+ if (notDeleted)
return;
- }
Files.Remove (fileName);
}
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/ProjectFileCollection.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/ProjectFileCollection.cs
index e70d1e46ef..2e0e1d39b4 100644
--- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/ProjectFileCollection.cs
+++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/ProjectFileCollection.cs
@@ -251,6 +251,11 @@ namespace MonoDevelop.Projects
}
return list.ToArray ();
}
+
+ internal void RemoveFilesInPath (FilePath path)
+ {
+ SetItems (this.Where (file => !file.FilePath.IsChildPathOf (path)));
+ }
public void Remove (string fileName)
{