async await client parameters

This commit is contained in:
Scott Pfeil 2024-01-17 15:44:07 -05:00
parent e04bd2cefe
commit f5e8c0b2e6
3 changed files with 48 additions and 76 deletions

View File

@ -58,78 +58,46 @@
getParameters(with: clientParameterModel, requestParameters: requestParameters, actionId: actionId, completionHandler: completionHandler) getParameters(with: clientParameterModel, requestParameters: requestParameters, actionId: actionId, completionHandler: completionHandler)
} }
open func getParameters(with model: ClientParameterModel, requestParameters: [String: Any], actionId: String, completionHandler:@escaping ([String: Any]?) -> ()) { open func getParameters(with model: ClientParameterModel, requestParameters: [String: Any], actionId: String) async -> [String: AnyHashable] {
let parametersWorkQueue = DispatchQueue(label: "com.mva.clientparameter")
let group = DispatchGroup()
let timeout = model.timeout ?? Self.DefaultTimeout let timeout = model.timeout ?? Self.DefaultTimeout
let parameterHandlerList = model.list.compactMap { createParametersHandler($0) } let parameterHandlerList = model.list.compactMap { createParametersHandler($0) }
let requestUUID = (0..<parameterHandlerList.count).map { _ in UUID().uuidString } return await withTaskGroup(of: [String: AnyHashable]?.self, returning: [String: AnyHashable].self) { group in
var returnedList = [[String: AnyHashable]?](repeating: nil, count: parameterHandlerList.count) for handler in parameterHandlerList {
group.addTask{
// Dispatch setup on queue to ensure setup is complete before completion callbacks. // Fetch the client parameter.
// Don't use [weak self]. Object is deallocated in the dispatch queue. do {
parametersWorkQueue.async(group: group, qos: .userInitiated) { MVMCoreLoggingHandler.shared()?.logCoreEvent(.clientParameterStartFetch(
name: handler.clientParameterModel.type,
var mergedParametersList: [String: AnyHashable] { uuid: handler.clientParameterModel.id,
return returnedList.enumerated().map { (index, element) -> [String: AnyHashable] in actionId: actionId))
guard let parameter = element else { let parameter: [String: AnyHashable]? = try await MVMCoreActionUtility.perform(withTimeout: .now() + .seconds(Int(timeout))) {
let parameter = await handler.fetchClientParameters(requestParameters: requestParameters, timingOutIn: timeout)
MVMCoreLoggingHandler.shared()?.logCoreEvent(.clientParameterFetchComplete(
name: handler.clientParameterModel.type,
uuid: handler.clientParameterModel.id,
actionId: actionId))
return parameter
}
return parameter
} catch MVMCoreActionUtilityError.timeOut {
// The client parameter timed out.
MVMCoreLoggingHandler.shared()?.logCoreEvent(.clientParameterTimeout( MVMCoreLoggingHandler.shared()?.logCoreEvent(.clientParameterTimeout(
name: model.list[index].type, name: model.list[index].type,
uuid: requestUUID[index], uuid: handler.clientParameterModel.id,
actionId: actionId)) actionId: actionId))
return parameterHandlerList[index].valueOnTimeout() return handler.valueOnTimeout()
} } catch {
return parameter return nil
}.reduce(into: [String: AnyHashable]()) { partialResult, next in
partialResult.merge(next) { first, last in first }
}
}
// Setup completion handlers. Barriered to ensure one happens after the other.
var complete = false
let timeoutWorkItem = DispatchWorkItem(qos: .userInitiated) {
completionHandler(mergedParametersList);
complete = true
}
let completionWorkItem = DispatchWorkItem(qos: .userInitiated) {
timeoutWorkItem.cancel()
if !complete { // In the case of firing after timeout.
completionHandler(mergedParametersList);
complete = true
}
}
// Setup timeout.
parametersWorkQueue.asyncAfter(deadline: .now() + .seconds(Int(timeout)), execute: timeoutWorkItem)
// Setup the parameter execution.
for (index, parameterHandler) in parameterHandlerList.enumerated() {
let parameterType = parameterHandler.clientParameterModel.type
MVMCoreLoggingHandler.shared()?.logCoreEvent(.clientParameterStartFetch(name: parameterType, uuid: requestUUID[index], actionId: actionId))
group.enter()
parameterHandler.fetchClientParameters(requestParameters: requestParameters,
timingOutIn: timeout) { (receivedParameter) in
// Queue the results for merge.
parametersWorkQueue.async {
guard !complete else {
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "Client \(parameterType) responded after task completion.")
return
}
guard returnedList[index] == nil else {
MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, message: "Client parameter \(parameterType) has already executed. The completion handler should only be called once!", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: String(describing: ClientParameterHandler.self))!)
return
}
MVMCoreLoggingHandler.shared()?.logCoreEvent(.clientParameterFetchComplete(name: parameterType, uuid: requestUUID[index], actionId: actionId))
returnedList[index] = receivedParameter
group.leave() // Leaving is only done after setup (barriered).
} }
} }
} }
// Callback when all parameters have been merged. return await group.reduce(into: [String: AnyHashable]()) { partialResult, parameter in
group.notify(queue: parametersWorkQueue, work: completionWorkItem); if let parameter = parameter {
partialResult.merge(parameter) { first, last in first }
}
}
} }
} }
} }

View File

@ -8,8 +8,13 @@
import Foundation import Foundation
/// A protocol defining models for client parameter fetching handlers.
public protocol ClientParameterModelProtocol: ModelProtocol { public protocol ClientParameterModelProtocol: ModelProtocol {
/// The type of client parameter.
var type: String { get } var type: String { get }
/// A unique identifier for this instance.
var id: String { get }
} }
public extension ClientParameterModelProtocol { public extension ClientParameterModelProtocol {

View File

@ -20,23 +20,22 @@ public protocol AnyClientParameterProtocol {
init(clientParameterModel: ClientParameterModelProtocol) throws init(clientParameterModel: ClientParameterModelProtocol) throws
} }
/// A protocol defining client parameter fetching handlers.
public protocol ClientParameterProtocol<Model>: ModelHandlerProtocol, AnyClientParameterProtocol { public protocol ClientParameterProtocol<Model>: ModelHandlerProtocol, AnyClientParameterProtocol {
associatedtype Model: ClientParameterModelProtocol
static var name: String { get } associatedtype Model: ClientParameterModelProtocol
var clientParameterModel: Model { get set } var clientParameterModel: Model { get set }
init(clientParameterModel: Model)
func fetchClientParameters(requestParameters: [String: Any], timingOutIn timeout: TimeInterval, completionHandler:@escaping ([String: AnyHashable]?) -> ())
/// Default parameter for timeout scenarios. It will use the protocol extension method bydefault. Can override to send custom values. func fetchClientParameters(requestParameters: [String: Any], timingOutIn timeout: TimeInterval) async -> [String: AnyHashable]?
func valueOnTimeout() -> [String: AnyHashable]
init(clientParameterModel: Model) /// Default parameter for timeout scenarios. It will use the protocol extension method bydefault. Can override to send custom values.
func valueOnTimeout() -> [String: AnyHashable]
} }
public extension ClientParameterProtocol { public extension ClientParameterProtocol {
/// Default Implementation should never be overridden /// Helper init to convert protocol to specific model. Default Implementation should never be overridden
init(clientParameterModel: ClientParameterModelProtocol) throws { init(clientParameterModel: ClientParameterModelProtocol) throws {
// Cast the clientParameterModel to ensure it matches the asscociatedType Model assigned to this Handler. // Cast the clientParameterModel to ensure it matches the asscociatedType Model assigned to this Handler.
guard let castedModel = clientParameterModel as? Model else { guard let castedModel = clientParameterModel as? Model else {
@ -48,7 +47,7 @@ public extension ClientParameterProtocol {
} }
func valueOnTimeout() -> [String: AnyHashable] { func valueOnTimeout() -> [String: AnyHashable] {
return [Self.name: "failed_to_collect"] return [clientParameterModel.type: "failed_to_collect"]
} }
/// The handler should call this method to pass the parameter back to the caller. /// The handler should call this method to pass the parameter back to the caller.
@ -57,7 +56,7 @@ public extension ClientParameterProtocol {
if isFlatMap { if isFlatMap {
completionHandler(parameter) completionHandler(parameter)
} else { } else {
completionHandler([Self.name: parameter]) completionHandler([clientParameterModel.type: parameter])
} }
} }
} }