From f5e8c0b2e68e0172f08f41db62ff231ceffe8f8d Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Wed, 17 Jan 2024 15:44:07 -0500 Subject: [PATCH] async await client parameters --- .../ClientParameterHandler.swift | 96 +++++++------------ .../ClientParameterModelProtocol.swift | 5 + .../ClientParameterProtocol.swift | 23 +++-- 3 files changed, 48 insertions(+), 76 deletions(-) diff --git a/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift b/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift index 42e8d6b..2773f59 100644 --- a/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift +++ b/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift @@ -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.. [String: AnyHashable] in - guard let parameter = element else { + 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)) + 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( name: model.list[index].type, - uuid: requestUUID[index], + uuid: handler.clientParameterModel.id, actionId: actionId)) - return parameterHandlerList[index].valueOnTimeout() - } - 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). + 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 } + } + } } } } diff --git a/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterModelProtocol.swift b/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterModelProtocol.swift index b742170..bbde12c 100644 --- a/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterModelProtocol.swift +++ b/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterModelProtocol.swift @@ -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 { diff --git a/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterProtocol.swift b/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterProtocol.swift index ccaea3b..ead6837 100644 --- a/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterProtocol.swift +++ b/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterProtocol.swift @@ -20,23 +20,22 @@ public protocol AnyClientParameterProtocol { init(clientParameterModel: ClientParameterModelProtocol) throws } +/// A protocol defining client parameter fetching handlers. public protocol ClientParameterProtocol: ModelHandlerProtocol, AnyClientParameterProtocol { - associatedtype Model: ClientParameterModelProtocol - static var name: String { get } - - var clientParameterModel: Model { get set } - - func fetchClientParameters(requestParameters: [String: Any], timingOutIn timeout: TimeInterval, completionHandler:@escaping ([String: AnyHashable]?) -> ()) + associatedtype Model: ClientParameterModelProtocol + + var clientParameterModel: Model { get set } + 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] + func fetchClientParameters(requestParameters: [String: Any], timingOutIn timeout: TimeInterval) async -> [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 { - /// 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]) } } }