Search code examples
c#azure-functionsazure-functions-isolated

Azure Functions Isolated worker model adding to Azure Storage queue error: System.ObjectDisposedException


Has anyone tried the following Microsoft quickstart "Connect Azure Functions to Azure Storage using Visual Studio Code" with Isolated worker model? https://learn.microsoft.com/en-gb/azure/azure-functions/functions-add-output-binding-storage-queue-vs-code?pivots=programming-language-csharp&tabs=isolated-process

I have created an async version and it works fine initially but then, after submitting 4 - 5 requests (by adding messages to the queue) got the following error and the function stops inserting messages to the queue:

[2024-12-08T05:54:01.909Z] Executed 'Functions.HttpTrigger3' (Failed, Id=d7c6cdb1-63a8-4c53-9e1f-abb7b6572ec4, Duration=70ms)
[2024-12-08T05:54:01.912Z] System.Private.CoreLib: Exception while executing function: Functions.HttpTrigger3. System.Private.CoreLib: Result: 
Failure
Exception: System.ObjectDisposedException: IFeatureCollection has been disposed.
[2024-12-08T05:54:01.915Z] Object name: 'Collection'.
[2024-12-08T05:54:01.916Z]    at Microsoft.AspNetCore.Http.Features.FeatureReferences`1.ThrowContextDisposed()
[2024-12-08T05:54:01.917Z]    at Microsoft.AspNetCore.Http.Features.FeatureReferences`1.ContextDisposed()
[2024-12-08T05:54:01.918Z]    at Microsoft.AspNetCore.Http.Features.FeatureReferences`1.Fetch[TFeature](TFeature& cached, Func`2 factory)      
[2024-12-08T05:54:01.919Z]    at Microsoft.AspNetCore.Http.DefaultHttpResponse.get_StatusCode()
[2024-12-08T05:54:01.920Z]    at Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.AspNetCoreHttpResponseData.get_StatusCode() in /mnt/vss/_work/1/s/extensions/Worker.Extensions.Http.AspNetCore/src/HttpDataModel/AspNetCoreHttpResponseData.cs:line 39
[2024-12-08T05:54:01.921Z]    at Microsoft.Azure.Functions.Worker.Rpc.RpcExtensions.ToRpcHttpAsync(HttpResponseData response, ObjectSerializer 
serializer) in D:\a\_work\1\s\src\DotNetWorker.Grpc\RpcExtensions.cs:line 88
[2024-12-08T05:54:01.922Z]    at Microsoft.Azure.Functions.Worker.Rpc.RpcExtensions.ToRpcAsync(Object value, ObjectSerializer serializer) in D:\a\_work\1\s\src\DotNetWorker.Grpc\RpcExtensions.cs:line 35
[2024-12-08T05:54:01.923Z]    at Microsoft.Azure.Functions.Worker.Handlers.InvocationHandler.InvokeAsync(InvocationRequest request) in D:\a\_work\1\s\src\DotNetWorker.Grpc\Handlers\InvocationHandler.cs:line 102
Stack:    at Microsoft.AspNetCore.Http.Features.FeatureReferences`1.ThrowContextDisposed()
[2024-12-08T05:54:01.924Z]    at Microsoft.AspNetCore.Http.Features.FeatureReferences`1.ContextDisposed()
[2024-12-08T05:54:01.924Z]    at Microsoft.AspNetCore.Http.Features.FeatureReferences`1.Fetch[TFeature](TFeature& cached, Func`2 factory)
[2024-12-08T05:54:01.925Z]    at Microsoft.AspNetCore.Http.DefaultHttpResponse.get_StatusCode()
[2024-12-08T05:54:01.926Z]    at Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.AspNetCoreHttpResponseData.get_StatusCode() in /mnt/vss/_work/1/s/extensions/Worker.Extensions.Http.AspNetCore/src/HttpDataModel/AspNetCoreHttpResponseData.cs:line 39
[2024-12-08T05:54:01.927Z]    at Microsoft.Azure.Functions.Worker.Rpc.RpcExtensions.ToRpcHttpAsync(HttpResponseData response, ObjectSerializer serializer) in D:\a\_work\1\s\src\DotNetWorker.Grpc\RpcExtensions.cs:line 88
[2024-12-08T05:54:01.928Z]    at Microsoft.Azure.Functions.Worker.Rpc.RpcExtensions.ToRpcAsync(Object value, ObjectSerializer serializer) in D:\a\_work\1\s\src\DotNetWorker.Grpc\RpcExtensions.cs:line 35
[2024-12-08T05:54:01.929Z]    at Microsoft.Azure.Functions.Worker.Handlers.InvocationHandler.InvokeAsync(InvocationRequest request) in D:\a\_work\1\s\src\DotNetWorker.Grpc\Handlers\InvocationHandler.cs:line 102.

After a couple of minutes the function app recovers, but then after submitting several more requests got the error again.

Source code is listed below. I found the problem is related to the MultiResponse as if I change it to write to the queue only or just return HTTP response it works fine. The In-process Model version works perfect when sending to the queue and returning HTTP response.

namespace HttpTriggerAppVSC
{
    public class HttpExampleVSC
    {
        private readonly ILogger<HttpExampleVSC> _logger;

        public HttpExampleVSC(ILogger<HttpExampleVSC> logger)
        {
            _logger = logger;
        }
    
        [Function("HttpTrigger3")]
        public async Task<MultiResponse> HttpTrigger3(
            [HttpTrigger(AuthorizationLevel.System, "get", "post")] HttpRequestData req,
            FunctionContext executionContext, CancellationToken cancellationToken)
        {
            var logger = executionContext.GetLogger("HttpExampleVSC");
            logger.LogInformation("C# HTTP trigger function processed a request.");
    
            string name = req.Query["name"];
    
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;
    
            string message = string.IsNullOrEmpty(name)
                ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
                : $"Hello, {name}. This HTTP triggered function executed successfully.";
    
    
            //if (cancellationToken.IsCancellationRequested)
            //{
            //    return null;
            //}
            
            var response = req.CreateResponse(HttpStatusCode.OK);
            response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
            await response.WriteStringAsync(message);
    
            // Return a response to both HTTP trigger and storage output binding.
            return new MultiResponse()
            {
                // Write a single message.
                Messages = new string[] { message },
                HttpResponse = response
            };
        }
    }
    
    public class MultiResponse
    {
        [QueueOutput("outqueue",Connection = "AzureStorageAccount")]
        public string[] Messages { get; set; }
        public HttpResponseData HttpResponse { get; set; }
    } 
}


Solution

    • The System.ObjectDisposedException error occurs due to the behavior of the Azure Functions Isolated Worker model, when attempting to combine an HTTP response and a queue output within a single return object, like MultiResponse.

    By using below function code successfully send the multiple messages into the queue.

    Function code:

    public class Function1
    {
        private readonly ILogger<Function1> _logger;
        private readonly string _queueName = "firstqueue";
    
        public Function1(ILogger<Function1> logger)
        {
            _logger = logger;
        }
    
        [Function("Function1")]
        public async Task<HttpResponseData> Run(
            [HttpTrigger(AuthorizationLevel.Function, "post", "get")] HttpRequestData req,
            FunctionContext context)
        {
            var logger = context.GetLogger("HttpTriggerWithQueue");
            logger.LogInformation("C# HTTP trigger function processed a request.");
    
            // Parse the request body to handle multiple messages
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            var messages = JsonConvert.DeserializeObject<List<string>>(requestBody) ?? new List<string>();
    
            if (!messages.Any())
            {
                var errorResponse = req.CreateResponse(System.Net.HttpStatusCode.BadRequest);
                await errorResponse.WriteStringAsync("Request body must contain an array of messages.");
                return errorResponse;
            }
    
            string connectionString = Environment.GetEnvironmentVariable("AzureWebJobsStorage1");
            QueueClient queueClient = new QueueClient(connectionString, _queueName);
            await queueClient.CreateIfNotExistsAsync();
    
            var failedMessages = new List<string>();
    
            // Send each message to the queue
            foreach (var message in messages)
            {
                try
                {
                    await queueClient.SendMessageAsync(message);
                }
                catch (Exception ex)
                {
                    logger.LogError($"Failed to send message '{message}' to queue: {ex.Message}");
                    failedMessages.Add(message);
                }
            }
    
            // Create the HTTP response
            var response = req.CreateResponse(System.Net.HttpStatusCode.OK);
            response.Headers.Add("Content-Type", "application/json");
    
            if (failedMessages.Any())
            {
                var errorDetails = new
                {
                    success = false,
                    failedMessages
                };
                await response.WriteStringAsync(JsonConvert.SerializeObject(errorDetails));
            }
            else
            {
                var successDetails = new
                {
                    success = true,
                    message = $"{messages.Count} messages processed successfully."
                };
                await response.WriteStringAsync(JsonConvert.SerializeObject(successDetails));
            }
    
            return response;
        }
    }
    

    Output:

    enter image description here

    enter image description here

    enter image description here