Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Async disposal
  • Loading branch information
BrennanConroy committed Dec 2, 2021
commit 822efc3edca5e3a3f457c75d640812d8501392e2
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtilities", "..\Common\tests\TestUtilities\TestUtilities.csproj", "{CAEE0409-CCC3-4EA6-AB54-177FD305D42D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bcl.AsyncInterfaces", "..\Microsoft.Bcl.AsyncInterfaces\ref\Microsoft.Bcl.AsyncInterfaces.csproj", "{39DA5B84-ECA2-42A2-BEBD-C056BDB8AD53}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bcl.AsyncInterfaces", "..\Microsoft.Bcl.AsyncInterfaces\src\Microsoft.Bcl.AsyncInterfaces.csproj", "{F59F4FD7-EA00-47EA-A09A-6F76CB079F9B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Runtime.CompilerServices.Unsafe", "..\System.Runtime.CompilerServices.Unsafe\ref\System.Runtime.CompilerServices.Unsafe.csproj", "{0D1C7DCB-970D-4099-AC9F-A01E75923EC6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Runtime.CompilerServices.Unsafe", "..\System.Runtime.CompilerServices.Unsafe\src\System.Runtime.CompilerServices.Unsafe.ilproj", "{AF838F1D-5C1C-472B-B31C-9A3B7507BB4B}"
Expand Down Expand Up @@ -31,6 +35,14 @@ Global
{CAEE0409-CCC3-4EA6-AB54-177FD305D42D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CAEE0409-CCC3-4EA6-AB54-177FD305D42D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CAEE0409-CCC3-4EA6-AB54-177FD305D42D}.Release|Any CPU.Build.0 = Release|Any CPU
{39DA5B84-ECA2-42A2-BEBD-C056BDB8AD53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{39DA5B84-ECA2-42A2-BEBD-C056BDB8AD53}.Debug|Any CPU.Build.0 = Debug|Any CPU
{39DA5B84-ECA2-42A2-BEBD-C056BDB8AD53}.Release|Any CPU.ActiveCfg = Release|Any CPU
{39DA5B84-ECA2-42A2-BEBD-C056BDB8AD53}.Release|Any CPU.Build.0 = Release|Any CPU
{F59F4FD7-EA00-47EA-A09A-6F76CB079F9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F59F4FD7-EA00-47EA-A09A-6F76CB079F9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F59F4FD7-EA00-47EA-A09A-6F76CB079F9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F59F4FD7-EA00-47EA-A09A-6F76CB079F9B}.Release|Any CPU.Build.0 = Release|Any CPU
{0D1C7DCB-970D-4099-AC9F-A01E75923EC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0D1C7DCB-970D-4099-AC9F-A01E75923EC6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0D1C7DCB-970D-4099-AC9F-A01E75923EC6}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -66,8 +78,10 @@ Global
GlobalSection(NestedProjects) = preSolution
{CAEE0409-CCC3-4EA6-AB54-177FD305D42D} = {6614EF7F-23FC-4809-AFF5-1ADBF1B6422C}
{AE81EE9F-1240-4AF1-BF21-7F451B7859E5} = {6614EF7F-23FC-4809-AFF5-1ADBF1B6422C}
{39DA5B84-ECA2-42A2-BEBD-C056BDB8AD53} = {111B1B5B-A004-4C05-9A8C-E0931DADA5FB}
{0D1C7DCB-970D-4099-AC9F-A01E75923EC6} = {111B1B5B-A004-4C05-9A8C-E0931DADA5FB}
{FD274A80-0D68-48A0-9AC7-279C9E69BC63} = {111B1B5B-A004-4C05-9A8C-E0931DADA5FB}
{F59F4FD7-EA00-47EA-A09A-6F76CB079F9B} = {85204CF5-0C88-4BBB-9E70-D8CCED82ED3D}
{AF838F1D-5C1C-472B-B31C-9A3B7507BB4B} = {85204CF5-0C88-4BBB-9E70-D8CCED82ED3D}
{1E52F495-578C-4FDB-86DD-87EAAE0A0BE7} = {85204CF5-0C88-4BBB-9E70-D8CCED82ED3D}
{25495BDC-0614-4FAC-B6EA-DF3F0E35A871} = {85204CF5-0C88-4BBB-9E70-D8CCED82ED3D}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ public sealed partial class ConcurrencyLimiter : System.Threading.RateLimiting.R
{
public ConcurrencyLimiter(System.Threading.RateLimiting.ConcurrencyLimiterOptions options) { }
protected override System.Threading.RateLimiting.RateLimitLease AcquireCore(int permitCount) { throw null; }
public override void Dispose() { }
protected override void Dispose(bool disposing) { }
protected override System.Threading.Tasks.ValueTask DisposeAsyncCore() { throw null; }
public override int GetAvailablePermits() { throw null; }
protected override System.Threading.Tasks.ValueTask<System.Threading.RateLimiting.RateLimitLease> WaitAsyncCore(int permitCount, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
Expand Down Expand Up @@ -43,12 +44,15 @@ public enum QueueProcessingOrder
OldestFirst = 0,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approved API called these:

ProcessOldest,
ProcessNewest

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approved API code was wrong, the comment above the code states:

We decided to rename the QueueProcessingOrder members from { ProcessOldest, ProcessNewest } to { OldestFirst, NewestFirst }

NewestFirst = 1,
}
public abstract partial class RateLimiter : System.IDisposable
public abstract partial class RateLimiter : System.IAsyncDisposable, System.IDisposable
{
protected RateLimiter() { }
public System.Threading.RateLimiting.RateLimitLease Acquire(int permitCount = 1) { throw null; }
protected abstract System.Threading.RateLimiting.RateLimitLease AcquireCore(int permitCount);
public abstract void Dispose();
public void Dispose() { }
protected virtual void Dispose(bool disposing) { }
public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
protected virtual System.Threading.Tasks.ValueTask DisposeAsyncCore() { throw null; }
public abstract int GetAvailablePermits();
public System.Threading.Tasks.ValueTask<System.Threading.RateLimiting.RateLimitLease> WaitAsync(int permitCount = 1, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
protected abstract System.Threading.Tasks.ValueTask<System.Threading.RateLimiting.RateLimitLease> WaitAsyncCore(int permitCount, System.Threading.CancellationToken cancellationToken);
Expand All @@ -68,7 +72,8 @@ public sealed partial class TokenBucketRateLimiter : System.Threading.RateLimiti
{
public TokenBucketRateLimiter(System.Threading.RateLimiting.TokenBucketRateLimiterOptions options) { }
protected override System.Threading.RateLimiting.RateLimitLease AcquireCore(int tokenCount) { throw null; }
public override void Dispose() { }
protected override void Dispose(bool disposing) { }
protected override System.Threading.Tasks.ValueTask DisposeAsyncCore() { throw null; }
public override int GetAvailablePermits() { throw null; }
public bool TryReplenish() { throw null; }
protected override System.Threading.Tasks.ValueTask<System.Threading.RateLimiting.RateLimitLease> WaitAsyncCore(int tokenCount, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
<PackageReference Include="System.Threading.Tasks.Extensions" Version="$(SystemThreadingTasksExtensionsVersion)" />
</ItemGroup>
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'netstandard2.1'))">
<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Bcl.AsyncInterfaces\ref\Microsoft.Bcl.AsyncInterfaces.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,7 @@ System.Threading.RateLimiting.RateLimitLease</PackageDescription>
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
<PackageReference Include="System.Threading.Tasks.Extensions" Version="$(SystemThreadingTasksExtensionsVersion)" />
</ItemGroup>
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'netstandard2.1'))">
<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Bcl.AsyncInterfaces\src\Microsoft.Bcl.AsyncInterfaces.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,19 @@ private void Release(int releaseCount)
}
}

public override void Dispose()
protected override void Dispose(bool disposing)
{
if (!disposing)
{
return;
}

lock (Lock)
{
if (_disposed)
{
return;
}
_disposed = true;
while (_queue.Count > 0)
{
Expand All @@ -215,6 +224,13 @@ public override void Dispose()
}
}

protected override ValueTask DisposeAsyncCore()
{
Dispose(true);

return default;
}

private sealed class ConcurrencyLease : RateLimitLease
{
private static readonly string[] s_allMetadataNames = new[] { MetadataName.ReasonPhrase.Name };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace System.Threading.RateLimiting
/// <summary>
/// Represents a limiter type that users interact with to determine if an operation can proceed.
/// </summary>
public abstract class RateLimiter : IDisposable
public abstract class RateLimiter : IAsyncDisposable, IDisposable
{
/// <summary>
/// An estimated count of available permits.
Expand Down Expand Up @@ -75,7 +75,44 @@ public ValueTask<RateLimitLease> WaitAsync(int permitCount = 1, CancellationToke
/// <returns>A task that completes when the requested permits are acquired or when the requested permits are denied.</returns>
protected abstract ValueTask<RateLimitLease> WaitAsyncCore(int permitCount, CancellationToken cancellationToken);

/// <interitdoc/>
public abstract void Dispose();
/// <summary>
/// Dispose method for implementations to write.
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing) { }

/// <summary>
/// Dispose the RateLimiter. This completes any queued acquires with a failed lease.
/// </summary>
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}

/// <summary>
/// DisposeAsync method for implementations to write.
/// </summary>
protected virtual ValueTask DisposeAsyncCore()
{
return default;
}

/// <summary>
/// Diposes the RateLimiter asynchronously.
/// </summary>
/// <returns>ValueTask representin the completion of the disposal.</returns>
public async ValueTask DisposeAsync()
{
// Perform async cleanup.
await DisposeAsyncCore().ConfigureAwait(false);

// Dispose of unmanaged resources.
Dispose(false);

// Suppress finalization.
GC.SuppressFinalize(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -280,10 +280,19 @@ private void ReplenishInternal(uint nowTicks)
}
}

public override void Dispose()
protected override void Dispose(bool disposing)
{
if (!disposing)
{
return;
}

lock (Lock)
{
if (_disposed)
{
return;
}
_disposed = true;
_renewTimer?.Dispose();
while (_queue.Count > 0)
Expand All @@ -297,6 +306,13 @@ public override void Dispose()
}
}

protected override ValueTask DisposeAsyncCore()
{
Dispose(true);

return default;
}

private sealed class TokenBucketLease : RateLimitLease
{
private static readonly string[] s_allMetadataNames = new[] { MetadataName.RetryAfter.Name };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,8 @@ public abstract class BaseRateLimiterTests

[Fact]
public abstract Task DisposeReleasesQueuedAcquires();

[Fact]
public abstract Task DisposeAsyncReleasesQueuedAcquires();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,35 @@ public override async Task DisposeReleasesQueuedAcquires()
Assert.False((await limiter.WaitAsync(1)).IsAcquired);
}

[Fact]
public override async Task DisposeAsyncReleasesQueuedAcquires()
{
var limiter = new ConcurrencyLimiter(new ConcurrencyLimiterOptions(1, QueueProcessingOrder.OldestFirst, 3));
using var lease = limiter.Acquire(1);

var wait1 = limiter.WaitAsync(1);
var wait2 = limiter.WaitAsync(1);
var wait3 = limiter.WaitAsync(1);
Assert.False(wait1.IsCompleted);
Assert.False(wait2.IsCompleted);
Assert.False(wait3.IsCompleted);

await limiter.DisposeAsync();

var failedLease = await wait1;
Assert.False(failedLease.IsAcquired);
failedLease = await wait2;
Assert.False(failedLease.IsAcquired);
failedLease = await wait3;
Assert.False(failedLease.IsAcquired);

lease.Dispose();

// Can't acquire any leases after disposal
Assert.False(limiter.Acquire(1).IsAcquired);
Assert.False((await limiter.WaitAsync(1)).IsAcquired);
}

[Fact]
public async Task ReasonMetadataOnFailedWaitAsync()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,33 @@ public override async Task DisposeReleasesQueuedAcquires()
Assert.False((await limiter.WaitAsync(1)).IsAcquired);
}

[Fact]
public override async Task DisposeAsyncReleasesQueuedAcquires()
{
var limiter = new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions(1, QueueProcessingOrder.OldestFirst, 3,
TimeSpan.Zero, 1, autoReplenishment: false));
var lease = limiter.Acquire(1);
var wait1 = limiter.WaitAsync(1);
var wait2 = limiter.WaitAsync(1);
var wait3 = limiter.WaitAsync(1);
Assert.False(wait1.IsCompleted);
Assert.False(wait2.IsCompleted);
Assert.False(wait3.IsCompleted);

await limiter.DisposeAsync();

lease = await wait1;
Assert.False(lease.IsAcquired);
lease = await wait2;
Assert.False(lease.IsAcquired);
lease = await wait3;
Assert.False(lease.IsAcquired);

// Can't acquire any leases after disposal
Assert.False(limiter.Acquire(1).IsAcquired);
Assert.False((await limiter.WaitAsync(1)).IsAcquired);
}

[Fact]
public async Task RetryMetadataOnFailedWaitAsync()
{
Expand Down