Example #1
0
 public GrpcWorkerChannelFactory(IScriptEventManager eventManager, IEnvironment environment, IRpcServer rpcServer, ILoggerFactory loggerFactory, IOptionsMonitor <LanguageWorkerOptions> languageWorkerOptions,
                                 IOptionsMonitor <ScriptApplicationHostOptions> applicationHostOptions, IRpcWorkerProcessFactory rpcWorkerProcessManager, ISharedMemoryManager sharedMemoryManager)
 {
     _eventManager            = eventManager;
     _loggerFactory           = loggerFactory;
     _rpcWorkerProcessFactory = rpcWorkerProcessManager;
     _environment             = environment;
     _applicationHostOptions  = applicationHostOptions;
     _sharedMemoryManager     = sharedMemoryManager;
 }
        internal GrpcWorkerChannel(
            string workerId,
            IScriptEventManager eventManager,
            RpcWorkerConfig workerConfig,
            IWorkerProcess rpcWorkerProcess,
            ILogger logger,
            IMetricsLogger metricsLogger,
            int attemptCount,
            IEnvironment environment,
            IOptionsMonitor <ScriptApplicationHostOptions> applicationHostOptions,
            ISharedMemoryManager sharedMemoryManager,
            IFunctionDataCache functionDataCache,
            IOptions <WorkerConcurrencyOptions> workerConcurrencyOptions)
        {
            _workerId                 = workerId;
            _eventManager             = eventManager;
            _workerConfig             = workerConfig;
            _runtime                  = workerConfig.Description.Language;
            _rpcWorkerProcess         = rpcWorkerProcess;
            _workerChannelLogger      = logger;
            _metricsLogger            = metricsLogger;
            _environment              = environment;
            _applicationHostOptions   = applicationHostOptions;
            _sharedMemoryManager      = sharedMemoryManager;
            _workerConcurrencyOptions = workerConcurrencyOptions;

            _workerCapabilities = new GrpcCapabilities(_workerChannelLogger);

            _inboundWorkerEvents = _eventManager.OfType <InboundGrpcEvent>()
                                   .Where(msg => msg.WorkerId == _workerId);

            _eventSubscriptions.Add(_inboundWorkerEvents
                                    .Where(msg => msg.IsMessageOfType(MsgType.RpcLog) && !msg.IsLogOfCategory(RpcLogCategory.System))
                                    .Subscribe(Log));

            _eventSubscriptions.Add(_inboundWorkerEvents
                                    .Where(msg => msg.IsMessageOfType(MsgType.RpcLog) && msg.IsLogOfCategory(RpcLogCategory.System))
                                    .Subscribe(SystemLog));

            _eventSubscriptions.Add(_eventManager.OfType <FileEvent>()
                                    .Where(msg => _workerConfig.Description.Extensions.Contains(Path.GetExtension(msg.FileChangeArguments.FullPath)))
                                    .Throttle(TimeSpan.FromMilliseconds(300)) // debounce
                                    .Subscribe(msg => _eventManager.Publish(new HostRestartEvent())));

            _eventSubscriptions.Add(_inboundWorkerEvents.Where(msg => msg.MessageType == MsgType.InvocationResponse)
                                    .Subscribe(async(msg) => await InvokeResponse(msg.Message.InvocationResponse)));

            _inboundWorkerEvents.Where(msg => msg.MessageType == MsgType.WorkerStatusResponse)
            .Subscribe((msg) => ReceiveWorkerStatusResponse(msg.Message.RequestId, msg.Message.WorkerStatusResponse));

            _startLatencyMetric = metricsLogger?.LatencyEvent(string.Format(MetricEventNames.WorkerInitializeLatency, workerConfig.Description.Language, attemptCount));

            _state = RpcWorkerChannelState.Default;
        }
Example #3
0
        public GrpcWorkerChannelTests()
        {
            _logger = new TestLogger("FunctionDispatcherTests");
            _testFunctionRpcService = new TestFunctionRpcService(_eventManager, _workerId, _logger, _expectedLogMsg);
            _testWorkerConfig       = TestHelpers.GetTestWorkerConfigs().FirstOrDefault();
            _testWorkerConfig.CountOptions.ProcessStartupTimeout    = TimeSpan.FromSeconds(5);
            _testWorkerConfig.CountOptions.InitializationTimeout    = TimeSpan.FromSeconds(5);
            _testWorkerConfig.CountOptions.EnvironmentReloadTimeout = TimeSpan.FromSeconds(5);

            _mockrpcWorkerProcess.Setup(m => m.StartProcessAsync()).Returns(Task.CompletedTask);
            _mockrpcWorkerProcess.Setup(m => m.Id).Returns(910);
            _testEnvironment = new TestEnvironment();
            _testEnvironment.SetEnvironmentVariable(FunctionDataCacheConstants.FunctionDataCacheEnabledSettingName, "1");
            _workerConcurrencyOptions = Options.Create(new WorkerConcurrencyOptions());
            _workerConcurrencyOptions.Value.CheckInterval = TimeSpan.FromSeconds(1);

            ILogger <MemoryMappedFileAccessor> mmapAccessorLogger = NullLogger <MemoryMappedFileAccessor> .Instance;

            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                _mapAccessor = new MemoryMappedFileAccessorWindows(mmapAccessorLogger);
            }
            else
            {
                _mapAccessor = new MemoryMappedFileAccessorUnix(mmapAccessorLogger, _testEnvironment);
            }
            _sharedMemoryManager = new SharedMemoryManager(_loggerFactory, _mapAccessor);
            _functionDataCache   = new FunctionDataCache(_sharedMemoryManager, _loggerFactory, _testEnvironment);

            var hostOptions = new ScriptApplicationHostOptions
            {
                IsSelfHost     = true,
                ScriptPath     = _scriptRootPath,
                LogPath        = Environment.CurrentDirectory, // not tested
                SecretsPath    = Environment.CurrentDirectory, // not tested
                HasParentScope = true
            };

            _hostOptionsMonitor = TestHelpers.CreateOptionsMonitor(hostOptions);

            _workerChannel = new GrpcWorkerChannel(
                _workerId,
                _eventManager,
                _testWorkerConfig,
                _mockrpcWorkerProcess.Object,
                _logger,
                _metricsLogger,
                0,
                _testEnvironment,
                _hostOptionsMonitor,
                _sharedMemoryManager,
                _functionDataCache,
                _workerConcurrencyOptions);
        }
Example #4
0
 public GrpcWorkerChannelFactory(IScriptEventManager eventManager, IEnvironment environment, IRpcServer rpcServer, ILoggerFactory loggerFactory, IOptionsMonitor <LanguageWorkerOptions> languageWorkerOptions,
                                 IOptionsMonitor <ScriptApplicationHostOptions> applicationHostOptions, IRpcWorkerProcessFactory rpcWorkerProcessManager, ISharedMemoryManager sharedMemoryManager, IFunctionDataCache functionDataCache, IOptions <WorkerConcurrencyOptions> workerConcurrencyOptions)
 {
     _eventManager             = eventManager;
     _loggerFactory            = loggerFactory;
     _rpcWorkerProcessFactory  = rpcWorkerProcessManager;
     _environment              = environment;
     _applicationHostOptions   = applicationHostOptions;
     _sharedMemoryManager      = sharedMemoryManager;
     _functionDataCache        = functionDataCache;
     _workerConcurrencyOptions = workerConcurrencyOptions;
 }
 public FunctionDataCache(ISharedMemoryManager sharedMemoryManager, ILoggerFactory loggerFactory, IEnvironment environment)
 {
     _logger = loggerFactory.CreateLogger <FunctionDataCache>();
     _sharedMemoryManager  = sharedMemoryManager;
     _maximumCapacityBytes = GetMaximumCapacityBytes(environment);
     _lock                  = new object();
     _localCache            = new Dictionary <FunctionDataCacheKey, SharedMemoryMetadata>();
     LRUList                = new LinkedList <FunctionDataCacheKey>();
     ActiveReferences       = new Dictionary <FunctionDataCacheKey, long>();
     RemainingCapacityBytes = _maximumCapacityBytes;
     IsEnabled              = GetIsEnabled(environment);
 }
        public ScriptInvocationContextExtensionsTests()
        {
            ILogger <MemoryMappedFileAccessor> logger = NullLogger <MemoryMappedFileAccessor> .Instance;

            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                _mapAccessor = new MemoryMappedFileAccessorWindows(logger);
            }
            else
            {
                _mapAccessor = new MemoryMappedFileAccessorUnix(logger);
            }
            _sharedMemoryManager = new SharedMemoryManager(_loggerFactory, _mapAccessor);
        }
Example #7
0
        public GrpcWorkerChannelTests()
        {
            _logger = new TestLogger("FunctionDispatcherTests");
            _testFunctionRpcService = new TestFunctionRpcService(_eventManager, _workerId, _logger, _expectedLogMsg);
            _testWorkerConfig       = TestHelpers.GetTestWorkerConfigs().FirstOrDefault();
            _mockrpcWorkerProcess.Setup(m => m.StartProcessAsync()).Returns(Task.CompletedTask);
            _testEnvironment = new TestEnvironment();
            ILogger <MemoryMappedFileAccessor> mmapAccessorLogger = NullLogger <MemoryMappedFileAccessor> .Instance;

            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                _mapAccessor = new MemoryMappedFileAccessorWindows(mmapAccessorLogger);
            }
            else
            {
                _mapAccessor = new MemoryMappedFileAccessorUnix(mmapAccessorLogger, _testEnvironment);
            }
            _sharedMemoryManager = new SharedMemoryManager(_loggerFactory, _mapAccessor);

            var hostOptions = new ScriptApplicationHostOptions
            {
                IsSelfHost     = true,
                ScriptPath     = _scriptRootPath,
                LogPath        = Environment.CurrentDirectory, // not tested
                SecretsPath    = Environment.CurrentDirectory, // not tested
                HasParentScope = true
            };

            _hostOptionsMonitor = TestHelpers.CreateOptionsMonitor(hostOptions);

            _workerChannel = new GrpcWorkerChannel(
                _workerId,
                _eventManager,
                _testWorkerConfig,
                _mockrpcWorkerProcess.Object,
                _logger,
                _metricsLogger,
                0,
                _testEnvironment,
                _hostOptionsMonitor,
                _sharedMemoryManager);
        }
Example #8
0
        public static async Task <InvocationRequest> ToRpcInvocationRequest(this ScriptInvocationContext context, ILogger logger, GrpcCapabilities capabilities, bool isSharedMemoryDataTransferEnabled, ISharedMemoryManager sharedMemoryManager)
        {
            bool excludeHttpTriggerMetadata = !string.IsNullOrEmpty(capabilities.GetCapabilityState(RpcWorkerConstants.RpcHttpTriggerMetadataRemoved));

            var invocationRequest = new InvocationRequest
            {
                FunctionId   = context.FunctionMetadata.GetFunctionId(),
                InvocationId = context.ExecutionContext.InvocationId.ToString(),
                TraceContext = GetRpcTraceContext(context.Traceparent, context.Tracestate, context.Attributes, logger),
            };

            SetRetryContext(context, invocationRequest);

            var rpcValueCache = new Dictionary <object, TypedData>();
            Dictionary <object, RpcSharedMemory> sharedMemValueCache = null;
            StringBuilder logBuilder       = null;
            bool          usedSharedMemory = false;

            if (isSharedMemoryDataTransferEnabled)
            {
                sharedMemValueCache = new Dictionary <object, RpcSharedMemory>();
                logBuilder          = new StringBuilder();
            }

            foreach (var input in context.Inputs)
            {
                RpcSharedMemory  sharedMemValue   = null;
                ParameterBinding parameterBinding = null;
                if (isSharedMemoryDataTransferEnabled)
                {
                    // Try to transfer this data over shared memory instead of RPC
                    if (input.val == null || !sharedMemValueCache.TryGetValue(input.val, out sharedMemValue))
                    {
                        sharedMemValue = await input.val.ToRpcSharedMemoryAsync(logger, invocationRequest.InvocationId, sharedMemoryManager);

                        if (input.val != null)
                        {
                            sharedMemValueCache.Add(input.val, sharedMemValue);
                        }
                    }
                }

                if (sharedMemValue != null)
                {
                    // Data was successfully transferred over shared memory; create a ParameterBinding accordingly
                    parameterBinding = new ParameterBinding
                    {
                        Name            = input.name,
                        RpcSharedMemory = sharedMemValue
                    };

                    usedSharedMemory = true;
                    logBuilder.AppendFormat("{0}:{1},", input.name, sharedMemValue.Count);
                }
                else
                {
                    // Data was not transferred over shared memory (either disabled, type not supported or some error); resort to RPC
                    TypedData rpcValue = null;
                    if (input.val == null || !rpcValueCache.TryGetValue(input.val, out rpcValue))
                    {
                        rpcValue = await input.val.ToRpc(logger, capabilities);

                        if (input.val != null)
                        {
                            rpcValueCache.Add(input.val, rpcValue);
                        }
                    }

                    parameterBinding = new ParameterBinding
                    {
                        Name = input.name,
                        Data = rpcValue
                    };
                }

                invocationRequest.InputData.Add(parameterBinding);
            }

            foreach (var pair in context.BindingData)
            {
                if (ShouldSkipBindingData(pair, context, excludeHttpTriggerMetadata))
                {
                    continue;
                }

                if (!rpcValueCache.TryGetValue(pair.Value, out TypedData rpcValue))
                {
                    rpcValue = await pair.Value.ToRpc(logger, capabilities);

                    rpcValueCache.Add(pair.Value, rpcValue);
                }

                invocationRequest.TriggerMetadata.Add(pair.Key, rpcValue);
            }

            if (usedSharedMemory)
            {
                logger.LogDebug("Shared memory usage for request of invocation Id: {Id} is {SharedMemoryUsage}", invocationRequest.InvocationId, logBuilder.ToString());
            }

            return(invocationRequest);
        }
        internal static async Task <object> ToObjectAsync(this RpcSharedMemory sharedMem, ILogger logger, string invocationId, ISharedMemoryManager sharedMemoryManager)
        {
            // Data was transferred by the worker using shared memory
            string mapName = sharedMem.Name;
            int    offset  = (int)sharedMem.Offset;
            int    count   = (int)sharedMem.Count;

            logger.LogTrace("Shared memory data transfer for invocation id: {Id} with shared memory map name: {MapName} and size: {Size} bytes", invocationId, mapName, count);

            switch (sharedMem.Type)
            {
            case RpcDataType.Bytes:
                return(await sharedMemoryManager.GetObjectAsync(mapName, offset, count, typeof(byte[])));

            case RpcDataType.String:
                return(await sharedMemoryManager.GetObjectAsync(mapName, offset, count, typeof(string)));

            default:
                logger.LogError("Unsupported shared memory data type: {SharedMemDataType} for invocation id: {Id}", sharedMem.Type, invocationId);
                throw new InvalidDataException($"Unsupported shared memory data type: {sharedMem.Type}");
            }
        }
        internal static async Task <RpcSharedMemory> ToRpcSharedMemoryAsync(this object value, ILogger logger, string invocationId, ISharedMemoryManager sharedMemoryManager)
        {
            if (!sharedMemoryManager.IsSupported(value))
            {
                return(null);
            }

            // Put the content into shared memory and get the name of the shared memory map written to
            SharedMemoryMetadata putResponse = await sharedMemoryManager.PutObjectAsync(value);

            if (putResponse == null)
            {
                logger.LogTrace("Cannot write to shared memory for invocation id: {Id}", invocationId);
                return(null);
            }

            // If written to shared memory successfully, add this shared memory map to the list of maps for this invocation
            sharedMemoryManager.AddSharedMemoryMapForInvocation(invocationId, putResponse.Name);

            RpcDataType?dataType = GetRpcDataType(value);

            if (!dataType.HasValue)
            {
                logger.LogTrace("Cannot get shared memory data type for invocation id: {Id}", invocationId);
                return(null);
            }

            // Generate a response
            RpcSharedMemory sharedMem = new RpcSharedMemory()
            {
                Name   = putResponse.Name,
                Offset = 0,
                Count  = putResponse.Count,
                Type   = dataType.Value
            };

            logger.LogTrace("Put object in shared memory for invocation id: {Id}", invocationId);
            return(sharedMem);
        }
        internal static async Task <RpcSharedMemory> ToRpcSharedMemoryAsync(this object value, DataType dataType, ILogger logger, string invocationId, ISharedMemoryManager sharedMemoryManager)
        {
            if (value == null)
            {
                return(new RpcSharedMemory());
            }

            if (!sharedMemoryManager.IsSupported(value))
            {
                return(null);
            }

            SharedMemoryMetadata sharedMemoryMetadata;
            bool needToFreeAfterInvocation = true;

            // Check if the cache is being used or not.
            // The binding extension will only hand out ICacheAwareReadObject if the FunctionDataCache is available and enabled.
            if (value is ICacheAwareReadObject obj)
            {
                if (obj.IsCacheHit)
                {
                    // Content was already present in shared memory (cache hit)
                    logger.LogTrace("Object already present in shared memory for invocation id: {Id}", invocationId);
                    sharedMemoryMetadata      = obj.CacheObject;
                    needToFreeAfterInvocation = false;
                }
                else
                {
                    // Put the content into shared memory and get the name of the shared memory map written to.
                    // This will make the SharedMemoryManager keep an active reference to the memory map.
                    sharedMemoryMetadata = await sharedMemoryManager.PutObjectAsync(obj.BlobStream);

                    if (sharedMemoryMetadata != null)
                    {
                        FunctionDataCacheKey cacheKey = obj.CacheKey;

                        // Try to add the object into the cache and keep an active ref-count for it so that it does not get
                        // evicted while it is still being used by the invocation.
                        if (obj.TryPutToCache(sharedMemoryMetadata, isIncrementActiveReference: true))
                        {
                            logger.LogTrace("Put object: {CacheKey} in cache with metadata: {SharedMemoryMetadata} for invocation id: {Id}", cacheKey, sharedMemoryMetadata, invocationId);
                            // We don't need to free the object after the invocation; it will be freed as part of the cache's
                            // eviction policy.
                            needToFreeAfterInvocation = false;
                        }
                        else
                        {
                            logger.LogTrace("Cannot put object: {CacheKey} in cache with metadata: {SharedMemoryMetadata} for invocation id: {Id}", cacheKey, sharedMemoryMetadata, invocationId);
                            // Since we could not add this object to the cache (and therefore the cache will not be able to evict
                            // it as part of its eviction policy) we will need to free it after the invocation is done.
                            needToFreeAfterInvocation = true;
                        }
                    }
                }
            }
            else
            {
                // Put the content into shared memory and get the name of the shared memory map written to
                sharedMemoryMetadata = await sharedMemoryManager.PutObjectAsync(value);

                needToFreeAfterInvocation = true;
            }

            // Check if the object was either already in shared memory or written to shared memory
            if (sharedMemoryMetadata == null)
            {
                logger.LogTrace("Cannot write to shared memory for invocation id: {Id}", invocationId);
                return(null);
            }

            RpcDataType?rpcDataType = GetRpcDataType(dataType);

            if (!rpcDataType.HasValue)
            {
                logger.LogTrace("Cannot get shared memory data type for invocation id: {Id}", invocationId);
                return(null);
            }

            // When using the cache, we don't need to free the memory map after using it;
            // it will be freed as per the eviction policy of the cache.
            // However, if either the cache was not enabled or the object could not be added to the cache,
            // we will need to free it after the invocation.
            if (needToFreeAfterInvocation)
            {
                // If written to shared memory successfully, add this shared memory map to the list of maps for this invocation
                // so that once the invocation is over, the memory map's resources can be freed.
                sharedMemoryManager.AddSharedMemoryMapForInvocation(invocationId, sharedMemoryMetadata.MemoryMapName);
            }

            // Generate a response
            RpcSharedMemory sharedMem = new RpcSharedMemory()
            {
                Name   = sharedMemoryMetadata.MemoryMapName,
                Offset = 0,
                Count  = sharedMemoryMetadata.Count,
                Type   = rpcDataType.Value
            };

            logger.LogTrace("Put object in shared memory for invocation id: {Id}", invocationId);
            return(sharedMem);
        }
        internal static async Task <object> ToObjectAsync(this RpcSharedMemory sharedMem, ILogger logger, string invocationId, ISharedMemoryManager sharedMemoryManager, bool isFunctionDataCacheEnabled)
        {
            // Data was transferred by the worker using shared memory
            string mapName = sharedMem.Name;
            int    offset  = (int)sharedMem.Offset;
            int    count   = (int)sharedMem.Count;

            logger.LogTrace("Shared memory data transfer for invocation id: {Id} with shared memory map name: {MapName} and size: {Size} bytes", invocationId, mapName, count);

            // If cache is enabled, we hold a reference (in the host) to the memory map created by the worker
            // so that the worker can be asked to drop its reference.
            // We will later add this object into the cache and then the memory map will be freed as per the eviction logic of the cache.
            if (isFunctionDataCacheEnabled)
            {
                switch (sharedMem.Type)
                {
                case RpcDataType.Bytes:
                case RpcDataType.String:
                    // This is where the SharedMemoryManager will hold a reference to the memory map
                    if (sharedMemoryManager.TryTrackSharedMemoryMap(mapName))
                    {
                        return(await sharedMemoryManager.GetObjectAsync(mapName, offset, count, typeof(SharedMemoryObject)));
                    }
                    break;

                default:
                    logger.LogError("Unsupported shared memory data type: {SharedMemDataType} with FunctionDataCache for invocation id: {Id}", sharedMem.Type, invocationId);
                    throw new InvalidDataException($"Unsupported shared memory data type with FunctionDataCache: {sharedMem.Type}");
                }
            }

            // If the cache is not used, we copy the object content from the memory map so that the worker
            // can be asked to drop the reference to the memory map.
            switch (sharedMem.Type)
            {
            case RpcDataType.Bytes:
                return(await sharedMemoryManager.GetObjectAsync(mapName, offset, count, typeof(byte[])));

            case RpcDataType.String:
                return(await sharedMemoryManager.GetObjectAsync(mapName, offset, count, typeof(string)));

            default:
                logger.LogError("Unsupported shared memory data type: {SharedMemDataType} for invocation id: {Id}", sharedMem.Type, invocationId);
                throw new InvalidDataException($"Unsupported shared memory data type: {sharedMem.Type}");
            }
        }