From 18ff31ab644887726bd8fa858a8841795643a100 Mon Sep 17 00:00:00 2001 From: Eguzki Astiz Lezaun Date: Wed, 15 May 2024 19:51:47 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A5=20wasm=20target?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/rust.yml | 10 - limitador/Cargo.toml | 3 +- limitador/README.md | 11 - limitador/src/counter.rs | 1 - limitador/src/storage/mod.rs | 1 - limitador/src/storage/wasm.rs | 292 --------------------------- limitador/tests/integration_tests.rs | 20 +- 7 files changed, 2 insertions(+), 336 deletions(-) delete mode 100644 limitador/src/storage/wasm.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 86c6195b..ed9db00a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -74,16 +74,6 @@ jobs: protoc-version: '3.19.4' - run: cargo bench - wasm-build: - name: Build for WASM - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - target: wasm32-unknown-unknown - - run: cargo build --target=wasm32-unknown-unknown --no-default-features --lib --manifest-path ./limitador/Cargo.toml - kind: name: Try in kind (Kubernetes in Docker) runs-on: ubuntu-latest diff --git a/limitador/Cargo.toml b/limitador/Cargo.toml index 9bebe4ac..456b3309 100644 --- a/limitador/Cargo.toml +++ b/limitador/Cargo.toml @@ -12,7 +12,6 @@ documentation = "https://docs.rs/limitador" readme = "README.md" edition = "2021" -# We make redis optional to be able to compile for wasm32. [features] default = ["disk_storage", "redis_storage"] disk_storage = ["rocksdb"] @@ -60,7 +59,7 @@ tokio = { version = "1", features = [ "rt-multi-thread", "macros", "time", -] } # wasm_storage tests use tokio::test +] } [[bench]] name = "bench" diff --git a/limitador/README.md b/limitador/README.md index 938c21d2..fd8f6f95 100644 --- a/limitador/README.md +++ b/limitador/README.md @@ -4,7 +4,6 @@ [![docs.rs](https://docs.rs/limitador/badge.svg)](https://docs.rs/limitador) An embeddable rate-limiter library supporting in-memory, Redis and disk data stores. -Limitador can also be compiled to WebAssembly. For the complete documentation of the crate's API, please refer to [docs.rs](https://docs.rs/limitador/latest/limitador/) @@ -14,13 +13,3 @@ For the complete documentation of the crate's API, please refer to [docs.rs](htt * `disk_storage`: support for using RocksDB as a local disk storage backend. * `lenient_conditions`: support for the deprecated syntax of `Condition`s * `default`: `redis_storage`. - -### WebAssembly support - -To use Limitador in a project that compiles to WASM, there are some features -that need to be disabled. Add this to your `Cargo.toml` instead: - -```toml -[dependencies] -limitador = { version = "0.3.0", default-features = false } -``` diff --git a/limitador/src/counter.rs b/limitador/src/counter.rs index d852da9c..cc51c744 100644 --- a/limitador/src/counter.rs +++ b/limitador/src/counter.rs @@ -40,7 +40,6 @@ impl Counter { } } - #[cfg(not(target_arch = "wasm32"))] pub(crate) fn key(&self) -> Self { Self { limit: self.limit.clone(), diff --git a/limitador/src/storage/mod.rs b/limitador/src/storage/mod.rs index abade6f0..d00c14b7 100644 --- a/limitador/src/storage/mod.rs +++ b/limitador/src/storage/mod.rs @@ -9,7 +9,6 @@ use thiserror::Error; #[cfg(feature = "disk_storage")] pub mod disk; pub mod in_memory; -pub mod wasm; #[cfg(feature = "redis_storage")] pub mod redis; diff --git a/limitador/src/storage/wasm.rs b/limitador/src/storage/wasm.rs deleted file mode 100644 index b62c4a66..00000000 --- a/limitador/src/storage/wasm.rs +++ /dev/null @@ -1,292 +0,0 @@ -use crate::counter::Counter; -use crate::limit::{Limit, Namespace}; -use crate::storage::{Authorization, CounterStorage, StorageErr}; -use std::collections::{HashMap, HashSet}; -use std::hash::Hash; -use std::sync::RwLock; -use std::time::{Duration, SystemTime}; - -// This is a storage implementation that can be compiled to WASM. It is very -// similar to the "InMemory" one. The InMemory implementation cannot be used in -// WASM, because it relies on std:time functions. This implementation avoids -// that. - -pub trait Clock: Sync + Send { - fn get_current_time(&self) -> SystemTime; -} - -pub struct CacheEntry { - pub value: V, - pub expires_at: SystemTime, -} - -impl CacheEntry { - fn is_expired(&self, current_time: SystemTime) -> bool { - current_time > self.expires_at - } -} - -pub struct Cache { - pub map: HashMap>, -} - -impl Cache { - pub fn new() -> Self { - Self { - map: HashMap::new(), - } - } - - pub fn get(&self, key: &K) -> Option<&CacheEntry> { - self.map.get(key) - } - - pub fn get_mut(&mut self, key: &K) -> Option<&mut CacheEntry> { - self.map.get_mut(key) - } - - pub fn insert(&mut self, key: &K, value: V, expires_at: SystemTime) { - self.map - .insert(key.clone(), CacheEntry { value, expires_at }); - } - - pub fn remove(&mut self, key: &K) { - self.map.remove(key); - } - - pub fn get_all(&mut self, current_time: SystemTime) -> Vec<(K, V, SystemTime)> { - self.map - .iter() - .filter(|(_key, cache_entry)| !cache_entry.is_expired(current_time)) - .map(|(key, cache_entry)| (key.clone(), cache_entry.value, cache_entry.expires_at)) - .collect() - } - - pub fn clear(&mut self) { - self.map.clear(); - } -} - -impl Default for Cache { - fn default() -> Self { - Self::new() - } -} - -pub struct WasmStorage { - limits_for_namespace: RwLock>>>, - pub counters: RwLock>, - pub clock: Box, -} - -impl CounterStorage for WasmStorage { - fn is_within_limits(&self, counter: &Counter, delta: i64) -> Result { - let stored_counters = self.counters.read().unwrap(); - Ok(self.counter_is_within_limits(counter, stored_counters.get(counter), delta)) - } - - fn add_counter(&self, _limit: &Limit) -> Result<(), StorageErr> { - Ok(()) - } - - fn update_counter(&self, counter: &Counter, delta: i64) -> Result<(), StorageErr> { - let mut counters = self.counters.write().unwrap(); - self.insert_or_update_counter(&mut counters, counter, delta); - Ok(()) - } - - fn check_and_update( - &self, - counters: &mut Vec, - delta: i64, - load_counters: bool, - ) -> Result { - // This makes the operator of check + update atomic - let mut stored_counters = self.counters.write().unwrap(); - - if load_counters { - let mut first_limited = None; - for counter in counters.iter_mut() { - let (remaining, expires_in) = match stored_counters.get(counter) { - Some(entry) => ( - entry.value - delta, - entry - .expires_at - .duration_since(self.clock.get_current_time()) - .unwrap_or(Duration::from_secs(0)), - ), - None => ( - counter.max_value() - delta, - Duration::from_secs(counter.seconds()), - ), - }; - counter.set_remaining(remaining); - counter.set_expires_in(expires_in); - - if first_limited.is_none() && remaining < 0 { - first_limited = Some(Authorization::Limited( - counter.limit().name().map(|n| n.to_owned()), - )) - } - } - if let Some(l) = first_limited { - return Ok(l); - } - } else { - for counter in counters.iter() { - if !self.counter_is_within_limits(counter, stored_counters.get(counter), delta) { - return Ok(Authorization::Limited( - counter.limit().name().map(|n| n.to_owned()), - )); - } - } - } - - for counter in counters.iter() { - self.insert_or_update_counter(&mut stored_counters, counter, delta) - } - - Ok(Authorization::Ok) - } - - fn get_counters(&self, limits: &HashSet) -> Result, StorageErr> { - // TODO: optimize to avoid iterating over all of them. - - let counters_with_vals: Vec = self - .counters - .write() - .unwrap() - .get_all(self.clock.get_current_time()) - .iter() - .filter(|(counter, _, _)| limits.contains(counter.limit())) - .map(|(counter, value, expires_at)| { - let mut counter_with_val = - Counter::new(counter.limit().clone(), counter.set_variables().clone()); - counter_with_val.set_remaining(*value); - counter_with_val.set_expires_in( - expires_at.duration_since(SystemTime::UNIX_EPOCH).unwrap() - - self - .clock - .get_current_time() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap(), - ); - counter_with_val - }) - .collect(); - - Ok(counters_with_vals.iter().cloned().collect()) - } - - fn delete_counters(&self, limits: HashSet) -> Result<(), StorageErr> { - for limit in limits { - self.delete_counters_of_limit(&limit); - } - - Ok(()) - } - - fn clear(&self) -> Result<(), StorageErr> { - self.counters.write().unwrap().clear(); - self.limits_for_namespace.write().unwrap().clear(); - Ok(()) - } -} - -impl WasmStorage { - pub fn new(clock: Box) -> Self { - Self { - limits_for_namespace: RwLock::new(HashMap::new()), - counters: RwLock::new(Cache::default()), - clock, - } - } - - pub fn add_counter(&self, counter: &Counter, value: i64, expires_at: SystemTime) { - self.counters - .write() - .unwrap() - .insert(counter, value, expires_at); - } - - fn delete_counters_of_limit(&self, limit: &Limit) { - if let Some(counters_by_limit) = self - .limits_for_namespace - .read() - .unwrap() - .get(limit.namespace()) - { - if let Some(counters_of_limit) = counters_by_limit.get(limit) { - let mut counters = self.counters.write().unwrap(); - for counter in counters_of_limit { - counters.remove(counter); - } - } - } - } - - fn add_counter_limit_association(&self, counter: &Counter) { - let namespace = counter.limit().namespace(); - - if let Some(counters_by_limit) = self - .limits_for_namespace - .write() - .unwrap() - .get_mut(namespace) - { - counters_by_limit - .get_mut(counter.limit()) - .unwrap() - .insert(counter.clone()); - } - } - - fn insert_or_update_counter( - &self, - counters: &mut Cache, - counter: &Counter, - delta: i64, - ) { - match counters.get_mut(counter) { - Some(entry) => { - if entry.is_expired(self.clock.get_current_time()) { - // TODO: remove duplication. "None" branch is identical. - counters.insert( - counter, - counter.max_value() - delta, - self.clock.get_current_time() + Duration::from_secs(counter.seconds()), - ); - } else { - entry.value -= delta; - } - } - None => { - counters.insert( - counter, - counter.max_value() - delta, - self.clock.get_current_time() + Duration::from_secs(counter.seconds()), - ); - - self.add_counter_limit_association(counter); - } - }; - } - - fn counter_is_within_limits( - &self, - counter: &Counter, - cache_entry: Option<&CacheEntry>, - delta: i64, - ) -> bool { - match cache_entry { - Some(entry) => { - if entry.is_expired(self.clock.get_current_time()) { - counter.max_value() - delta >= 0 - } else { - entry.value - delta >= 0 - } - } - None => counter.max_value() - delta >= 0, - } - } -} diff --git a/limitador/tests/integration_tests.rs b/limitador/tests/integration_tests.rs index ef89c9f8..276df09a 100644 --- a/limitador/tests/integration_tests.rs +++ b/limitador/tests/integration_tests.rs @@ -34,14 +34,6 @@ macro_rules! test_with_all_storage_impls { $function(&mut TestsLimiter::new_from_blocking_impl(rate_limiter)).await; } - #[tokio::test] - async fn [<$function _with_wasm_storage>]() { - let rate_limiter = RateLimiter::new_with_storage( - Box::new(WasmStorage::new(Box::new(TestClock {}))) - ); - $function(&mut TestsLimiter::new_from_blocking_impl(rate_limiter)).await; - } - #[cfg(feature = "redis_storage")] #[tokio::test] #[serial] @@ -93,26 +85,16 @@ mod test { } use self::limitador::counter::Counter; - use self::limitador::storage::wasm::Clock; use self::limitador::RateLimiter; use crate::helpers::tests_limiter::*; use limitador::limit::Limit; use limitador::storage::disk::{DiskStorage, OptimizeFor}; use limitador::storage::in_memory::InMemoryStorage; - use limitador::storage::wasm::WasmStorage; use std::collections::{HashMap, HashSet}; use std::thread::sleep; - use std::time::{Duration, SystemTime}; + use std::time::Duration; use tempfile::TempDir; - // This is only needed for the WASM-compatible storage. - pub struct TestClock {} - impl Clock for TestClock { - fn get_current_time(&self) -> SystemTime { - SystemTime::now() - } - } - test_with_all_storage_impls!(get_namespaces); test_with_all_storage_impls!(get_namespaces_returns_empty_when_there_arent_any); test_with_all_storage_impls!(get_namespaces_doesnt_return_the_ones_that_no_longer_have_limits);