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; }
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); }
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); }
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); }
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}"); } }