skp_ratelimit/algorithm/
gcra.rs1use std::time::Duration;
27
28use crate::algorithm::{current_timestamp_ms, timestamp_to_instant, Algorithm};
29use crate::decision::{Decision, DecisionMetadata, RateLimitInfo};
30use crate::error::Result;
31use crate::quota::Quota;
32use crate::storage::{Storage, StorageEntry};
33
34#[derive(Debug, Clone, Default)]
55pub struct GCRA;
56
57impl GCRA {
58 pub fn new() -> Self {
60 Self
61 }
62
63 fn calculate_decision(
65 &self,
66 current_tat: Option<u64>,
67 now: u64,
68 quota: &Quota,
69 ) -> (bool, u64) {
70 let period_ms = quota.period().as_millis() as u64;
71 let max_tat_offset_ms = quota.max_tat_offset().as_millis() as u64;
72
73 let effective_tat = current_tat.unwrap_or(now);
75
76 let new_tat = effective_tat.max(now) + period_ms;
78
79 let tat_offset = new_tat.saturating_sub(now);
81
82 if tat_offset <= max_tat_offset_ms + period_ms {
84 (true, new_tat)
86 } else {
87 (false, effective_tat)
89 }
90 }
91
92 fn build_info(&self, tat: u64, now: u64, quota: &Quota, allowed: bool) -> RateLimitInfo {
94 let period_ms = quota.period().as_millis() as u64;
95 let max_tat_offset_ms = quota.max_tat_offset().as_millis() as u64;
96 let limit = quota.effective_burst();
97
98 let tat_offset = tat.saturating_sub(now);
100 let remaining = if tat_offset == 0 {
101 limit
102 } else {
103 let used = (tat_offset / period_ms) + 1;
104 limit.saturating_sub(used)
105 };
106
107 let reset_at = if tat > now {
109 timestamp_to_instant(tat)
110 } else {
111 timestamp_to_instant(now)
112 };
113
114 let mut info = RateLimitInfo::new(limit, remaining, reset_at, timestamp_to_instant(now))
115 .with_algorithm("gcra")
116 .with_metadata(DecisionMetadata::new().with_tat(tat));
117
118 if !allowed {
120 let wait_ms = tat.saturating_sub(now).saturating_sub(max_tat_offset_ms);
121 if wait_ms > 0 {
122 info = info.with_retry_after(Duration::from_millis(wait_ms));
123 }
124 }
125
126 info
127 }
128}
129
130impl Algorithm for GCRA {
131 fn name(&self) -> &'static str {
132 "gcra"
133 }
134
135 async fn check_and_record<S: Storage>(
136 &self,
137 storage: &S,
138 key: &str,
139 quota: &Quota,
140 ) -> Result<Decision> {
141 let now = current_timestamp_ms();
142 let period_ms = quota.period().as_millis() as u64;
143
144 let ttl = Duration::from_millis(
146 quota.max_tat_offset().as_millis() as u64 + period_ms * 2
147 );
148
149 let decision = storage
150 .execute_atomic(key, ttl, |entry| {
151 let current_tat = entry.and_then(|e| e.tat);
152 let (allowed, new_tat) = self.calculate_decision(current_tat, now, quota);
153
154 let new_entry = StorageEntry::with_tat(new_tat);
155 let info = self.build_info(new_tat, now, quota, allowed);
156
157 let decision = if allowed {
158 Decision::allowed(info)
159 } else {
160 Decision::denied(info)
161 };
162
163 (new_entry, decision)
164 })
165 .await?;
166
167 Ok(decision)
168 }
169
170 async fn check<S: Storage>(
171 &self,
172 storage: &S,
173 key: &str,
174 quota: &Quota,
175 ) -> Result<Decision> {
176 let now = current_timestamp_ms();
177
178 let entry = storage.get(key).await?;
179 let current_tat = entry.and_then(|e| e.tat);
180
181 let (allowed, effective_tat) = self.calculate_decision(current_tat, now, quota);
182 let info = self.build_info(effective_tat, now, quota, allowed);
183
184 Ok(if allowed {
185 Decision::allowed(info)
186 } else {
187 Decision::denied(info)
188 })
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use crate::storage::MemoryStorage;
196
197 #[tokio::test]
198 async fn test_gcra_basic() {
199 let algorithm = GCRA::new();
200 let storage = MemoryStorage::new();
201 let quota = Quota::per_second(10); let decision = algorithm
205 .check_and_record(&storage, "user:1", "a)
206 .await
207 .unwrap();
208 assert!(decision.is_allowed());
209 }
210
211 #[tokio::test]
212 async fn test_gcra_burst() {
213 let algorithm = GCRA::new();
214 let storage = MemoryStorage::new();
215 let quota = Quota::per_second(1).with_burst(5);
217
218 for i in 1..=5 {
220 let decision = algorithm
221 .check_and_record(&storage, "user:1", "a)
222 .await
223 .unwrap();
224 assert!(decision.is_allowed(), "Request {} should be allowed", i);
225 }
226
227 let decision = algorithm
229 .check_and_record(&storage, "user:1", "a)
230 .await
231 .unwrap();
232 assert!(decision.is_denied(), "Request 6 should be denied");
233 assert!(decision.info().retry_after.is_some());
234 }
235
236 #[tokio::test]
237 async fn test_gcra_recovery() {
238 let algorithm = GCRA::new();
239 let storage = MemoryStorage::new();
240 let quota = Quota::per_second(10).with_burst(2);
242
243 algorithm.check_and_record(&storage, "user:1", "a).await.unwrap();
245 algorithm.check_and_record(&storage, "user:1", "a).await.unwrap();
246
247 let decision = algorithm
249 .check_and_record(&storage, "user:1", "a)
250 .await
251 .unwrap();
252 assert!(decision.is_denied());
253
254 tokio::time::sleep(Duration::from_millis(150)).await;
256
257 let decision = algorithm
259 .check_and_record(&storage, "user:1", "a)
260 .await
261 .unwrap();
262 assert!(decision.is_allowed());
263 }
264
265 #[tokio::test]
266 async fn test_gcra_check_without_record() {
267 let algorithm = GCRA::new();
268 let storage = MemoryStorage::new();
269 let quota = Quota::per_second(10).with_burst(5);
270
271 let decision = algorithm.check(&storage, "user:1", "a).await.unwrap();
273 assert!(decision.is_allowed());
274
275 let decision = algorithm.check(&storage, "user:1", "a).await.unwrap();
277 assert!(decision.is_allowed());
278
279 algorithm.check_and_record(&storage, "user:1", "a).await.unwrap();
281
282 let decision = algorithm.check(&storage, "user:1", "a).await.unwrap();
284 assert!(decision.info().remaining < 5);
285 }
286
287 #[tokio::test]
288 async fn test_gcra_separate_keys() {
289 let algorithm = GCRA::new();
290 let storage = MemoryStorage::new();
291 let quota = Quota::per_second(1).with_burst(1);
292
293 algorithm.check_and_record(&storage, "user:1", "a).await.unwrap();
295 let decision = algorithm.check_and_record(&storage, "user:1", "a).await.unwrap();
296 assert!(decision.is_denied());
297
298 let decision = algorithm.check_and_record(&storage, "user:2", "a).await.unwrap();
300 assert!(decision.is_allowed());
301 }
302
303 #[tokio::test]
304 async fn test_gcra_reset() {
305 let algorithm = GCRA::new();
306 let storage = MemoryStorage::new();
307 let quota = Quota::per_second(1).with_burst(1);
308
309 algorithm.check_and_record(&storage, "user:1", "a).await.unwrap();
311 let decision = algorithm.check_and_record(&storage, "user:1", "a).await.unwrap();
312 assert!(decision.is_denied());
313
314 algorithm.reset(&storage, "user:1").await.unwrap();
316
317 let decision = algorithm.check_and_record(&storage, "user:1", "a).await.unwrap();
319 assert!(decision.is_allowed());
320 }
321
322 #[test]
323 fn test_algorithm_name() {
324 let algorithm = GCRA::new();
325 assert_eq!(algorithm.name(), "gcra");
326 }
327}