Restore RequestAborted after timed HTTP workflow failures (#7712)
* Fix RequestAborted restoration for timed HTTP workflows * Add timeout restoration tests for HttpWorkflowsMiddleware
This commit is contained in:
parent
9c24f5efe5
commit
48a087e71e
|
|
@ -252,13 +252,15 @@ public class HttpWorkflowsMiddleware(RequestDelegate next)
|
|||
// Replace the original cancellation token with the combined one.
|
||||
httpContext.RequestAborted = combinedTokenSource.Token;
|
||||
|
||||
// Execute the action.
|
||||
var result = await action(httpContext.RequestAborted);
|
||||
|
||||
// Restore the original cancellation token.
|
||||
httpContext.RequestAborted = originalCancellationToken;
|
||||
|
||||
return result;
|
||||
try
|
||||
{
|
||||
return await action(httpContext.RequestAborted);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Restore the original cancellation token even when execution faults or is canceled.
|
||||
httpContext.RequestAborted = originalCancellationToken;
|
||||
}
|
||||
}
|
||||
|
||||
private HttpRouteData GetMatchingRoute(IServiceProvider serviceProvider, string path)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using Elsa.Http.Bookmarks;
|
||||
using Elsa.Http.Middleware;
|
||||
using Elsa.Http.Options;
|
||||
|
|
@ -14,6 +15,7 @@ namespace Elsa.Http.UnitTests.Middleware;
|
|||
|
||||
public class HttpWorkflowsMiddlewareTests
|
||||
{
|
||||
private static readonly MethodInfo ExecuteWithinTimeoutAsyncMethod = typeof(HttpWorkflowsMiddleware).GetMethod("ExecuteWithinTimeoutAsync", BindingFlags.Instance | BindingFlags.NonPublic)!;
|
||||
private const string CurrentTenantId = "tenant-a";
|
||||
private const string OtherTenantId = "tenant-b";
|
||||
private const string BookmarkHash = "http-endpoint:/colliding:get";
|
||||
|
|
@ -54,6 +56,63 @@ public class HttpWorkflowsMiddlewareTests
|
|||
Assert.False(filter.TenantAgnostic);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteWithinTimeoutAsync_RestoresRequestAbortedAfterSuccess()
|
||||
{
|
||||
using var requestAbortedSource = new CancellationTokenSource();
|
||||
var httpContext = new DefaultHttpContext { RequestAborted = requestAbortedSource.Token };
|
||||
var observedToken = CancellationToken.None;
|
||||
|
||||
var result = await ExecuteWithinTimeoutAsync(async cancellationToken =>
|
||||
{
|
||||
observedToken = cancellationToken;
|
||||
Assert.Equal(cancellationToken, httpContext.RequestAborted);
|
||||
await Task.CompletedTask;
|
||||
return 42;
|
||||
}, TimeSpan.FromSeconds(1), httpContext);
|
||||
|
||||
Assert.Equal(42, result);
|
||||
Assert.NotEqual(requestAbortedSource.Token, observedToken);
|
||||
Assert.Equal(requestAbortedSource.Token, httpContext.RequestAborted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteWithinTimeoutAsync_RestoresRequestAbortedAfterFault()
|
||||
{
|
||||
using var requestAbortedSource = new CancellationTokenSource();
|
||||
var httpContext = new DefaultHttpContext { RequestAborted = requestAbortedSource.Token };
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => ExecuteWithinTimeoutAsync<int>(_ => throw new InvalidOperationException("Boom"), TimeSpan.FromSeconds(1), httpContext));
|
||||
|
||||
Assert.Equal(requestAbortedSource.Token, httpContext.RequestAborted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteWithinTimeoutAsync_RestoresRequestAbortedAfterCancellation()
|
||||
{
|
||||
using var requestAbortedSource = new CancellationTokenSource();
|
||||
requestAbortedSource.Cancel();
|
||||
|
||||
var httpContext = new DefaultHttpContext { RequestAborted = requestAbortedSource.Token };
|
||||
var observedToken = CancellationToken.None;
|
||||
|
||||
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => ExecuteWithinTimeoutAsync<int>(cancellationToken =>
|
||||
{
|
||||
observedToken = cancellationToken;
|
||||
return Task.FromCanceled<int>(cancellationToken);
|
||||
}, TimeSpan.FromSeconds(1), httpContext));
|
||||
|
||||
Assert.True(observedToken.IsCancellationRequested);
|
||||
Assert.Equal(requestAbortedSource.Token, httpContext.RequestAborted);
|
||||
}
|
||||
|
||||
private async Task<T> ExecuteWithinTimeoutAsync<T>(Func<CancellationToken, Task<T>> action, TimeSpan? requestTimeout, HttpContext httpContext)
|
||||
{
|
||||
var method = ExecuteWithinTimeoutAsyncMethod.MakeGenericMethod(typeof(T));
|
||||
var task = (Task<T>)method.Invoke(_middleware, [action, requestTimeout, httpContext])!;
|
||||
return await task;
|
||||
}
|
||||
|
||||
private static IEnumerable<StoredBookmark> CreateCollidingHttpEndpointBookmarks()
|
||||
{
|
||||
yield return CreateBookmark("current-tenant-bookmark", CurrentTenantId);
|
||||
|
|
|
|||
Loading…
Reference in a new issue