/home/runner/work/feoxdb/feoxdb/src/core/store/atomic.rs
Line | Count | Source |
1 | | use bytes::{BufMut, BytesMut}; |
2 | | use std::sync::atomic::Ordering; |
3 | | use std::sync::Arc; |
4 | | |
5 | | use crate::constants::Operation; |
6 | | use crate::core::record::Record; |
7 | | use crate::error::{FeoxError, Result}; |
8 | | |
9 | | use super::FeoxStore; |
10 | | |
11 | | impl FeoxStore { |
12 | | /// Atomically increment a numeric counter. |
13 | | /// |
14 | | /// The value must be stored as an 8-byte little-endian i64. If the key doesn't exist, |
15 | | /// it will be created with the given delta value. If it exists, the value will be |
16 | | /// incremented atomically. |
17 | | /// |
18 | | /// # Value Format |
19 | | /// |
20 | | /// The value MUST be exactly 8 bytes representing a little-endian i64. |
21 | | /// Use `i64::to_le_bytes()` to create the initial value: |
22 | | /// ```rust,ignore |
23 | | /// let zero: i64 = 0; |
24 | | /// store.insert(b"counter", &zero.to_le_bytes())?; |
25 | | /// ``` |
26 | | /// |
27 | | /// # Arguments |
28 | | /// |
29 | | /// * `key` - The key of the counter |
30 | | /// * `delta` - The amount to increment by (can be negative for decrement) |
31 | | /// * `timestamp` - Optional timestamp for conflict resolution |
32 | | /// |
33 | | /// # Returns |
34 | | /// |
35 | | /// Returns the new value after incrementing. |
36 | | /// |
37 | | /// # Errors |
38 | | /// |
39 | | /// * `InvalidOperation` - Existing value is not exactly 8 bytes (not a valid i64) |
40 | | /// * `OlderTimestamp` - Timestamp is not newer than existing record |
41 | | /// |
42 | | /// # Example |
43 | | /// |
44 | | /// ```rust |
45 | | /// # use feoxdb::FeoxStore; |
46 | | /// # fn main() -> feoxdb::Result<()> { |
47 | | /// # let store = FeoxStore::new(None)?; |
48 | | /// // Initialize counter with proper binary format |
49 | | /// let initial: i64 = 0; |
50 | | /// store.insert(b"visits", &initial.to_le_bytes())?; |
51 | | /// |
52 | | /// // Increment atomically |
53 | | /// let val = store.atomic_increment(b"visits", 1)?; |
54 | | /// assert_eq!(val, 1); |
55 | | /// |
56 | | /// // Increment by 5 |
57 | | /// let val = store.atomic_increment(b"visits", 5)?; |
58 | | /// assert_eq!(val, 6); |
59 | | /// |
60 | | /// // Decrement by 2 |
61 | | /// let val = store.atomic_increment(b"visits", -2)?; |
62 | | /// assert_eq!(val, 4); |
63 | | /// |
64 | | /// // Or create new counter directly (starts at delta value) |
65 | | /// let downloads = store.atomic_increment(b"downloads", 100)?; |
66 | | /// assert_eq!(downloads, 100); |
67 | | /// # Ok(()) |
68 | | /// # } |
69 | | /// ``` |
70 | 1.13k | pub fn atomic_increment(&self, key: &[u8], delta: i64) -> Result<i64> { |
71 | 1.13k | self.atomic_increment_with_timestamp_and_ttl(key, delta, None, 0) |
72 | 1.13k | } |
73 | | |
74 | | /// Atomically increment/decrement with explicit timestamp. |
75 | | /// |
76 | | /// This is the advanced version that allows manual timestamp control. |
77 | | /// Most users should use `atomic_increment()` instead. |
78 | | /// |
79 | | /// # Arguments |
80 | | /// |
81 | | /// * `key` - The key to increment/decrement |
82 | | /// * `delta` - Amount to add (negative to decrement) |
83 | | /// * `timestamp` - Optional timestamp. If `None`, uses current time. |
84 | | /// |
85 | | /// # Errors |
86 | | /// |
87 | | /// * `OlderTimestamp` - Timestamp is not newer than existing record |
88 | 6 | pub fn atomic_increment_with_timestamp( |
89 | 6 | &self, |
90 | 6 | key: &[u8], |
91 | 6 | delta: i64, |
92 | 6 | timestamp: Option<u64>, |
93 | 6 | ) -> Result<i64> { |
94 | 6 | self.atomic_increment_with_timestamp_and_ttl(key, delta, timestamp, 0) |
95 | 6 | } |
96 | | |
97 | | /// Atomically increment/decrement with TTL support. |
98 | | /// |
99 | | /// # Arguments |
100 | | /// |
101 | | /// * `key` - The key to increment/decrement |
102 | | /// * `delta` - Amount to add (negative to decrement) |
103 | | /// * `ttl_seconds` - Time-to-live in seconds (0 for no expiry) |
104 | | /// |
105 | | /// # Errors |
106 | | /// |
107 | | /// * `InvalidOperation` - Value is not a valid i64 |
108 | 2 | pub fn atomic_increment_with_ttl( |
109 | 2 | &self, |
110 | 2 | key: &[u8], |
111 | 2 | delta: i64, |
112 | 2 | ttl_seconds: u64, |
113 | 2 | ) -> Result<i64> { |
114 | 2 | self.atomic_increment_with_timestamp_and_ttl(key, delta, None, ttl_seconds) |
115 | 2 | } |
116 | | |
117 | | /// Atomically increment/decrement with explicit timestamp and TTL. |
118 | | /// |
119 | | /// # Arguments |
120 | | /// |
121 | | /// * `key` - The key to increment/decrement |
122 | | /// * `delta` - Amount to add (negative to decrement) |
123 | | /// * `timestamp` - Optional timestamp. If `None`, uses current time. |
124 | | /// * `ttl_seconds` - Time-to-live in seconds (0 for no expiry) |
125 | | /// |
126 | | /// # Errors |
127 | | /// |
128 | | /// * `OlderTimestamp` - Timestamp is not newer than existing record |
129 | 1.14k | pub fn atomic_increment_with_timestamp_and_ttl( |
130 | 1.14k | &self, |
131 | 1.14k | key: &[u8], |
132 | 1.14k | delta: i64, |
133 | 1.14k | timestamp: Option<u64>, |
134 | 1.14k | ttl_seconds: u64, |
135 | 1.14k | ) -> Result<i64> { |
136 | 1.14k | if ttl_seconds > 0 { |
137 | 4 | self.ensure_ttl_write_supported()?0 ; |
138 | 1.13k | } |
139 | 1.14k | self.validate_new_key(key)?0 ; |
140 | | |
141 | 1.14k | let key_vec = key.to_vec(); |
142 | 1.14k | let explicit_timestamp = timestamp.filter(|timestamp| *timestamp8 != 0); |
143 | 1.14k | let mut observed = None; |
144 | | |
145 | | loop { |
146 | 1.68k | let Some(current1.66k ) = self.hash_table.read(key, |_, record| Arc::clone1.66k (record1.66k )) else { |
147 | 26 | let retired_at = observed |
148 | 26 | .as_ref() |
149 | 26 | .map_or(0, |record: &Arc<Record>| record2 .retirement_timestamp2 ()); |
150 | 26 | let timestamp = match explicit_timestamp { |
151 | 4 | Some(timestamp) => timestamp, |
152 | | None => { |
153 | 22 | let minimum = retired_at.checked_add(1).ok_or(FeoxError::OlderTimestamp)?0 ; |
154 | 22 | self.get_timestamp(key).max(minimum) |
155 | | } |
156 | | }; |
157 | 26 | if timestamp <= retired_at { |
158 | 0 | return Err(FeoxError::OlderTimestamp); |
159 | 26 | } |
160 | | |
161 | 26 | match self.hash_table.entry(key_vec.clone()) { |
162 | 0 | scc::hash_map::Entry::Occupied(_) => continue, |
163 | 26 | scc::hash_map::Entry::Vacant(entry) => { |
164 | 26 | let record_size = self.calculate_record_size(key.len(), 8); |
165 | 26 | let reservation10 = self.reserve_memory(record_size)?16 ; |
166 | 10 | let new_record = |
167 | 10 | counter_record(key_vec.clone(), delta, timestamp, ttl_seconds); |
168 | 10 | let ttl_expiry = new_record.ttl_expiry.load(Ordering::Acquire); |
169 | 10 | let buffered_record = self |
170 | 10 | .write_buffer |
171 | 10 | .as_ref() |
172 | 10 | .filter(|_| !self.memory_only1 ) |
173 | 10 | .map(|_| Arc::clone1 (&new_record1 )); |
174 | 10 | let entry_guard = entry.insert_entry(Arc::clone(&new_record)); |
175 | 10 | self.insert_into_tree(key_vec.clone(), new_record); |
176 | 10 | self.observe_published_timestamp( |
177 | 10 | key, |
178 | 10 | timestamp, |
179 | 10 | explicit_timestamp.is_some(), |
180 | | ); |
181 | 10 | reservation.commit(); |
182 | 10 | self.note_ttl_transition(0, ttl_expiry); |
183 | 10 | self.stats.record_count.fetch_add(1, Ordering::Relaxed); |
184 | 10 | drop(entry_guard); |
185 | | |
186 | 1 | if let (Some(write_buffer), Some(record)) = |
187 | 10 | (&self.write_buffer, buffered_record) |
188 | | { |
189 | 1 | write_buffer.add_write(Operation::Insert, record, 0)?0 ; |
190 | 9 | } |
191 | 10 | return Ok(delta); |
192 | | } |
193 | | } |
194 | | }; |
195 | | |
196 | 1.66k | let root = observed.get_or_insert_with(|| Arc::clone1.11k (¤t1.11k )); |
197 | 1.66k | if explicit_timestamp.is_some_and(|timestamp| timestamp4 <= current.timestamp4 ) { |
198 | 1 | return Err(FeoxError::OlderTimestamp); |
199 | 1.66k | } |
200 | | |
201 | 1.66k | let expiry = current.ttl_expiry.load(Ordering::Acquire); |
202 | 1.66k | if self.enable_ttl && expiry > 04 { |
203 | 4 | let now = self.get_timestamp_pub(); |
204 | 4 | if now > expiry { |
205 | 2 | self.retire_expired_if_current(key, ¤t, now)?0 ; |
206 | 2 | continue; |
207 | 2 | } |
208 | 1.65k | } |
209 | | |
210 | 1.66k | let (value, _, source) = match self.resolve_value(key, Arc::clone(¤t)) { |
211 | 1.66k | Ok(resolved) => resolved, |
212 | | Err(FeoxError::KeyNotFound) => { |
213 | 0 | let now = self.get_timestamp_pub(); |
214 | 0 | self.retire_expired_if_current(key, ¤t, now)?; |
215 | 0 | continue; |
216 | | } |
217 | 0 | Err(error) => return Err(error), |
218 | | }; |
219 | 1.66k | if value.len() != 8 { |
220 | 2 | return Err(FeoxError::InvalidOperation); |
221 | 1.65k | } |
222 | 1.65k | let current_value = i64::from_le_bytes( |
223 | 1.65k | value |
224 | 1.65k | .as_ref() |
225 | 1.65k | .try_into() |
226 | 1.65k | .map_err(|_| FeoxError::InvalidNumericValue)?0 , |
227 | | ); |
228 | 1.65k | let new_value = current_value.saturating_add(delta); |
229 | 1.65k | let timestamp = explicit_timestamp.unwrap_or_else(|| self1.65k .get_timestamp1.65k (key1.65k )); |
230 | | |
231 | 1.65k | match self.hash_table.entry(key_vec.clone()) { |
232 | 1.65k | scc::hash_map::Entry::Occupied(mut entry) => { |
233 | 1.65k | let old_record = entry.get(); |
234 | 1.65k | if !Arc::ptr_eq(old_record, &source) { |
235 | 545 | if explicit_timestamp |
236 | 545 | .is_some_and(|timestamp| timestamp0 <= root.retirement_timestamp()0 ) |
237 | | { |
238 | 0 | return Err(FeoxError::OlderTimestamp); |
239 | 545 | } |
240 | 545 | continue; |
241 | 1.11k | } |
242 | 1.11k | if timestamp <= old_record.timestamp { |
243 | 0 | return Err(FeoxError::OlderTimestamp); |
244 | 1.11k | } |
245 | | |
246 | 1.11k | let old_size = old_record.calculate_size(); |
247 | 1.11k | let new_size = self.calculate_record_size(old_record.key.len(), 8); |
248 | 1.11k | let reservation = self.reserve_memory(new_size.saturating_sub(old_size))?0 ; |
249 | 1.11k | let old_record = Arc::clone(old_record); |
250 | 1.11k | let record = counter_record(key_vec.clone(), new_value, timestamp, ttl_seconds); |
251 | 1.11k | let old_expiry = old_record.ttl_expiry.load(Ordering::Acquire); |
252 | 1.11k | let new_expiry = record.ttl_expiry.load(Ordering::Acquire); |
253 | | |
254 | 1.11k | old_record.link_successor(&record); |
255 | 1.11k | old_record.refcount.store(0, Ordering::Release); |
256 | 1.11k | entry.insert(Arc::clone(&record)); |
257 | 1.11k | self.publish_to_tree(&key_vec, Arc::clone(&record)); |
258 | 1.11k | self.observe_published_timestamp(key, timestamp, explicit_timestamp.is_some()); |
259 | 1.11k | reservation.commit(); |
260 | 1.11k | self.note_ttl_transition(old_expiry, new_expiry); |
261 | 1.11k | if old_size > new_size { |
262 | 0 | self.release_memory(old_size - new_size); |
263 | 1.11k | } |
264 | 1.11k | drop(entry); |
265 | | |
266 | 1.11k | if !self.memory_only { |
267 | 101 | if self.enable_caching { |
268 | 101 | if let Some(cache) = &self.cache { |
269 | 101 | cache.remove_for_record(&key_vec, &old_record); |
270 | 101 | }0 |
271 | 0 | } |
272 | | |
273 | 101 | if let Some(write_buffer) = &self.write_buffer { |
274 | 101 | write_buffer.add_replacement(record, old_record)?0 ; |
275 | 0 | } |
276 | 1.01k | } |
277 | 1.11k | return Ok(new_value); |
278 | | } |
279 | | scc::hash_map::Entry::Vacant(_) => { |
280 | 0 | if explicit_timestamp |
281 | 0 | .is_some_and(|timestamp| timestamp <= root.retirement_timestamp()) |
282 | | { |
283 | 0 | return Err(FeoxError::OlderTimestamp); |
284 | 0 | } |
285 | | } |
286 | | } |
287 | | } |
288 | 1.14k | } |
289 | | |
290 | | /// Insert a key only when it does not already exist. |
291 | | /// |
292 | | /// The existence check and insertion happen under the hash-table entry guard, so |
293 | | /// concurrent callers cannot both create the same key. |
294 | | /// |
295 | | /// # Returns |
296 | | /// |
297 | | /// Returns `Ok(true)` when this call inserted the key and `Ok(false)` when another |
298 | | /// value already exists. |
299 | | /// |
300 | | /// # Example |
301 | | /// |
302 | | /// ```rust |
303 | | /// # use feoxdb::FeoxStore; |
304 | | /// # fn main() -> feoxdb::Result<()> { |
305 | | /// let store = FeoxStore::new(None)?; |
306 | | /// assert!(store.insert_if_absent(b"job:1", b"first")?); |
307 | | /// assert!(!store.insert_if_absent(b"job:1", b"second")?); |
308 | | /// assert_eq!(store.get(b"job:1")?, b"first"); |
309 | | /// # Ok(()) |
310 | | /// # } |
311 | | /// ``` |
312 | 16.0k | pub fn insert_if_absent(&self, key: &[u8], value: &[u8]) -> Result<bool> { |
313 | 16.0k | let start = std::time::Instant::now(); |
314 | 16.0k | self.validate_key_value(key, value)?0 ; |
315 | 16.0k | let key_vec = key.to_vec(); |
316 | | |
317 | 16.0k | match self.hash_table.entry(key_vec.clone()) { |
318 | 6.61k | scc::hash_map::Entry::Occupied(_) => Ok(false), |
319 | 9.42k | scc::hash_map::Entry::Vacant(entry) => { |
320 | 9.42k | let record_size = self.calculate_record_size(key.len(), value.len()); |
321 | 9.42k | let reservation9.40k = self.reserve_memory(record_size)?15 ; |
322 | | |
323 | 9.40k | let timestamp = self.get_timestamp(key); |
324 | 9.40k | let record = Arc::new(Record::new(key_vec.clone(), value.to_vec(), timestamp)); |
325 | 9.40k | let buffered_record = self |
326 | 9.40k | .write_buffer |
327 | 9.40k | .as_ref() |
328 | 9.40k | .filter(|_| !self.memory_only1 ) |
329 | 9.40k | .map(|_| Arc::clone1 (&record1 )); |
330 | 9.40k | let entry_guard = entry.insert_entry(Arc::clone(&record)); |
331 | | |
332 | 9.40k | self.insert_into_tree(key_vec, record); |
333 | 9.40k | reservation.commit(); |
334 | 9.40k | self.stats.record_count.fetch_add(1, Ordering::Relaxed); |
335 | 9.40k | drop(entry_guard); |
336 | 9.40k | self.stats |
337 | 9.40k | .record_insert(start.elapsed().as_nanos() as u64, false); |
338 | | |
339 | 9.40k | if let (Some(write_buffer1 ), Some(record1 )) = (&self.write_buffer, buffered_record) { |
340 | 1 | write_buffer.add_write(Operation::Insert, record, 0)?0 ; |
341 | 9.40k | } |
342 | | |
343 | 9.40k | Ok(true) |
344 | | } |
345 | | } |
346 | 16.0k | } |
347 | | |
348 | | /// Atomically compare and swap a value. |
349 | | /// |
350 | | /// Compares the current value of a key with an expected value, and if they match, |
351 | | /// atomically replaces it with a new value. This operation is atomic within the |
352 | | /// HashMap shard, preventing race conditions. |
353 | | /// |
354 | | /// # Arguments |
355 | | /// |
356 | | /// * `key` - The key to check and potentially update |
357 | | /// * `expected` - The expected current value |
358 | | /// * `new_value` - The new value to set if comparison succeeds |
359 | | /// |
360 | | /// # Returns |
361 | | /// |
362 | | /// Returns `Ok(true)` if the swap succeeded (current value matched expected). |
363 | | /// Returns `Ok(false)` if the current value didn't match or key doesn't exist. |
364 | | /// |
365 | | /// # Errors |
366 | | /// |
367 | | /// * `InvalidKeySize` - Key is invalid |
368 | | /// * `InvalidValueSize` - New value is too large |
369 | | /// * `OutOfMemory` - Memory limit exceeded |
370 | | /// * `IoError` - Failed to read value from disk |
371 | | /// |
372 | | /// # Example |
373 | | /// |
374 | | /// ```rust |
375 | | /// # use feoxdb::FeoxStore; |
376 | | /// # fn main() -> feoxdb::Result<()> { |
377 | | /// # let store = FeoxStore::new(None)?; |
378 | | /// store.insert(b"config", b"v1")?; |
379 | | /// |
380 | | /// // Successful CAS - value matches |
381 | | /// let swapped = store.compare_and_swap(b"config", b"v1", b"v2")?; |
382 | | /// assert_eq!(swapped, true); |
383 | | /// |
384 | | /// // Failed CAS - value doesn't match |
385 | | /// let swapped = store.compare_and_swap(b"config", b"v1", b"v3")?; |
386 | | /// assert_eq!(swapped, false); // Value is now "v2", not "v1" |
387 | | /// |
388 | | /// // CAS on non-existent key |
389 | | /// let swapped = store.compare_and_swap(b"missing", b"any", b"new")?; |
390 | | /// assert_eq!(swapped, false); |
391 | | /// # Ok(()) |
392 | | /// # } |
393 | | /// ``` |
394 | 2.31k | pub fn compare_and_swap(&self, key: &[u8], expected: &[u8], new_value: &[u8]) -> Result<bool> { |
395 | 2.31k | self.compare_and_swap_with_timestamp_and_ttl(key, expected, new_value, None, 0) |
396 | 2.31k | } |
397 | | |
398 | | /// Compare and swap with explicit timestamp. |
399 | | /// |
400 | | /// This is the advanced version that allows manual timestamp control for |
401 | | /// conflict resolution. Most users should use `compare_and_swap()` instead. |
402 | | /// |
403 | | /// # Arguments |
404 | | /// |
405 | | /// * `key` - The key to check and potentially update |
406 | | /// * `expected` - The expected current value |
407 | | /// * `new_value` - The new value to set if comparison succeeds |
408 | | /// * `timestamp` - Optional timestamp. If `None`, uses current time. |
409 | | /// |
410 | | /// # Errors |
411 | | /// |
412 | | /// * `OlderTimestamp` - Timestamp is not newer than existing record |
413 | 3 | pub fn compare_and_swap_with_timestamp( |
414 | 3 | &self, |
415 | 3 | key: &[u8], |
416 | 3 | expected: &[u8], |
417 | 3 | new_value: &[u8], |
418 | 3 | timestamp: Option<u64>, |
419 | 3 | ) -> Result<bool> { |
420 | 3 | self.compare_and_swap_with_timestamp_and_ttl(key, expected, new_value, timestamp, 0) |
421 | 3 | } |
422 | | |
423 | | /// Compare and swap with TTL support. |
424 | | /// |
425 | | /// # Arguments |
426 | | /// |
427 | | /// * `key` - The key to check and potentially update |
428 | | /// * `expected` - The expected current value |
429 | | /// * `new_value` - The new value to set if comparison succeeds |
430 | | /// * `ttl_seconds` - Time-to-live in seconds (0 for no expiry) |
431 | | /// |
432 | | /// # Errors |
433 | | /// |
434 | | /// * `InvalidKeySize` - Key is invalid |
435 | | /// * `InvalidValueSize` - New value is too large |
436 | 1 | pub fn compare_and_swap_with_ttl( |
437 | 1 | &self, |
438 | 1 | key: &[u8], |
439 | 1 | expected: &[u8], |
440 | 1 | new_value: &[u8], |
441 | 1 | ttl_seconds: u64, |
442 | 1 | ) -> Result<bool> { |
443 | 1 | self.compare_and_swap_with_timestamp_and_ttl(key, expected, new_value, None, ttl_seconds) |
444 | 1 | } |
445 | | |
446 | | /// Compare and swap with explicit timestamp and TTL. |
447 | | /// |
448 | | /// # Arguments |
449 | | /// |
450 | | /// * `key` - The key to check and potentially update |
451 | | /// * `expected` - The expected current value |
452 | | /// * `new_value` - The new value to set if comparison succeeds |
453 | | /// * `timestamp` - Optional timestamp. If `None`, uses current time. |
454 | | /// * `ttl_seconds` - Time-to-live in seconds (0 for no expiry) |
455 | | /// |
456 | | /// # Errors |
457 | | /// |
458 | | /// * `OlderTimestamp` - Timestamp is not newer than existing record |
459 | 2.32k | pub fn compare_and_swap_with_timestamp_and_ttl( |
460 | 2.32k | &self, |
461 | 2.32k | key: &[u8], |
462 | 2.32k | expected: &[u8], |
463 | 2.32k | new_value: &[u8], |
464 | 2.32k | timestamp: Option<u64>, |
465 | 2.32k | ttl_seconds: u64, |
466 | 2.32k | ) -> Result<bool> { |
467 | 2.32k | if ttl_seconds > 0 { |
468 | 1 | self.ensure_ttl_write_supported()?0 ; |
469 | 2.32k | } |
470 | 2.32k | let start = std::time::Instant::now(); |
471 | 2.32k | self.validate_key_value(key, new_value)?0 ; |
472 | 2.32k | let key_vec = key.to_vec(); |
473 | | |
474 | 1.79k | let initial_record = { |
475 | 2.32k | let record2.32k = match self |
476 | 2.32k | .hash_table |
477 | 2.32k | .read(&key_vec, |_, record| Arc::clone2.32k (record2.32k )) |
478 | | { |
479 | 2.32k | Some(record) => record, |
480 | 1 | None => return Ok(false), |
481 | | }; |
482 | 2.32k | let (value, cache_hit, source) = match self.resolve_value(key, record) { |
483 | 2.32k | Ok(resolved) => resolved, |
484 | 0 | Err(FeoxError::KeyNotFound | FeoxError::StaleExtent) => return Ok(false), |
485 | 0 | Err(error) => return Err(error), |
486 | | }; |
487 | | |
488 | 2.32k | if !cache_hit { |
489 | 1 | if let Some(cache) = &self.cache { |
490 | 1 | cache.insert_for_record(key_vec.clone(), value.clone(), &source); |
491 | 1 | }0 |
492 | 2.31k | } |
493 | 2.32k | if value.as_ref() != expected { |
494 | 525 | return Ok(false); |
495 | 1.79k | } |
496 | 1.79k | source |
497 | | }; |
498 | | |
499 | 1.79k | let timestamp = self.resolve_timestamp(key, timestamp); |
500 | 1.79k | self.replace_record_if_current( |
501 | 1.79k | &key_vec, |
502 | 1.79k | &initial_record, |
503 | 1.79k | new_value, |
504 | 1.79k | timestamp, |
505 | 1.79k | ttl_seconds, |
506 | 1.79k | start, |
507 | | ) |
508 | 2.32k | } |
509 | | |
510 | 1.80k | pub(super) fn replace_record_if_current( |
511 | 1.80k | &self, |
512 | 1.80k | key: &[u8], |
513 | 1.80k | expected: &Arc<Record>, |
514 | 1.80k | new_value: &[u8], |
515 | 1.80k | timestamp: (u64, bool), |
516 | 1.80k | ttl_seconds: u64, |
517 | 1.80k | start: std::time::Instant, |
518 | 1.80k | ) -> Result<bool> { |
519 | 1.80k | let (timestamp, explicit_timestamp) = timestamp; |
520 | 1.80k | match self.hash_table.entry(key.to_vec()) { |
521 | 1.80k | scc::hash_map::Entry::Occupied(mut entry) => { |
522 | 1.80k | let old_record = entry.get(); |
523 | | |
524 | 1.80k | if !Arc::ptr_eq(old_record, expected) { |
525 | 665 | return Ok(false); |
526 | 1.13k | } |
527 | | |
528 | 1.13k | if timestamp <= old_record.timestamp { |
529 | 1 | return Err(FeoxError::OlderTimestamp); |
530 | 1.13k | } |
531 | | |
532 | 1.13k | let old_size = old_record.calculate_size(); |
533 | 1.13k | let new_size = self.calculate_record_size(key.len(), new_value.len()); |
534 | 1.13k | let reservation1.11k = self.reserve_memory(new_size.saturating_sub(old_size))?16 ; |
535 | 1.11k | let old_record_arc = Arc::clone(old_record); |
536 | 1.11k | let old_expiry = old_record_arc.ttl_expiry.load(Ordering::Acquire); |
537 | | |
538 | | // Create new record with TTL if specified |
539 | 1.11k | let new_record = if ttl_seconds > 0 { |
540 | 1 | let ttl_expiry = |
541 | 1 | timestamp.saturating_add(ttl_seconds.saturating_mul(1_000_000_000)); |
542 | 1 | Arc::new(Record::new_with_timestamp_ttl( |
543 | 1 | key.to_vec(), |
544 | 1 | new_value.to_vec(), |
545 | 1 | timestamp, |
546 | 1 | ttl_expiry, |
547 | | )) |
548 | | } else { |
549 | 1.11k | Arc::new(Record::new(key.to_vec(), new_value.to_vec(), timestamp)) |
550 | | }; |
551 | | |
552 | 1.11k | old_record_arc.link_successor(&new_record); |
553 | 1.11k | old_record_arc.refcount.store(0, Ordering::Release); |
554 | 1.11k | entry.insert(Arc::clone(&new_record)); |
555 | 1.11k | self.publish_to_tree(key, Arc::clone(&new_record)); |
556 | 1.11k | self.observe_published_timestamp(key, timestamp, explicit_timestamp); |
557 | 1.11k | reservation.commit(); |
558 | 1.11k | self.note_ttl_transition(old_expiry, new_record.ttl_expiry.load(Ordering::Acquire)); |
559 | 1.11k | if old_size > new_size { |
560 | 3 | self.release_memory(old_size - new_size); |
561 | 1.11k | } |
562 | 1.11k | drop(entry); |
563 | | |
564 | 1.11k | self.stats |
565 | 1.11k | .record_insert(start.elapsed().as_nanos() as u64, true); |
566 | | |
567 | 1.11k | if !self.memory_only { |
568 | 3 | if self.enable_caching { |
569 | 3 | if let Some(ref cache) = self.cache { |
570 | 3 | cache.remove_for_record(key, &old_record_arc); |
571 | 3 | }0 |
572 | 0 | } |
573 | | |
574 | 3 | if let Some(ref wb) = self.write_buffer { |
575 | 3 | wb.add_replacement(new_record, old_record_arc)?0 ; |
576 | 0 | } |
577 | 1.11k | } |
578 | | |
579 | 1.11k | Ok(true) |
580 | | } |
581 | 0 | scc::hash_map::Entry::Vacant(_) => Ok(false), |
582 | | } |
583 | 1.80k | } |
584 | | } |
585 | | |
586 | 1.12k | fn counter_record(key: Vec<u8>, value: i64, timestamp: u64, ttl_seconds: u64) -> Arc<Record> { |
587 | 1.12k | let mut bytes = BytesMut::with_capacity(std::mem::size_of::<i64>()); |
588 | 1.12k | bytes.put_i64_le(value); |
589 | 1.12k | let value = bytes.freeze(); |
590 | | |
591 | 1.12k | if ttl_seconds > 0 { |
592 | 4 | Arc::new(Record::new_from_bytes_with_ttl( |
593 | 4 | key, |
594 | 4 | value, |
595 | 4 | timestamp, |
596 | 4 | timestamp.saturating_add(ttl_seconds.saturating_mul(1_000_000_000)), |
597 | | )) |
598 | | } else { |
599 | 1.11k | Arc::new(Record::new_from_bytes(key, value, timestamp)) |
600 | | } |
601 | 1.12k | } |