src/dotnet/ReSharperPlugin.DotNetDisassembler/AsmViewerUpdateCoordinator.cs (110 lines of code) (raw):

using System; using System.Threading.Tasks; using JetBrains.Application.Parts; using JetBrains.Core; using JetBrains.DocumentModel.Storage; using JetBrains.Lifetimes; using JetBrains.ProjectModel; using JetBrains.ReSharper.Feature.Services.Protocol; using JetBrains.Rider.Model; using JetBrains.TextControl; using JetBrains.Util; using Microsoft.Extensions.Caching.Memory; using ReSharperPlugin.DotNetDisassembler.JitDisasm; using ReSharperPlugin.DotNetDisassembler.JitDisasmAdapters; using Result = JetBrains.Core.Result; using static JetBrains.Util.Logging.Logger; namespace ReSharperPlugin.DotNetDisassembler; [SolutionComponent(Instantiation.DemandAnyThreadSafe)] public class AsmViewerUpdateCoordinator( ISolution solution, AsmMethodLocator methodLocator, AsmCompilationService compilationService) { private const int CacheSize = 50; private static readonly TimeSpan SlidingExpiration = TimeSpan.FromMinutes(30); private static readonly ILogger Logger = GetLogger<AsmViewerUpdateCoordinator>(); private readonly AsmViewerModel _model = solution.GetProtocolSolution().GetAsmViewerModel(); private readonly IDocumentStorageHelpers _storageHelpers = solution.GetComponent<IDocumentStorageHelpers>(); private readonly Lazy<MemoryCache> _cache = new(() => new MemoryCache(new MemoryCacheOptions { SizeLimit = CacheSize })); private readonly object _cacheLock = new(); private int _cacheVersion; public async Task<Result<string, Error>> CompileAsync(ITextControl textControl, JitConfiguration jitConfiguration, Lifetime lifetime) { try { var declarationResult = methodLocator.FindDeclarationAtCaret(textControl); if (!declarationResult.Succeed) return Result.FailWithValue(declarationResult.FailValue); if (lifetime.IsNotAlive) return Result.FailWithValue(new Error(AsmViewerErrorCode.UpdateCancelled)); var declarationData = declarationResult.Value; var configuration = JitDisasmConfigurationFactory.Create(jitConfiguration); var cacheKey = new CacheKey(declarationData.FilePath, declarationData.FileStamp, declarationData.Target.MemberFilter, configuration, declarationData.ProjectContext); int myCacheVersion; lock (_cacheLock) { if (_cache.Value.TryGetValue(cacheKey, out CacheEntry entry)) { Logger.Info("Cache hit for method: {0}, returning cached result (version: {1})", declarationData.Target.MemberFilter, entry.Version); return entry.Result; } myCacheVersion = ++_cacheVersion; Logger.Info("Cache miss for method: {0}, starting compilation (version: {1})", declarationData.Target.MemberFilter, myCacheVersion); } _storageHelpers.SaveAllDocuments(); _model.IsLoading.Value = true; try { var compilationResult = await compilationService.CompileAsync( declarationData.Target, configuration, declarationData.ProjectContext, lifetime); if (lifetime.IsNotAlive) { Logger.Info("Lifetime is not alive, returning cancelled"); return Result.FailWithValue(new Error(AsmViewerErrorCode.UpdateCancelled)); } lock (_cacheLock) { if (_cacheVersion != myCacheVersion) return compilationResult; var cacheEntryOptions = new MemoryCacheEntryOptions() .SetSize(1) .SetSlidingExpiration(SlidingExpiration); _cache.Value.Set(cacheKey, new CacheEntry(myCacheVersion, compilationResult), cacheEntryOptions); Logger.Verbose("Cache updated (version: {0}), success: {1}", myCacheVersion, compilationResult.Succeed); } return compilationResult; } finally { _model.IsLoading.Value = false; } } catch (OperationCanceledException) { return Result.FailWithValue(new Error(AsmViewerErrorCode.UpdateCancelled)); } catch (Exception ex) { Logger.LogException(ex); return Result.FailWithValue(new Error(AsmViewerErrorCode.UnknownError, ex.Message)); } } public void InvalidateCache() { lock (_cacheLock) { if (_cache.IsValueCreated) { _cache.Value.Compact(1.0); } Logger.Info("Cache invalidated"); } } private sealed record CacheKey(string FilePath, long FileStamp, string MethodId, JitDisasmConfiguration Configuration, JitDisasmProjectContext ProjectContext); private sealed record CacheEntry(int Version, Result<string, Error> Result); }