Skip to content

Commit

Permalink
Allow auto-dispose of factory generated services (#179)
Browse files Browse the repository at this point in the history
* allow factory generated services to be disposed automatically

* Apply suggestions from review
  • Loading branch information
sensslen authored Feb 6, 2025
1 parent c97c6b6 commit f3e987d
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 24 deletions.
181 changes: 170 additions & 11 deletions src/Jab.FunctionalTests.Common/ContainerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,25 @@ public void DisposingScopeDisposesServices()
[Scoped(typeof(IService), typeof(DisposableServiceImplementation))]
internal partial class DisposingScopeDisposesServicesContainer { }

[Fact]
public void DisposingScopeDisposesFactoryGeneratedServices()
{
DisposingScopeDisposesFactoryGeneratedServicesContainer c = new();
var scope = c.CreateScope();
var service = Assert.IsType<DisposableServiceImplementation>(scope.GetService<IService>());

scope.Dispose();

Assert.Equal(1, service.DisposalCount);
}

[ServiceProvider]
[Scoped(typeof(IService), Factory = nameof(CreateDisposableService))]
internal partial class DisposingScopeDisposesFactoryGeneratedServicesContainer
{
private static IService CreateDisposableService() => new DisposableServiceImplementation();
}

#if NETCOREAPP
[Fact]
public async Task DisposingScopeDisposesAsyncServices()
Expand All @@ -572,6 +591,34 @@ public async Task DisposingScopeDisposesAsyncServices()
[ServiceProvider]
[Scoped(typeof(IService), typeof(AsyncDisposableServiceImplementation))]
internal partial class DisposingScopeDisposesAsyncServicesContainer { }

[Fact]
public async Task DisposingScopeDisposesFactoryGeneratedAsyncServices()
{
DisposingScopeDisposesFactoryGeneratedAsyncServicesContainer c = new();
var scope = c.CreateScope();
var service = Assert.IsType<AsyncDisposableServiceImplementation>(scope.GetService<IService>());

await scope.DisposeAsync();

Assert.Equal(1, service.AsyncDisposalCount);
Assert.Equal(0, service.DisposalCount);

scope = c.CreateScope();
service = Assert.IsType<AsyncDisposableServiceImplementation>(scope.GetService<IService>());

scope.Dispose();

Assert.Equal(0, service.AsyncDisposalCount);
Assert.Equal(1, service.DisposalCount);
}

[ServiceProvider]
[Scoped(typeof(IService), Factory = nameof(CreateService))]
internal partial class DisposingScopeDisposesFactoryGeneratedAsyncServicesContainer
{
private static IService CreateService() => new AsyncDisposableServiceImplementation();
}
#endif

[Fact]
Expand All @@ -589,6 +636,24 @@ public void DisposingProviderDisposesRootScopedServices()
[Scoped(typeof(IService), typeof(DisposableServiceImplementation))]
internal partial class DisposingProviderDisposesRootScopedServicesContainer { }

[Fact]
public void DisposingProviderDisposesFactoryGeneratedRootScopedServicesIfInstructedTo()
{
DisposingProviderDisposesFactoryGeneratedRootScopedServicesContainer c = new();
var service = Assert.IsType<DisposableServiceImplementation>(c.GetService<IService>());

c.Dispose();

Assert.Equal(1, service.DisposalCount);
}

[ServiceProvider]
[Scoped(typeof(IService), Factory = nameof(GenerateService))]
internal partial class DisposingProviderDisposesFactoryGeneratedRootScopedServicesContainer
{
private static IService GenerateService() => new DisposableServiceImplementation();
}

[Fact]
public void DisposingProviderDisposesRootSingletonServices()
{
Expand All @@ -604,6 +669,24 @@ public void DisposingProviderDisposesRootSingletonServices()
[Singleton(typeof(IService), typeof(DisposableServiceImplementation))]
internal partial class DisposingProviderDisposesRootSingletonServicesContainer { }

[Fact]
public void DisposingProviderDisposesFactoryGeneratedRootSingletonServices()
{
DisposingProviderDisposesFactoryGeneratedRootSingletonServicesContainer c = new();
var service = Assert.IsType<DisposableServiceImplementation>(c.GetService<IService>());

c.Dispose();

Assert.Equal(1, service.DisposalCount);
}

[ServiceProvider]
[Singleton(typeof(IService), Factory = nameof(GetService))]
internal partial class DisposingProviderDisposesFactoryGeneratedRootSingletonServicesContainer
{
private static IService GetService() => new DisposableServiceImplementation();
}

#if NETCOREAPP
[Fact]
public async Task DisposingProviderDisposesRootSingAsyncServices()
Expand All @@ -620,6 +703,25 @@ public async Task DisposingProviderDisposesRootSingAsyncServices()
[ServiceProvider]
[Scoped(typeof(IService), typeof(AsyncDisposableServiceImplementation))]
internal partial class DisposingProviderDisposesRootSingAsyncServicesContainer { }

[Fact]
public async Task DisposingProviderDisposesFactoryGeneratedRootSingAsyncServices()
{
DisposingProviderDisposesFactoryGeneratedRootSingAsyncServicesContainer c = new();
var service = Assert.IsType<AsyncDisposableServiceImplementation>(c.GetService<IService>());

await c.DisposeAsync();

Assert.Equal(1, service.AsyncDisposalCount);
Assert.Equal(0, service.DisposalCount);
}

[ServiceProvider]
[Scoped(typeof(IService), Factory = nameof(CreateService))]
internal partial class DisposingProviderDisposesFactoryGeneratedRootSingAsyncServicesContainer
{
private static IService CreateService() => new AsyncDisposableServiceImplementation();
}
#endif

#if NETCOREAPP
Expand All @@ -643,6 +745,30 @@ public async Task DisposingProviderDisposesAllSingletonEnumerableServices()
[Scoped(typeof(IService), typeof(DisposableServiceImplementation))]
[Scoped(typeof(IService), typeof(DisposableServiceImplementation))]
internal partial class DisposingProviderDisposesAllSingletonEnumerableServicesContainer { }

[Fact]
public async Task DisposingProviderDisposesAllFactoryGeneratedSingletonEnumerableServicesWhenInstructedTo()
{
DisposingProviderDisposesAllFactoryGeneratedSingletonEnumerableServicesContainer c = new();
var services = Assert.IsType<IService[]>(c.GetService<IEnumerable<IService>>());

await c.DisposeAsync();

foreach (var service in services)
{
var disposableService = Assert.IsType<DisposableServiceImplementation>(service);
Assert.Equal(1, disposableService.DisposalCount);
}
}

[ServiceProvider]
[Scoped(typeof(IService), Factory = nameof(CreateService))]
[Scoped(typeof(IService), Factory = nameof(CreateService))]
[Scoped(typeof(IService), Factory = nameof(CreateService))]
internal partial class DisposingProviderDisposesAllFactoryGeneratedSingletonEnumerableServicesContainer
{
private static IService CreateService() => new DisposableServiceImplementation();
}
#endif

[Fact]
Expand All @@ -668,6 +794,32 @@ public void DisposingProviderDisposesTransients()
[Transient(typeof(IService), typeof(DisposableServiceImplementation))]
internal partial class DisposingProviderDisposesTransientsContainer { }

[Fact]
public void DisposingProviderDisposesFactoryGeneratedTransients()
{
DisposingProviderDisposesFactoryGeneratedTransientsContainer c = new();
List<IService> services = new();
for (int i = 0; i < 5; i++)
{
services.Add(c.GetService<IService>());
}

c.Dispose();

foreach (var service in services)
{
var disposableService = Assert.IsType<DisposableServiceImplementation>(service);
Assert.Equal(1, disposableService.DisposalCount);
}
}

[ServiceProvider]
[Transient(typeof(IService), Factory = nameof(CreateService))]
internal partial class DisposingProviderDisposesFactoryGeneratedTransientsContainer
{
private static IService CreateService() => new DisposableServiceImplementation();
}

[Fact]
public void DisposingScopeDisposesTransients()
{
Expand All @@ -694,24 +846,31 @@ public void DisposingScopeDisposesTransients()
internal partial class DisposingScopeDisposesTransientsContainer { }

[Fact]
public void DisposingProviderDisposesRootSingletonFactoryServices()
public void DisposingScopeDisposesFactoryGeneratedTransients()
{
DisposingProviderDisposesRootSingletonServicesContainer c = new();
var service = Assert.IsType<DisposableServiceImplementation>(c.GetService<IService>());
DisposingScopeDisposesFactoryGeneratedTransientsContainer c = new();
var scope = c.CreateScope();

c.Dispose();
List<IService> services = new();
for (int i = 0; i < 5; i++)
{
services.Add(scope.GetService<IService>());
}

Assert.Equal(1, service.DisposalCount);
scope.Dispose();

foreach (var service in services)
{
var disposableService = Assert.IsType<DisposableServiceImplementation>(service);
Assert.Equal(1, disposableService.DisposalCount);
}
}

[ServiceProvider]
[Singleton(typeof(IService), Factory = nameof(CreateDisposableServiceImplementation))]
internal partial class DisposingProviderDisposesRootSingletonFactoryServicesContainer
[Transient(typeof(IService), Factory = nameof(CreateService))]
internal partial class DisposingScopeDisposesFactoryGeneratedTransientsContainer
{
internal IService CreateDisposableServiceImplementation()
{
return new DisposableServiceImplementation();
}
private IService CreateService() => new DisposableServiceImplementation();
}

[Fact]
Expand Down
2 changes: 2 additions & 0 deletions src/Jab/Attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public SingletonAttribute(Type serviceType, Type implementationType)
class TransientAttribute : Attribute
{
public Type ServiceType { get; }

public string? Name { get; set; }

public Type? ImplementationType { get; }
Expand Down Expand Up @@ -118,6 +119,7 @@ public TransientAttribute(Type serviceType, Type implementationType)
class ScopedAttribute : Attribute
{
public Type ServiceType { get; }

public string? Name { get; set; }

public Type? ImplementationType { get; }
Expand Down
4 changes: 2 additions & 2 deletions src/Jab/ServiceProviderBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ ServiceCallSite BuiltInCallSite(ServiceCallSite callSite)
{
var constructedFactoryMethod = factoryMethod.ConstructedFrom.Construct(genericType.TypeArguments,
genericType.TypeArgumentNullableAnnotations);
callSite = CreateFactoryCallSite(
callSite = CreateFactoryCallSite(
identity,
genericType,
registration.Lifetime,
Expand Down Expand Up @@ -571,7 +571,7 @@ ImmutableArray<IParameterSymbol> GetDelegateParameters(ITypeSymbol type)
parameters.ToArray(),
namedParameters.ToArray(),
lifetime,
false);
true);

return factoryCallSite;
}
Expand Down
12 changes: 1 addition & 11 deletions src/Jab/ServiceRegistration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,6 @@ internal record ServiceRegistration(
ISymbol? InstanceMember,
ISymbol? FactoryMember,
Location? Location,
MemberLocation MemberLocation)
{
public INamedTypeSymbol ServiceType { get; } = ServiceType;
public string? Name { get; } = Name;
public INamedTypeSymbol? ImplementationType { get; } = ImplementationType;
public ISymbol? InstanceMember { get; } = InstanceMember;
public ISymbol? FactoryMember { get; } = FactoryMember;
public ServiceLifetime Lifetime { get; } = Lifetime;
public Location? Location { get; } = Location;
public MemberLocation MemberLocation { get; } = MemberLocation;
}
MemberLocation MemberLocation);

internal record RootService(INamedTypeSymbol Service, Location? Location);

0 comments on commit f3e987d

Please sign in to comment.