From 1a012c5d493dd31aceea8fe29f23a2d58d252a11 Mon Sep 17 00:00:00 2001 From: Alexandru Macocian Date: Thu, 3 Jun 2021 13:03:29 +0200 Subject: [PATCH] Scoped ServiceManager calls original ServiceManager when resolving singletons --- LICENSE | 2 +- Slim.Tests/MemoryLeakTests.cs | 45 +++++++++++++++++++++++++++++++ Slim.Tests/ServiceManagerTests.cs | 2 +- Slim/ServiceManager.cs | 22 ++++++++++++--- Slim/Slim.csproj | 6 ++--- 5 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 Slim.Tests/MemoryLeakTests.cs diff --git a/LICENSE b/LICENSE index 5844c61..7b4b612 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Macocian Alexandru Victor +Copyright (c) 2021 Macocian Alexandru Victor Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Slim.Tests/MemoryLeakTests.cs b/Slim.Tests/MemoryLeakTests.cs new file mode 100644 index 0000000..8a5b7ab --- /dev/null +++ b/Slim.Tests/MemoryLeakTests.cs @@ -0,0 +1,45 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Slim.Tests.Models; +using System; +using System.Threading.Tasks; + +namespace Slim.Tests +{ + [TestClass] + public sealed class MemoryLeakTests + { + private const int ScopesCount = 10000; + + [TestMethod] + public async Task CreateAndReleaseScopesDisposeManagerGCCollects() + { + var di = new ServiceManager(); + di.RegisterSingleton(); + di.RegisterScoped(); + var independentService = di.GetService(); + var dependentService = di.GetService(); + + var weakRefs = new WeakReference[ScopesCount]; + for (int i = 0; i < ScopesCount; i++) + { + var scopedDi = di.CreateScope(); + weakRefs[i] = new WeakReference(scopedDi); + var scopedDependentService = scopedDi.GetService(); + scopedDependentService.Should().NotBeNull(); + scopedDependentService.Should().NotBe(dependentService); + scopedDependentService.IndependentService.Should().NotBeNull(); + scopedDependentService.IndependentService.Should().Be(independentService); + } + + await Task.Delay(1500); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + foreach(var weakRef in weakRefs) + { + weakRef.IsAlive.Should().BeFalse(); + } + } + } +} diff --git a/Slim.Tests/ServiceManagerTests.cs b/Slim.Tests/ServiceManagerTests.cs index c349022..3dad0f0 100644 --- a/Slim.Tests/ServiceManagerTests.cs +++ b/Slim.Tests/ServiceManagerTests.cs @@ -429,9 +429,9 @@ namespace Slim.Tests { var di = new ServiceManager(); di.RegisterSingleton(); + var scopedDi = di.CreateScope(); var service = di.GetService(); - var scopedDi = di.CreateScope(); var scopedService = scopedDi.GetService(); service.Should().Be(scopedService); diff --git a/Slim/ServiceManager.cs b/Slim/ServiceManager.cs index 99eacb2..9fb0009 100644 --- a/Slim/ServiceManager.cs +++ b/Slim/ServiceManager.cs @@ -673,7 +673,7 @@ namespace Slim var dictionary = new Dictionary(); foreach (var kvp in this.Instances) { - if (this.InterfaceMapping.First(s => s.Value.Item1 == kvp.Key).Value.Item2 is not Lifetime.Scoped) + if (this.InterfaceMapping.First(s => s.Value.Item1 == kvp.Key).Value.Item2 is Lifetime.Singleton) { dictionary.Add(kvp.Key, kvp.Value); } @@ -687,8 +687,22 @@ namespace Slim var factories = new Dictionary(this.Factories); var exceptionHandlers = new Dictionary(this.ExceptionHandlers); var resolvers = new List(this.Resolvers); - var singletons = this.GetSingletons(); - return new ServiceManager(interfaceMapping, singletons, factories, exceptionHandlers, resolvers); + /* + * For singletons, create a factory in scoped service manager that should call this manager + * to retrieve the singleton. + * This means that if the singleton has not yet been created in this service manager, + * the scoped service manager will not create a new one but instead ask the original service manager to + * create the singleton instead. + */ + foreach (var kvp in this.InterfaceMapping) + { + if (kvp.Value.Item2 is Lifetime.Singleton) + { + factories[kvp.Key] = new Func((_) => this.PrepareAndGetService(kvp.Key)); + } + } + + return new ServiceManager(interfaceMapping, new Dictionary(), factories, exceptionHandlers, resolvers); } } -} +} \ No newline at end of file diff --git a/Slim/Slim.csproj b/Slim/Slim.csproj index 35271e7..52da80a 100644 --- a/Slim/Slim.csproj +++ b/Slim/Slim.csproj @@ -8,10 +8,10 @@ Service lifetime management and dependency injection framework. - 1.4.2.0 - 1.4.2.0 + 1.4.3.0 + 1.4.3.0 https://github.com/AlexMacocian/Slim - 1.4.2 + 1.4.3 true true true