Skip to main content

ri/queue/backends/
memory_backend.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#![allow(non_snake_case)]
19
20//! # In-Memory Queue Implementation
21//!
22//! This file implements an in-memory queue backend for the Ri queue system. The in-memory queue
23//! provides a lightweight, fast queue implementation suitable for testing, development, and
24//! scenarios where durability is not a strict requirement. It also supports optional persistence
25//! to disk for basic durability.
26//!
27//! ## Key Components
28//!
29//! - **RiMemoryQueue**: Main in-memory queue implementation
30//! - **MemoryQueueState**: Internal state management for the queue
31//! - **MemoryQueueProducer**: Producer implementation for sending messages
32//! - **MemoryQueueConsumer**: Consumer implementation for receiving messages
33//!
34//! ## Design Principles
35//!
36//! 1. **Lightweight**: Minimal dependencies and overhead
37//! 2. **Fast Performance**: In-memory operations for low latency
38//! 3. **Optional Persistence**: Can be configured to persist messages to disk
39//! 4. **Consumer Groups**: Supports multiple consumer groups with message distribution
40//! 5. **Async-First**: All operations are asynchronous
41//! 6. **Thread-safe**: Uses Arc and RwLock for safe concurrent access
42//! 7. **Durable Option**: Optional disk persistence for message durability
43//! 8. **Simple API**: Implements the standard RiQueue interfaces
44//! 9. **Non-blocking**: Uses tokio's spawn_blocking for file I/O operations
45//! 10. **Message Retry**: Supports message requeueing with retry count increment
46//!
47//! ## Usage
48//!
49//! ```rust
50//! use ri::queue::{RiQueue, RiQueueMessage, RiQueueProducer, RiQueueConsumer};
51//! use ri::queue::backends::RiMemoryQueue;
52//! use ri::core::RiResult;
53//! use serde_json::json;
54//!
55//! async fn example() -> RiResult<()> {
56//!     // Create a basic in-memory queue
57//!     let queue = RiMemoryQueue::new("example_queue");
58//!     
59//!     // Or create a queue with disk persistence
60//!     // let queue = RiMemoryQueue::with_persistence("example_queue", "/tmp/queue_persistence");
61//!     
62//!     // Create a producer
63//!     let producer = queue.create_producer().await?;
64//!     
65//!     // Create a message
66//!     let payload = json!({ "key": "value" }).to_string().into_bytes();
67//!     let message = RiQueueMessage::new(payload);
68//!     
69//!     // Send the message
70//!     producer.send(message).await?;
71//!     
72//!     // Create a consumer
73//!     let consumer = queue.create_consumer("consumer_group_1").await?;
74//!     
75//!     // Receive a message
76//!     if let Some(message) = consumer.receive().await? {
77//!         // Process the message
78//!         let payload = String::from_utf8_lossy(&message.payload);
79//!         println!("Received message: {}", payload);
80//!         
81//!         // Acknowledge the message
82//!         consumer.ack(&message.id).await?;
83//!     }
84//!     
85//!     Ok(())
86//! }
87//! ```
88
89use crate::core::RiResult;
90use crate::queue::{RiQueue, RiQueueConsumer, RiQueueMessage, RiQueueProducer, RiQueueStats};
91use async_trait::async_trait;
92use std::collections::{HashMap, VecDeque};
93use std::fs::{File, OpenOptions};
94use std::io::{Read, Write};
95use std::path::Path;
96use std::sync::Arc;
97use tokio::sync::{Mutex, RwLock};
98use tokio::task::spawn_blocking;
99
100/// Internal state management for the in-memory queue.
101///
102/// This struct holds the queue's messages and consumer-specific queues. It is protected by a
103/// RwLock to ensure thread-safe access.
104struct MemoryQueueState {
105    /// Main queue of messages waiting to be consumed
106    messages: VecDeque<RiQueueMessage>,
107    /// Map of consumer group names to their respective message queues
108    consumers: HashMap<String, VecDeque<RiQueueMessage>>,
109}
110
111impl MemoryQueueState {
112    /// Creates a new MemoryQueueState with empty queues.
113    ///
114    /// # Returns
115    ///
116    /// A new MemoryQueueState instance
117    fn new() -> Self {
118        Self {
119            messages: VecDeque::new(),
120            consumers: HashMap::new(),
121        }
122    }
123}
124
125/// In-memory queue implementation.
126///
127/// This struct implements the RiQueue trait for an in-memory queue backend. It supports optional
128/// disk persistence for message durability.
129pub struct RiMemoryQueue {
130    /// Name of the queue
131    name: String,
132    /// Internal queue state protected by a RwLock
133    state: Arc<RwLock<MemoryQueueState>>,
134    /// Optional path for disk persistence
135    persistence_path: Option<String>,
136}
137
138#[allow(dead_code)]
139impl RiMemoryQueue {
140    /// Creates a new in-memory queue without persistence.
141    ///
142    /// # Parameters
143    ///
144    /// - `name`: The name of the queue
145    ///
146    /// # Returns
147    ///
148    /// A new RiMemoryQueue instance
149    pub fn new(name: &str) -> Self {
150        Self {
151            name: name.to_string(),
152            state: Arc::new(RwLock::new(MemoryQueueState::new())),
153            persistence_path: None,
154        }
155    }
156
157    /// Creates a new in-memory queue with disk persistence.
158    ///
159    /// # Parameters
160    ///
161    /// - `name`: The name of the queue
162    /// - `persistence_path`: Path to the file where messages will be persisted
163    ///
164    /// # Returns
165    ///
166    /// A new RiMemoryQueue instance with persistence enabled
167    pub fn with_persistence(name: &str, persistence_path: &str) -> Self {
168        let queue = Self {
169            name: name.to_string(),
170            state: Arc::new(RwLock::new(MemoryQueueState::new())),
171            persistence_path: Some(persistence_path.to_string()),
172        };
173
174        // Load messages from disk if persistence is enabled
175        if let Err(e) = queue.load_messages() {
176            log::warn!("Failed to load persisted messages for queue '{name}': {e}");
177        }
178
179        queue
180    }
181
182    /// Loads messages from disk if persistence is enabled.
183    ///
184    /// # Returns
185    ///
186    /// A `RiResult<()>` indicating success or failure
187    fn load_messages(&self) -> RiResult<()> {
188        if let Some(path) = &self.persistence_path {
189            if Path::new(path).exists() {
190                let mut file = File::open(path)?;
191                
192                // Security: Check file size before reading to prevent memory exhaustion
193                const MAX_PERSISTENCE_FILE_SIZE: u64 = 100 * 1024 * 1024; // 100 MB
194                let metadata = file.metadata()?;
195                if metadata.len() > MAX_PERSISTENCE_FILE_SIZE {
196                    log::warn!(
197                        "[Ri.MemoryQueue] Persistence file too large: {} bytes (max {} bytes)",
198                        metadata.len(), MAX_PERSISTENCE_FILE_SIZE
199                    );
200                    return Err(crate::core::RiError::Other(format!(
201                        "Persistence file too large: {} bytes (max {} bytes)",
202                        metadata.len(), MAX_PERSISTENCE_FILE_SIZE
203                    )));
204                }
205                
206                let mut content = String::new();
207                file.read_to_string(&mut content)?;
208
209                if !content.is_empty() {
210                    // Security: Use bounded deserialization
211                    let messages: VecDeque<RiQueueMessage> = serde_json::from_str(&content)
212                        .map_err(|e| {
213                            log::warn!("[Ri.MemoryQueue] Failed to deserialize messages: {}", e);
214                            crate::core::RiError::Other(format!("Failed to deserialize messages: {}", e))
215                        })?;
216                    
217                    // Security: Limit number of messages loaded
218                    const MAX_MESSAGES: usize = 100000;
219                    let messages: VecDeque<RiQueueMessage> = messages.into_iter().take(MAX_MESSAGES).collect();
220                    
221                    let mut state = self.state.blocking_write();
222                    state.messages = messages;
223                }
224            }
225        }
226        Ok(())
227    }
228
229    /// Saves messages to disk if persistence is enabled.
230    ///
231    /// # Returns
232    ///
233    /// A `RiResult<()>` indicating success or failure
234    fn save_messages(&self) -> RiResult<()> {
235        if let Some(path) = &self.persistence_path {
236            let state = self.state.blocking_read();
237            let content = serde_json::to_string(&state.messages)?;
238
239            let mut file = OpenOptions::new()
240                .write(true)
241                .create(true)
242                .truncate(true)
243                .open(path)?;
244
245            file.write_all(content.as_bytes())?;
246        }
247        Ok(())
248    }
249}
250
251#[async_trait]
252impl RiQueue for RiMemoryQueue {
253    /// Creates a new producer for this queue.
254    ///
255    /// # Returns
256    ///
257    /// A `RiResult<Box<dyn RiQueueProducer>>` containing the producer
258    async fn create_producer(&self) -> RiResult<Box<dyn RiQueueProducer>> {
259        Ok(Box::new(MemoryQueueProducer {
260            state: self.state.clone(),
261            persistence_path: self.persistence_path.clone(),
262        }))
263    }
264
265    /// Creates a new consumer for this queue with the given consumer group.
266    ///
267    /// # Parameters
268    ///
269    /// - `consumer_group`: The name of the consumer group
270    ///
271    /// # Returns
272    ///
273    /// A `RiResult<Box<dyn RiQueueConsumer>>` containing the consumer
274    async fn create_consumer(
275        &self,
276        consumer_group: &str,
277    ) -> RiResult<Box<dyn RiQueueConsumer>> {
278        Ok(Box::new(MemoryQueueConsumer {
279            state: self.state.clone(),
280            consumer_group: consumer_group.to_string(),
281            paused: Arc::new(Mutex::new(false)),
282            persistence_path: self.persistence_path.clone(),
283        }))
284    }
285
286    /// Gets statistics for this queue.
287    ///
288    /// # Returns
289    ///
290    /// A `RiResult<RiQueueStats>` containing the queue statistics
291    async fn get_stats(&self) -> RiResult<RiQueueStats> {
292        let state = self.state.read().await;
293        Ok(RiQueueStats {
294            queue_name: self.name.clone(),
295            message_count: state.messages.len() as u64,
296            consumer_count: state.consumers.len() as u32,
297            producer_count: 1,
298            processed_messages: 0,
299            failed_messages: 0,
300            avg_processing_time_ms: 0.0,
301            total_bytes_sent: 0,
302            total_bytes_received: 0,
303            last_message_time: 0,
304        })
305    }
306
307    /// Purges all messages from this queue.
308    ///
309    /// # Returns
310    ///
311    /// A `RiResult<()>` indicating success or failure
312    async fn purge(&self) -> RiResult<()> {
313        let mut state = self.state.write().await;
314        state.messages.clear();
315        state.consumers.clear();
316
317        // Clear persistence file if enabled
318        if let Some(path) = &self.persistence_path {
319            let path_clone = path.clone();
320            spawn_blocking(move || {
321                if Path::new(&path_clone).exists() {
322                    if let Err(e) = std::fs::remove_file(&path_clone) {
323                        log::warn!("Failed to remove persistence file '{path_clone}': {e}");
324                    }
325                }
326            })
327            .await
328            .map_err(|e| {
329                log::error!("Failed to execute persistence file removal: {e}");
330                crate::core::RiError::Other(format!("Failed to clear persistence: {e}"))
331            })?;
332        }
333
334        Ok(())
335    }
336
337    /// Deletes this queue.
338    ///
339    /// # Returns
340    ///
341    /// A `RiResult<()>` indicating success or failure
342    async fn delete(&self) -> RiResult<()> {
343        self.purge().await
344    }
345}
346
347/// Producer implementation for the in-memory queue.
348///
349/// This struct implements the RiQueueProducer trait for sending messages to the in-memory queue.
350struct MemoryQueueProducer {
351    /// Shared queue state
352    state: Arc<RwLock<MemoryQueueState>>,
353    /// Optional path for disk persistence
354    persistence_path: Option<String>,
355}
356
357#[async_trait]
358impl RiQueueProducer for MemoryQueueProducer {
359    /// Sends a single message to the queue.
360    ///
361    /// # Parameters
362    ///
363    /// - `message`: The message to send
364    ///
365    /// # Returns
366    ///
367    /// A `RiResult<()>` indicating success or failure
368    async fn send(&self, message: RiQueueMessage) -> RiResult<()> {
369        let mut state = self.state.write().await;
370        state.messages.push_back(message);
371
372        // Save to disk if persistence is enabled
373        if let Some(path) = &self.persistence_path {
374            let messages_clone = state.messages.clone();
375            let path_clone = path.clone();
376
377            let _ = spawn_blocking(move || {
378            let content = serde_json::to_string(&messages_clone)
379                .map_err(|e| {
380                    log::error!("Failed to serialize messages for persistence: {e}");
381                    crate::core::RiError::Serde(format!("Serialization failed: {e}"))
382                })?;
383            let mut file = OpenOptions::new()
384                .write(true)
385                .create(true)
386                .truncate(true)
387                .open(path_clone)
388                .map_err(|e| {
389                    log::error!("Failed to open persistence file: {e}");
390                    crate::core::RiError::Io(format!("File open failed: {e}"))
391                })?;
392            file.write_all(content.as_bytes())
393                .map_err(|e| {
394                    log::error!("Failed to write persistence file: {e}");
395                    crate::core::RiError::Io(format!("File write failed: {e}"))
396                })?;
397            Ok::<(), crate::core::RiError>(())
398        })
399        .await
400        .map_err(|e| {
401            log::error!("Failed to execute persistence task: {e}");
402            crate::core::RiError::Other(format!("Persistence task failed: {e}"))
403        });
404        }
405
406        Ok(())
407    }
408
409    /// Sends multiple messages to the queue in a batch.
410    ///
411    /// # Parameters
412    ///
413    /// - `messages`: A vector of messages to send
414    ///
415    /// # Returns
416    ///
417    /// A `RiResult<()>` indicating success or failure
418    async fn send_batch(&self, messages: Vec<RiQueueMessage>) -> RiResult<()> {
419        let mut state = self.state.write().await;
420        for message in messages {
421            state.messages.push_back(message);
422        }
423
424        // Save to disk if persistence is enabled
425        if let Some(path) = &self.persistence_path {
426            let messages_clone = state.messages.clone();
427            let path_clone = path.clone();
428
429            let _ = spawn_blocking(move || {
430            let content = serde_json::to_string(&messages_clone)
431                .map_err(|e| {
432                    log::error!("Failed to serialize messages for persistence: {e}");
433                    crate::core::RiError::Serde(format!("Serialization failed: {e}"))
434                })?;
435            let mut file = OpenOptions::new()
436                .write(true)
437                .create(true)
438                .truncate(true)
439                .open(path_clone)
440                .map_err(|e| {
441                    log::error!("Failed to open persistence file: {e}");
442                    crate::core::RiError::Io(format!("File open failed: {e}"))
443                })?;
444            file.write_all(content.as_bytes())
445                .map_err(|e| {
446                    log::error!("Failed to write persistence file: {e}");
447                    crate::core::RiError::Io(format!("File write failed: {e}"))
448                })?;
449            Ok::<(), crate::core::RiError>(())
450        })
451        .await
452        .map_err(|e| {
453            log::error!("Failed to execute persistence task: {e}");
454            crate::core::RiError::Other(format!("Persistence task failed: {e}"))
455        });
456        }
457
458        Ok(())
459    }
460}
461
462/// Consumer implementation for the in-memory queue.
463///
464/// This struct implements the RiQueueConsumer trait for receiving messages from the in-memory queue.
465struct MemoryQueueConsumer {
466    /// Shared queue state
467    state: Arc<RwLock<MemoryQueueState>>,
468    /// Name of the consumer group
469    consumer_group: String,
470    /// Flag indicating if the consumer is paused
471    paused: Arc<Mutex<bool>>,
472    /// Optional path for disk persistence
473    persistence_path: Option<String>,
474}
475
476#[async_trait]
477impl RiQueueConsumer for MemoryQueueConsumer {
478    /// Receives a message from the queue.
479    ///
480    /// # Returns
481    ///
482    /// A `RiResult<Option<RiQueueMessage>>` containing the message if available, or None if no message is available
483    async fn receive(&self) -> RiResult<Option<RiQueueMessage>> {
484        let paused = *self.paused.lock().await;
485        if paused {
486            return Ok(None);
487        }
488
489        let mut state = self.state.write().await;
490
491        // If consumer queue exists and has messages, return one
492        if let Some(consumer_queue) = state.consumers.get_mut(&self.consumer_group) {
493            if let Some(message) = consumer_queue.pop_front() {
494                return Ok(Some(message));
495            }
496        }
497
498        // If main queue has messages, move one to consumer queue
499        if let Some(message) = state.messages.pop_front() {
500            let mut consumer_queue = VecDeque::new();
501            consumer_queue.push_back(message.clone());
502            state
503                .consumers
504                .insert(self.consumer_group.clone(), consumer_queue);
505
506            // Save to disk if persistence is enabled (since main queue changed)
507            if let Some(path) = &self.persistence_path {
508                let messages_clone = state.messages.clone();
509                let path_clone = path.clone();
510
511                let _ = spawn_blocking(move || {
512                    let content = serde_json::to_string(&messages_clone)
513                        .map_err(|e| {
514                            log::error!("Failed to serialize messages for persistence: {e}");
515                            crate::core::RiError::Serde(format!("Serialization failed: {e}"))
516                        })?;
517                    let mut file = OpenOptions::new()
518                        .write(true)
519                        .create(true)
520                        .truncate(true)
521                        .open(path_clone)
522                        .map_err(|e| {
523                            log::error!("Failed to open persistence file: {e}");
524                            crate::core::RiError::Io(format!("File open failed: {e}"))
525                        })?;
526                    file.write_all(content.as_bytes())
527                        .map_err(|e| {
528                            log::error!("Failed to write persistence file: {e}");
529                            crate::core::RiError::Io(format!("File write failed: {e}"))
530                        })?;
531                    Ok::<(), crate::core::RiError>(())
532                })
533                .await
534                .map_err(|e| {
535                    log::error!("Failed to execute persistence task: {e}");
536                    crate::core::RiError::Other(format!("Persistence task failed: {e}"))
537                });
538            }
539
540            Ok(Some(message))
541        } else {
542            Ok(None)
543        }
544    }
545
546    /// Acknowledges a message, indicating it has been successfully processed.
547    ///
548    /// For in-memory queues, acknowledgment is implicit when the message is received.
549    ///
550    /// # Parameters
551    ///
552    /// - `_message_id`: The ID of the message to acknowledge (not used for in-memory queues)
553    ///
554    /// # Returns
555    ///
556    /// A `RiResult<()>` indicating success or failure
557    async fn ack(&self, _message_id: &str) -> RiResult<()> {
558        // In memory queue, acknowledgment is implicit when message is received
559        Ok(())
560    }
561
562    /// Negatively acknowledges a message, indicating it failed to process and should be retried.
563    ///
564    /// # Parameters
565    ///
566    /// - `message_id`: The ID of the message to negatively acknowledge
567    ///
568    /// # Returns
569    ///
570    /// A `RiResult<()>` indicating success or failure
571    async fn nack(&self, message_id: &str) -> RiResult<()> {
572        // Find the message in consumer queue and put it back in main queue
573        let mut state = self.state.write().await;
574
575        if let Some(consumer_queue) = state.consumers.get_mut(&self.consumer_group) {
576            // Find the message by ID
577            let mut message_to_requeue: Option<RiQueueMessage> = None;
578
579            // Iterate through consumer queue to find the message
580            let mut index = 0;
581            for (i, message) in consumer_queue.iter().enumerate() {
582                if message.id == message_id {
583                    message_to_requeue = Some(message.clone());
584                    index = i;
585                    break;
586                }
587            }
588
589            // If found, remove from consumer queue and put back in main queue
590            if let Some(mut message) = message_to_requeue {
591                consumer_queue.remove(index);
592                message.increment_retry();
593                state.messages.push_back(message);
594
595                // Save to disk if persistence is enabled
596                if let Some(path) = &self.persistence_path {
597                    let messages_clone = state.messages.clone();
598                    let path_clone = path.clone();
599
600                    let _ = spawn_blocking(move || {
601                        let content = serde_json::to_string(&messages_clone)
602                            .map_err(|e| {
603                                log::error!("Failed to serialize messages for persistence: {e}");
604                                crate::core::RiError::Serde(format!("Serialization failed: {e}"))
605                            })?;
606                        let mut file = OpenOptions::new()
607                            .write(true)
608                            .create(true)
609                            .truncate(true)
610                            .open(path_clone)
611                            .map_err(|e| {
612                                log::error!("Failed to open persistence file: {e}");
613                                crate::core::RiError::Io(format!("File open failed: {e}"))
614                            })?;
615                        file.write_all(content.as_bytes())
616                            .map_err(|e| {
617                                log::error!("Failed to write persistence file: {e}");
618                                crate::core::RiError::Io(format!("File write failed: {e}"))
619                            })?;
620                        Ok::<(), crate::core::RiError>(())
621                    })
622                    .await
623                    .map_err(|e| {
624                        log::error!("Failed to execute persistence task: {e}");
625                        crate::core::RiError::Other(format!("Persistence task failed: {e}"))
626                    });
627                }
628            }
629        }
630
631        Ok(())
632    }
633
634    /// Pauses message consumption.
635    ///
636    /// # Returns
637    ///
638    /// A `RiResult<()>` indicating success or failure
639    async fn pause(&self) -> RiResult<()> {
640        let mut paused = self.paused.lock().await;
641        *paused = true;
642        Ok(())
643    }
644
645    /// Resumes message consumption after pausing.
646    ///
647    /// # Returns
648    ///
649    /// A `RiResult<()>` indicating success or failure
650    async fn resume(&self) -> RiResult<()> {
651        let mut paused = self.paused.lock().await;
652        *paused = false;
653        Ok(())
654    }
655}