Skip to content

Commit 99504b6

Browse files
authored
TAP update for rate limiter
use async/await with RateLimiter
2 parents 71d0a30 + f82bf57 commit 99504b6

4 files changed

Lines changed: 106 additions & 14 deletions

File tree

src/Solnet.Rpc/Core/Http/JsonRpcClient.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ protected async Task<RequestResult<T>> SendRequest<T>(JsonRpcRequest req)
7878
try
7979
{
8080
// pre-flight check with rate limiter if set
81-
_rateLimiter?.Fire();
82-
81+
await (_rateLimiter?.WaitFireAsync() ?? Task.CompletedTask);
82+
8383
// logging
8484
_logger?.LogInformation(new EventId(req.Id, req.Method), $"Sending request: {requestJson}");
8585

@@ -189,8 +189,8 @@ public async Task<RequestResult<JsonRpcBatchResponse>> SendBatchRequestAsync(Jso
189189
try
190190
{
191191
// pre-flight check with rate limiter if set
192-
_rateLimiter?.Fire();
193-
192+
await (_rateLimiter?.WaitFireAsync() ?? Task.CompletedTask);
193+
194194
_logger?.LogInformation(new EventId(id_for_log, $"[batch of {reqs.Count}]"), $"Sending request: {requestsJson}");
195195

196196
// create byte buffer to avoid charset=utf-8 in content-type header

src/Solnet.Rpc/Utilities/IRateLimiter.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Text;
5+
using System.Threading;
56
using System.Threading.Tasks;
67

78
namespace Solnet.Rpc.Utilities
@@ -14,14 +15,22 @@ public interface IRateLimiter
1415

1516
/// <summary>
1617
/// Fire or block until we can fire.
17-
/// </summary>
18-
void Fire();
18+
/// </summary>
19+
[Obsolete]
20+
void Fire();
21+
22+
/// <summary>
23+
/// Fire or block until we can fire.
24+
/// </summary>
25+
/// <param name="cancellationToken"></param>
26+
/// <returns></returns>
27+
Task WaitFireAsync(CancellationToken cancellationToken = default);
1928

2029
/// <summary>
2130
/// Would a fire method succeed?
2231
/// </summary>
2332
/// <returns></returns>
24-
bool CanFire();
25-
26-
}
33+
bool CanFire();
34+
35+
}
2736
}

src/Solnet.Rpc/Utilities/RateLimiter.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ public bool CanFire()
5151

5252
/// <summary>
5353
/// Pre-fire check - this will block if fire rates exceed defined limits until valid.
54-
/// </summary>
54+
/// </summary>
55+
[Obsolete]
5556
public void Fire()
5657
{
5758

@@ -65,8 +66,26 @@ public void Fire()
6566
if (_duration_ms > 0)
6667
_hit_list.Enqueue(DateTime.UtcNow);
6768

68-
}
69-
69+
}
70+
71+
/// <summary>
72+
/// Pre-fire check - this will block if fire rates exceed defined limits until valid.
73+
/// </summary>
74+
/// <param name="cancellationToken"></param>
75+
/// <returns></returns>
76+
public async Task WaitFireAsync(CancellationToken cancellationToken = default)
77+
{
78+
var checkTime = DateTime.UtcNow;
79+
var resumeTime = NextFireAllowed(checkTime);
80+
var snoozeMs = resumeTime.Subtract(checkTime).TotalMilliseconds;
81+
while (DateTime.UtcNow <= resumeTime)
82+
await Task.Delay(50, cancellationToken);
83+
84+
// record this trigger
85+
if (_duration_ms > 0)
86+
_hit_list.Enqueue(DateTime.UtcNow);
87+
}
88+
7089
/// <summary>
7190
/// When is a next fire allowed?
7291
/// </summary>

test/Solnet.Rpc.Test/SolanaRpcRateLimitingTests.cs

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,76 @@ public void TestTwoHitsPerSecond()
6969
Console.WriteLine(limit);
7070

7171
// observe why this may break the build
72-
var finalTimeCheck= DateTime.UtcNow;
72+
var finalTimeCheck = DateTime.UtcNow;
7373
Console.WriteLine($" ExecTime diff {finalTimeCheck.Subtract(timeCheck).TotalMilliseconds}ms");
7474
Console.WriteLine($"TimeCheck diff {finalTimeCheck.Subtract(twoSecondsLater).TotalMilliseconds}ms");
75-
Assert.IsTrue(finalTimeCheck.Subtract(timeCheck).TotalMilliseconds>2000, $"ExecTime diff {finalTimeCheck.Subtract(timeCheck).TotalMilliseconds}ms");
75+
Assert.IsTrue(finalTimeCheck.Subtract(timeCheck).TotalMilliseconds > 2000, $"ExecTime diff {finalTimeCheck.Subtract(timeCheck).TotalMilliseconds}ms");
7676
}
77+
78+
79+
[TestMethod]
80+
public async Task TestMaxSpeed_NoLimits_Async()
81+
{
82+
// allow unlimited fires instantly
83+
var limit = RateLimiter.Create();
84+
Assert.IsTrue(limit.CanFire());
85+
await limit.WaitFireAsync();
86+
await limit.WaitFireAsync();
87+
await limit.WaitFireAsync();
88+
await limit.WaitFireAsync();
89+
await limit.WaitFireAsync();
90+
await limit.WaitFireAsync();
91+
await limit.WaitFireAsync();
92+
}
93+
94+
[TestMethod]
95+
public async Task TestMaxSpeed_WithinLimits_Async()
96+
{
97+
// allow unlimited fires instantly
98+
var limit = RateLimiter.Create().AllowHits(100).PerSeconds(10);
99+
Assert.IsTrue(limit.CanFire());
100+
await limit.WaitFireAsync();
101+
await limit.WaitFireAsync();
102+
await limit.WaitFireAsync();
103+
await limit.WaitFireAsync();
104+
await limit.WaitFireAsync();
105+
await limit.WaitFireAsync();
106+
await limit.WaitFireAsync();
107+
await limit.WaitFireAsync();
108+
await limit.WaitFireAsync();
109+
}
110+
111+
[TestMethod]
112+
public async Task TestTwoHitsPerSecond_Async()
113+
{
114+
// allow 2 hits per second
115+
var timeCheck = DateTime.UtcNow;
116+
var limit = RateLimiter.Create().AllowHits(2).PerSeconds(1);
117+
var twoSecondsLater = timeCheck.AddSeconds(2);
118+
Console.WriteLine(limit);
119+
Assert.IsTrue(limit.CanFire());
120+
Console.WriteLine(limit);
121+
await limit.WaitFireAsync();
122+
Console.WriteLine(limit);
123+
await limit.WaitFireAsync();
124+
Console.WriteLine(limit);
125+
await limit.WaitFireAsync();
126+
Console.WriteLine(limit);
127+
await limit.WaitFireAsync();
128+
Console.WriteLine(limit);
129+
await limit.WaitFireAsync();
130+
Console.WriteLine(limit);
131+
await limit.WaitFireAsync();
132+
Console.WriteLine(limit);
133+
await limit.WaitFireAsync();
134+
Console.WriteLine(limit);
77135

136+
// observe why this may break the build
137+
var finalTimeCheck = DateTime.UtcNow;
138+
Console.WriteLine($" ExecTime diff {finalTimeCheck.Subtract(timeCheck).TotalMilliseconds}ms");
139+
Console.WriteLine($"TimeCheck diff {finalTimeCheck.Subtract(twoSecondsLater).TotalMilliseconds}ms");
140+
Assert.IsTrue(finalTimeCheck.Subtract(timeCheck).TotalMilliseconds > 2000, $"ExecTime diff {finalTimeCheck.Subtract(timeCheck).TotalMilliseconds}ms");
141+
}
78142
}
79143

80144
}

0 commit comments

Comments
 (0)