diff --git a/CHANGELOG b/CHANGELOG index 4662de0e..80286a15 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,9 @@ # Factory Changelog +### 1.2.4 + +* Recursive lock required in scope resolution + ### 1.2.3 * Streamline critical path through scope caching mechanism diff --git a/Sources/Factory/Factory.swift b/Sources/Factory/Factory.swift index beaac786..87a9b621 100644 --- a/Sources/Factory/Factory.swift +++ b/Sources/Factory/Factory.swift @@ -109,7 +109,6 @@ public struct ParameterFactory
{ /// Empty convenience class for user dependencies. public class Container: SharedContainer { - } /// Base class for all containers. @@ -227,7 +226,7 @@ open class SharedContainer { } } - private var lock = NSLock() + private var lock = NSRecursiveLock() private var cache: [UUID:AnyBox] = .init(minimumCapacity: 64) } diff --git a/Tests/FactoryTests/FactoryDefectTests.swift b/Tests/FactoryTests/FactoryDefectTests.swift index 0c8e1318..4290de26 100644 --- a/Tests/FactoryTests/FactoryDefectTests.swift +++ b/Tests/FactoryTests/FactoryDefectTests.swift @@ -50,8 +50,34 @@ final class FactoryDefectTests: XCTestCase { XCTAssertNil(service1.service) } + // Nested injection when both are on the same scope locks thread. If this test passes then thread wasn't locked... + func testSingletondScopeLocking() throws { + let service1: LockingTestA? = Container.lockingTestA() + XCTAssertNotNil(service1) + let service2: LockingTestA? = Container.lockingTestA() + XCTAssertNotNil(service2) + let text1 = Container.singletonService().text() + let text2 = Container.singletonService().text() + XCTAssertTrue(text1 == text2) + } + } -private class TestLazyInjectionOccursOnce { +fileprivate class TestLazyInjectionOccursOnce { @LazyInjected(Container.nilSService) var service } + +extension Container { + fileprivate static var lockingTestA = Factory(scope: .singleton) { LockingTestA() } + fileprivate static var lockingTestB = Factory(scope: .singleton) { LockingTestB() } +} + +// classes for recursive resolution test +fileprivate class LockingTestA { + @Injected(Container.lockingTestB) var b: LockingTestB + init() {} +} + +fileprivate class LockingTestB { + init() {} +} diff --git a/Tests/FactoryTests/FactoryScopeTests.swift b/Tests/FactoryTests/FactoryScopeTests.swift index 44a93273..f4d51a0f 100644 --- a/Tests/FactoryTests/FactoryScopeTests.swift +++ b/Tests/FactoryTests/FactoryScopeTests.swift @@ -6,7 +6,7 @@ final class FactoryScopeTests: XCTestCase { override func setUp() { super.setUp() Container.Registrations.reset() - Container.Scope.reset() + Container.Scope.reset(includingSingletons: true) } func testUniqueScope() throws {