public async Task ToObject_CollectionBytes_VerifyFailure(bool isFunctionDataCacheEnabled) { ILogger <RpcSharedMemory> logger = NullLogger <RpcSharedMemory> .Instance; string invocationId = Guid.NewGuid().ToString(); long contentSize = 16 * 1024; // 16KB, enough to try out the functionality we need to test using (SharedMemoryManager manager = new SharedMemoryManager(_loggerFactory, _mapAccessor)) { // Create a SharedMemoryMap string mapName = Guid.NewGuid().ToString(); SharedMemoryMap sharedMemoryMap = CreateSharedMemoryMap(mapName, contentSize); // Wite content into it byte[] content = TestUtils.GetRandomBytesInArray((int)contentSize); long bytesWritten = await sharedMemoryMap.PutBytesAsync(content); Assert.Equal(contentSize, bytesWritten); // Create a RpcSharedMemory object pointing to the shared memory region we created earlier // Although the type is not correct, instead of Bytes it is CollectionBytes (unsupported) RpcSharedMemory rpcSharedMemory = new RpcSharedMemory { Name = mapName, Count = contentSize, Offset = 0, Type = RpcDataType.CollectionBytes }; // Try to convert the object but this should fail since the type we are requested is unsupported await Assert.ThrowsAsync <InvalidDataException>(async() => await rpcSharedMemory.ToObjectAsync(logger, invocationId, manager, isFunctionDataCacheEnabled)); // Dispose off the created resources sharedMemoryMap.Dispose(); } }
public async Task ToObject_Bytes_FunctionDataCacheEnabled_VerifySuccess() { ILogger <RpcSharedMemory> logger = NullLogger <RpcSharedMemory> .Instance; string invocationId = Guid.NewGuid().ToString(); long contentSize = 16 * 1024; // 16KB, enough to try out the functionality we need to test using (SharedMemoryManager manager = new SharedMemoryManager(_loggerFactory, _mapAccessor)) { // Create a SharedMemoryMap string mapName = Guid.NewGuid().ToString(); SharedMemoryMap sharedMemoryMap = CreateSharedMemoryMap(mapName, contentSize); // Wite content into it byte[] content = TestUtils.GetRandomBytesInArray((int)contentSize); long bytesWritten = await sharedMemoryMap.PutBytesAsync(content); Assert.Equal(contentSize, bytesWritten); // Create a RpcSharedMemory object pointing to the shared memory region we created earlier RpcSharedMemory rpcSharedMemory = new RpcSharedMemory { Name = mapName, Count = contentSize, Offset = 0, Type = RpcDataType.Bytes }; // Convert RpcSharedMemory object into byte[] object rpcObj = await rpcSharedMemory.ToObjectAsync(logger, invocationId, manager, isFunctionDataCacheEnabled : true); Assert.NotNull(rpcObj); // Since the FunctionDataCache is enabled, the object should be a SharedMemoryObject Assert.IsType <SharedMemoryObject>(rpcObj); SharedMemoryObject sharedMemoryObject = rpcObj as SharedMemoryObject; // Verify that the read object is correct Assert.Equal(mapName, sharedMemoryObject.MemoryMapName); Assert.Equal(contentSize, sharedMemoryObject.Count); // Since the FunctionDataCache is enabled, ensure that the SharedMemoryManager is tracking the object that was read Assert.Equal(1, manager.AllocatedSharedMemoryMaps.Count); Assert.True(manager.AllocatedSharedMemoryMaps.TryGetValue(mapName, out _)); // Dispose off the created resources sharedMemoryMap.Dispose(); } }
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}"); } }
internal static async Task <RpcSharedMemory> ToRpcSharedMemoryAsync(this object value, ILogger logger, string invocationId, ISharedMemoryManager sharedMemoryManager) { if (value == null) { return(new RpcSharedMemory()); } 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 <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}"); } }
public async Task ToRpcInvocationRequest_RpcSharedMemoryDataTransfer() { var logger = new TestLogger("test"); var httpContext = new DefaultHttpContext(); httpContext.Request.Host = new HostString("local"); httpContext.Request.Path = "/test"; httpContext.Request.Method = "Post"; var poco = new TestPoco { Id = 1, Name = "Test" }; var bindingData = new Dictionary <string, object> { { "req", httpContext.Request }, { "$request", httpContext.Request }, { "headers", httpContext.Request.Headers.ToDictionary(p => p.Key, p => p.Value) }, { "query", httpContext.Request.QueryString.ToString() }, { "sys", new SystemBindingData() } }; const int inputStringLength = 2 * 1024 * 1024; string inputString = TestUtils.GetRandomString(inputStringLength); const int inputBytesLength = 2 * 1024 * 1024; byte[] inputBytes = TestUtils.GetRandomBytesInArray(inputBytesLength); var inputs = new List <(string name, DataType type, object val)> { ("req", DataType.String, httpContext.Request), ("fooStr", DataType.String, inputString), ("fooBytes", DataType.Binary, inputBytes), }; var invocationContext = new ScriptInvocationContext() { ExecutionContext = new ExecutionContext() { InvocationId = Guid.NewGuid(), FunctionName = "Test", }, BindingData = bindingData, Inputs = inputs, ResultSource = new TaskCompletionSource <ScriptInvocationResult>(), Logger = logger, AsyncExecutionContext = System.Threading.ExecutionContext.Capture() }; var functionMetadata = new FunctionMetadata { Name = "Test" }; var httpTriggerBinding = new BindingMetadata { Name = "req", Type = "httpTrigger", Direction = BindingDirection.In, Raw = new JObject() }; var fooStrInputBinding = new BindingMetadata { Name = "fooStr", Type = "fooStr", Direction = BindingDirection.In }; var fooBytesInputBinding = new BindingMetadata { Name = "fooBytes", Type = "fooBytes", Direction = BindingDirection.In }; var httpOutputBinding = new BindingMetadata { Name = "res", Type = "http", Direction = BindingDirection.Out, Raw = new JObject(), DataType = DataType.String }; functionMetadata.Bindings.Add(httpTriggerBinding); functionMetadata.Bindings.Add(fooStrInputBinding); functionMetadata.Bindings.Add(fooBytesInputBinding); functionMetadata.Bindings.Add(httpOutputBinding); invocationContext.FunctionMetadata = functionMetadata; GrpcCapabilities capabilities = new GrpcCapabilities(logger); var result = await invocationContext.ToRpcInvocationRequest(logger, capabilities, isSharedMemoryDataTransferEnabled : true, _sharedMemoryManager); Assert.Equal(3, result.InputData.Count); Assert.Equal("fooStr", result.InputData[1].Name); Assert.Equal("fooBytes", result.InputData[2].Name); // The input data should be transferred over shared memory RpcSharedMemory sharedMem1 = result.InputData[1].RpcSharedMemory; // This is what the expected byte[] representation of the string should be // We use that to find expected length byte[] contentBytes = Encoding.UTF8.GetBytes(inputString); Assert.Equal(contentBytes.Length, sharedMem1.Count); // Check that the name of the shared memory map is a valid GUID Assert.True(Guid.TryParse(sharedMem1.Name, out _)); // Check the type being sent Assert.Equal(sharedMem1.Type, RpcDataType.String); // The input data should be transferred over shared memory RpcSharedMemory sharedMem2 = result.InputData[2].RpcSharedMemory; Assert.Equal(inputBytes.Length, sharedMem2.Count); // Check that the name of the shared memory map is a valid GUID Assert.True(Guid.TryParse(sharedMem2.Name, out _)); // Check the type being sent Assert.Equal(sharedMem2.Type, RpcDataType.Bytes); }
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); }
public async Task ToRpcInvocationRequest_RpcSharedMemoryDataTransfer_UsingFunctionDataCache_CacheMiss() { var logger = new TestLogger("test"); var httpContext = new DefaultHttpContext(); httpContext.Request.Host = new HostString("local"); httpContext.Request.Path = "/test"; httpContext.Request.Method = "Post"; var poco = new TestPoco { Id = 1, Name = "Test" }; var bindingData = new Dictionary <string, object> { { "req", httpContext.Request }, { "$request", httpContext.Request }, { "headers", httpContext.Request.Headers.ToDictionary(p => p.Key, p => p.Value) }, { "query", httpContext.Request.QueryString.ToString() }, { "sys", new SystemBindingData() } }; const int inputStringLength = 2 * 1024 * 1024; string inputString = TestUtils.GetRandomString(inputStringLength); Stream inputStream1 = new MemoryStream(); StreamWriter inputStreamWriter1 = new StreamWriter(inputStream1); await inputStreamWriter1.WriteAsync(inputString); await inputStreamWriter1.FlushAsync(); inputStream1.Seek(0, SeekOrigin.Begin); FunctionDataCacheKey key1 = new FunctionDataCacheKey("fooStr", "0x1"); MockCacheAwareReadObject cacheObj1 = new MockCacheAwareReadObject(key1, inputStream1, _functionDataCache); const int inputBytesLength = 2 * 1024 * 1024; byte[] inputBytes = TestUtils.GetRandomBytesInArray(inputBytesLength); Stream inputStream2 = new MemoryStream(inputBytes); inputStream2.Seek(0, SeekOrigin.Begin); FunctionDataCacheKey key2 = new FunctionDataCacheKey("fooBytes", "0x1"); MockCacheAwareReadObject cacheObj2 = new MockCacheAwareReadObject(key2, inputStream2, _functionDataCache); var inputs = new List <(string name, DataType type, object val)> { ("req", DataType.String, httpContext.Request), ("fooStr", DataType.String, cacheObj1), ("fooBytes", DataType.Binary, cacheObj2), }; var invocationContext = new ScriptInvocationContext() { ExecutionContext = new ExecutionContext() { InvocationId = Guid.NewGuid(), FunctionName = "Test", }, BindingData = bindingData, Inputs = inputs, ResultSource = new TaskCompletionSource <ScriptInvocationResult>(), Logger = logger, AsyncExecutionContext = System.Threading.ExecutionContext.Capture() }; var functionMetadata = new FunctionMetadata { Name = "Test" }; var httpTriggerBinding = new BindingMetadata { Name = "req", Type = "httpTrigger", Direction = BindingDirection.In, Raw = new JObject() }; var fooStrInputBinding = new BindingMetadata { Name = "fooStr", Type = "fooStr", Direction = BindingDirection.In }; var fooBytesInputBinding = new BindingMetadata { Name = "fooBytes", Type = "fooBytes", Direction = BindingDirection.In }; var httpOutputBinding = new BindingMetadata { Name = "res", Type = "http", Direction = BindingDirection.Out, Raw = new JObject(), DataType = DataType.String }; functionMetadata.Bindings.Add(httpTriggerBinding); functionMetadata.Bindings.Add(fooStrInputBinding); functionMetadata.Bindings.Add(fooBytesInputBinding); functionMetadata.Bindings.Add(httpOutputBinding); invocationContext.FunctionMetadata = functionMetadata; GrpcCapabilities capabilities = new GrpcCapabilities(logger); var result = await invocationContext.ToRpcInvocationRequest(logger, capabilities, isSharedMemoryDataTransferEnabled : true, _sharedMemoryManager); Assert.Equal(3, result.InputData.Count); Assert.Equal("fooStr", result.InputData[1].Name); Assert.Equal("fooBytes", result.InputData[2].Name); // The input data should be transferred over shared memory RpcSharedMemory sharedMem1 = result.InputData[1].RpcSharedMemory; // This is what the expected byte[] representation of the string should be // We use that to find expected length byte[] contentBytes = Encoding.UTF8.GetBytes(inputString); Assert.Equal(contentBytes.Length, sharedMem1.Count); // Check that the name of the shared memory map is a valid GUID Assert.True(Guid.TryParse(sharedMem1.Name, out _)); // Check the type being sent Assert.Equal(sharedMem1.Type, RpcDataType.String); // The input data should be transferred over shared memory RpcSharedMemory sharedMem2 = result.InputData[2].RpcSharedMemory; Assert.Equal(inputBytes.Length, sharedMem2.Count); // Check that the name of the shared memory map is a valid GUID Assert.True(Guid.TryParse(sharedMem2.Name, out _)); // Check the type being sent Assert.Equal(sharedMem2.Type, RpcDataType.Bytes); // Check that the inputs were inserted into shared memory object inputStringReadObj = await _sharedMemoryManager.GetObjectAsync(sharedMem1.Name, 0, (int)sharedMem1.Count, typeof(string)); Assert.NotNull(inputStringReadObj); string inputStringRead = inputStringReadObj as string; Assert.Equal(inputString, inputStringRead); object inputBytesReadObj = await _sharedMemoryManager.GetObjectAsync(sharedMem2.Name, 0, (int)sharedMem2.Count, typeof(byte[])); Assert.NotNull(inputBytesReadObj); byte[] inputBytesRead = inputBytesReadObj as byte[]; Assert.Equal(inputBytes, inputBytesRead); // Check that the inputs were not marked to be removed after the invocation Assert.Empty(_sharedMemoryManager.InvocationSharedMemoryMaps); // Check that the inputs were inserted into the cache Assert.True(_functionDataCache.TryGet(key1, isIncrementActiveReference: false, out _)); Assert.True(_functionDataCache.TryGet(key2, isIncrementActiveReference: false, out _)); }
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); }