Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(SimpleResolver): Function loadOrResolve should be atomically performed #11

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 57 additions & 21 deletions Sources/InfomaniakDI/SimpleResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -76,8 +77,14 @@ public final class SimpleResolver: SimpleResolvable, SimpleStorable, CustomDebug
/// Resolved object collection
var store = [String: Any]()

private var queueSpecificKey: DispatchSpecificKey<AnyObject> = 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

Expand All @@ -99,36 +106,65 @@ public final class SimpleResolver: SimpleResolvable, SimpleStorable, CustomDebug
resolver: SimpleResolvable) throws -> Service {
let serviceIdentifier = buildIdentifier(type: type, forIdentifier: customIdentifier)

// load form store
var fetchedService: Any?
queue.sync {
fetchedService = store[serviceIdentifier]
}
if let service = fetchedService as? Service {
return service
guard notOnInternalQueue else {
return try loadOrResolve(
serviceIdentifier: serviceIdentifier,
factoryParameters: factoryParameters,
resolver: resolver
)
}

// load service from factory
var factory: Factoryable?
var resolvedService: Service?
var resolveError: Error?
queue.sync {
factory = factories[serviceIdentifier]
}
guard let factory = factory else {
throw ErrorDomain.factoryMissing(identifier: serviceIdentifier)
do {
resolvedService = try loadOrResolve(
serviceIdentifier: serviceIdentifier,
factoryParameters: factoryParameters,
resolver: resolver
)
} catch {
resolveError = error
}
}

// 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)")
guard let resolvedService else {
guard let resolveError else {
throw ErrorDomain.unknown
}
throw resolveError
}

// keep in store built object for later
queue.sync {
return resolvedService
}

private func loadOrResolve<Service>(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)")
}

store[serviceIdentifier] = service
return service
}
}

return service
private var notOnInternalQueue: Bool {
if DispatchQueue.getSpecific(key: queueSpecificKey) != nil {
return false
} else {
return true
}
}

// MARK: internal
Expand Down