Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .autover/autover.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@
"Name": "Amazon.Lambda.Core",
"Path": "Libraries/src/Amazon.Lambda.Core/Amazon.Lambda.Core.csproj"
},
{
"Name": "Amazon.Lambda.DurableExecution",
"Path": "Libraries/src/Amazon.Lambda.DurableExecution/Amazon.Lambda.DurableExecution.csproj",
"PrereleaseLabel": "preview"
},
{
"Name": "Amazon.Lambda.DynamoDBEvents",
"Path": "Libraries/src/Amazon.Lambda.DynamoDBEvents/Amazon.Lambda.DynamoDBEvents.csproj"
Expand Down
11 changes: 11 additions & 0 deletions .autover/changes/91693d62-b0c7-49b0-a74f-531aa1509864.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"Projects": [
{
"Name": "Amazon.Lambda.DurableExecution",
"Type": "Patch",
"ChangelogMessages": [
"Initial preview release of the Durable Execution SDK for .NET. Build long-running Lambda workflows with automatic checkpointing via `StepAsync`, `WaitAsync`, `RunInChildContextAsync`, `CreateCallbackAsync`, and `WaitForCallbackAsync` on `IDurableContext`."
]
}
]
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ global.json

**/cdk.out/**
**/.DS_Store

# JetBrains Rider per-project cache
**/*.lscache
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ The available projects are:
* Amazon.Lambda.ConfigEvents
* Amazon.Lambda.ConnectEvents
* Amazon.Lambda.Core
* Amazon.Lambda.DurableExecution
* Amazon.Lambda.DynamoDBEvents
* Amazon.Lambda.DynamoDBEvents.SDK.Convertor
* Amazon.Lambda.KafkaEvents
Expand Down
2,267 changes: 2,267 additions & 0 deletions Docs/durable-execution-design.md

Large diffs are not rendered by default.

62 changes: 61 additions & 1 deletion Libraries/Libraries.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.5.11709.299 stable
VisualStudioVersion = 18.5.11709.299
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AAB54E74-20B1-42ED-BC3D-CE9F7BC7FD12}"
EndProject
Expand Down Expand Up @@ -155,6 +155,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResponseStreamingFunctionHa
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCoreStreamingApiGatewayTest", "test\Amazon.Lambda.RuntimeSupport.Tests\AspNetCoreStreamingApiGatewayTest\AspNetCoreStreamingApiGatewayTest.csproj", "{0768FA72-CF49-2B59-BC4C-E4CE579E5D93}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amazon.Lambda.DurableExecution", "src\Amazon.Lambda.DurableExecution\Amazon.Lambda.DurableExecution.csproj", "{9097B5A4-E100-47FD-A676-0B666A36FAFF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amazon.Lambda.DurableExecution.Tests", "test\Amazon.Lambda.DurableExecution.Tests\Amazon.Lambda.DurableExecution.Tests.csproj", "{57150BA6-3826-431F-8F58-B1D11FAFC5D4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amazon.Lambda.DurableExecution.IntegrationTests", "test\Amazon.Lambda.DurableExecution.IntegrationTests\Amazon.Lambda.DurableExecution.IntegrationTests.csproj", "{CA132CAB-FF4F-4312-B3A3-66DE9D360F27}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amazon.Lambda.DurableExecution.AotPublishTest", "test\Amazon.Lambda.DurableExecution.AotPublishTest\Amazon.Lambda.DurableExecution.AotPublishTest.csproj", "{16B1B1CC-3AFC-4DC7-8DB6-D14AE12924A2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -969,6 +977,54 @@ Global
{0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Release|x64.Build.0 = Release|Any CPU
{0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Release|x86.ActiveCfg = Release|Any CPU
{0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Release|x86.Build.0 = Release|Any CPU
{9097B5A4-E100-47FD-A676-0B666A36FAFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9097B5A4-E100-47FD-A676-0B666A36FAFF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9097B5A4-E100-47FD-A676-0B666A36FAFF}.Debug|x64.ActiveCfg = Debug|Any CPU
{9097B5A4-E100-47FD-A676-0B666A36FAFF}.Debug|x64.Build.0 = Debug|Any CPU
{9097B5A4-E100-47FD-A676-0B666A36FAFF}.Debug|x86.ActiveCfg = Debug|Any CPU
{9097B5A4-E100-47FD-A676-0B666A36FAFF}.Debug|x86.Build.0 = Debug|Any CPU
{9097B5A4-E100-47FD-A676-0B666A36FAFF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9097B5A4-E100-47FD-A676-0B666A36FAFF}.Release|Any CPU.Build.0 = Release|Any CPU
{9097B5A4-E100-47FD-A676-0B666A36FAFF}.Release|x64.ActiveCfg = Release|Any CPU
{9097B5A4-E100-47FD-A676-0B666A36FAFF}.Release|x64.Build.0 = Release|Any CPU
{9097B5A4-E100-47FD-A676-0B666A36FAFF}.Release|x86.ActiveCfg = Release|Any CPU
{9097B5A4-E100-47FD-A676-0B666A36FAFF}.Release|x86.Build.0 = Release|Any CPU
{57150BA6-3826-431F-8F58-B1D11FAFC5D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{57150BA6-3826-431F-8F58-B1D11FAFC5D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{57150BA6-3826-431F-8F58-B1D11FAFC5D4}.Debug|x64.ActiveCfg = Debug|Any CPU
{57150BA6-3826-431F-8F58-B1D11FAFC5D4}.Debug|x64.Build.0 = Debug|Any CPU
{57150BA6-3826-431F-8F58-B1D11FAFC5D4}.Debug|x86.ActiveCfg = Debug|Any CPU
{57150BA6-3826-431F-8F58-B1D11FAFC5D4}.Debug|x86.Build.0 = Debug|Any CPU
{57150BA6-3826-431F-8F58-B1D11FAFC5D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{57150BA6-3826-431F-8F58-B1D11FAFC5D4}.Release|Any CPU.Build.0 = Release|Any CPU
{57150BA6-3826-431F-8F58-B1D11FAFC5D4}.Release|x64.ActiveCfg = Release|Any CPU
{57150BA6-3826-431F-8F58-B1D11FAFC5D4}.Release|x64.Build.0 = Release|Any CPU
{57150BA6-3826-431F-8F58-B1D11FAFC5D4}.Release|x86.ActiveCfg = Release|Any CPU
{57150BA6-3826-431F-8F58-B1D11FAFC5D4}.Release|x86.Build.0 = Release|Any CPU
{CA132CAB-FF4F-4312-B3A3-66DE9D360F27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CA132CAB-FF4F-4312-B3A3-66DE9D360F27}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CA132CAB-FF4F-4312-B3A3-66DE9D360F27}.Debug|x64.ActiveCfg = Debug|Any CPU
{CA132CAB-FF4F-4312-B3A3-66DE9D360F27}.Debug|x64.Build.0 = Debug|Any CPU
{CA132CAB-FF4F-4312-B3A3-66DE9D360F27}.Debug|x86.ActiveCfg = Debug|Any CPU
{CA132CAB-FF4F-4312-B3A3-66DE9D360F27}.Debug|x86.Build.0 = Debug|Any CPU
{CA132CAB-FF4F-4312-B3A3-66DE9D360F27}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CA132CAB-FF4F-4312-B3A3-66DE9D360F27}.Release|Any CPU.Build.0 = Release|Any CPU
{CA132CAB-FF4F-4312-B3A3-66DE9D360F27}.Release|x64.ActiveCfg = Release|Any CPU
{CA132CAB-FF4F-4312-B3A3-66DE9D360F27}.Release|x64.Build.0 = Release|Any CPU
{CA132CAB-FF4F-4312-B3A3-66DE9D360F27}.Release|x86.ActiveCfg = Release|Any CPU
{CA132CAB-FF4F-4312-B3A3-66DE9D360F27}.Release|x86.Build.0 = Release|Any CPU
{16B1B1CC-3AFC-4DC7-8DB6-D14AE12924A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{16B1B1CC-3AFC-4DC7-8DB6-D14AE12924A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{16B1B1CC-3AFC-4DC7-8DB6-D14AE12924A2}.Debug|x64.ActiveCfg = Debug|Any CPU
{16B1B1CC-3AFC-4DC7-8DB6-D14AE12924A2}.Debug|x64.Build.0 = Debug|Any CPU
{16B1B1CC-3AFC-4DC7-8DB6-D14AE12924A2}.Debug|x86.ActiveCfg = Debug|Any CPU
{16B1B1CC-3AFC-4DC7-8DB6-D14AE12924A2}.Debug|x86.Build.0 = Debug|Any CPU
{16B1B1CC-3AFC-4DC7-8DB6-D14AE12924A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{16B1B1CC-3AFC-4DC7-8DB6-D14AE12924A2}.Release|Any CPU.Build.0 = Release|Any CPU
{16B1B1CC-3AFC-4DC7-8DB6-D14AE12924A2}.Release|x64.ActiveCfg = Release|Any CPU
{16B1B1CC-3AFC-4DC7-8DB6-D14AE12924A2}.Release|x64.Build.0 = Release|Any CPU
{16B1B1CC-3AFC-4DC7-8DB6-D14AE12924A2}.Release|x86.ActiveCfg = Release|Any CPU
{16B1B1CC-3AFC-4DC7-8DB6-D14AE12924A2}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1045,6 +1101,10 @@ Global
{80594C21-C6EB-469E-83CC-68F9F661CA5E} = {1DE4EE60-45BA-4EF7-BE00-B9EB861E4C69}
{E404A7AC-812B-BC03-CA76-02C0BC2BA7F9} = {B5BD0336-7D08-492C-8489-42C987E29B39}
{0768FA72-CF49-2B59-BC4C-E4CE579E5D93} = {B5BD0336-7D08-492C-8489-42C987E29B39}
{9097B5A4-E100-47FD-A676-0B666A36FAFF} = {AAB54E74-20B1-42ED-BC3D-CE9F7BC7FD12}
{57150BA6-3826-431F-8F58-B1D11FAFC5D4} = {1DE4EE60-45BA-4EF7-BE00-B9EB861E4C69}
{CA132CAB-FF4F-4312-B3A3-66DE9D360F27} = {1DE4EE60-45BA-4EF7-BE00-B9EB861E4C69}
{16B1B1CC-3AFC-4DC7-8DB6-D14AE12924A2} = {1DE4EE60-45BA-4EF7-BE00-B9EB861E4C69}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {503678A4-B8D1-4486-8915-405A3E9CF0EB}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">

<Import Project="..\..\..\buildtools\common.props" />

<PropertyGroup>
<TargetFrameworks>$(DefaultPackageTargets)</TargetFrameworks>
<Description>Amazon Lambda .NET SDK for Durable Execution - write multi-step workflows that persist state automatically.</Description>
<AssemblyTitle>Amazon.Lambda.DurableExecution</AssemblyTitle>
<Version>0.0.1</Version>
<AssemblyName>Amazon.Lambda.DurableExecution</AssemblyName>
<PackageId>Amazon.Lambda.DurableExecution</PackageId>
<PackageTags>AWS;Amazon;Lambda;Durable;Workflow</PackageTags>
<IsTrimmable>true</IsTrimmable>
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors>IL2026,IL2067,IL2075,IL3050</WarningsAsErrors>
<!-- DurableExecution intentionally consumes the preview ILambdaContext.Serializer
API. The whole package is in development (0.x), so suppressing project-wide
is appropriate; downstream users still see AWSLAMBDA001 in their own code. -->
<NoWarn>$(NoWarn);AWSLAMBDA001</NoWarn>
</PropertyGroup>

<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>Amazon.Lambda.DurableExecution.Tests, PublicKey="0024000004800000940000000602000000240000525341310004000001000100db5f59f098d27276c7833875a6263a3cc74ab17ba9a9df0b52aedbe7252745db7274d5271fd79c1f08f668ecfa8eaab5626fa76adc811d3c8fc55859b0d09d3bc0a84eecd0ba891f2b8a2fc55141cdcc37c2053d53491e650a479967c3622762977900eddbf1252ed08a2413f00a28f3a0752a81203f03ccb7f684db373518b4"</_Parameter1>
</AssemblyAttribute>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Amazon.Lambda.Core\Amazon.Lambda.Core.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="AWSSDK.Lambda" Version="4.0.13.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
</ItemGroup>

</Project>
80 changes: 80 additions & 0 deletions Libraries/src/Amazon.Lambda.DurableExecution/CallbackConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

namespace Amazon.Lambda.DurableExecution;

/// <summary>
/// Configuration for callback operations created via
/// <see cref="IDurableContext.CreateCallbackAsync{T}(string?, CallbackConfig?, System.Threading.CancellationToken)"/>.
/// </summary>
public class CallbackConfig
{
private TimeSpan _timeout = TimeSpan.Zero;
private TimeSpan _heartbeatTimeout = TimeSpan.Zero;

/// <summary>
/// Maximum total time the service will wait for the external system to
/// complete the callback. <see cref="TimeSpan.Zero"/> (default) means no
/// overall timeout — only <see cref="HeartbeatTimeout"/> applies (if set).
/// </summary>
/// <remarks>
/// The service's timer granularity is 1 second, so values strictly between
/// <see cref="TimeSpan.Zero"/> and 1 second are rejected to avoid silent
/// rounding. Use <see cref="TimeSpan.Zero"/> to disable the timeout, or a
/// value of at least 1 second.
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when set to a positive value less than 1 second.
/// </exception>
public TimeSpan Timeout
{
get => _timeout;
set
{
ValidateTimeout(value, nameof(Timeout));
_timeout = value;
}
}

/// <summary>
/// Maximum gap between heartbeat signals from the external system before
/// the service marks the callback as timed-out.
/// <see cref="TimeSpan.Zero"/> (default) means no heartbeat timeout.
/// </summary>
/// <remarks>
/// The service's timer granularity is 1 second, so values strictly between
/// <see cref="TimeSpan.Zero"/> and 1 second are rejected to avoid silent
/// rounding. Use <see cref="TimeSpan.Zero"/> to disable the heartbeat
/// timeout, or a value of at least 1 second.
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when set to a positive value less than 1 second.
/// </exception>
public TimeSpan HeartbeatTimeout
{
get => _heartbeatTimeout;
set
{
ValidateTimeout(value, nameof(HeartbeatTimeout));
_heartbeatTimeout = value;
}
}

private static void ValidateTimeout(TimeSpan value, string paramName)
{
// Allow Zero (means "not set"); reject negative; reject sub-second
// positive values to mirror WaitAsync's behavior and prevent silent
// rounding-up inside BuildCallbackOptions.
if (value < TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(
paramName, value, $"{paramName} must be non-negative.");
}
if (value > TimeSpan.Zero && value < TimeSpan.FromSeconds(1))
{
throw new ArgumentOutOfRangeException(
paramName, value,
$"{paramName} must be at least 1 second (or TimeSpan.Zero to disable).");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

namespace Amazon.Lambda.DurableExecution;

/// <summary>
/// Base exception type for callback failures surfaced from
/// <see cref="ICallback{T}.GetResultAsync(System.Threading.CancellationToken)"/>
/// or
/// <see cref="IDurableContext.WaitForCallbackAsync{T}(System.Func{string, IWaitForCallbackContext, System.Threading.Tasks.Task}, string?, WaitForCallbackConfig?, System.Threading.CancellationToken)"/>.
/// Concrete subclasses distinguish failure modes — pattern-match
/// <see cref="CallbackFailedException"/>, <see cref="CallbackTimeoutException"/>,
/// or <see cref="CallbackSubmitterException"/> in <c>catch</c> clauses.
/// </summary>
public class CallbackException : DurableExecutionException
{
/// <summary>The callback ID associated with the failure (if known).</summary>
public string? CallbackId { get; init; }

/// <summary>The fully-qualified type name of the original error, if known.</summary>
public string? ErrorType { get; init; }

/// <summary>Optional structured error data attached by the external system.</summary>
public string? ErrorData { get; init; }

/// <summary>Stack trace of the original error, captured before serialization.</summary>
public IReadOnlyList<string>? OriginalStackTrace { get; init; }

/// <summary>Creates an empty <see cref="CallbackException"/>.</summary>
public CallbackException() { }

/// <summary>Creates a <see cref="CallbackException"/> with the given message.</summary>
public CallbackException(string message) : base(message) { }

/// <summary>Creates a <see cref="CallbackException"/> wrapping an inner exception.</summary>
public CallbackException(string message, Exception innerException) : base(message, innerException) { }
}

/// <summary>
/// Thrown when the external system reports a failure result for a callback
/// (via <c>SendDurableExecutionCallbackFailure</c>).
/// </summary>
public class CallbackFailedException : CallbackException
{
/// <summary>Creates an empty <see cref="CallbackFailedException"/>.</summary>
public CallbackFailedException() { }

/// <summary>Creates a <see cref="CallbackFailedException"/> with the given message.</summary>
public CallbackFailedException(string message) : base(message) { }

/// <summary>Creates a <see cref="CallbackFailedException"/> wrapping an inner exception.</summary>
public CallbackFailedException(string message, Exception innerException) : base(message, innerException) { }
}

/// <summary>
/// Thrown when the durable execution service marks a callback as timed-out —
/// either the overall <see cref="CallbackConfig.Timeout"/> or the
/// <see cref="CallbackConfig.HeartbeatTimeout"/> elapsed.
/// </summary>
public class CallbackTimeoutException : CallbackException
{
/// <summary>Creates an empty <see cref="CallbackTimeoutException"/>.</summary>
public CallbackTimeoutException() { }

/// <summary>Creates a <see cref="CallbackTimeoutException"/> with the given message.</summary>
public CallbackTimeoutException(string message) : base(message) { }

/// <summary>Creates a <see cref="CallbackTimeoutException"/> wrapping an inner exception.</summary>
public CallbackTimeoutException(string message, Exception innerException) : base(message, innerException) { }
}

/// <summary>
/// Thrown only from
/// <see cref="IDurableContext.WaitForCallbackAsync{T}(System.Func{string, IWaitForCallbackContext, System.Threading.Tasks.Task}, string?, WaitForCallbackConfig?, System.Threading.CancellationToken)"/>
/// when the user-supplied submitter delegate (the step that hands the callback
/// ID to the external system) fails after retries are exhausted. Wraps the
/// underlying <see cref="StepException"/> as <see cref="System.Exception.InnerException"/>.
/// </summary>
public class CallbackSubmitterException : CallbackException
{
/// <summary>Creates an empty <see cref="CallbackSubmitterException"/>.</summary>
public CallbackSubmitterException() { }

/// <summary>Creates a <see cref="CallbackSubmitterException"/> with the given message.</summary>
public CallbackSubmitterException(string message) : base(message) { }

/// <summary>Creates a <see cref="CallbackSubmitterException"/> wrapping an inner exception.</summary>
public CallbackSubmitterException(string message, Exception innerException) : base(message, innerException) { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

namespace Amazon.Lambda.DurableExecution;

/// <summary>
/// Configuration for a child context.
/// </summary>
/// <remarks>
/// A child context is a logical sub-workflow with its own deterministic
/// operation-ID space, persisted as a <c>CONTEXT</c> operation. Use
/// <see cref="IDurableContext.RunInChildContextAsync{T}(System.Func{IDurableContext, System.Threading.Tasks.Task{T}}, string?, ChildContextConfig?, System.Threading.CancellationToken)"/>
/// (and overloads) to run code inside one.
/// </remarks>
public sealed class ChildContextConfig
{
/// <summary>
/// Operation sub-type label for observability (e.g. <c>"WaitForCallback"</c>).
/// Surfaces on the wire <c>OperationUpdate.SubType</c> field.
/// </summary>
public string? SubType { get; set; }

/// <summary>
/// Optional function to transform exceptions thrown by the child context's
/// user function before they surface to the caller. Useful for wrapping
/// low-level errors into domain-specific exceptions.
/// </summary>
/// <remarks>
/// Applied when the user function throws (the mapped exception propagates
/// to the caller of <c>RunInChildContextAsync</c>) and on replay of a
/// <c>FAILED</c> child context (the constructed
/// <see cref="ChildContextException"/> is mapped before being thrown).
/// </remarks>
public Func<Exception, Exception>? ErrorMapping { get; set; }
}
Loading
Loading