From ae2ae0c6069b4641facffcb2d1587244460ee663 Mon Sep 17 00:00:00 2001 From: Dylan Connell Date: Thu, 11 Dec 2025 11:31:48 -0600 Subject: [PATCH 1/2] Update .gitattributes Keeping LF eol standard --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index dfe07704..2e4a88ee 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ # Auto detect text files and perform LF normalization * text=auto +* text eol=lf \ No newline at end of file From 2373796792505c8c73d6ce8e0683a2248cb91faf Mon Sep 17 00:00:00 2001 From: Dylan Connell Date: Thu, 11 Dec 2025 11:37:24 -0600 Subject: [PATCH 2/2] Updated Loading of solution to Batch Loading Loading of new solutions now occurs in shards, defaulted to 10 files before clearing RAM usage. Could add this to user settings in the future if necessary. Tested with fairly large codebase Harden-Windows-Security and ran into no issues with RAM usage. Fix for issue #22 --- .../CustomMsBuildProjectLoader.Worker.cs | 45 +++++++++++-------- .../CustomMsBuildProjectLoader.cs | 38 +++++++++++----- 2 files changed, 54 insertions(+), 29 deletions(-) diff --git a/src/SharpIDE.Application/Features/Analysis/ProjectLoader/CustomMsBuildProjectLoader.Worker.cs b/src/SharpIDE.Application/Features/Analysis/ProjectLoader/CustomMsBuildProjectLoader.Worker.cs index efa5719f..64110f8c 100644 --- a/src/SharpIDE.Application/Features/Analysis/ProjectLoader/CustomMsBuildProjectLoader.Worker.cs +++ b/src/SharpIDE.Application/Features/Analysis/ProjectLoader/CustomMsBuildProjectLoader.Worker.cs @@ -1,13 +1,14 @@ -using System.Collections.Immutable; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.MSBuild; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; namespace SharpIDE.Application.Features.Analysis.ProjectLoader; @@ -111,19 +112,21 @@ private async Task DoOperationAndReportProgressAsync(ProjectLo return result; } - public async Task<(ImmutableArray, Dictionary)> LoadAsync(CancellationToken cancellationToken) + public async IAsyncEnumerable<(ImmutableArray, Dictionary)> LoadInShardsAsync( + [EnumeratorCancellation] CancellationToken cancellationToken, + int batchSize = 10) { - var results = ImmutableArray.CreateBuilder(); var processedPaths = new HashSet(PathUtilities.Comparer); + var batchResults = ImmutableArray.CreateBuilder(); + var batchFileInfoMap = new Dictionary(); + int count = 0; foreach (var projectPath in _requestedProjectPaths) { cancellationToken.ThrowIfCancellationRequested(); if (!_pathResolver.TryGetAbsoluteProjectPath(projectPath, _baseDirectory, _requestedProjectOptions.OnPathFailure, out var absoluteProjectPath)) - { - continue; // Failure should already be reported. - } + continue; if (!processedPaths.Add(absoluteProjectPath)) { @@ -131,26 +134,30 @@ private async Task DoOperationAndReportProgressAsync(ProjectLo new WorkspaceDiagnostic( WorkspaceDiagnosticKind.Warning, string.Format(WorkspaceMSBuildResources.Duplicate_project_discovered_and_skipped_0, absoluteProjectPath))); - continue; } var projectFileInfos = await LoadProjectInfosFromPathAsync(absoluteProjectPath, _requestedProjectOptions, cancellationToken).ConfigureAwait(false); + batchResults.AddRange(projectFileInfos); - results.AddRange(projectFileInfos); - } - - foreach (var (projectPath, projectInfos) in _pathToDiscoveredProjectInfosMap) - { - cancellationToken.ThrowIfCancellationRequested(); + foreach (var info in projectFileInfos) + { + var projectId = _projectMap.GetOrCreateProjectId(info?.FilePath ?? string.Empty); + batchFileInfoMap[projectId] = _projectIdToFileInfoMap[projectId]; + } - if (!processedPaths.Contains(projectPath)) + count++; + if (count % batchSize == 0) { - results.AddRange(projectInfos); + yield return (batchResults.ToImmutableAndClear(), new Dictionary(batchFileInfoMap)); + batchFileInfoMap.Clear(); } } - return (results.ToImmutableAndClear(), _projectIdToFileInfoMap); + if (batchResults.Count > 0) + { + yield return (batchResults.ToImmutableAndClear(), new Dictionary(batchFileInfoMap)); + } } private async Task> LoadProjectFileInfosAsync(string projectPath, DiagnosticReportingOptions reportingOptions, CancellationToken cancellationToken) diff --git a/src/SharpIDE.Application/Features/Analysis/ProjectLoader/CustomMsBuildProjectLoader.cs b/src/SharpIDE.Application/Features/Analysis/ProjectLoader/CustomMsBuildProjectLoader.cs index 8dadef8c..a710baeb 100644 --- a/src/SharpIDE.Application/Features/Analysis/ProjectLoader/CustomMsBuildProjectLoader.cs +++ b/src/SharpIDE.Application/Features/Analysis/ProjectLoader/CustomMsBuildProjectLoader.cs @@ -1,8 +1,7 @@ -using System.Collections.Immutable; using Microsoft.Build.Framework; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.MSBuild; +using System.Collections.Immutable; namespace SharpIDE.Application.Features.Analysis.ProjectLoader; // I really don't like having to duplicate this, but we need to use IAnalyzerAssemblyLoaderProvider rather than IAnalyzerService, @@ -52,7 +51,19 @@ public partial class CustomMsBuildProjectLoader(Workspace workspace, ImmutableDi discoveredProjectOptions, this.LoadMetadataForReferencedProjects); - return await worker.LoadAsync(cancellationToken).ConfigureAwait(false); + var allProjectInfos = ImmutableArray.CreateBuilder(); + var allFileInfoMap = new Dictionary(); + + await foreach (var (projectInfos, fileInfoMap) in worker.LoadInShardsAsync(cancellationToken).ConfigureAwait(false)) + { + allProjectInfos.AddRange(projectInfos); + foreach (var kvp in fileInfoMap) + { + allFileInfoMap.TryAdd(kvp.Key, kvp.Value); + } + } + + return (allProjectInfos.ToImmutable(), allFileInfoMap); } /// @@ -70,10 +81,7 @@ public partial class CustomMsBuildProjectLoader(Workspace workspace, ImmutableDi ILogger? msbuildLogger = null, CancellationToken cancellationToken = default) { - if (solutionFilePath == null) - { - throw new ArgumentNullException(nameof(solutionFilePath)); - } + ArgumentNullException.ThrowIfNull(solutionFilePath); var reportingMode = GetReportingModeForUnrecognizedProjects(); @@ -109,15 +117,25 @@ public partial class CustomMsBuildProjectLoader(Workspace workspace, ImmutableDi discoveredProjectOptions: reportingOptions, preferMetadataForReferencesOfDiscoveredProjects: false); - var (projectInfos, projectFileInfos) = await worker.LoadAsync(cancellationToken).ConfigureAwait(false); + var allSolutionInfos = ImmutableArray.CreateBuilder(); + var allFileInfoMap = new Dictionary(); + + await foreach (var (projectInfos, fileInfoMap) in worker.LoadInShardsAsync(cancellationToken).ConfigureAwait(false)) + { + allSolutionInfos.AddRange(projectInfos); + foreach (var kvp in fileInfoMap) + { + allFileInfoMap.TryAdd(kvp.Key, kvp.Value); + } + } // construct workspace from loaded project infos var solutionInfo = SolutionInfo.Create( SolutionId.CreateNewId(debugName: absoluteSolutionPath), version: default, absoluteSolutionPath, - projectInfos); + allSolutionInfos); - return (solutionInfo, projectFileInfos); + return (solutionInfo, allFileInfoMap); } }