Skip to main content

ri/c/
cache.rs

1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of Ri.
4//! The Ri project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//!     http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18//! # Cache Module C API
19//!
20//! This module provides C language bindings for Ri's caching subsystem. The cache module
21//! delivers high-performance in-memory data caching capabilities for accelerating application
22//! performance, reducing database load, and improving system throughput. This C API enables
23//! C/C++ applications to leverage Ri's sophisticated caching infrastructure including memory
24//! caching, distributed caching support, and intelligent cache eviction policies.
25//!
26//! ## Module Architecture
27//!
28//! The caching module comprises three primary components:
29//!
30//! - **RiCacheConfig**: Configuration container for cache system parameters. Controls cache
31//!   size limits, eviction policies, expiration timeouts, and connection settings for
32//!   distributed cache backends. The configuration object is essential for initializing
33//!   cache managers with appropriate resource limits and behavior characteristics.
34//!
35//! - **RiCacheManager**: Central cache management interface providing unified operations
36//!   across different cache backends. Handles cache lifecycle, backend selection, and
37//!   provides high-level cache operations including get, set, delete, and invalidation.
38//!   The cache manager supports automatic serialization of complex types and provides
39//!   consistent API regardless of underlying storage implementation.
40//!
41//! - **RiMemoryCache**: In-memory cache implementation using concurrent data structures.
42//!   Provides thread-safe caching with O(1) average-case operations for read and write.
43//!   The memory cache implements sophisticated eviction policies to manage memory usage
44//!   and prevent unbounded growth. Ideal for single-instance deployments or as a
45//!   local cache tier in multi-level caching architectures.
46//!
47//! ## Cache Strategies
48//!
49//! The caching system implements multiple strategies optimized for different use cases:
50//!
51//! - **LRU (Least Recently Used)**: Evicts least recently accessed items when capacity
52//!   is reached. Optimal for workloads with temporal locality where recently accessed
53//!   items are likely to be accessed again. Memory-efficient implementation using
54//!   linked hash map for O(1) access and eviction.
55//!
56//! - **LFU (Least Frequently Used)**: Evicts items with lowest access frequency.
57//!   Suitable for workloads where access frequency correlates with importance.
58//!   Maintains frequency counters for eviction decisions. More computationally
59//!   expensive than LRU but provides better hit rates for certain access patterns.
60//!
61//! - **TTL-Based Expiration**: Automatic expiration based on time-to-live values.
62//!   Each cache entry has associated expiration timestamp. Entries are lazily
63//!   removed during access or via background cleanup tasks. Ensures data freshness
64//!   for time-sensitive cached content.
65//!
66//! - **Write-Through/Write-Behind**: Cache synchronization strategies for persistent
67//!   backends. Write-through updates cache and backend simultaneously. Write-behind
68//!   queues writes for batch processing improving write throughput.
69//!
70//! ## Memory Management
71//!
72//! All C API objects use opaque pointers with manual memory management responsibilities:
73//!
74//! - Objects must be allocated using constructor functions
75//! - Destructor functions must be called to release memory
76//! - Null pointer checks required before all operations
77//! - Double-free prevention is caller's responsibility
78//!
79//! ## Thread Safety
80//!
81//! All underlying implementations provide thread-safe concurrent access:
82//!
83//! - Memory cache uses fine-grained locking or lock-free data structures
84//! - Operations achieve high throughput under concurrent load
85//! - C API itself requires external synchronization for multi-threaded access
86//!
87//! ## Performance Characteristics
88//!
89//! Cache operations have the following performance profiles:
90//!
91//! - Cache hit (memory): O(1) average, O(n) worst case for hash collisions
92//! - Cache miss: O(1) plus backend fetch latency
93//! - Cache write: O(1) amortized
94//! - Eviction: O(1) for LRU, O(log n) for LFU
95//!
96//! ## Integration with Distributed Systems
97//!
98//! The cache module supports integration with distributed cache backends:
99//!
100//! - Redis cluster support for horizontal scaling
101//! - Memcached protocol compatibility
102//! - Consistent hashing for distribution
103//! - Automatic failover and replication
104//!
105//! ## Usage Example
106//!
107//! ```c
108//! // Create cache configuration
109//! RiCacheConfig* config = ri_cache_config_new();
110//! ri_cache_config_set_max_size(config, 10000);
111//! ri_cache_config_set_ttl(config, 3600);
112//!
113//! // Create memory cache instance
114//! RiMemoryCache* cache = ri_memory_cache_new();
115//!
116//! // Store cached value
117//! const char* key = "user:12345";
118//! const char* value = "{\"name\":\"John\",\"age\":30}";
119//! ri_memory_cache_set(cache, key, value, strlen(value));
120//!
121//! // Retrieve cached value
122//! size_t value_len;
123//! char* cached = ri_memory_cache_get(cache, key, &value_len);
124//! if (cached != NULL) {
125//!     // Process cached data
126//!     free(cached);
127//! }
128//!
129//! // Cleanup
130//! ri_memory_cache_free(cache);
131//! ri_cache_config_free(config);
132//! ```
133//!
134//! ## Dependencies
135//!
136//! This module depends on the following Ri components:
137//!
138//! - `crate::cache`: Rust cache implementation
139//! - `crate::prelude`: Common types and traits
140//!
141//! ## Feature Flags
142//!
143//! The cache module is enabled by default with the "cache" feature flag.
144//! Disable this feature to reduce binary size when caching is not required.
145
146use crate::cache::{RiCacheConfig, RiCacheManager, RiMemoryCache, RiCachePolicy, RiCacheStats};
147use std::ffi::{c_char, c_int};
148use std::sync::Arc;
149
150c_wrapper!(CRiCacheConfig, RiCacheConfig);
151
152c_wrapper!(CRiCacheManager, RiCacheManager);
153
154c_wrapper!(CRiMemoryCache, RiMemoryCache);
155
156c_constructor!(ri_cache_config_new, CRiCacheConfig, RiCacheConfig, RiCacheConfig::default());
157
158c_destructor!(ri_cache_config_free, CRiCacheConfig);
159
160#[repr(C)]
161pub struct CRiCachePolicy {
162    pub ttl_secs: u64,
163    pub ttl_set: bool,
164    pub refresh_on_access: bool,
165    pub max_size: usize,
166    pub max_size_set: bool,
167}
168
169pub const RI_CACHE_POLICY_LRU: c_int = 0;
170pub const RI_CACHE_POLICY_LFU: c_int = 1;
171pub const RI_CACHE_POLICY_TTL: c_int = 2;
172
173#[no_mangle]
174pub extern "C" fn ri_cache_policy_new() -> CRiCachePolicy {
175    let default = RiCachePolicy::default();
176    CRiCachePolicy {
177        ttl_secs: default.ttl.map(|d| d.as_secs()).unwrap_or(0),
178        ttl_set: default.ttl.is_some(),
179        refresh_on_access: default.refresh_on_access,
180        max_size: default.max_size.unwrap_or(0),
181        max_size_set: default.max_size.is_some(),
182    }
183}
184
185#[no_mangle]
186pub extern "C" fn ri_cache_policy_with_ttl(ttl_secs: u64) -> CRiCachePolicy {
187    let mut policy = ri_cache_policy_new();
188    policy.ttl_secs = ttl_secs;
189    policy.ttl_set = true;
190    policy
191}
192
193#[no_mangle]
194pub extern "C" fn ri_memory_cache_new() -> *mut CRiMemoryCache {
195    let cache = RiMemoryCache::new();
196    Box::into_raw(Box::new(CRiMemoryCache::new(cache)))
197}
198
199c_destructor!(ri_memory_cache_free, CRiMemoryCache);
200
201#[no_mangle]
202pub extern "C" fn ri_cache_manager_new() -> *mut CRiCacheManager {
203    let backend: Arc<dyn crate::cache::RiCache + Send + Sync> = Arc::new(RiMemoryCache::new());
204    let manager = RiCacheManager::new(backend);
205    Box::into_raw(Box::new(CRiCacheManager::new(manager)))
206}
207
208#[no_mangle]
209pub extern "C" fn ri_cache_manager_free(manager: *mut CRiCacheManager) {
210    if !manager.is_null() {
211        unsafe {
212            let _ = Box::from_raw(manager);
213        }
214    }
215}
216
217#[no_mangle]
218pub extern "C" fn ri_cache_manager_get(
219    manager: *mut CRiCacheManager,
220    key: *const c_char,
221    out_value: *mut *mut c_char,
222) -> c_int {
223    if manager.is_null() || key.is_null() || out_value.is_null() {
224        return -1;
225    }
226
227    unsafe {
228        let key_str = match std::ffi::CStr::from_ptr(key).to_str() {
229            Ok(s) => s,
230            Err(_) => return -2,
231        };
232
233        let rt = match tokio::runtime::Runtime::new() {
234            Ok(r) => r,
235            Err(_) => return -3,
236        };
237
238        let result: crate::core::RiResult<Option<String>> = rt.block_on(async {
239            (*manager).inner.get(key_str).await
240        });
241
242        match result {
243            Ok(Some(value)) => {
244                match std::ffi::CString::new(value) {
245                    Ok(c_str) => {
246                        *out_value = c_str.into_raw();
247                        0
248                    }
249                    Err(_) => -4,
250                }
251            }
252            Ok(None) => 1,
253            Err(_) => -5,
254        }
255    }
256}
257
258#[no_mangle]
259pub extern "C" fn ri_cache_manager_set(
260    manager: *mut CRiCacheManager,
261    key: *const c_char,
262    value: *const c_char,
263    ttl_secs: u64,
264) -> c_int {
265    if manager.is_null() || key.is_null() || value.is_null() {
266        return -1;
267    }
268
269    unsafe {
270        let key_str = match std::ffi::CStr::from_ptr(key).to_str() {
271            Ok(s) => s,
272            Err(_) => return -2,
273        };
274
275        let value_str = match std::ffi::CStr::from_ptr(value).to_str() {
276            Ok(s) => s,
277            Err(_) => return -3,
278        };
279
280        let ttl = if ttl_secs > 0 { Some(ttl_secs) } else { None };
281
282        let rt = match tokio::runtime::Runtime::new() {
283            Ok(r) => r,
284            Err(_) => return -4,
285        };
286
287        let result: crate::core::RiResult<()> = rt.block_on(async {
288            (*manager).inner.set(key_str, &value_str, ttl).await
289        });
290
291        match result {
292            Ok(()) => 0,
293            Err(_) => -5,
294        }
295    }
296}
297
298#[no_mangle]
299pub extern "C" fn ri_cache_manager_delete(
300    manager: *mut CRiCacheManager,
301    key: *const c_char,
302) -> c_int {
303    if manager.is_null() || key.is_null() {
304        return -1;
305    }
306
307    unsafe {
308        let key_str = match std::ffi::CStr::from_ptr(key).to_str() {
309            Ok(s) => s,
310            Err(_) => return -2,
311        };
312
313        let rt = match tokio::runtime::Runtime::new() {
314            Ok(r) => r,
315            Err(_) => return -3,
316        };
317
318        let result: crate::core::RiResult<bool> = rt.block_on(async {
319            (*manager).inner.delete(key_str).await
320        });
321
322        match result {
323            Ok(deleted) => if deleted { 0 } else { 1 },
324            Err(_) => -4,
325        }
326    }
327}
328
329#[repr(C)]
330pub struct CRiCacheStats {
331    pub hits: u64,
332    pub misses: u64,
333    pub entries: usize,
334    pub memory_usage_bytes: usize,
335    pub avg_hit_rate: f64,
336    pub hit_count: u64,
337    pub miss_count: u64,
338    pub eviction_count: u64,
339}
340
341#[no_mangle]
342pub extern "C" fn ri_cache_manager_stats(
343    manager: *mut CRiCacheManager,
344    out_stats: *mut CRiCacheStats,
345) -> c_int {
346    if manager.is_null() || out_stats.is_null() {
347        return -1;
348    }
349
350    unsafe {
351        let rt = match tokio::runtime::Runtime::new() {
352            Ok(r) => r,
353            Err(_) => return -2,
354        };
355
356        let stats: RiCacheStats = rt.block_on(async {
357            (*manager).inner.stats().await
358        });
359
360        *out_stats = CRiCacheStats {
361            hits: stats.hits,
362            misses: stats.misses,
363            entries: stats.entries,
364            memory_usage_bytes: stats.memory_usage_bytes,
365            avg_hit_rate: stats.avg_hit_rate,
366            hit_count: stats.hit_count,
367            miss_count: stats.miss_count,
368            eviction_count: stats.eviction_count,
369        };
370
371        0
372    }
373}
374
375#[no_mangle]
376pub extern "C" fn ri_cache_manager_exists(
377    manager: *mut CRiCacheManager,
378    key: *const c_char,
379) -> c_int {
380    if manager.is_null() || key.is_null() {
381        return -1;
382    }
383
384    unsafe {
385        let key_str = match std::ffi::CStr::from_ptr(key).to_str() {
386            Ok(s) => s,
387            Err(_) => return -2,
388        };
389
390        let rt = match tokio::runtime::Runtime::new() {
391            Ok(r) => r,
392            Err(_) => return -3,
393        };
394
395        let exists: bool = rt.block_on(async {
396            (*manager).inner.exists(key_str).await
397        });
398
399        if exists { 0 } else { 1 }
400    }
401}
402
403#[no_mangle]
404pub extern "C" fn ri_cache_manager_clear(manager: *mut CRiCacheManager) -> c_int {
405    if manager.is_null() {
406        return -1;
407    }
408
409    unsafe {
410        let rt = match tokio::runtime::Runtime::new() {
411            Ok(r) => r,
412            Err(_) => return -2,
413        };
414
415        let result: crate::core::RiResult<()> = rt.block_on(async {
416            (*manager).inner.clear().await
417        });
418
419        match result {
420            Ok(()) => 0,
421            Err(_) => -3,
422        }
423    }
424}