Skip to content

Commit ae8e77d

Browse files
Simplify
1 parent 16a0b29 commit ae8e77d

File tree

1 file changed

+66
-82
lines changed

1 file changed

+66
-82
lines changed

src/Features/Core/Portable/Extensions/ExtensionMessageHandlerService.cs

Lines changed: 66 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ internal sealed class ExtensionMessageHandlerService(
5656
/// <summary>
5757
/// Extensions assembly load contexts and loaded handlers, indexed by extension folder path.
5858
/// </summary>
59-
private readonly Dictionary<string, AsyncLazy<IExtensionFolder>> _folderPathToExtensionFolder = new();
59+
private readonly Dictionary<string, AsyncLazy<ExtensionFolder>> _folderPathToExtensionFolder = new();
6060

6161
/// <summary>
6262
/// Cached handlers of document-related messages, indexed by handler message name.
@@ -118,7 +118,7 @@ public async ValueTask<RegisterExtensionResponse> RegisterExtensionInCurrentProc
118118
var assemblyFolderPath = Path.GetDirectoryName(assemblyFilePath)
119119
?? throw new InvalidOperationException($"Unable to get the directory name for {assemblyFilePath}.");
120120

121-
AsyncLazy<IExtensionFolder> lazyExtensionFolder;
121+
AsyncLazy<ExtensionFolder> lazyExtensionFolder;
122122
using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
123123
{
124124
lazyExtensionFolder = _folderPathToExtensionFolder.GetOrAdd(
@@ -286,58 +286,59 @@ private static async Task<ImmutableArray<IExtensionMessageHandlerWrapper<TResult
286286
return result.ToImmutable();
287287
}
288288

289-
private interface IExtensionFolder
289+
private abstract class ExtensionFolder
290290
{
291-
AsyncLazy<AssemblyHandlers> RegisterAssembly(string assemblyFilePath);
291+
private readonly Dictionary<string, AsyncLazy<AssemblyHandlers>> _assemblyFilePathToHandlers = new();
292292

293-
/// <summary>
294-
/// Unregisters this assembly path from this extension folder. If this was the last registered path, then this
295-
/// will return true so that this folder can be unloaded.
296-
/// </summary>
297-
bool UnregisterAssembly(string assemblyFilePath);
298-
299-
ValueTask AddHandlersAsync<TResult>(string messageName, bool isSolution, ArrayBuilder<IExtensionMessageHandlerWrapper<TResult>> result, CancellationToken cancellationToken);
300-
}
301-
302-
/// <summary>
303-
/// Trivial placeholder impl of <see cref="IExtensionFolder"/> when we fail for some reason to even process the
304-
/// folder we are told contains extensions.
305-
/// </summary>
306-
private sealed class TrivialExtensionFolder : IExtensionFolder
307-
{
308-
public static readonly TrivialExtensionFolder Instance = new();
309-
310-
/// <summary>
311-
/// No lock needed as registration/unregistration must happen serially.
312-
/// </summary>
313-
private readonly List<string> _registeredFilePaths = [];
293+
protected abstract AssemblyHandlers CreateAssemblyHandlers(string assemblyFilePath, CancellationToken cancellationToken);
314294

315295
public AsyncLazy<AssemblyHandlers> RegisterAssembly(string assemblyFilePath)
316296
{
317-
_registeredFilePaths.Add(assemblyFilePath);
318-
return AsyncLazy.Create(AssemblyHandlers.Empty);
297+
lock (_assemblyFilePathToHandlers)
298+
{
299+
return _assemblyFilePathToHandlers.GetOrAdd(
300+
assemblyFilePath,
301+
static (assemblyFilePath, @this) => AsyncLazy.Create(
302+
static (args, cancellationToken) => args.@this.CreateAssemblyHandlers(args.assemblyFilePath, cancellationToken),
303+
(assemblyFilePath, @this)),
304+
this);
305+
}
319306
}
320307

308+
/// <summary>
309+
/// Unregisters this assembly path from this extension folder. If this was the last registered path, then this
310+
/// will return true so that this folder can be unloaded.
311+
/// </summary>
321312
public bool UnregisterAssembly(string assemblyFilePath)
322313
{
323-
_registeredFilePaths.Remove(assemblyFilePath);
324-
return _registeredFilePaths.Count == 0;
314+
lock (_assemblyFilePathToHandlers)
315+
{
316+
_assemblyFilePathToHandlers.Remove(assemblyFilePath);
317+
return _assemblyFilePathToHandlers.Count == 0;
318+
}
325319
}
326320

327-
public ValueTask AddHandlersAsync<TResult>(string messageName, bool isSolution, ArrayBuilder<IExtensionMessageHandlerWrapper<TResult>> result, CancellationToken cancellationToken)
328-
=> default;
329-
}
330-
331-
private sealed class ExtensionFolder(
332-
ExtensionMessageHandlerService extensionMessageHandlerService,
333-
IAnalyzerAssemblyLoaderInternal analyzerAssemblyLoader) : IExtensionFolder
334-
{
335-
private readonly ExtensionMessageHandlerService _extensionMessageHandlerService = extensionMessageHandlerService;
336-
private readonly IAnalyzerAssemblyLoaderInternal _analyzerAssemblyLoader = analyzerAssemblyLoader;
321+
public async ValueTask AddHandlersAsync<TResult>(string messageName, bool isSolution, ArrayBuilder<IExtensionMessageHandlerWrapper<TResult>> result, CancellationToken cancellationToken)
322+
{
323+
foreach (var (_, lazyHandlers) in _assemblyFilePathToHandlers)
324+
{
325+
cancellationToken.ThrowIfCancellationRequested();
337326

338-
private ImmutableDictionary<string, AsyncLazy<AssemblyHandlers>> _assemblyFilePathToHandlers = ImmutableDictionary<string, AsyncLazy<AssemblyHandlers>>.Empty;
327+
var handlers = await lazyHandlers.GetValueAsync(cancellationToken).ConfigureAwait(false);
328+
if (isSolution)
329+
{
330+
if (handlers.WorkspaceMessageHandlers.TryGetValue(messageName, out var handler))
331+
result.Add((IExtensionMessageHandlerWrapper<TResult>)handler);
332+
}
333+
else
334+
{
335+
if (handlers.DocumentMessageHandlers.TryGetValue(messageName, out var handler))
336+
result.Add((IExtensionMessageHandlerWrapper<TResult>)handler);
337+
}
338+
}
339+
}
339340

340-
public static IExtensionFolder Create(
341+
public static ExtensionFolder Create(
341342
ExtensionMessageHandlerService extensionMessageHandlerService,
342343
string assemblyFolderPath,
343344
CancellationToken cancellationToken)
@@ -365,33 +366,42 @@ public static IExtensionFolder Create(
365366
analyzerAssemblyLoader.AddDependencyLocation(dll);
366367
}
367368

368-
return new ExtensionFolder(extensionMessageHandlerService, analyzerAssemblyLoader);
369+
return new ShadowCopyExtensionFolder(extensionMessageHandlerService, analyzerAssemblyLoader);
369370
}
370371
catch (Exception ex) when (FatalError.ReportAndCatch(ex, ErrorSeverity.Critical))
371372
{
372373
// TODO: Log this exception so the client knows something went wrong.
373374
return new TrivialExtensionFolder();
374375
}
375376
}
377+
}
376378

377-
public AsyncLazy<AssemblyHandlers> RegisterAssembly(string assemblyFilePath)
378-
{
379-
return ImmutableInterlocked.GetOrAdd(
380-
ref _assemblyFilePathToHandlers,
381-
assemblyFilePath,
382-
static (assemblyFilePath, @this) => AsyncLazy.Create(
383-
static (args, cancellationToken) => CreateAssemblyHandlers(args.@this, args.assemblyFilePath, cancellationToken),
384-
(assemblyFilePath, @this)),
385-
this);
386-
}
379+
/// <summary>
380+
/// Trivial placeholder impl of <see cref="ExtensionFolder"/> when we fail for some reason to even process the
381+
/// folder we are told contains extensions.
382+
/// </summary>
383+
private sealed class TrivialExtensionFolder : ExtensionFolder
384+
{
385+
protected override AssemblyHandlers CreateAssemblyHandlers(string assemblyFilePath, CancellationToken cancellationToken)
386+
=> AssemblyHandlers.Empty;
387+
}
388+
389+
/// <summary>
390+
/// Standard impl of <see cref="ExtensionFolder"/> that uses a shadow copy loader to load extensions.
391+
/// </summary>
392+
private sealed class ShadowCopyExtensionFolder(
393+
ExtensionMessageHandlerService extensionMessageHandlerService,
394+
IAnalyzerAssemblyLoaderInternal analyzerAssemblyLoader) : ExtensionFolder
395+
{
396+
private readonly ExtensionMessageHandlerService _extensionMessageHandlerService = extensionMessageHandlerService;
397+
private readonly IAnalyzerAssemblyLoaderInternal _analyzerAssemblyLoader = analyzerAssemblyLoader;
387398

388-
private static AssemblyHandlers CreateAssemblyHandlers(
389-
ExtensionFolder @this, string assemblyFilePath, CancellationToken cancellationToken)
399+
protected override AssemblyHandlers CreateAssemblyHandlers(string assemblyFilePath, CancellationToken cancellationToken)
390400
{
391401
try
392402
{
393-
var assembly = @this._analyzerAssemblyLoader.LoadFromPath(assemblyFilePath);
394-
var factory = @this._extensionMessageHandlerService._customMessageHandlerFactory;
403+
var assembly = _analyzerAssemblyLoader.LoadFromPath(assemblyFilePath);
404+
var factory = _extensionMessageHandlerService._customMessageHandlerFactory;
395405

396406
var messageWorkspaceHandlers = factory
397407
.CreateWorkspaceMessageHandlers(assembly, extensionIdentifier: assemblyFilePath, cancellationToken)
@@ -415,32 +425,6 @@ private static AssemblyHandlers CreateAssemblyHandlers(
415425
return AssemblyHandlers.Empty;
416426
}
417427
}
418-
419-
public async ValueTask AddHandlersAsync<TResult>(string messageName, bool isSolution, ArrayBuilder<IExtensionMessageHandlerWrapper<TResult>> result, CancellationToken cancellationToken)
420-
{
421-
foreach (var (_, lazy) in _assemblyFilePathToHandlers)
422-
{
423-
cancellationToken.ThrowIfCancellationRequested();
424-
425-
var handlers = await lazy.GetValueAsync(cancellationToken).ConfigureAwait(false);
426-
if (isSolution)
427-
{
428-
if (handlers.WorkspaceMessageHandlers.TryGetValue(messageName, out var handler))
429-
result.Add((IExtensionMessageHandlerWrapper<TResult>)handler);
430-
}
431-
else
432-
{
433-
if (handlers.DocumentMessageHandlers.TryGetValue(messageName, out var handler))
434-
result.Add((IExtensionMessageHandlerWrapper<TResult>)handler);
435-
}
436-
}
437-
}
438-
439-
public bool UnregisterAssembly(string assemblyFilePath)
440-
{
441-
_assemblyFilePathToHandlers.Remove(assemblyFilePath);
442-
return _assemblyFilePathToHandlers.IsEmpty;
443-
}
444428
}
445429

446430
private sealed class AssemblyHandlers

0 commit comments

Comments
 (0)