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}");
            }
        }
Beispiel #4
0
        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}");
            }
        }
Beispiel #6
0
        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);
        }
Beispiel #7
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);
        }
        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);
        }