@@ -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