Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Azure Function, either In-Process or Independent Worker, catches exception but then continues executing code after the throw reaching 200 OK. #2484

Open
jsquire opened this issue May 16, 2024 · 0 comments

Comments

@jsquire
Copy link
Member

jsquire commented May 16, 2024

Issue Transfer

This issue has been transferred from the Azure SDK for .NET repository, #44059.

Please be aware that @radumi is the author of the original issue and include them for any questions or replies.

Details

Azure Function, either In-Process or Independent Worker, catches exception but then continues executing code after the throw reaching 200 OK instead of just 400 BadRequest

[FunctionName(nameof(ChoiceInPHttpTrigger))]
public static async Task<HttpResponseMessage> RunAsync([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, ILogger log)
{
	Task<HttpResponseMessage> tResp = ProcessInP.ProcessChoiceInPHttpTriggerAsync(req, log);
	HttpResponseMessage resp = await tResp;
	return resp;
}

public static async Task<HttpResponseMessage> ProcessChoiceInPHttpTriggerAsync(HttpRequest req, ILogger log, Processor proc = null)
{
	string signature = nameof(ProcessChoiceInPHttpTriggerAsync);

	try
	{
		signature = GetSignature(req);
		log.LogTrace($"{signature} BEGIN {req?.Method} content len={req?.ContentLength} type={req?.ContentType} isJson={req?.IsJsonContentType()}");

		LocalState lst = await LocalStateFactoryInP.CreateStateAsync(req);
		lst.ResultData = await Process.ApplyProcessorAsync(lst, proc ?? new Processor(log));  //Exception is just thrown from inside there from a synchronous static method

		log.LogWarning($"WARN {signature} IF YOU SEE THIS OK STATUS there should NOT be an exception in the scope above!");
		HttpResponseMessage resp = ResponseFactory.CreateResponse(HttpStatusCode.OK, lst.ResultData);

		log.LogTrace($"{signature} END status={resp.StatusCode} success={resp.IsSuccessStatusCode}");
		return resp;
	}
	catch (ArgumentException ex)
	{
		log.LogError($"ERROR {signature} status={HttpStatusCode.BadRequest} message={ex.Message}");
		return ResponseFactory.CreateResponseString(HttpStatusCode.BadRequest, ex.Message);
	}
	catch (ApplicationException ex)
	{
		log.LogError($"ERROR {signature} status={HttpStatusCode.ExpectationFailed} message={ex.Message}");
		return ResponseFactory.CreateResponseString(HttpStatusCode.ExpectationFailed, ex.Message);
	}
	catch (Exception ex)
	{
		log.LogError($"ERROR {signature} status={HttpStatusCode.InternalServerError} message={ex.Message} {ex.StackTrace}");
		return ResponseFactory.CreateResponseString(HttpStatusCode.InternalServerError, "See log for details");
	}
}

public static async Task<Result> ApplyProcessorAsync(LocalState lst = null, Processor proc = null)
{
	Validator.ValidateInput(lst);

	var res = new Result();

	res.Data = await proc.SomeProc.HitSomeDBReadUpperCaseAsync(lst.RepresentationData.Data);

	//Tests run in about 6 seconds
	//res.Length = await proc.OtherProc.HitSomeStorageReadLengthAsync(res.Data);
	//res.LenOverride = await proc.OtherProcInherit.HitSomeStorageReadLengthAsync(res.Data);

	//Tests run in about 4.5 seconds
	IOtherProcessor[] ops = new List<IOtherProcessor>() { proc.OtherProc, proc.OtherProcInherit }.ToArray();
	int[] r = await ComputeAsync(res.Data, ops);
	res.Length = r[0];
	res.LenOverride = r[1];

	Validator.ValidateResult(res);

	return res;
}

public static bool ValidateInput(LocalState lst)
{
	if (lst == null)
		throw new ArgumentNullException("lst", "LocalState missing");

	if (lst.RepresentationData == null)
		throw new ArgumentException("lst.RepresentationData", "Representation missing");

	if (string.IsNullOrEmpty(lst.RepresentationData.Data))
		throw new ArgumentException("lst.RepresentationData.Data", "Representation data missing"); // IT THROWS HERE

	return true;
}

Expected behavior

Log from Debug in VStudio - works as expected also like in XUnit test - catches exception returning BadRequest

[2024-05-15T19:08:58.029Z] Found C:\Test\Choice-AzF-SMC_C#\ChoiceAzF_InProcess\ChoiceAzF_InProcess.csproj. Using for user secrets file configuration.
Functions:
ChoiceInPHttpTrigger: [GET,POST] http://localhost:7039/api/ChoiceInPHttpTrigger
For detailed output, run func with --verbose flag.
[2024-05-15T19:09:07.099Z] Host lock lease acquired by instance ID '000000000000000000000000153F1E48'.
[2024-05-15T19:09:28.090Z] Executing 'ChoiceInPHttpTrigger' (Reason='This function was programmatically called via the host APIs.', Id=30781b89-2059-4a5c-920e-6be3610eae91)

[2024-05-15T19:09:28.188Z] ERROR 0HN3L2A5CL2DU:00000002 Process status=BadRequest message=lst.RepresentationData.Data (Parameter 'Representation data missing')

[2024-05-15T19:09:28.220Z] Executed 'ChoiceInPHttpTrigger' (Succeeded, Id=30781b89-2059-4a5c-920e-6be3610eae91, Duration=148ms)

Only works in Individual Worker model by adding [FromBody] RData representationData parameter
Still, I see the ERROR BadRequest logged twice
ProcessChoiceInWHttpTriggerAsync status=BadRequest message=lst.RepresentationData.Data (Parameter 'Representation data missing')

Connected!
2024-05-15T18:50:51Z   [Information]   Executing 'Functions.ChoiceInWHttpTrigger' (Reason='This function was programmatically called via the host APIs.', Id=9498f5d3-3b2d-4506-851e-a128d1f06ddc)
2024-05-15T18:50:51Z   [Verbose]   Sending invocation id: '9498f5d3-3b2d-4506-851e-a128d1f06ddc
2024-05-15T18:50:51Z   [Verbose]   Posting invocation id:9498f5d3-3b2d-4506-851e-a128d1f06ddc on workerId:1473cb2a-03a8-4bef-9aa5-3cff83557996
2024-05-15T18:50:51Z   [Information]   00-92640c9328aa71d06d492cce580bf5a4-f50925cec95a7974-00 ProcessChoiceInWHttpTriggerAsync BEGIN verb=POST

2024-05-15T18:50:51Z   [Error]   ERROR 00-92640c9328aa71d06d492cce580bf5a4-f50925cec95a7974-00 ProcessChoiceInWHttpTriggerAsync status=BadRequest message=lst.RepresentationData.Data (Parameter 'Representation data missing')

2024-05-15T18:50:51Z   [Information]   Executed 'Functions.ChoiceInWHttpTrigger' (Succeeded, Id=9498f5d3-3b2d-4506-851e-a128d1f06ddc, Duration=258ms)
2024-05-15T18:50:52Z   [Information]   Executing 'Functions.ChoiceInWHttpTrigger' (Reason='This function was programmatically called via the host APIs.', Id=bf9dcb41-917b-4a95-b4ea-5855e9fa6c0e)
2024-05-15T18:50:52Z   [Verbose]   Sending invocation id: 'bf9dcb41-917b-4a95-b4ea-5855e9fa6c0e
2024-05-15T18:50:52Z   [Verbose]   Posting invocation id:bf9dcb41-917b-4a95-b4ea-5855e9fa6c0e on workerId:1473cb2a-03a8-4bef-9aa5-3cff83557996
2024-05-15T18:50:52Z   [Information]   00-997a8f60572f4daaa1ddc4c448cf827a-f6ed3b529d0f24bc-01 ProcessChoiceInWHttpTriggerAsync BEGIN verb=POST

2024-05-15T18:50:52Z   [Error]   ERROR 00-997a8f60572f4daaa1ddc4c448cf827a-f6ed3b529d0f24bc-01 ProcessChoiceInWHttpTriggerAsync status=BadRequest message=lst.RepresentationData.Data (Parameter 'Representation data missing')

2024-05-15T18:50:52Z   [Information]   Executed 'Functions.ChoiceInWHttpTrigger' (Succeeded, Id=bf9dcb41-917b-4a95-b4ea-5855e9fa6c0e, Duration=12ms)
[Function(nameof(ChoiceInWHttpTrigger))]
//public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req, [FromBody] RData representationData, FunctionContext ctx)
public async Task<HttpResponseData> RunAsync(
	[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req,
	[FromBody] RData representationData,
	FunctionContext ctx)
{
	try
	{
		Task<HttpResponseData> tResp = ProcessInW.ProcessChoiceInWHttpTriggerAsync(req, Log, representationData, Procs);
		HttpResponseData resp = await tResp;
		return resp;
	}
	catch (Exception ex)
	{
		Log.LogError($"ERROR {nameof(ChoiceInWHttpTrigger)} ex={ex.Message} {ex.StackTrace}");
		return req.CreateResponse(HttpStatusCode.InternalServerError);
	}
}

public static async Task<HttpResponseData> ProcessChoiceInWHttpTriggerAsync(HttpRequestData req, ILogger log, IData payload, Processor proc = null)
{
	string signature = nameof(ProcessChoiceInWHttpTriggerAsync);

	try
	{
		signature = GetSignature(req, nameof(ProcessChoiceInWHttpTriggerAsync));
		log.LogInformation($"{signature} BEGIN verb={req?.Method}");

		LocalState lst = LocalStateFactoryInW.CreateState(req, payload);
		lst.ResultData = await Process.ApplyProcessorAsync(lst, proc ?? new Processor(log)); //Exception is just thrown from inside there from a synchronous static method

		log.LogWarning($"WARN {signature} IF YOU SEE THIS OK STATUS there should NOT be an exception in the scope above!");
		HttpResponseData resp = ResponseFactory.Response(req.CreateResponse(HttpStatusCode.OK), lst.ResultData);

		log.LogInformation($"{signature} END status={resp.StatusCode}");
		return resp;
	}
	catch (ArgumentException ex)
	{
		log.LogError($"ERROR {signature} status={HttpStatusCode.BadRequest} message={ex.Message}");
		return ResponseFactory.ResponseString(req.CreateResponse(HttpStatusCode.BadRequest), ex.Message);
	}
	catch (ApplicationException ex)
	{
		log.LogError($"ERROR {signature} status={HttpStatusCode.ExpectationFailed} message={ex.Message}");
		return ResponseFactory.ResponseString(req.CreateResponse(HttpStatusCode.ExpectationFailed), ex.Message);
	}
	catch (Exception ex)
	{
		log.LogError($"ERROR {signature} status={HttpStatusCode.InternalServerError} message={ex.Message} {ex.StackTrace}");
		return ResponseFactory.ResponseString(req.CreateResponse(HttpStatusCode.InternalServerError), "See log for details");
	}
}

Program.cs of the Independent Worker example is :

using Microsoft.Extensions.Hosting;
using System.Diagnostics.CodeAnalysis;

//var host = new HostBuilder().ConfigureFunctionsWorkerDefaults().Build();
//await host.RunAsync();
//host.Run();

namespace ChoiceAzFIndividualWorker
{
	[ExcludeFromCodeCoverage]
	static class Program
	{
		static async Task Main(string[] args)
		{
			var host = new HostBuilder().ConfigureFunctionsWorkerDefaults().Build();
			await host.RunAsync();
			//host.Run();
		}
	}
}

Actual behavior

Log running in Azure - after exception caught, still executes "Process IF YOU SEE THIS OK STATUS there should NOT be an exception in the scope above!"

Connected!
2024-05-15T19:15:50Z [Information] Executing 'ChoiceInPHttpTrigger' (Reason='This function was programmatically called via the host APIs.', Id=fdef415e-1402-4943-8edf-200fd9796ba3)
2024-05-15T19:15:51Z [Verbose] 4000020e-0000-ec00-b63f-84710c7967bb Process BEGIN POST content len=0 type=application/json isJson=True

2024-05-15T19:15:51Z [Error] ERROR 4000020e-0000-ec00-b63f-84710c7967bb Process status=BadRequest message=lst.RepresentationData.Data (Parameter 'Representation data missing')

2024-05-15T19:15:51Z [Information] Executed 'ChoiceInPHttpTrigger' (Succeeded, Id=fdef415e-1402-4943-8edf-200fd9796ba3, Duration=62ms)
2024-05-15T19:15:51Z [Information] Executing 'ChoiceInPHttpTrigger' (Reason='This function was programmatically called via the host APIs.', Id=d7fb7cb6-12f7-4821-a218-925c011f0f49)
2024-05-15T19:15:51Z [Verbose] 4000020f-0000-ec00-b63f-84710c7967bb Process BEGIN POST content len=2 type=application/json isJson=True

2024-05-15T19:15:51Z [Warning] WARN 4000020f-0000-ec00-b63f-84710c7967bb Process IF YOU SEE THIS OK STATUS there should NOT be an exception in the scope above!

2024-05-15T19:15:51Z [Verbose] 4000020f-0000-ec00-b63f-84710c7967bb Process END status=OK success=True
2024-05-15T19:15:51Z [Information] Executed 'ChoiceInPHttpTrigger' (Succeeded, Id=d7fb7cb6-12f7-4821-a218-925c011f0f49, Duration=242ms)

Reproduction Steps

Function is https://choiceaesb.azurewebsites.net/api/ChoiceInPHttpTrigger
POST with nothing in the body to see the 200 OK coming after the throw.

The one that works is https://choiceiwaesb.azurewebsites.net/api/ChoiceInWHttpTrigger
POST { "Data": "" } or { "Data": null } in the body to get the clean 400 Bad Request
POST { "Data": "test" } to get the clean 200 OK

Environment

Runtime 4.34.1.22669, .Net 6.0 hosted on Windows, AustraliaEastPlan (Y1: 0)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant