Skip to content

Commit 8fbe8b3

Browse files
authored
Update ContextReference & DefaultFromBodyConversionFeature to remove invocation cancellation token usage (#2894)
1 parent bf563c4 commit 8fbe8b3

File tree

22 files changed

+357
-97
lines changed

22 files changed

+357
-97
lines changed

eng/ci/templates/jobs/run-integration-tests-windows.yml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,20 @@ jobs:
4444
displayName: 'Run E2E Tests'
4545
inputs:
4646
command: test
47-
arguments: -v n --no-build -c Release
47+
arguments: -v n --no-build -c Release --filter "FullyQualifiedName!~Microsoft.Azure.Functions.Worker.E2ETests.AspNetCore" # skip AspNetCore tests
4848
projects: |
4949
**\E2ETests.csproj
5050
**\SdkE2ETests.csproj
51+
env:
52+
DOTNET_VERSION: $(dotnetVersion)
53+
54+
- task: DotNetCoreCLI@2
55+
displayName: 'Run E2E AspNetCore Tests'
56+
condition: ne(variables['dotnetVersion'], 'netfx') # Skip if dotnetVersion is netfx
57+
inputs:
58+
command: test
59+
arguments: -v n --no-build -c Release --filter "FullyQualifiedName~Microsoft.Azure.Functions.Worker.E2ETests.AspNetCore" # only AspNetCore tests
60+
projects: |
61+
**\E2ETests.csproj
62+
env:
63+
DOTNET_VERSION: $(dotnetVersion)

extensions/Worker.Extensions.Http.AspNetCore/release_notes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
### Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore <version>
88

9-
- <entry>
9+
- Update `ContextReference` to no longer use a given invocation's cancellation token (#2894)
1010

1111
### Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.Analyzers <version>
1212

extensions/Worker.Extensions.Http.AspNetCore/src/Coordinator/ContextReference.cs

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,14 @@
88

99
namespace Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore
1010
{
11-
internal class ContextReference : IDisposable
11+
internal class ContextReference
1212
{
1313
private readonly TaskCompletionSource<bool> _functionStartTask = new();
1414
private readonly TaskCompletionSource<bool> _functionCompletionTask = new();
1515

1616
private TaskCompletionSource<HttpContext> _httpContextValueSource = new();
1717
private TaskCompletionSource<FunctionContext> _functionContextValueSource = new();
1818

19-
private CancellationToken _token;
20-
private CancellationTokenRegistration _tokenRegistration;
21-
2219
public ContextReference(string invocationId)
2320
{
2421
InvocationId = invocationId;
@@ -32,18 +29,6 @@ public ContextReference(string invocationId)
3229

3330
public TaskCompletionSource<FunctionContext> FunctionContextValueSource { get => _functionContextValueSource; set => _functionContextValueSource = value; }
3431

35-
internal void SetCancellationToken(CancellationToken token)
36-
{
37-
_token = token;
38-
_tokenRegistration = _token.Register(() =>
39-
{
40-
_functionStartTask.TrySetCanceled();
41-
_functionCompletionTask.TrySetCanceled();
42-
_functionContextValueSource.TrySetCanceled();
43-
_httpContextValueSource.TrySetCanceled();
44-
});
45-
}
46-
4732
internal Task InvokeFunctionAsync()
4833
{
4934
_functionStartTask.SetResult(true);
@@ -59,27 +44,12 @@ internal void CompleteFunction()
5944

6045
if (_httpContextValueSource.Task.IsCompleted)
6146
{
62-
if (_httpContextValueSource.Task.IsCanceled || _token.IsCancellationRequested)
63-
{
64-
_functionCompletionTask.TrySetCanceled();
65-
}
66-
else
67-
{
68-
_functionCompletionTask.TrySetResult(true);
69-
}
47+
_functionCompletionTask.TrySetResult(true);
7048
}
7149
else
7250
{
7351
// we should never reach here b/c the class that calls this needs httpContextValueSource to complete to reach this method
7452
}
7553
}
76-
77-
public void Dispose()
78-
{
79-
if (_tokenRegistration != default)
80-
{
81-
_tokenRegistration.Dispose();
82-
}
83-
}
8454
}
8555
}

extensions/Worker.Extensions.Http.AspNetCore/src/Coordinator/DefaultHttpCoordinator.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System;
@@ -44,7 +44,6 @@ public async Task<FunctionContext> SetHttpContextAsync(string invocationId, Http
4444
public async Task<HttpContext> SetFunctionContextAsync(string invocationId, FunctionContext context)
4545
{
4646
var contextRef = _contextReferenceList.GetOrAdd(invocationId, static id => new ContextReference(id));
47-
contextRef.SetCancellationToken(context.CancellationToken);
4847
contextRef.FunctionContextValueSource.SetResult(context);
4948

5049
_logger.FunctionContextSet(invocationId);
@@ -85,7 +84,6 @@ public void CompleteFunctionInvocation(string invocationId)
8584
if (_contextReferenceList.TryRemove(invocationId, out var contextRef))
8685
{
8786
contextRef.CompleteFunction();
88-
contextRef.Dispose();
8987
}
9088
else
9189
{

extensions/Worker.Extensions.Http/release_notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
### Microsoft.Azure.Functions.Worker.Extensions.Http <version>
88

99
- The 'FromBody' converter now utilizes `DeserializeAsync` for deserializing JSON content from the request body, enhancing support for asynchronous deserialization. (#2901)
10+
- Update `DefaultFromBodyConversionFeature` to no longer use a given invocation's cancellation token (#2894)

extensions/Worker.Extensions.Http/src/DefaultFromBodyConversionFeature.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ internal class DefaultFromBodyConversionFeature : IFromBodyConversionFeature
5555
_ when HasJsonContentType(requestData) =>
5656
await (requestData.FunctionContext.InstanceServices.GetService<IOptions<WorkerOptions>>()?.Value?.Serializer
5757
?? throw new InvalidOperationException("A serializer is not configured for the worker."))
58-
.DeserializeAsync(requestData.Body, targetType, context.CancellationToken),
58+
.DeserializeAsync(requestData.Body, targetType, CancellationToken.None),
5959
_ => throw new InvalidOperationException($"The type '{targetType}' is not supported by the '{nameof(DefaultFromBodyConversionFeature)}'.")
6060
};
6161

setup-e2e-tests.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ if ($SkipBuildOnPack -eq $true)
113113
$AdditionalPackArgs += "--no-build"
114114
}
115115

116-
.\tools\devpack.ps1 -E2E -AdditionalPackArgs $AdditionalPackArgs
116+
.\tools\devpack.ps1 -DotnetVersion $DotnetVersion -E2E -AdditionalPackArgs $AdditionalPackArgs
117117

118118
if ($SkipStorageEmulator -And $SkipCosmosDBEmulator)
119119
{

test/E2ETests/E2EApps/E2EApp/local.settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"IsEncrypted": false,
2+
"IsEncrypted": false,
33
"Values": {
44
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
55
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Microsoft.Extensions.Logging;
7+
using Microsoft.AspNetCore.Http;
8+
using Microsoft.AspNetCore.Mvc;
9+
using System;
10+
11+
namespace Microsoft.Azure.Functions.Worker.E2EApp
12+
{
13+
public class CancellationHttpFunctions(ILogger<CancellationHttpFunctions> logger)
14+
{
15+
private readonly ILogger<CancellationHttpFunctions> _logger = logger;
16+
17+
[Function(nameof(HttpWithCancellationTokenNotUsed))]
18+
public async Task<IActionResult> HttpWithCancellationTokenNotUsed(
19+
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req)
20+
{
21+
_logger.LogInformation("HttpWithCancellationTokenNotUsed processed a request.");
22+
23+
await SimulateWork(CancellationToken.None);
24+
25+
return new OkObjectResult("Processing completed successfully.");
26+
}
27+
28+
[Function(nameof(HttpWithCancellationTokenIgnored))]
29+
public async Task<IActionResult> HttpWithCancellationTokenIgnored(
30+
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req,
31+
CancellationToken cancellationToken)
32+
{
33+
_logger.LogInformation("HttpWithCancellationTokenIgnored processed a request.");
34+
35+
await SimulateWork(cancellationToken);
36+
37+
return new OkObjectResult("Processing completed successfully.");
38+
}
39+
40+
[Function(nameof(HttpWithCancellationTokenHandled))]
41+
public async Task<IActionResult> HttpWithCancellationTokenHandled(
42+
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
43+
CancellationToken cancellationToken)
44+
{
45+
_logger.LogInformation("HttpWithCancellationTokenHandled processed a request.");
46+
47+
try
48+
{
49+
await SimulateWork(cancellationToken);
50+
51+
return new OkObjectResult("Processing completed successfully.");
52+
}
53+
catch (OperationCanceledException)
54+
{
55+
_logger.LogWarning("Request was cancelled.");
56+
57+
// Take precautions like noting how far along you are with processing the batch
58+
await Task.Delay(1000);
59+
60+
return new ObjectResult(new { statusCode = StatusCodes.Status499ClientClosedRequest, message = "Request was cancelled." });
61+
}
62+
}
63+
64+
private async Task SimulateWork(CancellationToken cancellationToken)
65+
{
66+
_logger.LogInformation("Starting work...");
67+
68+
for (int i = 0; i < 5; i++)
69+
{
70+
// Simulate work
71+
await Task.Delay(1000, cancellationToken);
72+
_logger.LogWarning($"Work iteration {i + 1} completed.");
73+
}
74+
75+
_logger.LogInformation("Work completed.");
76+
}
77+
}
78+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net8.0</TargetFramework>
4+
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
5+
<OutputType>Exe</OutputType>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<AssemblyName>Microsoft.Azure.Functions.Worker.E2EAspNetCoreApp</AssemblyName>
9+
<RootNamespace>Microsoft.Azure.Functions.Worker.E2EAspNetCoreApp</RootNamespace>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<ProjectReference Include="..\..\..\..\extensions\Worker.Extensions.Http\src\Worker.Extensions.Http.csproj" />
14+
<ProjectReference Include="..\..\..\..\extensions\Worker.Extensions.Http.AspNetCore\src\Worker.Extensions.Http.AspNetCore.csproj" />
15+
</ItemGroup>
16+
17+
<ItemGroup>
18+
<None Update="host.json">
19+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
20+
</None>
21+
<None Update="local.settings.json">
22+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
23+
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
24+
</None>
25+
</ItemGroup>
26+
27+
<ItemGroup>
28+
<FrameworkReference Include="Microsoft.AspNetCore.App" />
29+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.0" />
30+
<PackageReference Condition="$(TestBuild) != 'true'" Include="Microsoft.Azure.Functions.Worker" Version="2.0.0" />
31+
</ItemGroup>
32+
</Project>

0 commit comments

Comments
 (0)