-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rate-Limiter API #28
Open
branh0821
wants to merge
13
commits into
master
Choose a base branch
from
leaky_bucket
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Rate-Limiter API #28
Changes from 9 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
4ea1b0e
Created files for leaky bucket API
branh0821 c930045
Skeleton code structure for LB
branh0821 8774264
Modified to token bucket + key algorithm.
branh0821 32b9d61
Core functionality of token bucket is mostly finished.
branh0821 f6c2ac9
Added test--dosent work yet so need to fix
branh0821 a28331a
Fixed test--should work.
branh0821 2ae8577
Fixed test--should work.
branh0821 7d53b19
Fixed test--should work.
branh0821 b68f329
Fixed test--should work.
branh0821 5dde252
Delete launch.json
branh0821 a767505
Modified the update function to process error handling.
branh0821 48acdca
Error handling
branh0821 d0a7126
Error handling
branh0821 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
{ | ||
// Use IntelliSense to learn about possible attributes. | ||
// Hover to view descriptions of existing attributes. | ||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | ||
"version": "0.2.0", | ||
"configurations": [ | ||
{ | ||
"type": "lldb", | ||
"request": "launch", | ||
"name": "Debug unit tests in library 'bubble'", | ||
"cargo": { | ||
"args": [ | ||
"test", | ||
"--no-run", | ||
"--lib", | ||
"--package=bubble" | ||
], | ||
"filter": { | ||
"name": "bubble", | ||
"kind": "lib" | ||
} | ||
}, | ||
"args": [], | ||
"cwd": "${workspaceFolder}" | ||
}, | ||
{ | ||
"type": "lldb", | ||
"request": "launch", | ||
"name": "Debug executable 'bubble'", | ||
"cargo": { | ||
"args": [ | ||
"build", | ||
"--bin=bubble", | ||
"--package=bubble" | ||
], | ||
"filter": { | ||
"name": "bubble", | ||
"kind": "bin" | ||
} | ||
}, | ||
"args": [], | ||
"cwd": "${workspaceFolder}" | ||
}, | ||
{ | ||
"type": "lldb", | ||
"request": "launch", | ||
"name": "Debug unit tests in executable 'bubble'", | ||
"cargo": { | ||
"args": [ | ||
"test", | ||
"--no-run", | ||
"--bin=bubble", | ||
"--package=bubble" | ||
], | ||
"filter": { | ||
"name": "bubble", | ||
"kind": "bin" | ||
} | ||
}, | ||
"args": [], | ||
"cwd": "${workspaceFolder}" | ||
}, | ||
{ | ||
"type": "lldb", | ||
"request": "launch", | ||
"name": "Debug integration test 'user_test'", | ||
"cargo": { | ||
"args": [ | ||
"test", | ||
"--no-run", | ||
"--test=user_test", | ||
"--package=bubble" | ||
], | ||
"filter": { | ||
"name": "user_test", | ||
"kind": "test" | ||
} | ||
}, | ||
"args": [], | ||
"cwd": "${workspaceFolder}" | ||
}, | ||
{ | ||
"type": "lldb", | ||
"request": "launch", | ||
"name": "Debug integration test 'helper'", | ||
"cargo": { | ||
"args": [ | ||
"test", | ||
"--no-run", | ||
"--test=helper", | ||
"--package=bubble" | ||
], | ||
"filter": { | ||
"name": "helper", | ||
"kind": "test" | ||
} | ||
}, | ||
"args": [], | ||
"cwd": "${workspaceFolder}" | ||
} | ||
] | ||
} |
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 |
---|---|---|
|
@@ -21,6 +21,7 @@ base64 = "0.21.0" | |
# SendGrid | ||
sendgrid = "0.19.0" | ||
|
||
# Token | ||
|
||
|
||
[dev-dependencies] | ||
|
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,3 +1,4 @@ | ||
pub mod email; | ||
pub mod password; | ||
pub mod session; | ||
pub mod token; |
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,236 @@ | ||
use std::collections::HashMap; | ||
|
||
use std::sync::{Arc, Mutex}; | ||
|
||
use std::thread; | ||
|
||
use std::time::{Duration, Instant}; | ||
|
||
pub struct LimiterConfig<'a> { | ||
name: &'a str, | ||
size: usize, | ||
rate: f64, | ||
} | ||
|
||
#[non_exhaustive] | ||
struct Configs; | ||
|
||
impl Configs { | ||
pub const MESSAGES: LimiterConfig<'_> = LimiterConfig { | ||
name: "messages", | ||
size: 60, | ||
rate: 60.0 / 0.017, | ||
}; | ||
pub const CLIENT_CREATE: LimiterConfig<'_> = LimiterConfig { | ||
name: "messages", | ||
size: 2, | ||
rate: 1.0, | ||
}; | ||
pub const CLIENT_UPDATE: LimiterConfig<'_> = LimiterConfig { | ||
name: "messages", | ||
size: 4, | ||
rate: 2.0, | ||
}; | ||
pub const CLIENT_DELETE: LimiterConfig<'_> = LimiterConfig { | ||
name: "messages", | ||
size: 2, | ||
rate: 1.0, | ||
}; | ||
|
||
pub const USER_REGISTRATION: LimiterConfig<'_> = LimiterConfig { | ||
name: "registration", | ||
size: 6, | ||
rate: 12.0, | ||
}; | ||
pub const USER_CONFIRM_REGISTRATION: LimiterConfig<'_> = LimiterConfig { | ||
name: "confirm_registration", | ||
size: 5, | ||
rate: 10.0, | ||
}; | ||
pub const USER_LOGIN_ATTEMPT: LimiterConfig<'_> = LimiterConfig { | ||
name: "login", | ||
size: 10, | ||
rate: 10.0 / 144.0, | ||
}; | ||
pub const USER_FORGOT_PASSWORD: LimiterConfig<'_> = LimiterConfig { | ||
name: "forgot", | ||
size: 10, | ||
rate: 10.0 / 144.0, | ||
}; | ||
pub const USER_CHANGE_EMAIL: LimiterConfig<'_> = LimiterConfig { | ||
name: "change", | ||
size: 6, | ||
rate: 6.0 / 10.0, | ||
}; | ||
pub const USER_DELETE_USER: LimiterConfig<'_> = LimiterConfig { | ||
name: "delete_user", | ||
size: 6, | ||
rate: 6.0 / 10.0, | ||
}; | ||
} | ||
|
||
#[derive(Copy, Clone)] | ||
pub struct Bucket { | ||
capacity: usize, | ||
refill_rate: f64, | ||
last_refill_time: Instant, | ||
current_tokens: usize, | ||
} | ||
|
||
impl Bucket { | ||
fn new(capacity: usize, refill_rate: f64) -> Bucket { | ||
Bucket { | ||
capacity, | ||
refill_rate, | ||
current_tokens: 0, | ||
last_refill_time: Instant::now(), | ||
} | ||
} | ||
} | ||
|
||
|
||
pub struct TokenBucket { | ||
buckets: Mutex<HashMap<String, Bucket>>, | ||
} | ||
|
||
impl TokenBucket { | ||
fn new() -> TokenBucket { | ||
TokenBucket { | ||
buckets: Mutex::new(HashMap::new()), | ||
} | ||
} | ||
|
||
fn add_bucket(&self, capacity: usize, refill_rate: f64, name: &str) { | ||
self.buckets | ||
.lock() | ||
.unwrap() | ||
.insert(name.to_string(), Bucket::new(capacity, refill_rate)); | ||
} | ||
|
||
fn seed_buckets(&self, configs: &[LimiterConfig]) { | ||
for config in configs { | ||
self.add_bucket(config.size, config.rate, config.name); | ||
} | ||
} | ||
|
||
fn update(&self, to_update: &str) { | ||
let mut buckets = self.buckets.lock().unwrap(); | ||
let mut bucket = buckets.get_mut(to_update).unwrap(); | ||
let now = Instant::now(); | ||
let elapsed = now.duration_since(bucket.last_refill_time); | ||
|
||
let tokens_to_add = (elapsed.as_secs_f64() * bucket.refill_rate) as usize; | ||
if tokens_to_add > 0 { | ||
bucket.current_tokens = | ||
std::cmp::min(bucket.capacity, bucket.current_tokens + tokens_to_add); | ||
bucket.last_refill_time = now; | ||
} | ||
} | ||
|
||
fn handle(&self, num_tokens: usize, to_update: &str) -> bool { | ||
let mut buckets = self.buckets.lock().unwrap(); | ||
let mut bucket = buckets.get_mut(to_update).unwrap(); | ||
self.update(to_update); | ||
|
||
let tokens_in_bucket = bucket.current_tokens; | ||
if tokens_in_bucket >= num_tokens { | ||
bucket.current_tokens -= num_tokens; | ||
true | ||
} else { | ||
false | ||
} | ||
} | ||
} | ||
|
||
#[derive(Clone)] | ||
struct Service { | ||
name: String, | ||
token_bucket: Arc<TokenBucket>, | ||
} | ||
|
||
impl Service { | ||
fn new(name: String, token_bucket: Arc<TokenBucket>) -> Service { | ||
Service { name, token_bucket } | ||
} | ||
|
||
fn handle_request(&self, num_tokens: usize, bucket_to_handle: &str) { | ||
if self.token_bucket.handle(num_tokens, bucket_to_handle) { | ||
println!( | ||
"Request handled by service {} for configuration {}: {:?}", | ||
self.name, | ||
bucket_to_handle, | ||
thread::current().id() | ||
); | ||
} else { | ||
println!( | ||
"Rate limit exceeded by service {} for configuration {}: {:?}", | ||
self.name, | ||
bucket_to_handle, | ||
thread::current().id() | ||
); | ||
} | ||
} | ||
} | ||
|
||
#[test] | ||
fn test_token_bucket() { | ||
let token_bucket = Arc::new(TokenBucket::new()); | ||
|
||
// Seed the token bucket with predefined configurations | ||
let configs = [ | ||
Configs::MESSAGES, | ||
Configs::CLIENT_CREATE, | ||
Configs::CLIENT_UPDATE, | ||
Configs::CLIENT_DELETE, | ||
Configs::USER_REGISTRATION, | ||
Configs::USER_CONFIRM_REGISTRATION, | ||
Configs::USER_LOGIN_ATTEMPT, | ||
Configs::USER_FORGOT_PASSWORD, | ||
Configs::USER_CHANGE_EMAIL, | ||
Configs::USER_DELETE_USER, | ||
]; | ||
token_bucket.seed_buckets(&configs); | ||
|
||
// Create service instances | ||
let service_a = Service::new("A".to_string(), token_bucket.clone()); | ||
let service_b = Service::new("B".to_string(), token_bucket.clone()); | ||
|
||
// Perform requests to the services | ||
for _ in 0..10 { | ||
// Handle requests for the "messages" service | ||
let service_a = service_a.clone(); | ||
thread::spawn(move || { | ||
service_a.handle_request(1, Configs::MESSAGES.name); | ||
}); | ||
|
||
// Handle requests for the "registration" service | ||
let service_b = service_b.clone(); | ||
thread::spawn(move || { | ||
service_b.handle_request(1, Configs::USER_REGISTRATION.name); | ||
}); | ||
|
||
// Sleep for a short duration between requests | ||
thread::sleep(Duration::from_millis(100)); | ||
} | ||
|
||
// Sleep for a longer duration to allow token refilling | ||
thread::sleep(Duration::from_secs(2)); | ||
|
||
// Perform more requests after token refilling | ||
for _ in 0..5 { | ||
// Handle requests for the "messages" service | ||
let service_a = service_a.clone(); | ||
thread::spawn(move || { | ||
service_a.handle_request(1, Configs::MESSAGES.name); | ||
}); | ||
|
||
// Handle requests for the "registration" service | ||
let service_b = service_b.clone(); | ||
thread::spawn(move || { | ||
service_b.handle_request(1, Configs::USER_REGISTRATION.name); | ||
}); | ||
|
||
// Sleep for a short duration between requests | ||
thread::sleep(Duration::from_millis(100)); | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is ugly. macro?