From 83b9b96cd56a467fe21c833142046d394cf267b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Tue, 18 Mar 2025 15:50:40 +0100 Subject: [PATCH 1/2] fix(SimpleResolver): Function loadOrResolve should be atomically executed in the serial queue --- Sources/InfomaniakDI/SimpleResolver.swift | 58 ++++++++++++++--------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/Sources/InfomaniakDI/SimpleResolver.swift b/Sources/InfomaniakDI/SimpleResolver.swift index 5e30a2f..c60fafa 100644 --- a/Sources/InfomaniakDI/SimpleResolver.swift +++ b/Sources/InfomaniakDI/SimpleResolver.swift @@ -65,6 +65,7 @@ public final class SimpleResolver: SimpleResolvable, SimpleStorable, CustomDebug enum ErrorDomain: Error { case factoryMissing(identifier: String) case typeMissmatch(expected: String, got: String) + case unknown } /// One singleton to rule them all @@ -99,36 +100,49 @@ public final class SimpleResolver: SimpleResolvable, SimpleStorable, CustomDebug resolver: SimpleResolvable) throws -> Service { let serviceIdentifier = buildIdentifier(type: type, forIdentifier: customIdentifier) - // load form store - var fetchedService: Any? + var resolvedService: Service? + var resolveError: Error? queue.sync { - fetchedService = store[serviceIdentifier] - } - if let service = fetchedService as? Service { - return service + do { + resolvedService = try loadOrResolve( + serviceIdentifier: serviceIdentifier, + factoryParameters: factoryParameters, + resolver: resolver + ) + } catch { + resolveError = error + } } - // load service from factory - var factory: Factoryable? - queue.sync { - factory = factories[serviceIdentifier] - } - guard let factory = factory else { - throw ErrorDomain.factoryMissing(identifier: serviceIdentifier) + guard let resolvedService else { + guard let resolveError else { + throw ErrorDomain.unknown + } + throw resolveError } - // Apply factory closure - let builtType = try factory.build(factoryParameters: factoryParameters, resolver: resolver) - guard let service = builtType as? Service else { - throw ErrorDomain.typeMissmatch(expected: "\(Service.Type.self)", got: "\(builtType.self)") - } + return resolvedService + } + + private func loadOrResolve(serviceIdentifier: String, + factoryParameters: [String: Any]?, + resolver: SimpleResolvable) throws -> Service { + if let fetchedObject = store[serviceIdentifier], + let fetchedService = fetchedObject as? Service { + return fetchedService + } else { + guard let factory = factories[serviceIdentifier] else { + throw ErrorDomain.factoryMissing(identifier: serviceIdentifier) + } + + let builtType = try factory.build(factoryParameters: factoryParameters, resolver: resolver) + guard let service = builtType as? Service else { + throw ErrorDomain.typeMissmatch(expected: "\(Service.Type.self)", got: "\(builtType.self)") + } - // keep in store built object for later - queue.sync { store[serviceIdentifier] = service + return service } - - return service } // MARK: internal From f56c68bd79c9ac2165f4f1b8217cecaccd990e7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Tue, 18 Mar 2025 16:27:41 +0100 Subject: [PATCH 2/2] fix(SimpleResolver): Solve a re-entry issue with serial queue execution --- Sources/InfomaniakDI/SimpleResolver.swift | 26 +++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/Sources/InfomaniakDI/SimpleResolver.swift b/Sources/InfomaniakDI/SimpleResolver.swift index c60fafa..0d3fd5e 100644 --- a/Sources/InfomaniakDI/SimpleResolver.swift +++ b/Sources/InfomaniakDI/SimpleResolver.swift @@ -77,8 +77,14 @@ public final class SimpleResolver: SimpleResolvable, SimpleStorable, CustomDebug /// Resolved object collection var store = [String: Any]() + private var queueSpecificKey: DispatchSpecificKey = DispatchSpecificKey() + /// A serial queue for thread safety - private let queue = DispatchQueue(label: "com.infomaniakDI.resolver") + private lazy var queue: DispatchQueue = { + let serialQueue = DispatchQueue(label: "com.infomaniakDI.resolver") + serialQueue.setSpecific(key: self.queueSpecificKey, value: serialQueue) + return serialQueue + }() // MARK: SimpleStorable @@ -100,6 +106,14 @@ public final class SimpleResolver: SimpleResolvable, SimpleStorable, CustomDebug resolver: SimpleResolvable) throws -> Service { let serviceIdentifier = buildIdentifier(type: type, forIdentifier: customIdentifier) + guard notOnInternalQueue else { + return try loadOrResolve( + serviceIdentifier: serviceIdentifier, + factoryParameters: factoryParameters, + resolver: resolver + ) + } + var resolvedService: Service? var resolveError: Error? queue.sync { @@ -128,7 +142,7 @@ public final class SimpleResolver: SimpleResolvable, SimpleStorable, CustomDebug factoryParameters: [String: Any]?, resolver: SimpleResolvable) throws -> Service { if let fetchedObject = store[serviceIdentifier], - let fetchedService = fetchedObject as? Service { + let fetchedService = fetchedObject as? Service { return fetchedService } else { guard let factory = factories[serviceIdentifier] else { @@ -145,6 +159,14 @@ public final class SimpleResolver: SimpleResolvable, SimpleStorable, CustomDebug } } + private var notOnInternalQueue: Bool { + if DispatchQueue.getSpecific(key: queueSpecificKey) != nil { + return false + } else { + return true + } + } + // MARK: internal func buildIdentifier(type: Any.Type,