Skip to content

Commit f32cfd2

Browse files
committed
Make transient scope disposal tracking thread safe with a semaphore
1 parent bfac0a4 commit f32cfd2

8 files changed

Lines changed: 61 additions & 23 deletions

File tree

Main/CodeGeneration/CodeGenerationVisitor.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,8 @@ public void VisitIScopeCallNode(IScopeCallNode scopeCall)
164164
public void VisitITransientScopeCallNode(ITransientScopeCallNode transientScopeCall)
165165
{
166166
VisitIElementNode(transientScopeCall.ScopeConstruction);
167-
var owner = transientScopeCall.ContainerReference is { } containerReference
168-
? $"{containerReference}."
169-
: "";
170-
_code.AppendLine($"{owner}{transientScopeCall.TransientScopeDisposalReference}.{nameof(List<object>.Add)}({transientScopeCall.ScopeConstruction.Reference});");
167+
if (transientScopeCall.TransientScopeDisposalParameter?.Calling.Reference is {} reference)
168+
_code.AppendLine($"{reference}.{nameof(List<object>.Add)}({transientScopeCall.ScopeConstruction.Reference});");
171169
GenerateInitialization(transientScopeCall.Initialization, transientScopeCall.ScopeConstruction.Reference);
172170
VisitIFunctionCallNode(transientScopeCall);
173171
}

Main/CodeGeneration/Nodes/ContainerNodeGenerator.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ protected override void PostGeneralContent(StringBuilder code, ICodeGenerationVi
5858
private interface {{_containerNode.ScopeInterface}} : {{GenerateDisposalInterfaceAssignments()}} {}
5959
6060
private {{_wellKnownTypes.ListOfObject.FullName()}} {{_containerNode.TransientScopeDisposalReference}} = new {{_wellKnownTypes.ListOfObject.FullName()}}();
61+
private {{_wellKnownTypes.SemaphoreSlim.FullName()}} {{_containerNode.TransientScopeDisposalSemaphoreReference}} = new {{_wellKnownTypes.SemaphoreSlim.FullName()}}(1);
6162
""");
6263

6364
visitor.VisitITransientScopeInterfaceNode(_containerNode.TransientScopeInterface);

Main/CodeGeneration/Nodes/FunctionNodeGenerator.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,22 @@ private void GenerateOneFunction(
175175
var containerReference = _range.ContainerReference is { } reference
176176
? $"{reference}."
177177
: "";
178+
179+
var awaitPrefix = isAsyncAwait ? "await " : "";
180+
var waitMethod = isAsyncAwait ? nameof(SemaphoreSlim.WaitAsync) : nameof(SemaphoreSlim.Wait);
181+
178182
code.AppendLine(
179-
$"{containerReference}{_container.TransientScopeDisposalReference}.{nameof(List<object>.AddRange)}({_function.TransientScopeDisposalNode.Reference});");
183+
$$"""
184+
{{awaitPrefix}}{{containerReference}}{{_container.TransientScopeDisposalSemaphoreReference}}.{{waitMethod}}();
185+
try
186+
{
187+
{{containerReference}}{{_container.TransientScopeDisposalReference}}.{{nameof(List<object>.AddRange)}}({{_function.TransientScopeDisposalNode.Reference}});
188+
}
189+
finally
190+
{
191+
{{containerReference}}{{_container.TransientScopeDisposalSemaphoreReference}}.{{nameof(SemaphoreSlim.Release)}}();
192+
}
193+
""");
180194
}
181195

182196

Main/CodeGeneration/Nodes/RangeNodeGenerator.cs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,23 @@ _wellKnownTypes.IAsyncDisposable is not null
193193
{
194194
case IContainerNode container:
195195
code.AppendLine(
196-
$"{_wellKnownTypes.Object.FullName()}[] {_disposeUtility.DisposableRangeInterfaceData.InterfaceNameFullyQualified}.{_disposeUtility.DisposableRangeInterfaceData.TransientScopesPropertyName} => {_wellKnownTypesCollections.Enumerable}.{nameof(Enumerable.ToArray)}({container.TransientScopeDisposalReference});");
196+
$$"""
197+
{{_wellKnownTypes.Object.FullName()}}[] {{_disposeUtility.DisposableRangeInterfaceData.InterfaceNameFullyQualified}}.{{_disposeUtility.DisposableRangeInterfaceData.TransientScopesPropertyName}}
198+
{
199+
get
200+
{
201+
{{container.TransientScopeDisposalSemaphoreReference}}.{{nameof(SemaphoreSlim.Wait)}}();
202+
try
203+
{
204+
return {{_wellKnownTypesCollections.Enumerable}}.{{nameof(Enumerable.ToArray)}}({{container.TransientScopeDisposalReference}});
205+
}
206+
finally
207+
{
208+
{{container.TransientScopeDisposalSemaphoreReference}}.{{nameof(SemaphoreSlim.Release)}}();
209+
}
210+
}
211+
}
212+
""");
197213
break;
198214
default:
199215
code.AppendLine(
@@ -275,10 +291,19 @@ void GenerateDisposeFunction(bool isAsync)
275291
switch (_rangeNode)
276292
{
277293
case ITransientScopeNode transientScope:
294+
var waitMethod = isAsync ? nameof(SemaphoreSlim.WaitAsync) : nameof(SemaphoreSlim.Wait);
278295
code.AppendLine(
279296
$$"""
280-
{{transientScope.ContainerReference}}.{{transientScope.TransientScopeDisposalReference}}.{{nameof(List<object>.Remove)}}(this);
281-
{{transientScope.ContainerReference}}.{{transientScope.TransientScopeDisposalReference}}.{{nameof(List<object>.TrimExcess)}}();
297+
{{awaitPrefix}}{{transientScope.ContainerReference}}.{{_containerNode.TransientScopeDisposalSemaphoreReference}}.{{waitMethod}}();
298+
try
299+
{
300+
{{transientScope.ContainerReference}}.{{_containerNode.TransientScopeDisposalReference}}.{{nameof(List<object>.Remove)}}(this);
301+
{{transientScope.ContainerReference}}.{{_containerNode.TransientScopeDisposalReference}}.{{nameof(List<object>.TrimExcess)}}();
302+
}
303+
finally
304+
{
305+
{{transientScope.ContainerReference}}.{{_containerNode.TransientScopeDisposalSemaphoreReference}}.{{nameof(SemaphoreSlim.Release)}}();
306+
}
282307
""");
283308
break;
284309
}

Main/MsContainer/ExecuteLevelContainer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ private IExecuteContainer DIE_Factory_IExecuteContainer(
4949
DisposeUtility disposeUtility,
5050
ReferenceGeneratorCounter referenceGeneratorCounter)
5151
{
52-
#pragma warning disable CA2000 *** Manually added for disposal
52+
#pragma warning disable CA2000 // Manually added for disposal
5353
var container = ContainerLevelContainer.DIE_CreateContainer(
5454
DIE_Factory_GeneratorExecutionContext,
5555
containerInfo,

Main/Nodes/Ranges/ContainerNode.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ internal interface IContainerNode : IRangeNode
2525
ITransientScopeInterfaceNode TransientScopeInterface { get; }
2626
string ScopeInterface { get; }
2727
string TransientScopeDisposalReference { get; }
28+
string TransientScopeDisposalSemaphoreReference { get; }
2829
IFunctionCallNode BuildContainerInstanceCall(string? ownerReference, INamedTypeSymbol type, IFunctionNode callingFunction);
2930
IReadOnlyList<ICreateContainerFunctionNode> CreateContainerFunctions { get; }
3031
bool AsyncDisposablesPossible { get; }
@@ -60,6 +61,7 @@ internal sealed partial class ContainerNode : RangeNode, IContainerNode, IContai
6061
public ITransientScopeInterfaceNode TransientScopeInterface => _lazyTransientScopeInterfaceNode.Value;
6162
public string ScopeInterface { get; }
6263
public string TransientScopeDisposalReference { get; }
64+
public string TransientScopeDisposalSemaphoreReference { get; }
6365

6466
public IFunctionCallNode BuildContainerInstanceCall(
6567
string? ownerReference,
@@ -141,6 +143,8 @@ internal ContainerNode(
141143
_containerNodeGenerator = containerNodeGenerator;
142144

143145
TransientScopeDisposalReference = referenceGenerator.Generate("transientScopeDisposal");
146+
147+
TransientScopeDisposalSemaphoreReference = referenceGenerator.Generate("transientScopeDisposalLock");
144148

145149
GenerateEmptyConstructor = !_containerInfo.ContainerType.InstanceConstructors.Any(ic => !ic.IsImplicitlyDeclared);
146150

Main/Nodes/Ranges/TransientScopeNode.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ namespace MrMeeseeks.DIE.Nodes.Ranges;
1515
internal interface ITransientScopeNode : IScopeNodeBase
1616
{
1717
string TransientScopeInterfaceName { get; }
18-
string TransientScopeDisposalReference { get; }
1918
ITransientScopeCallNode BuildTransientScopeCallFunction(
2019
string containerParameter,
2120
INamedTypeSymbol type,
@@ -77,7 +76,6 @@ internal TransientScopeNode(
7776
_transientScopeNodeGenerator = transientScopeNodeGenerator;
7877
_createTransientScopeFunctionNodeFactory = createTransientScopeFunctionNodeFactory;
7978
TransientScopeInterfaceName = parentContainer.TransientScopeInterface.Name;
80-
TransientScopeDisposalReference = parentContainer.TransientScopeDisposalReference;
8179
}
8280
protected override string ContainerParameterForScope => ContainerReference;
8381

@@ -92,7 +90,6 @@ public override IFunctionCallNode BuildTransientScopeInstanceCall(INamedTypeSymb
9290
ParentContainer.TransientScopeInterface.BuildTransientScopeInstanceCall($"({Constants.ThisKeyword} as {ParentContainer.TransientScopeInterface.FullName})", type, callingFunction);
9391

9492
public string TransientScopeInterfaceName { get; }
95-
public string TransientScopeDisposalReference { get; }
9693

9794
public ITransientScopeCallNode BuildTransientScopeCallFunction(
9895
string containerParameter,

Sample/Container.cs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
using System;
2-
using System.Threading.Tasks;
32
using MrMeeseeks.DIE.Configuration.Attributes;
43
using MrMeeseeks.DIE.UserUtility;
54

65
namespace MrMeeseeks.DIE.Sample;
76

8-
//*
9-
internal class Class : IContainerInstance, ITaskInitializer
10-
{
11-
public async Task InitializeAsync()
12-
{
13-
await Task.Delay(1000);
14-
}
15-
}
7+
internal sealed class Disposable : IDisposable { public void Dispose() { } }
168

17-
[CreateFunction(typeof(Func<Class>), "Create")]
9+
internal sealed class Class(ClassBelow _, Disposable __) : ITransientScopeRoot;
10+
11+
internal sealed class ClassBelow(ClassS _, Disposable __) : ITransientScopeRoot;
12+
13+
internal sealed class ClassS(ClassBelowS _, Disposable __) : IScopeRoot;
14+
15+
internal sealed class ClassBelowS(Disposable __) : IScopeRoot;
16+
17+
[CreateFunction(typeof(Class), "Create")]
1818
internal sealed partial class Container;
19-
//*/

0 commit comments

Comments
 (0)