Skip to content

Commit abae3cb

Browse files
authored
Merge pull request #393 from dotnet-maestro-bot/merge/release/2.2-to-master
[automated] Merge branch 'release/2.2' => 'master'
2 parents d226c33 + 6dbe32c commit abae3cb

File tree

4 files changed

+54
-1
lines changed

4 files changed

+54
-1
lines changed

build/dependencies.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,5 @@
3131
</PropertyGroup>
3232
<PropertyGroup Label="Package Versions: Pinned" />
3333
<Import Project="$(DotNetPackageVersionPropsPath)" Condition=" '$(DotNetPackageVersionPropsPath)' != '' " />
34+
<PropertyGroup Label="Package Versions: Pinned" />
3435
</Project>

korebuild-lock.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
version:3.0.0-alpha1-20180907.9
2-
commithash:f997365a8832ff0a3cbd9a98df45734ac2723fa0
2+
commithash:f997365a8832ff0a3cbd9a98df45734ac2723fa0

src/Microsoft.Extensions.Primitives/CancellationChangeToken.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ public CancellationChangeToken(CancellationToken cancellationToken)
3131
/// <inheritdoc />
3232
public IDisposable RegisterChangeCallback(Action<object> callback, object state)
3333
{
34+
// Don't capture the current ExecutionContext and its AsyncLocals onto the token registration causing them to live forever
35+
var restoreFlow = false;
36+
if (!ExecutionContext.IsFlowSuppressed())
37+
{
38+
ExecutionContext.SuppressFlow();
39+
restoreFlow = true;
40+
}
41+
3442
try
3543
{
3644
return Token.Register(callback, state);
@@ -40,6 +48,14 @@ public IDisposable RegisterChangeCallback(Action<object> callback, object state)
4048
// Reset the flag so that we can indicate to future callers that this wouldn't work.
4149
ActiveChangeCallbacks = false;
4250
}
51+
finally
52+
{
53+
// Restore the current ExecutionContext
54+
if (restoreFlow)
55+
{
56+
ExecutionContext.RestoreFlow();
57+
}
58+
}
4359

4460
return NullDisposable.Instance;
4561
}

test/Microsoft.Extensions.Primitives.Tests/ChangeTokenTest.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Threading;
56
using Xunit;
67

78
namespace Microsoft.Extensions.Primitives
@@ -87,5 +88,40 @@ public void ChangesFireAfterExceptionsWithState()
8788
Assert.Equal(2, count);
8889
Assert.NotNull(callbackState);
8990
}
91+
92+
[Fact]
93+
public void AsyncLocalsNotCapturedAndRestored()
94+
{
95+
// Capture clean context
96+
var executionContext = ExecutionContext.Capture();
97+
98+
var cancellationTokenSource = new CancellationTokenSource();
99+
var cancellationToken = cancellationTokenSource.Token;
100+
var cancellationChangeToken = new CancellationChangeToken(cancellationToken);
101+
var executed = false;
102+
103+
// Set AsyncLocal
104+
var asyncLocal = new AsyncLocal<int>();
105+
asyncLocal.Value = 1;
106+
107+
// Register Callback
108+
cancellationChangeToken.RegisterChangeCallback(al =>
109+
{
110+
// AsyncLocal not set, when run on clean context
111+
// A suppressed flow runs in current context, rather than restoring the captured context
112+
Assert.Equal(0, ((AsyncLocal<int>) al).Value);
113+
executed = true;
114+
}, asyncLocal);
115+
116+
// AsyncLocal should still be set
117+
Assert.Equal(1, asyncLocal.Value);
118+
119+
// Check AsyncLocal is not restored by running on clean context
120+
ExecutionContext.Run(executionContext, cts => ((CancellationTokenSource)cts).Cancel(), cancellationTokenSource);
121+
122+
// AsyncLocal should still be set
123+
Assert.Equal(1, asyncLocal.Value);
124+
Assert.True(executed);
125+
}
90126
}
91127
}

0 commit comments

Comments
 (0)