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)
}
open func getParameters(with model: ClientParameterModel, requestParameters: [String: Any], actionId: String, completionHandler:@escaping ([String: Any]?) -> ()) {
let parametersWorkQueue = DispatchQueue(label: "com.mva.clientparameter")
let group = DispatchGroup()
open func getParameters(with model: ClientParameterModel, requestParameters: [String: Any], actionId: String) async -> [String: AnyHashable] {
let timeout = model.timeout ?? Self.DefaultTimeout
let parameterHandlerList = model.list.compactMap { createParametersHandler($0) }
let requestUUID = (0..<parameterHandlerList.count).map { _ in UUID().uuidString }
var returnedList = [[String: AnyHashable]?](repeating: nil, count: parameterHandlerList.count)
// Dispatch setup on queue to ensure setup is complete before completion callbacks.
// Don't use [weak self]. Object is deallocated in the dispatch queue.
parametersWorkQueue.async(group: group, qos: .userInitiated) {
var mergedParametersList: [String: AnyHashable] {
return returnedList.enumerated().map { (index, element) -> [String: AnyHashable] in
guard let parameter = element else {
MVMCoreLoggingHandler.shared()?.logCoreEvent(.clientParameterTimeout(
name: model.list[index].type,
uuid: requestUUID[index],
return await withTaskGroup(of: [String: AnyHashable]?.self, returning: [String: AnyHashable].self) { group in
for handler in parameterHandlerList {
group.addTask{
// Fetch the client parameter.
do {
MVMCoreLoggingHandler.shared()?.logCoreEvent(.clientParameterStartFetch(
name: handler.clientParameterModel.type,
uuid: handler.clientParameterModel.id,
actionId: actionId))
return parameterHandlerList[index].valueOnTimeout()
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
}.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).
} catch MVMCoreActionUtilityError.timeOut {
// The client parameter timed out.
MVMCoreLoggingHandler.shared()?.logCoreEvent(.clientParameterTimeout(
name: model.list[index].type,
uuid: handler.clientParameterModel.id,
actionId: actionId))
return handler.valueOnTimeout()
} catch {
return nil
}
}
}
// Callback when all parameters have been merged.
group.notify(queue: parametersWorkQueue, work: completionWorkItem);
return await group.reduce(into: [String: AnyHashable]()) { partialResult, parameter in
if let parameter = parameter {
partialResult.merge(parameter) { first, last in first }
}
}
}
}
}

View File

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

View File

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