Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 7 additions & 0 deletions Amazon-SP-API-CSharp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FikaAmazonAPI.SampleCode",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FikaAmazonAPI", "Source\FikaAmazonAPI\FikaAmazonAPI.csproj", "{D6BE954D-174D-4C19-A0B6-46F020878E72}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Source\Tests\Tests.csproj", "{4CB44101-8A9E-454A-B272-038C5FAF9F23}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -32,13 +34,18 @@ Global
{D6BE954D-174D-4C19-A0B6-46F020878E72}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D6BE954D-174D-4C19-A0B6-46F020878E72}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D6BE954D-174D-4C19-A0B6-46F020878E72}.Release|Any CPU.Build.0 = Release|Any CPU
{4CB44101-8A9E-454A-B272-038C5FAF9F23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4CB44101-8A9E-454A-B272-038C5FAF9F23}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4CB44101-8A9E-454A-B272-038C5FAF9F23}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4CB44101-8A9E-454A-B272-038C5FAF9F23}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{FC494085-19C4-4835-B053-F9B74FFB978A} = {3472E85C-6C29-4196-AA16-B95898241C04}
{D6BE954D-174D-4C19-A0B6-46F020878E72} = {3472E85C-6C29-4196-AA16-B95898241C04}
{4CB44101-8A9E-454A-B272-038C5FAF9F23} = {3472E85C-6C29-4196-AA16-B95898241C04}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F072E7DD-BF35-43CC-BF83-E5947CA2D772}
Expand Down
2 changes: 1 addition & 1 deletion Source/FikaAmazonAPI.SampleCode/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ static async Task Main(string[] args)
RefreshToken: config.GetSection("FikaAmazonAPI:RefreshToken").Value,
rateLimitingHandler: new RateLimitingHandler());

var tasks = new[] { 1..10 }.Select(x =>
var tasks = Enumerable.Range(1, 10).Select(x =>
Task.Run(() =>
{
var amazonConnection = connectionFactory.RequestScopedConnection(
Expand Down
16 changes: 8 additions & 8 deletions Source/FikaAmazonAPI/Utils/RateLimits.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using System;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

[assembly: InternalsVisibleTo("Tests")]
namespace FikaAmazonAPI.Utils
{
internal class RateLimits
Expand Down Expand Up @@ -47,10 +48,9 @@ public async Task WaitForPermittedRequest(CancellationToken cancellationToken =
}

int ratePeriodMs = GetRatePeriodMs();
var requestsUsedAtOnset = RequestsUsed;

// when requests used more than zero, replenish according to time elapsed
IncrementAvailableTokens(debugMode);
DecrementRequestsUsed(debugMode);

var nextRequestsSent = RequestsUsed + 1;
var nextRequestsSentTxt = (nextRequestsSent > Burst) ? "FULL" : nextRequestsSent.ToString();
Expand All @@ -76,7 +76,7 @@ public async Task WaitForPermittedRequest(CancellationToken cancellationToken =
else
{
// replenish token
IncrementAvailableTokens(debugMode);
DecrementRequestsUsed(debugMode);
}

if (RequestsUsed <= 0)
Expand All @@ -87,7 +87,7 @@ public async Task WaitForPermittedRequest(CancellationToken cancellationToken =
}

// now remove token from bucket for pending request
requestIsPermitted = TryDecrementAvailableTokens(debugMode);
requestIsPermitted = TryIncrementRequestsUsed(debugMode);
}
}

Expand All @@ -97,13 +97,13 @@ internal void SetRateLimit(decimal rate)
}

// increments available tokens, unless another thread has already incremented them.
private void IncrementAvailableTokens(bool isDebug)
private void DecrementRequestsUsed(bool isDebug)
{
WriteDebug($"Attempting to increment tokens", isDebug);
lock (_locker)
{
var ratePeriodMilliseconds = GetRatePeriodMs();
var requestsToReplenish = ratePeriodMilliseconds == 0 ? 0 : (DateTime.UtcNow - LastRequestReplenished).Milliseconds / ratePeriodMilliseconds;
var requestsToReplenish = ratePeriodMilliseconds == 0 ? 0 : (int)((DateTime.UtcNow - LastRequestReplenished).TotalMilliseconds / ratePeriodMilliseconds);
WriteDebug($"{requestsToReplenish} tokens to replenish since {LastRequestReplenished}", isDebug);
if (requestsToReplenish == 0 || RequestsUsed == 0)
{
Expand All @@ -118,7 +118,7 @@ private void IncrementAvailableTokens(bool isDebug)
}

// will try to decrement available tokens, will fail if another thread has used last of burst quota
private bool TryDecrementAvailableTokens(bool isDebug)
private bool TryIncrementRequestsUsed(bool isDebug)
{
var succeeded = false;

Expand Down
1 change: 1 addition & 0 deletions Source/Tests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using NUnit.Framework;
40 changes: 40 additions & 0 deletions Source/Tests/RateLimitsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using FikaAmazonAPI.Utils;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Tests
{
[TestFixture]
public class RateLimitsTests
{
[Test]
[TestCase(0.1, 1, 3, 20000)]
[TestCase(0.5, 1, 3, 4000)]
[TestCase(1, 5, 10, 5000)]
public async Task WaitForPermittedRequest_WaitsExpectedLengthOfTime(decimal rate, int burst, int numberRequests, int expectedWaitMilliseconds)
{
// Arrange
var rateLimit = new RateLimits(rate, burst, RateLimitType.UNSET);

var stopwatch = new Stopwatch();
var cancellationToken = new CancellationToken();

// Act
stopwatch.Start();
for (int i = 0; i < numberRequests; i++)
{
await rateLimit.WaitForPermittedRequest(cancellationToken, debugMode: true);
}
stopwatch.Stop();

// Assert
Assert.That(stopwatch.ElapsedMilliseconds, Is.GreaterThanOrEqualTo(expectedWaitMilliseconds));
// allow a second for additional time taken by the test to run
Assert.That(stopwatch.ElapsedMilliseconds, Is.LessThanOrEqualTo(expectedWaitMilliseconds + 1000));
}
}
}
24 changes: 24 additions & 0 deletions Source/Tests/Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Include="NUnit.Analyzers" Version="3.6.1" />
<PackageReference Include="coverlet.collector" Version="6.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\FikaAmazonAPI\FikaAmazonAPI.csproj" />
</ItemGroup>

</Project>
Loading