Skip to content

Commit bfac0a4

Browse files
committed
Replace List<object> with thread safe ConcurrentStack<object> for disposables
1 parent b4e737b commit bfac0a4

7 files changed

Lines changed: 42 additions & 31 deletions

File tree

Main/CodeGeneration/CodeGenerationVisitor.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Microsoft.CodeAnalysis.CSharp;
1+
using System.Collections.Concurrent;
2+
using Microsoft.CodeAnalysis.CSharp;
23
using MrMeeseeks.DIE.Configuration;
34
using MrMeeseeks.DIE.Extensions;
45
using MrMeeseeks.DIE.Nodes.Elements;
@@ -155,7 +156,7 @@ public void VisitIInitialTransientScopeSubDisposalNode(IInitialTransientScopeSub
155156
public void VisitIScopeCallNode(IScopeCallNode scopeCall)
156157
{
157158
VisitIElementNode(scopeCall.ScopeConstruction);
158-
_code.AppendLine($"{scopeCall.SubDisposalReference}.{nameof(List<object>.Add)}({scopeCall.ScopeConstruction.Reference});");
159+
_code.AppendLine($"{scopeCall.SubDisposalReference}.{nameof(ConcurrentStack<object>.Push)}({scopeCall.ScopeConstruction.Reference});");
159160
GenerateInitialization(scopeCall.Initialization, scopeCall.ScopeConstruction.Reference);
160161
VisitIFunctionCallNode(scopeCall);
161162
}
@@ -264,7 +265,7 @@ public void VisitIRangedInstanceFunctionGroupNode(IRangedInstanceFunctionGroupNo
264265
}
265266

266267
public void VisitIInitialSubDisposalNode(IInitialSubDisposalNode element) =>
267-
_code.AppendLine($"{element.TypeFullName} {element.Reference} = new {element.TypeFullName}({element.SubDisposalCount});");
268+
_code.AppendLine($"{element.TypeFullName} {element.Reference} = new {element.TypeFullName}();");
268269

269270
public void VisitIWrappedAsyncFunctionCallNode(IWrappedAsyncFunctionCallNode functionCallNode)
270271
{
@@ -384,7 +385,7 @@ public void VisitIThreadLocalNode(IThreadLocalNode threadLocalNode)
384385
_code.AppendLine(
385386
$"{threadLocalNode.TypeFullName} {threadLocalNode.Reference} = new {threadLocalNode.TypeFullName}({threadLocalNode.MethodGroup}, false);");
386387
if (threadLocalNode.SubDisposalReference is {} subDisposalReference)
387-
_code.AppendLine($"{subDisposalReference}.Add({threadLocalNode.Reference});");
388+
_code.AppendLine($"{subDisposalReference}.{nameof(ConcurrentStack<object>.Push)}({threadLocalNode.Reference});");
388389
}
389390

390391
public void VisitITupleNode(ITupleNode tupleNode)
@@ -523,7 +524,7 @@ public void VisitIImplementationNode(IImplementationNode implementationNode)
523524
$"{implementationNode.TypeFullName} {implementationNode.Reference} = {cast}new {implementationNode.ConstructorCallName}({constructorParameters}){objectInitializerParameter};");
524525

525526
if (implementationNode.AggregateForDisposal)
526-
_code.AppendLine($"{implementationNode.SubDisposalReference}.{nameof(List<object>.Add)}({implementationNode.Reference});");
527+
_code.AppendLine($"{implementationNode.SubDisposalReference}.{nameof(ConcurrentStack<object>.Push)}({implementationNode.Reference});");
527528

528529
if (implementationNode.Initializer is {} init)
529530
{

Main/CodeGeneration/DisposeUtility.cs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections.Concurrent;
12
using System.Threading;
23
using System.Threading.Tasks;
34
using MrMeeseeks.DIE.MsContainer;
@@ -355,10 +356,10 @@ void GenerateDisposeChunk(bool isAsync)
355356

356357
code.AppendLine(
357358
$$"""
358-
private static {{asyncModifier}}{{returnType}} {{functionName}}({{DisposableRangeInterfaceData.InterfaceNameFullyQualified}} {{disposableElementParameterReference}}, {{_wellKnownTypes.ListOfObject.FullName()}} disposables)
359+
private static {{asyncModifier}}{{returnType}} {{functionName}}({{DisposableRangeInterfaceData.InterfaceNameFullyQualified}} {{disposableElementParameterReference}}, {{_wellKnownTypes.ConcurrentStackOfObject.FullName()}} disposables)
359360
{
360361
{{taskYieldLine}}
361-
for (var i = disposables.{{nameof(List<object>.Count)}} - 1; i >= 0; i--)
362+
while (disposables.{{nameof(ConcurrentStack<object>.TryPop)}}(out {{_wellKnownTypes.Object.FullName()}}? maybeDisposable) && maybeDisposable is {} disposable)
362363
{
363364
""");
364365

@@ -382,7 +383,7 @@ void IfClause(string clauseFunctionName, string disposableType, string disposabl
382383
var exceptionReference = _referenceGenerator.Generate("exception");
383384
code.AppendLine(
384385
$$"""
385-
if ({{disposableElementParameterReference}}.{{clauseFunctionName}}(disposables[i]) && disposables[i] is {{disposableType}} {{disposableReference}} && {{disposableCall}}({{disposableReference}}) is {{_wellKnownTypes.Exception}} {{exceptionReference}})
386+
if ({{disposableElementParameterReference}}.{{clauseFunctionName}}(disposable) && disposable is {{disposableType}} {{disposableReference}} && {{disposableCall}}({{disposableReference}}) is {{_wellKnownTypes.Exception}} {{exceptionReference}})
386387
{
387388
yield return {{exceptionReference}};
388389
}
@@ -405,7 +406,7 @@ void GenerateDisposeExceptionHandling(bool syncOnlyMode)
405406

406407
code.AppendLine(
407408
$$"""
408-
internal static {{_wellKnownTypes.Exception.FullName()}} {{name}}({{DisposableRangeInterfaceData.InterfaceNameFullyQualified}} {{disposableElementParameterReference}}, {{_wellKnownTypes.Exception.FullName()}} exception, {{_wellKnownTypes.ListOfObject.FullName()}} subDisposal, {{_wellKnownTypes.ListOfObject.FullName()}}? transientScopeDisposal = null)
409+
internal static {{_wellKnownTypes.Exception.FullName()}} {{name}}({{DisposableRangeInterfaceData.InterfaceNameFullyQualified}} {{disposableElementParameterReference}}, {{_wellKnownTypes.Exception.FullName()}} exception, {{_wellKnownTypes.ConcurrentStackOfObject.FullName()}} subDisposal, {{_wellKnownTypes.ListOfObject.FullName()}}? transientScopeDisposal = null)
409410
{
410411
if ({{AggregateExceptionRoutineFullyQualified}}(Inner()) is { } aggregateException)
411412
return aggregateException;
@@ -442,7 +443,7 @@ void GenerateDisposeExceptionHandlingToException()
442443

443444
code.AppendLine(
444445
$$"""
445-
internal static {{_wellKnownTypes.Exception.FullName()}} {{_disposeExceptionHandlingName}}({{DisposableRangeInterfaceData.InterfaceNameFullyQualified}} {{disposableElementParameterReference}}, {{_wellKnownTypes.Exception.FullName()}} exception, {{_wellKnownTypes.ListOfObject.FullName()}} subDisposal, {{_wellKnownTypes.ListOfObject.FullName()}}? transientScopeDisposal = null) =>
446+
internal static {{_wellKnownTypes.Exception.FullName()}} {{_disposeExceptionHandlingName}}({{DisposableRangeInterfaceData.InterfaceNameFullyQualified}} {{disposableElementParameterReference}}, {{_wellKnownTypes.Exception.FullName()}} exception, {{_wellKnownTypes.ConcurrentStackOfObject.FullName()}} subDisposal, {{_wellKnownTypes.ListOfObject.FullName()}}? transientScopeDisposal = null) =>
446447
throw new {{_syncDisposalTriggeredExceptionName}}({{_disposeExceptionHandlingAsyncName}}({{disposableElementParameterReference}}, exception, subDisposal, transientScopeDisposal), exception);
447448
""");
448449
}
@@ -458,7 +459,7 @@ void GenerateDisposeExceptionHandlingAsync()
458459

459460
code.AppendLine(
460461
$$"""
461-
internal static async {{_wellKnownTypes.TaskOfException.FullName()}} {{_disposeExceptionHandlingAsyncName}}({{DisposableRangeInterfaceData.InterfaceNameFullyQualified}} {{disposableElementParameterReference}}, {{_wellKnownTypes.Exception.FullName()}} exception, {{_wellKnownTypes.ListOfObject.FullName()}} subDisposal, {{_wellKnownTypes.ListOfObject.FullName()}}? transientScopeDisposal = null)
462+
internal static async {{_wellKnownTypes.TaskOfException.FullName()}} {{_disposeExceptionHandlingAsyncName}}({{DisposableRangeInterfaceData.InterfaceNameFullyQualified}} {{disposableElementParameterReference}}, {{_wellKnownTypes.Exception.FullName()}} exception, {{_wellKnownTypes.ConcurrentStackOfObject.FullName()}} subDisposal, {{_wellKnownTypes.ListOfObject.FullName()}}? transientScopeDisposal = null)
462463
{
463464
if (await {{AggregateExceptionRoutineAsyncFullyQualified}}(Inner()) is { } aggregateException && aggregateException.InnerExceptions.Count > 1)
464465
return aggregateException;
@@ -494,7 +495,7 @@ void GenerateDisposableRangeInterface()
494495
internal interface {{_disposableRangeInterfaceName}}
495496
{
496497
internal {{_wellKnownTypes.Object.FullName()}}[] {{DisposableRangeInterfaceData.TransientScopesPropertyName}} { get; }
497-
internal {{_wellKnownTypes.ListOfListOfObject.FullName()}} {{DisposableRangeInterfaceData.DisposablesPropertyName}} { get; }
498+
internal {{_wellKnownTypes.ConcurrentStackOfConcurrentStackOfObject.FullName()}} {{DisposableRangeInterfaceData.DisposablesPropertyName}} { get; }
498499
internal {{_wellKnownTypes.ConcurrentBagOfSyncDisposable.FullName()}} {{DisposableRangeInterfaceData.UserDefinedSyncDisposablesPropertyName}} { get; }
499500
internal bool {{DisposableRangeInterfaceData.SyncClauseFunctionName}}({{_wellKnownTypes.Object.FullName()}} disposable);
500501
""");
@@ -574,9 +575,9 @@ void GenerateSyncDispose(bool syncOnlyMode)
574575

575576
code.AppendLine(
576577
$$"""
577-
for (var i = {{disposeParamReference}}.{{DisposableRangeInterfaceData.DisposablesPropertyName}}.{{nameof(List<List<object>>.Count)}} - 1; i >= 0; i--)
578+
while ({{disposeParamReference}}.{{DisposableRangeInterfaceData.DisposablesPropertyName}}.{{nameof(ConcurrentStack<ConcurrentStack<object>>.TryPop)}}(out {{_wellKnownTypes.ConcurrentStackOfObject.FullName()}}? maybeDisposables) && maybeDisposables is {} disposables)
578579
{
579-
foreach (var exception in {{DisposeChunkFullyQualified}}(({{DisposableRangeInterfaceData.InterfaceNameFullyQualified}}) {{disposeParamReference}}, {{disposeParamReference}}.{{DisposableRangeInterfaceData.DisposablesPropertyName}}[i]))
580+
foreach (var exception in {{DisposeChunkFullyQualified}}(({{DisposableRangeInterfaceData.InterfaceNameFullyQualified}}) {{disposeParamReference}}, disposables))
580581
{
581582
yield return exception;
582583
}
@@ -644,9 +645,9 @@ void GenerateAsyncDispose()
644645
if (transientScope is {{_wellKnownTypes.IAsyncDisposable.FullName()}} asyncDisposable && await {{DisposeSingularAsyncFullyQualified}}(asyncDisposable) is {{_wellKnownTypes.Exception.FullName()}} exception)
645646
yield return exception;
646647
}
647-
for (var i = {{disposeParamReference}}.{{DisposableRangeInterfaceData.DisposablesPropertyName}}.{{nameof(List<List<object>>.Count)}} - 1; i >= 0; i--)
648+
while ({{disposeParamReference}}.{{DisposableRangeInterfaceData.DisposablesPropertyName}}.{{nameof(ConcurrentStack<ConcurrentStack<object>>.TryPop)}}(out {{_wellKnownTypes.ConcurrentStackOfObject.FullName()}}? maybeDisposables) && maybeDisposables is {} disposables)
648649
{
649-
await foreach (var exception in {{DisposeChunkAsyncFullyQualified}}({{disposeParamReference}}, {{disposeParamReference}}.{{DisposableRangeInterfaceData.DisposablesPropertyName}}[i]))
650+
await foreach (var exception in {{DisposeChunkAsyncFullyQualified}}({{disposeParamReference}}, disposables))
650651
{
651652
yield return exception;
652653
}

Main/CodeGeneration/Nodes/FunctionNodeGenerator.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections.Concurrent;
12
using System.Threading;
23
using System.Threading.Tasks;
34
using Microsoft.CodeAnalysis.CSharp;
@@ -166,7 +167,7 @@ private void GenerateOneFunction(
166167
if (!_function.IsSubDisposalAsParameter)
167168
{
168169
code.AppendLine(
169-
$"{_range.DisposalHandling.CollectionReference}.{nameof(List<List<object>>.Add)}({_function.SubDisposalNode.Reference});");
170+
$"{_range.DisposalHandling.CollectionReference}.{nameof(ConcurrentStack<ConcurrentStack<object>>.Push)}({_function.SubDisposalNode.Reference});");
170171
}
171172

172173
if (!_function.IsTransientScopeDisposalAsParameter)

Main/CodeGeneration/Nodes/RangeNodeGenerator.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections.Concurrent;
12
using System.Threading;
23
using System.Threading.Tasks;
34
using MrMeeseeks.DIE.Configuration;
@@ -74,7 +75,7 @@ public void Generate(StringBuilder code, ICodeGenerationVisitor visitor)
7475
code.AppendLine(
7576
$$"""
7677
private {{_wellKnownTypes.Int32.FullName()}} {{_rangeNode.ResolutionCounterReference}} = 0;
77-
private {{_wellKnownTypes.ListOfListOfObject.FullName()}} {{_rangeNode.DisposalHandling.CollectionReference}} = new {{_wellKnownTypes.ListOfListOfObject.FullName()}}();
78+
private {{_wellKnownTypes.ConcurrentStackOfConcurrentStackOfObject.FullName()}} {{_rangeNode.DisposalHandling.CollectionReference}} = new {{_wellKnownTypes.ConcurrentStackOfConcurrentStackOfObject.FullName()}}();
7879
""");
7980
foreach (var initializedInstance in _rangeNode.InitializedInstances)
8081
visitor.VisitIInitializedInstanceNode(initializedInstance);
@@ -202,7 +203,7 @@ _wellKnownTypes.IAsyncDisposable is not null
202203

203204
code.AppendLine(
204205
$$"""
205-
{{_wellKnownTypes.ListOfListOfObject.FullName()}} {{_disposeUtility.DisposableRangeInterfaceData.InterfaceNameFullyQualified}}.{{_disposeUtility.DisposableRangeInterfaceData.DisposablesPropertyName}} => {{_rangeNode.DisposalHandling.CollectionReference}};
206+
{{_wellKnownTypes.ConcurrentStackOfConcurrentStackOfObject.FullName()}} {{_disposeUtility.DisposableRangeInterfaceData.InterfaceNameFullyQualified}}.{{_disposeUtility.DisposableRangeInterfaceData.DisposablesPropertyName}} => {{_rangeNode.DisposalHandling.CollectionReference}};
206207
{{_wellKnownTypes.ConcurrentBagOfSyncDisposable.FullName()}} {{_disposeUtility.DisposableRangeInterfaceData.InterfaceNameFullyQualified}}.{{_disposeUtility.DisposableRangeInterfaceData.UserDefinedSyncDisposablesPropertyName}} => {{_rangeNode.DisposalHandling.SyncCollectionReference}};
207208
""");
208209

Main/Nodes/Elements/InitialSubDisposalNode.cs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,14 @@ internal abstract class InitialSubDisposalNode : IInitialSubDisposalNode
1818
{
1919
private readonly Lazy<string> _reference;
2020

21-
protected InitialSubDisposalNode(
22-
Lazy<string> reference,
23-
WellKnownTypes wellKnownTypes)
24-
{
25-
TypeFullName = wellKnownTypes.ListOfObject.FullName();
26-
_reference = reference;
27-
}
28-
21+
protected InitialSubDisposalNode(Lazy<string> reference) => _reference = reference;
22+
2923
public void Build(PassedContext passedContext) { }
3024
public abstract void Accept(INodeVisitor visitor);
3125

3226
public abstract int SubDisposalCount { get; }
3327

34-
public string TypeFullName { get; }
28+
public abstract string TypeFullName { get; }
3529
public string Reference => _reference.Value;
3630
}
3731

@@ -43,10 +37,14 @@ internal InitialOrdinarySubDisposalNode(
4337
Lazy<IFunctionNode> parentFunction,
4438
Lazy<IReferenceGenerator> referenceGenerator,
4539
WellKnownTypes wellKnownTypes)
46-
: base(referenceGenerator.Select(rg => rg.Generate("subDisposal")), wellKnownTypes) =>
40+
: base(referenceGenerator.Select(rg => rg.Generate("subDisposal")))
41+
{
4742
_parentFunction = parentFunction;
43+
TypeFullName = wellKnownTypes.ConcurrentStackOfObject.FullName();
44+
}
4845

4946
public override int SubDisposalCount => _parentFunction.Value.GetSubDisposalCount();
47+
public override string TypeFullName { get; }
5048
}
5149

5250
internal sealed partial class InitialTransientScopeSubDisposalNode : InitialSubDisposalNode, IInitialTransientScopeSubDisposalNode
@@ -57,8 +55,12 @@ internal InitialTransientScopeSubDisposalNode(
5755
Lazy<IFunctionNode> parentFunction,
5856
Lazy<IReferenceGenerator> referenceGenerator,
5957
WellKnownTypes wellKnownTypes)
60-
: base(referenceGenerator.Select(rg => rg.Generate("transientScopeSubDisposal")), wellKnownTypes) =>
58+
: base(referenceGenerator.Select(rg => rg.Generate("transientScopeSubDisposal")))
59+
{
6160
_parentFunction = parentFunction;
61+
TypeFullName = wellKnownTypes.ListOfObject.FullName();
62+
}
6263

6364
public override int SubDisposalCount => _parentFunction.Value.GetTransientScopeDisposalCount();
65+
public override string TypeFullName { get; }
6466
}

Main/Nodes/Functions/SubDisposalNodeChooser.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,5 @@ internal InnerFunctionSubDisposalNodeChooser(
4646
}
4747

4848
public IElementNode ChooseSubDisposalNode() =>
49-
_parameterNodeFactory(_wellKnownTypes.ListOfObject).EnqueueBuildJobTo(_parentContainer.BuildQueue, PassedContext.Empty);
49+
_parameterNodeFactory(_wellKnownTypes.ConcurrentStackOfObject).EnqueueBuildJobTo(_parentContainer.BuildQueue, PassedContext.Empty);
5050
}

Main/WellKnownTypes.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ internal sealed record WellKnownTypes(
2525
INamedTypeSymbol? ConcurrentDictionaryOfAsyncDisposable, // .NET Standard 2.1
2626
INamedTypeSymbol ListOfObject, // .NET Standard 2.0
2727
INamedTypeSymbol ListOfListOfObject, // .NET Standard 2.0
28+
INamedTypeSymbol ConcurrentStackOfObject, // .NET Standard 2.0
29+
INamedTypeSymbol ConcurrentStackOfConcurrentStackOfObject, // .NET Standard 2.0
2830
INamedTypeSymbol ConcurrentDictionaryOfRuntimeTypeHandleToObject, // .NET Standard 2.0
2931
INamedTypeSymbol Exception, // .NET Standard 2.0
3032
// ReSharper disable InconsistentNaming
@@ -53,6 +55,7 @@ internal static WellKnownTypes Create(Compilation compilation)
5355
var runtimeTypeHandle = compilation.GetTypeByMetadataNameOrThrow("System.RuntimeTypeHandle");
5456
var @object = compilation.GetTypeByMetadataNameOrThrow("System.Object");
5557
var list = compilation.GetTypeByMetadataNameOrThrow("System.Collections.Generic.List`1");
58+
var concurrentStack = compilation.GetTypeByMetadataNameOrThrow("System.Collections.Concurrent.ConcurrentStack`1");
5659
var valueTask1 = compilation.GetTypeByMetadataName("System.Threading.Tasks.ValueTask`1");
5760
var enumerable1 = compilation.GetTypeByMetadataNameOrThrow("System.Collections.Generic.IEnumerable`1");
5861
var exception = compilation.GetTypeByMetadataNameOrThrow("System.Exception");
@@ -81,6 +84,8 @@ internal static WellKnownTypes Create(Compilation compilation)
8184
ConcurrentDictionaryOfRuntimeTypeHandleToObject: concurrentDictionary2.Construct(runtimeTypeHandle, @object),
8285
ListOfObject: list.Construct(@object),
8386
ListOfListOfObject: list.Construct(list.Construct(@object)),
87+
ConcurrentStackOfObject: concurrentStack.Construct(@object),
88+
ConcurrentStackOfConcurrentStackOfObject: concurrentStack.Construct(concurrentStack.Construct(@object)),
8489
Exception: exception,
8590
IEnumerableOfException: enumerable1.Construct(exception),
8691
IAsyncEnumerableOfException: compilation.GetTypeByMetadataName("System.Collections.Generic.IAsyncEnumerable`1")?.Construct(exception),

0 commit comments

Comments
 (0)