/// <summary> /// Implementation method to actual invoke the corresponding function. /// InvocationRequest messages are processed in parallel when there are multiple PowerShellManager instances in the pool. /// </summary> private void ProcessInvocationRequestImpl(StreamingMessage request, AzFunctionInfo functionInfo, PowerShellManager psManager) { InvocationRequest invocationRequest = request.InvocationRequest; StreamingMessage response = NewStreamingMessageTemplate( request.RequestId, StreamingMessage.ContentOneofCase.InvocationResponse, out StatusResult status); response.InvocationResponse.InvocationId = invocationRequest.InvocationId; try { // Invoke the function and return a hashtable of out binding data Hashtable results = functionInfo.Type == AzFunctionType.OrchestrationFunction ? InvokeOrchestrationFunction(psManager, functionInfo, invocationRequest) : InvokeSingleActivityFunction(psManager, functionInfo, invocationRequest); BindOutputFromResult(response.InvocationResponse, functionInfo, results); } catch (Exception e) { status.Status = StatusResult.Types.Status.Failure; status.Exception = e.ToRpcException(); } finally { _powershellPool.ReclaimUsedWorker(psManager); } _msgStream.Write(response); }
/// <summary> /// Set the 'ReturnValue' and 'OutputData' based on the invocation results appropriately. /// </summary> private void BindOutputFromResult(InvocationResponse response, AzFunctionInfo functionInfo, Hashtable results) { switch (functionInfo.Type) { case AzFunctionType.RegularFunction: // Set out binding data and return response to be sent back to host foreach (KeyValuePair <string, ReadOnlyBindingInfo> binding in functionInfo.OutputBindings) { // if one of the bindings is '$return' we need to set the ReturnValue string outBindingName = binding.Key; if (string.Equals(outBindingName, AzFunctionInfo.DollarReturn, StringComparison.OrdinalIgnoreCase)) { response.ReturnValue = results[outBindingName].ToTypedData(_powerShellManager); continue; } ParameterBinding paramBinding = new ParameterBinding() { Name = outBindingName, Data = results[outBindingName].ToTypedData(_powerShellManager) }; response.OutputData.Add(paramBinding); } break; case AzFunctionType.OrchestrationFunction: case AzFunctionType.ActivityFunction: response.ReturnValue = results[AzFunctionInfo.DollarReturn].ToTypedData(_powerShellManager); break; default: throw new InvalidOperationException("Unreachable code."); } }
/// <summary> /// Set the 'ReturnValue' and 'OutputData' based on the invocation results appropriately. /// </summary> private static void BindOutputFromResult(InvocationResponse response, AzFunctionInfo functionInfo, IDictionary results) { if (functionInfo.DurableFunctionInfo.Type != DurableFunctionType.OrchestrationFunction) { // Set out binding data and return response to be sent back to host foreach (var(bindingName, bindingInfo) in functionInfo.OutputBindings) { var outValue = results[bindingName]; var transformedValue = Utils.TransformOutBindingValueAsNeeded(bindingName, bindingInfo, outValue); var dataToUse = transformedValue.ToTypedData(); // if one of the bindings is '$return' we need to set the ReturnValue if (string.Equals(bindingName, AzFunctionInfo.DollarReturn, StringComparison.OrdinalIgnoreCase)) { response.ReturnValue = dataToUse; continue; } var paramBinding = new ParameterBinding() { Name = bindingName, Data = dataToUse }; response.OutputData.Add(paramBinding); } } if (functionInfo.DurableFunctionInfo.ProvidesForcedDollarReturnValue) { response.ReturnValue = results[AzFunctionInfo.DollarReturn].ToTypedData(); } }
/// <summary> /// Method to process a InvocationRequest. /// This method checks out a worker from the pool, and then starts the actual invocation in a threadpool thread. /// </summary> internal StreamingMessage ProcessInvocationRequest(StreamingMessage request) { AzFunctionInfo functionInfo = null; PowerShellManager psManager = null; try { functionInfo = _functionLoader.GetFunctionInfo(request.InvocationRequest.FunctionId); psManager = _powershellPool.CheckoutIdleWorker(request, functionInfo); Task.Run(() => ProcessInvocationRequestImpl(request, functionInfo, psManager)); } catch (Exception e) { _powershellPool.ReclaimUsedWorker(psManager); StreamingMessage response = NewStreamingMessageTemplate( request.RequestId, StreamingMessage.ContentOneofCase.InvocationResponse, out StatusResult status); response.InvocationResponse.InvocationId = request.InvocationRequest.InvocationId; status.Status = StatusResult.Types.Status.Failure; status.Exception = e.ToRpcException(); return(response); } return(null); }
/// <summary> /// Method to process a InvocationRequest. /// This method checks out a worker from the pool, and then starts the actual invocation in a threadpool thread. /// </summary> internal StreamingMessage ProcessInvocationRequest(StreamingMessage request) { Exception error = null; try { if (_dependencyManager.DependencyDownloadTask != null && !_dependencyManager.DependencyDownloadTask.IsCompleted) { var rpcLogger = new RpcLogger(_msgStream); rpcLogger.SetContext(request.RequestId, request.InvocationRequest?.InvocationId); rpcLogger.Log(LogLevel.Information, PowerShellWorkerStrings.DependencyDownloadInProgress, isUserLog: true); _dependencyManager.WaitOnDependencyDownload(); } if (_dependencyManager.DependencyError != null) { error = _dependencyManager.DependencyError; } else { AzFunctionInfo functionInfo = FunctionLoader.GetFunctionInfo(request.InvocationRequest.FunctionId); PowerShellManager psManager = _powershellPool.CheckoutIdleWorker(request, functionInfo); if (_powershellPool.UpperBound == 1) { // When the concurrency upper bound is 1, we can handle only one invocation at a time anyways, // so it's better to just do it on the current thread to reduce the required synchronization. ProcessInvocationRequestImpl(request, functionInfo, psManager); } else { // When the concurrency upper bound is more than 1, we have to handle the invocation in a worker // thread, so multiple invocations can make progress at the same time, even though by time-sharing. Task.Run(() => ProcessInvocationRequestImpl(request, functionInfo, psManager)); } } } catch (Exception e) { error = e; } if (error != null) { StreamingMessage response = NewStreamingMessageTemplate( request.RequestId, StreamingMessage.ContentOneofCase.InvocationResponse, out StatusResult status); response.InvocationResponse.InvocationId = request.InvocationRequest.InvocationId; status.Status = StatusResult.Types.Status.Failure; status.Exception = error.ToRpcException(); return(response); } return(null); }
private static TraceContext GetTraceContext(AzFunctionInfo functionInfo, InvocationRequest invocationRequest) { if (!functionInfo.HasTraceContextParam) { return(null); } return(new TraceContext( invocationRequest.TraceContext.TraceParent, invocationRequest.TraceContext.TraceState, invocationRequest.TraceContext.Attributes)); }
private static RetryContext GetRetryContext(AzFunctionInfo functionInfo, InvocationRequest invocationRequest) { if (!functionInfo.HasRetryContextParam) { return(null); } return(new RetryContext( invocationRequest.RetryContext.RetryCount, invocationRequest.RetryContext.MaxRetryCount, invocationRequest.RetryContext.Exception)); }
private Hashtable InvokeFunction( AzFunctionInfo functionInfo, PowerShellManager psManager, FunctionInvocationPerformanceStopwatch stopwatch, InvocationRequest invocationRequest) { var triggerMetadata = GetTriggerMetadata(functionInfo, invocationRequest); var traceContext = GetTraceContext(functionInfo, invocationRequest); stopwatch.OnCheckpoint(FunctionInvocationPerformanceStopwatch.Checkpoint.MetadataAndTraceContextReady); return(psManager.InvokeFunction(functionInfo, triggerMetadata, traceContext, invocationRequest.InputData, stopwatch)); }
/// <summary> /// Invoke a regular function or an activity function. /// </summary> private Hashtable InvokeSingleActivityFunction( PowerShellManager psManager, AzFunctionInfo functionInfo, InvocationRequest invocationRequest, FunctionInvocationPerformanceStopwatch stopwatch) { const string InvocationId = "InvocationId"; const string FunctionDirectory = "FunctionDirectory"; const string FunctionName = "FunctionName"; // Bundle all TriggerMetadata into Hashtable to send down to PowerShell Hashtable triggerMetadata = null; TraceContext traceContext = null; if (functionInfo.HasTriggerMetadataParam) { triggerMetadata = new Hashtable(StringComparer.OrdinalIgnoreCase); foreach (var dataItem in invocationRequest.TriggerMetadata) { // MapField<K, V> is case-sensitive, but powershell is case-insensitive, // so for keys differ only in casing, the first wins. if (!triggerMetadata.ContainsKey(dataItem.Key)) { triggerMetadata.Add(dataItem.Key, dataItem.Value.ToObject()); } } if (!triggerMetadata.ContainsKey(InvocationId)) { triggerMetadata.Add(InvocationId, invocationRequest.InvocationId); } if (!triggerMetadata.ContainsKey(FunctionDirectory)) { triggerMetadata.Add(FunctionDirectory, functionInfo.FuncDirectory); } if (!triggerMetadata.ContainsKey(FunctionName)) { triggerMetadata.Add(FunctionName, functionInfo.FuncName); } } if (functionInfo.HasTraceContextParam) { traceContext = new TraceContext(invocationRequest.TraceContext.TraceParent, invocationRequest.TraceContext.TraceState, invocationRequest.TraceContext.Attributes); } stopwatch.OnCheckpoint(FunctionInvocationPerformanceStopwatch.Checkpoint.MetadataAndTraceContextReady); return(psManager.InvokeFunction(functionInfo, triggerMetadata, traceContext, invocationRequest.InputData, stopwatch)); }
/// <summary> /// Method to process a InvocationRequest. /// This method checks out a worker from the pool, and then starts the actual invocation in a threadpool thread. /// </summary> internal StreamingMessage ProcessInvocationRequest(StreamingMessage request) { try { var stopwatch = new FunctionInvocationPerformanceStopwatch(); stopwatch.OnStart(); // Will block if installing dependencies is required _dependencyManager.WaitForDependenciesAvailability( () => { var rpcLogger = new RpcLogger(_msgStream); rpcLogger.SetContext(request.RequestId, request.InvocationRequest?.InvocationId); return(rpcLogger); }); stopwatch.OnCheckpoint(FunctionInvocationPerformanceStopwatch.Checkpoint.DependenciesAvailable); AzFunctionInfo functionInfo = FunctionLoader.GetFunctionInfo(request.InvocationRequest.FunctionId); PowerShellManager psManager = _powershellPool.CheckoutIdleWorker( request.RequestId, request.InvocationRequest?.InvocationId, functionInfo.FuncName, functionInfo.OutputBindings); stopwatch.OnCheckpoint(FunctionInvocationPerformanceStopwatch.Checkpoint.RunspaceAvailable); // When the concurrency upper bound is more than 1, we have to handle the invocation in a worker // thread, so multiple invocations can make progress at the same time, even though by time-sharing. Task.Run(() => ProcessInvocationRequestImpl(request, functionInfo, psManager, stopwatch)); } catch (Exception e) { StreamingMessage response = NewStreamingMessageTemplate( request.RequestId, StreamingMessage.ContentOneofCase.InvocationResponse, out StatusResult status); response.InvocationResponse.InvocationId = request.InvocationRequest.InvocationId; status.Status = StatusResult.Types.Status.Failure; status.Exception = e.ToRpcException(); return(response); } return(null); }
/// <summary> /// Invoke a regular function or an activity function. /// </summary> private Hashtable InvokeSingleActivityFunction(PowerShellManager psManager, AzFunctionInfo functionInfo, InvocationRequest invocationRequest) { // Bundle all TriggerMetadata into Hashtable to send down to PowerShell var triggerMetadata = new Hashtable(StringComparer.OrdinalIgnoreCase); foreach (var dataItem in invocationRequest.TriggerMetadata) { // MapField<K, V> is case-sensitive, but powershell is case-insensitive, // so for keys differ only in casing, the first wins. if (!triggerMetadata.ContainsKey(dataItem.Key)) { triggerMetadata.Add(dataItem.Key, dataItem.Value.ToObject()); } } return(psManager.InvokeFunction(functionInfo, triggerMetadata, invocationRequest.InputData)); }
/// <summary> /// Method to process a InvocationRequest. /// This method checks out a worker from the pool, and then starts the actual invocation in a threadpool thread. /// </summary> internal StreamingMessage ProcessInvocationRequest(StreamingMessage request) { try { // Will block if installing dependencies is required _dependencyManager.WaitForDependenciesAvailability( () => { var rpcLogger = new RpcLogger(_msgStream); rpcLogger.SetContext(request.RequestId, request.InvocationRequest?.InvocationId); return(rpcLogger); }); AzFunctionInfo functionInfo = FunctionLoader.GetFunctionInfo(request.InvocationRequest.FunctionId); PowerShellManager psManager = _powershellPool.CheckoutIdleWorker(request, functionInfo); if (_powershellPool.UpperBound == 1) { // When the concurrency upper bound is 1, we can handle only one invocation at a time anyways, // so it's better to just do it on the current thread to reduce the required synchronization. ProcessInvocationRequestImpl(request, functionInfo, psManager); } else { // When the concurrency upper bound is more than 1, we have to handle the invocation in a worker // thread, so multiple invocations can make progress at the same time, even though by time-sharing. Task.Run(() => ProcessInvocationRequestImpl(request, functionInfo, psManager)); } } catch (Exception e) { StreamingMessage response = NewStreamingMessageTemplate( request.RequestId, StreamingMessage.ContentOneofCase.InvocationResponse, out StatusResult status); response.InvocationResponse.InvocationId = request.InvocationRequest.InvocationId; status.Status = StatusResult.Types.Status.Failure; status.Exception = e.ToRpcException(); return(response); } return(null); }
private static Hashtable GetTriggerMetadata(AzFunctionInfo functionInfo, InvocationRequest invocationRequest) { if (!functionInfo.HasTriggerMetadataParam) { return(null); } const string InvocationId = "InvocationId"; const string FunctionDirectory = "FunctionDirectory"; const string FunctionName = "FunctionName"; var triggerMetadata = new Hashtable(StringComparer.OrdinalIgnoreCase); foreach (var dataItem in invocationRequest.TriggerMetadata) { // MapField<K, V> is case-sensitive, but powershell is case-insensitive, // so for keys differ only in casing, the first wins. if (!triggerMetadata.ContainsKey(dataItem.Key)) { triggerMetadata.Add(dataItem.Key, dataItem.Value.ToObject()); } } if (!triggerMetadata.ContainsKey(InvocationId)) { triggerMetadata.Add(InvocationId, invocationRequest.InvocationId); } if (!triggerMetadata.ContainsKey(FunctionDirectory)) { triggerMetadata.Add(FunctionDirectory, functionInfo.FuncDirectory); } if (!triggerMetadata.ContainsKey(FunctionName)) { triggerMetadata.Add(FunctionName, functionInfo.FuncName); } return(triggerMetadata); }
/// <summary> /// Invoke an orchestration function. /// </summary> private Hashtable InvokeOrchestrationFunction( PowerShellManager psManager, AzFunctionInfo functionInfo, InvocationRequest invocationRequest, FunctionInvocationPerformanceStopwatch stopwatch) { if (!Utils.AreDurableFunctionsEnabled()) { throw new NotImplementedException(PowerShellWorkerStrings.DurableFunctionNotSupported); } // Quote from https://docs.microsoft.com/en-us/azure/azure-functions/durable-functions-bindings: // // "Orchestrator functions should never use any input or output bindings other than the orchestration trigger binding. // Doing so has the potential to cause problems with the Durable Task extension because those bindings may not obey the single-threading and I/O rules." // // Therefore, it's by design that 'InputData' contains only one item, which is the metadata of the orchestration context. var context = invocationRequest.InputData[0]; var durableFuncContext = JsonConvert.DeserializeObject <OrchestrationContext>(context.Data.String); return(psManager.InvokeOrchestrationFunction(functionInfo, context.Name, durableFuncContext)); }
/// <summary> /// Invoke an orchestration function. /// </summary> private Hashtable InvokeOrchestrationFunction(AzFunctionInfo functionInfo, InvocationRequest invocationRequest) { throw new NotImplementedException("Durable function is not yet supported for PowerShell"); }
/// <summary> /// Invoke an orchestration function. /// </summary> private Hashtable InvokeOrchestrationFunction(PowerShellManager psManager, AzFunctionInfo functionInfo, InvocationRequest invocationRequest) { throw new NotImplementedException(PowerShellWorkerStrings.DurableFunctionNotSupported); }
/// <summary> /// Helper method to set the output binding metadata for the function that is about to run. /// </summary> internal static void RegisterFunctionMetadata(Guid instanceId, AzFunctionInfo functionInfo) { var outputBindings = functionInfo.OutputBindings; OutputBindingCache.AddOrUpdate(instanceId, outputBindings, (key, value) => outputBindings); }