Skip to content

Commit 8a2428b

Browse files
authored
fix(ttl_map): two minor fixes for ttl_map configuration parsing (#225)
This commit adds two small changes to improve how we parse TTLMapConfigs. 1. It strictly enforces that `tick` is an integer multiple of `ttl`. Previously, we only recommened that in comments, with the consequence that: ``` true_ttl = desired_ttl - (desired_ttl % tick) <= desired_ttl ``` In the worst case, if `tick` is close to `ttl / 2` then the true `ttl` would be equivalent to `tick`. For example, if the requested `ttl` was 60 seconds, and the tick was 31 seconds, we would only allocate a single list of inputs to delete and free them every tick. 2. There was a small bug in the validation logic that did not correctly validate that tick was nonzero due to an incorrect application of DeMorgan's law. This commit fixes that to correctly check that `tick` is nonzero.
1 parent 731ad2a commit 8a2428b

File tree

1 file changed

+34
-6
lines changed

1 file changed

+34
-6
lines changed

src/common/ttl_map.rs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,9 @@ struct TTLMapMetrics {
133133
/// TTLMapConfig configures the TTLMap parameters such as the TTL and tick period.
134134
pub struct TTLMapConfig {
135135
/// How often the map is checks for expired entries.
136-
/// This must be less than `ttl`. It's recommended to set `ttl` to a multiple
137-
/// of `tick`.
136+
/// This must be less than `ttl` and `ttl` must be a multiple of `tick`.
138137
pub tick: Duration,
139-
/// The time-to-live for entries
138+
/// The time-to-live for entries.
140139
pub ttl: Duration,
141140
}
142141

@@ -150,12 +149,18 @@ impl Default for TTLMapConfig {
150149
}
151150

152151
impl TTLMapConfig {
153-
fn is_valid(&self, tick: Duration, ttl: Duration) -> Result<(), DataFusionError> {
154-
if tick > ttl && !tick.is_zero() {
152+
fn is_valid(&self) -> Result<(), DataFusionError> {
153+
if self.tick > self.ttl || self.tick.is_zero() {
155154
return Err(DataFusionError::Configuration(
156155
"`tick` must be nonzero and <= `ttl`".to_string(),
157156
));
158157
}
158+
159+
if self.ttl.as_nanos() % self.tick.as_nanos() != 0 {
160+
return Err(DataFusionError::Configuration(
161+
"`ttl` must be an integer multiple of tick".to_string(),
162+
));
163+
}
159164
Ok(())
160165
}
161166
}
@@ -167,7 +172,7 @@ where
167172
{
168173
// try_new creates a new TTLMap.
169174
pub fn try_new(config: TTLMapConfig) -> Result<Self, DataFusionError> {
170-
config.is_valid(config.tick, config.ttl)?;
175+
config.is_valid()?;
171176
let mut map = Self::_new(config);
172177
map._start_gc();
173178
Ok(map)
@@ -291,6 +296,29 @@ mod tests {
291296
use std::sync::atomic::Ordering;
292297
use tokio::time::Duration;
293298

299+
#[tokio::test]
300+
async fn test_config_validations() {
301+
// Stores (tick, ttl, is_valid)
302+
let cases = [
303+
(Duration::from_secs(1), Duration::from_secs(10), true),
304+
(Duration::from_secs(2), Duration::from_secs(10), true),
305+
(Duration::from_secs(10), Duration::from_secs(10), true),
306+
(Duration::from_millis(250), Duration::from_secs(10), true),
307+
(Duration::from_secs(1), Duration::from_secs(0), false),
308+
(Duration::from_secs(0), Duration::from_secs(1), false),
309+
(Duration::from_secs(0), Duration::from_secs(0), false),
310+
(Duration::from_secs(3), Duration::from_secs(10), false),
311+
(Duration::from_secs(11), Duration::from_secs(10), false),
312+
// 252 is divisible by 3, so the ttl is not an integer multiple of the tick.
313+
(Duration::from_millis(252), Duration::from_secs(10), false),
314+
];
315+
316+
for (tick, ttl, is_valid) in cases {
317+
let config = TTLMapConfig { tick, ttl };
318+
assert_eq!(config.is_valid().is_ok(), is_valid);
319+
}
320+
}
321+
294322
#[tokio::test]
295323
async fn test_basic_insert_and_get() {
296324
let ttl_map = TTLMap::<String, i32>::_new(TTLMapConfig::default());

0 commit comments

Comments
 (0)