-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Reimplement CancellationTokenSourcePool (#1192)
- Loading branch information
Showing
10 changed files
with
209 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
103 changes: 93 additions & 10 deletions
103
src/Polly.Core.Tests/Utils/CancellationTokenSourcePoolTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,116 @@ | ||
using System; | ||
using System.Threading; | ||
using Moq; | ||
using Polly.Core.Tests.Helpers; | ||
using Polly.Utils; | ||
using Xunit; | ||
|
||
namespace Polly.Core.Tests.Utils; | ||
|
||
public class CancellationTokenSourcePoolTests | ||
{ | ||
public static IEnumerable<object[]> TimeProviders() | ||
{ | ||
yield return new object[] { TimeProvider.System }; | ||
yield return new object[] { new FakeTimeProvider() }; | ||
} | ||
|
||
[Fact] | ||
public void RentReturn_Reusable_EnsureProperBehavior() | ||
public void ArgValidation_Ok() | ||
{ | ||
var pool = CancellationTokenSourcePool.Create(TimeProvider.System); | ||
|
||
Assert.Throws<ArgumentOutOfRangeException>(() => pool.Get(TimeSpan.Zero)); | ||
var e = Assert.Throws<ArgumentOutOfRangeException>(() => pool.Get(TimeSpan.FromMilliseconds(-2))); | ||
e.Message.Should().StartWith("Invalid delay specified."); | ||
e.ActualValue.Should().Be(TimeSpan.FromMilliseconds(-2)); | ||
|
||
pool.Get(System.Threading.Timeout.InfiniteTimeSpan).Should().NotBeNull(); | ||
} | ||
|
||
[MemberData(nameof(TimeProviders))] | ||
[Theory] | ||
public void RentReturn_Reusable_EnsureProperBehavior(object timeProvider) | ||
{ | ||
var cts = CancellationTokenSourcePool.Get(); | ||
CancellationTokenSourcePool.Return(cts); | ||
var pool = CancellationTokenSourcePool.Create(GetTimeProvider(timeProvider)); | ||
var cts = pool.Get(System.Threading.Timeout.InfiniteTimeSpan); | ||
pool.Return(cts); | ||
|
||
var cts2 = CancellationTokenSourcePool.Get(); | ||
var cts2 = pool.Get(System.Threading.Timeout.InfiniteTimeSpan); | ||
#if NET6_0_OR_GREATER | ||
cts2.Should().BeSameAs(cts); | ||
if (timeProvider == TimeProvider.System) | ||
{ | ||
cts2.Should().BeSameAs(cts); | ||
} | ||
else | ||
{ | ||
cts2.Should().NotBeSameAs(cts); | ||
} | ||
#else | ||
cts2.Should().NotBeSameAs(cts); | ||
#endif | ||
} | ||
|
||
[Fact] | ||
public void RentReturn_NotReusable_EnsureProperBehavior() | ||
[MemberData(nameof(TimeProviders))] | ||
[Theory] | ||
public void RentReturn_NotReusable_EnsureProperBehavior(object timeProvider) | ||
{ | ||
var cts = CancellationTokenSourcePool.Get(); | ||
var pool = CancellationTokenSourcePool.Create(GetTimeProvider(timeProvider)); | ||
var cts = pool.Get(System.Threading.Timeout.InfiniteTimeSpan); | ||
cts.Cancel(); | ||
CancellationTokenSourcePool.Return(cts); | ||
pool.Return(cts); | ||
|
||
cts.Invoking(c => c.Token).Should().Throw<ObjectDisposedException>(); | ||
|
||
var cts2 = CancellationTokenSourcePool.Get(); | ||
var cts2 = pool.Get(System.Threading.Timeout.InfiniteTimeSpan); | ||
cts2.Token.Should().NotBeNull(); | ||
} | ||
|
||
[MemberData(nameof(TimeProviders))] | ||
[Theory] | ||
public async Task Rent_Cancellable_EnsureCancelled(object timeProvider) | ||
{ | ||
if (timeProvider is Mock<TimeProvider> fakeTimeProvider) | ||
{ | ||
fakeTimeProvider | ||
.Setup(v => v.CancelAfter(It.IsAny<CancellationTokenSource>(), TimeSpan.FromMilliseconds(1))) | ||
.Callback<CancellationTokenSource, TimeSpan>((source, _) => source.Cancel()); | ||
} | ||
else | ||
{ | ||
fakeTimeProvider = null!; | ||
} | ||
|
||
var pool = CancellationTokenSourcePool.Create(GetTimeProvider(timeProvider)); | ||
var cts = pool.Get(TimeSpan.FromMilliseconds(1)); | ||
|
||
await Task.Delay(100); | ||
|
||
cts.IsCancellationRequested.Should().BeTrue(); | ||
fakeTimeProvider?.VerifyAll(); | ||
} | ||
|
||
[MemberData(nameof(TimeProviders))] | ||
[Theory] | ||
public async Task Rent_NotCancellable_EnsureNotCancelled(object timeProvider) | ||
{ | ||
var pool = CancellationTokenSourcePool.Create(GetTimeProvider(timeProvider)); | ||
var cts = pool.Get(System.Threading.Timeout.InfiniteTimeSpan); | ||
|
||
await Task.Delay(20); | ||
|
||
cts.IsCancellationRequested.Should().BeFalse(); | ||
|
||
if (timeProvider is Mock<TimeProvider> fakeTimeProvider) | ||
{ | ||
fakeTimeProvider | ||
.Verify(v => v.CancelAfter(It.IsAny<CancellationTokenSource>(), It.IsAny<TimeSpan>()), Times.Never()); | ||
} | ||
} | ||
|
||
private static TimeProvider GetTimeProvider(object timeProvider) => timeProvider switch | ||
{ | ||
Mock<TimeProvider> m => m.Object, | ||
_ => (TimeProvider)timeProvider | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
src/Polly.Core/Utils/CancellationTokenSourcePool.Disposable.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
namespace Polly.Utils; | ||
|
||
internal abstract partial class CancellationTokenSourcePool | ||
{ | ||
private sealed class DisposableCancellationTokenSourcePool : CancellationTokenSourcePool | ||
{ | ||
private readonly TimeProvider _timeProvider; | ||
|
||
public DisposableCancellationTokenSourcePool(TimeProvider timeProvider) => _timeProvider = timeProvider; | ||
|
||
protected override CancellationTokenSource GetCore(TimeSpan delay) | ||
{ | ||
var source = new CancellationTokenSource(); | ||
|
||
if (IsCancellable(delay)) | ||
{ | ||
_timeProvider.CancelAfter(source, delay); | ||
} | ||
|
||
return source; | ||
} | ||
|
||
public override void Return(CancellationTokenSource source) => source.Dispose(); | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
src/Polly.Core/Utils/CancellationTokenSourcePool.Pooled.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
namespace Polly.Utils; | ||
|
||
internal abstract partial class CancellationTokenSourcePool | ||
{ | ||
#if NET6_0_OR_GREATER | ||
private sealed class PooledCancellationTokenSourcePool : CancellationTokenSourcePool | ||
{ | ||
public static readonly PooledCancellationTokenSourcePool SystemInstance = new(TimeProvider.System); | ||
|
||
public PooledCancellationTokenSourcePool(TimeProvider timeProvider) => _timeProvider = timeProvider; | ||
|
||
private readonly ObjectPool<CancellationTokenSource> _pool = new( | ||
static () => new CancellationTokenSource(), | ||
static cts => true); | ||
private readonly TimeProvider _timeProvider; | ||
|
||
protected override CancellationTokenSource GetCore(TimeSpan delay) | ||
{ | ||
var source = _pool.Get(); | ||
|
||
if (IsCancellable(delay)) | ||
{ | ||
_timeProvider.CancelAfter(source, delay); | ||
} | ||
|
||
return source; | ||
} | ||
|
||
public override void Return(CancellationTokenSource source) | ||
{ | ||
if (source.TryReset()) | ||
{ | ||
_pool.Return(source); | ||
} | ||
else | ||
{ | ||
source.Dispose(); | ||
} | ||
} | ||
} | ||
#endif | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,33 @@ | ||
namespace Polly.Utils | ||
namespace Polly.Utils; | ||
|
||
#pragma warning disable CA1716 // Identifiers should not match keywords | ||
|
||
internal abstract partial class CancellationTokenSourcePool | ||
{ | ||
internal static class CancellationTokenSourcePool | ||
public static CancellationTokenSourcePool Create(TimeProvider timeProvider) | ||
{ | ||
#if NET6_0_OR_GREATER | ||
private static readonly ObjectPool<CancellationTokenSource> Pool = new( | ||
static () => new CancellationTokenSource(), | ||
static cts => true); | ||
#endif | ||
public static CancellationTokenSource Get() | ||
if (timeProvider == TimeProvider.System) | ||
{ | ||
#if NET6_0_OR_GREATER | ||
return Pool.Get(); | ||
#else | ||
return new CancellationTokenSource(); | ||
#endif | ||
return PooledCancellationTokenSourcePool.SystemInstance; | ||
} | ||
#endif | ||
return new DisposableCancellationTokenSourcePool(timeProvider); | ||
} | ||
|
||
public static void Return(CancellationTokenSource source) | ||
public CancellationTokenSource Get(TimeSpan delay) | ||
{ | ||
if (delay <= TimeSpan.Zero && delay != System.Threading.Timeout.InfiniteTimeSpan) | ||
{ | ||
#if NET6_0_OR_GREATER | ||
if (source.TryReset()) | ||
{ | ||
Pool.Return(source); | ||
return; | ||
} | ||
#endif | ||
source.Dispose(); | ||
throw new ArgumentOutOfRangeException(nameof(delay), delay, "Invalid delay specified."); | ||
} | ||
|
||
return GetCore(delay); | ||
} | ||
|
||
protected abstract CancellationTokenSource GetCore(TimeSpan delay); | ||
|
||
public abstract void Return(CancellationTokenSource source); | ||
|
||
protected static bool IsCancellable(TimeSpan delay) => delay != System.Threading.Timeout.InfiniteTimeSpan; | ||
} |