コード例 #1
0
        /// <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);
        }
コード例 #2
0
        /// <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.");
            }
        }
コード例 #3
0
        /// <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();
            }
        }
コード例 #4
0
        /// <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);
        }
コード例 #5
0
        /// <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);
        }
コード例 #6
0
        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));
        }
コード例 #7
0
        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));
        }
コード例 #8
0
        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));
        }
コード例 #9
0
        /// <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));
        }
コード例 #10
0
        /// <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));
        }
コード例 #12
0
        /// <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);
        }
コード例 #13
0
        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);
        }
コード例 #14
0
        /// <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");
 }
コード例 #16
0
 /// <summary>
 /// Invoke an orchestration function.
 /// </summary>
 private Hashtable InvokeOrchestrationFunction(PowerShellManager psManager, AzFunctionInfo functionInfo, InvocationRequest invocationRequest)
 {
     throw new NotImplementedException(PowerShellWorkerStrings.DurableFunctionNotSupported);
 }
コード例 #17
0
        /// <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);
        }