From 2c06eafccc537a84c7576fbbdd44410b284244d0 Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Tue, 17 Sep 2024 15:59:28 -0700 Subject: [PATCH] Set EnableUserCodeException capability to true by default (#2702) --- release_notes.md | 26 +++++++++++-------- .../Hosting/WorkerOptions.cs | 10 ++++--- test/DotNetWorkerTests/GrpcWorkerTests.cs | 4 ++- .../Handlers/InvocationHandlerTests.cs | 24 ++++++++++------- 4 files changed, 39 insertions(+), 25 deletions(-) diff --git a/release_notes.md b/release_notes.md index 2cf75c52..96bef8d7 100644 --- a/release_notes.md +++ b/release_notes.md @@ -16,21 +16,25 @@ - Capability `IncludeEmptyEntriesInMessagePayload` is now enabled by default (#2701) - This means that empty entries will be included in the function trigger message payload by default. - To disable this capability and return to the old behaviour, set `IncludeEmptyEntriesInMessagePayload` to `false` in the worker options. - - ```csharp - var host = new HostBuilder() - .ConfigureFunctionsWorkerDefaults(builder => - { - }, options => - { - options.IncludeEmptyEntriesInMessagePayload = false; - }) - .Build(); - ``` +- Capability `EnableUserCodeException` is now enabled by default (#2702) + - This means that exceptions thrown by user code will be surfaced to the Host as their original exception type, instead of being wrapped in an RpcException. + - To disable this capability and return to the old behaviour, set `EnableUserCodeException` to `false` in the worker options. + - The `EnableUserCodeException` property in WorkerOptions has been marked as obsolete and may be removed in a future release. - Rename `ILoggerExtensions` to `FunctionsLoggerExtensions` to avoid naming conflict issues (#2716) - Updating `Azure.Core` to 1.41.0 - Updated service registrations for bootstrapping methods to ensure idempotency. +##### Setting worker options example + +```csharp +var host = new HostBuilder() +.ConfigureFunctionsWorkerDefaults(options => +{ + options.EnableUserCodeException = false; + options.IncludeEmptyEntriesInMessagePayload = false; +}) +``` + ### Microsoft.Azure.Functions.Worker.Grpc 2.0.0 - Removed fallback command line argument reading code for grpc worker startup options. (#1908) diff --git a/src/DotNetWorker.Core/Hosting/WorkerOptions.cs b/src/DotNetWorker.Core/Hosting/WorkerOptions.cs index f1e98f8b..c71b6dc1 100644 --- a/src/DotNetWorker.Core/Hosting/WorkerOptions.cs +++ b/src/DotNetWorker.Core/Hosting/WorkerOptions.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Text.Json; using Azure.Core.Serialization; @@ -32,13 +33,16 @@ namespace Microsoft.Azure.Functions.Worker // Enable these by default, although they are not strictly required and can be removed { WorkerCapabilities.HandlesWorkerTerminateMessage, bool.TrueString }, { WorkerCapabilities.HandlesInvocationCancelMessage, bool.TrueString }, - { WorkerCapabilities.IncludeEmptyEntriesInMessagePayload, bool.TrueString } + { WorkerCapabilities.IncludeEmptyEntriesInMessagePayload, bool.TrueString }, + { WorkerCapabilities.EnableUserCodeException, bool.TrueString } }; /// - /// Gets or sets the flag for opting in to unwrapping user-code-thrown - /// exceptions when they are surfaced to the Host. + /// Gets or sets a value indicating whether exceptions thrown by user code should be unwrapped + /// and surfaced to the Host as their original exception type, instead of being wrapped in an RpcException. + /// The default value is . /// + [Obsolete("This is now the default behavior. This property may be unavailable in future releases.", false)] public bool EnableUserCodeException { get => GetBoolCapability(nameof(EnableUserCodeException)); diff --git a/test/DotNetWorkerTests/GrpcWorkerTests.cs b/test/DotNetWorkerTests/GrpcWorkerTests.cs index ee2cd473..aa393842 100644 --- a/test/DotNetWorkerTests/GrpcWorkerTests.cs +++ b/test/DotNetWorkerTests/GrpcWorkerTests.cs @@ -9,7 +9,6 @@ using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using Azure.Core.Serialization; using Microsoft.Azure.Functions.Tests; using Microsoft.Azure.Functions.Worker.Context.Features; using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata; @@ -178,6 +177,8 @@ namespace Microsoft.Azure.Functions.Worker.Tests [Theory] [InlineData("IncludeEmptyEntriesInMessagePayload", true, "IncludeEmptyEntriesInMessagePayload", true, "True")] [InlineData("IncludeEmptyEntriesInMessagePayload", false, "IncludeEmptyEntriesInMessagePayload", false)] + [InlineData("EnableUserCodeException", true, "EnableUserCodeException", true, "True")] + [InlineData("EnableUserCodeException", false, "EnableUserCodeException", false)] public void InitRequest_ReturnsExpectedCapabilities_BasedOnWorkerOptions( string booleanPropertyName, bool booleanPropertyValue, @@ -233,6 +234,7 @@ namespace Microsoft.Azure.Functions.Worker.Tests } Assert.Collection(response.Capabilities.OrderBy(p => p.Key), + c => AssertKeyAndValue(c, "EnableUserCodeException", bool.TrueString), c => AssertKeyAndValue(c, "HandlesInvocationCancelMessage", bool.TrueString), c => AssertKeyAndValue(c, "IncludeEmptyEntriesInMessagePayload", bool.TrueString), c => AssertKeyAndValue(c, "RawHttpBodyBytes", bool.TrueString), diff --git a/test/DotNetWorkerTests/Handlers/InvocationHandlerTests.cs b/test/DotNetWorkerTests/Handlers/InvocationHandlerTests.cs index 95363894..061706ac 100644 --- a/test/DotNetWorkerTests/Handlers/InvocationHandlerTests.cs +++ b/test/DotNetWorkerTests/Handlers/InvocationHandlerTests.cs @@ -77,14 +77,15 @@ namespace Microsoft.Azure.Functions.Worker.Tests { _mockApplication .Setup(m => m.InvokeFunctionAsync(It.IsAny())) - .Throws(new AggregateException(new Exception[] { new TaskCanceledException() })); + .Throws(new AggregateException(new TaskCanceledException())); var request = TestUtility.CreateInvocationRequest("abc"); var invocationHandler = CreateInvocationHandler(); var response = await invocationHandler.InvokeAsync(request); Assert.Equal(StatusResult.Types.Status.Cancelled, response.Result.Status); - Assert.Contains("TaskCanceledException", response.Result.Exception.Message); + Assert.Equal("System.AggregateException", response.Result.Exception.Type); + Assert.Contains("A task was canceled", response.Result.Exception.Message); } [Fact] @@ -129,15 +130,16 @@ namespace Microsoft.Azure.Functions.Worker.Tests } /// - /// Test unwrapping user code exception functionality. + /// Test unwrapping user code exception functionality. + /// EnableUserCodeException capability is true by default. /// [Fact] public async Task InvokeAsync_UserCodeThrowsException_OptionEnabled() { var exceptionMessage = "user code exception"; + var mockOptions = new OptionsWrapper(new() { - EnableUserCodeException = true, Serializer = new JsonObjectSerializer() }); @@ -156,23 +158,24 @@ namespace Microsoft.Azure.Functions.Worker.Tests } /// - /// Test keeping user code exception wrapped as RpcException. + /// Test keeping user code exception wrapped as RpcException. /// [Fact] public async Task InvokeAsync_UserCodeThrowsException_OptionDisabled() { var exceptionMessage = "user code exception"; - var mockOptions = new WorkerOptions() + var mockOptions = new OptionsWrapper(new() { + Serializer = new JsonObjectSerializer(), EnableUserCodeException = false - }; + }); _mockApplication .Setup(m => m.InvokeFunctionAsync(It.IsAny())) .Throws(new Exception(exceptionMessage)); var request = TestUtility.CreateInvocationRequest("abc"); - var invocationHandler = CreateInvocationHandler(); + var invocationHandler = CreateInvocationHandler(workerOptions: mockOptions); var response = await invocationHandler.InvokeAsync(request); Assert.Equal(StatusResult.Types.Status.Failure, response.Result.Status); @@ -239,8 +242,9 @@ namespace Microsoft.Azure.Functions.Worker.Tests var response = await invocationHandler.InvokeAsync(request); Assert.Equal(StatusResult.Types.Status.Failure, response.Result.Status); - Assert.Contains("InvalidOperationException: whoops", response.Result.Exception.Message); - Assert.Contains("CreateContext", response.Result.Exception.Message); + Assert.Equal("System.InvalidOperationException", response.Result.Exception.Type); + Assert.Contains("whoops", response.Result.Exception.Message); + Assert.Contains("CreateContext", response.Result.Exception.StackTrace); } [Fact]