GoogleCloudPlatform/functions-framework-dotnet

HTTPClient problem if called on OnCompleted

lucas-zimerman opened this issue · 3 comments

I noticed that if you try to make a Request inside of context.Response.OnCompleted you'll get the following exception:

System.Net.Http.HttpRequestException The SSL connection could not be established, see inner exception
Authentication failed because the remote party has closed the transport stream.

And I was able to reproduce with the following example:

using System;
using System.Net.Http;
using System.Threading.Tasks;
using Google.Cloud.Functions.Framework;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;

namespace Cloud.Functions.Examples.MultiProjectFunction
{
    public class Function : IHttpFunction
    {
        private readonly ILogger<Function> _logger;

        public Function(ILogger<Function> logger)
        {
            _logger = logger;
        }

        public async Task SendStuff(string name)
        {
            try
            {
                var client = new HttpClient();
                var content = new StringContent("{}");
                content.Headers.Add("X-Sentry-Auth",
                    "Sentry sentry_version=7,sentry_client=sentry.dotnet,sentry_key=eb18e953812b41c3aeb042e666fd3b5c,sentry_timestamp=1632864997");
                _logger.LogInformation(name + ": Sending Request");
                var response =
                    await client.PostAsync("https://o447951.ingest.sentry.io/api/5428537/envelope/", content);
                _logger.LogInformation(name + ": Sent, Status = " + response.StatusCode);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Whoops, failed");
                var e = ex;
                while (e != null)
                {
                    _logger.LogInformation(e.GetType() + " " + e.Message + " " + e.StackTrace);
                    e = e.InnerException;
                }
            }
        }
        public async Task HandleAsync(HttpContext context)
        {
            context.Response.OnCompleted(async () =>
            {
                _logger.LogInformation("Starting OnComplete");
                await Task.Delay(1000);
                await SendStuff("OnCompleted");
                _logger.LogInformation("End OnComplete");
            });
            await context.Response.WriteAsync("Hello World");
        }
    }

Is there a way to make that HTTP request work when called from OnCompleted?
NOTE: The error only happens when running on GCP, the same error doesn't happen on a local machine.

Locally I get the following output

2021-09-28T22:05:37.669Z [Cloud.Functions.Examples.MultiProjectFunction.Function] [info] Starting OnComplete
2021-09-28T22:05:38.692Z [Cloud.Functions.Examples.MultiProjectFunction.Function] [info] OnCompleted: Sending Request
2021-09-28T22:05:39.150Z [Cloud.Functions.Examples.MultiProjectFunction.Function] [info] OnCompleted: Sent, Status = BadRequest
2021-09-28T22:05:39.150Z [Cloud.Functions.Examples.MultiProjectFunction.Function] [info] End OnComplete

I suspect this is because the behavior of the Functions infrastructure is to effectively pause the machine when a request completes, whereas you want to keep executing after that. I'll have to talk with Functions folks to check that my understanding is correct, and figure out the best advice.

I've been in touch with the product team, and yes, this is expected behavior due to the way CPU is throttled as soon as the response is returned. See the Function execution timeline for more information.

We may work on providing more flexibility around this in the future, but it's unlikely to change in the short term. For now, all I can recommend is that you make the HTTP call before returning the response. (If the HTTP call itself is likely to take a while, you could potentially put it in a PubSub queue that another function handles, of course.)

I see, thank you for the clarification.