ruchy/runtime/
transaction.rs

1//! Transactional state management for REPL evaluation
2//!
3//! Provides atomic evaluation with rollback capability for safe experimentation.
4use crate::runtime::interpreter::Value;
5use crate::runtime::safe_arena::{SafeArena as Arena, TransactionalArena};
6use anyhow::{anyhow, Result};
7use std::collections::HashMap;
8use std::time::{Duration, Instant};
9// ============================================================================
10// Transactional REPL State
11// ============================================================================
12/// Transaction ID for tracking evaluation transactions
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub struct TransactionId(pub u64);
15/// Transactional wrapper for REPL state with O(1) checkpoint/rollback
16pub struct TransactionalState {
17    /// Current bindings
18    bindings: HashMap<String, Value>,
19    /// Binding mutability tracking
20    binding_mutability: HashMap<String, bool>,
21    /// Transaction stack
22    transactions: Vec<Transaction>,
23    /// Next transaction ID
24    next_tx_id: u64,
25    /// Arena for memory allocation
26    arena: TransactionalArena,
27    /// Maximum transaction depth
28    max_depth: usize,
29}
30/// A single transaction in the stack
31#[derive(Debug, Clone)]
32struct Transaction {
33    id: TransactionId,
34    /// Snapshot of bindings at transaction start
35    bindings_snapshot: HashMap<String, Value>,
36    /// Snapshot of mutability at transaction start
37    mutability_snapshot: HashMap<String, bool>,
38    /// Arena checkpoint
39    arena_checkpoint: usize,
40    /// Start time for timeout tracking
41    start_time: Instant,
42    /// Transaction metadata
43    metadata: TransactionMetadata,
44}
45/// Metadata about a transaction
46#[derive(Debug, Clone)]
47pub struct TransactionMetadata {
48    /// Description of the transaction
49    pub description: String,
50    /// Memory limit for this transaction
51    pub memory_limit: Option<usize>,
52    /// Time limit for this transaction
53    pub time_limit: Option<Duration>,
54    /// Whether this is a speculative evaluation
55    pub speculative: bool,
56}
57impl Default for TransactionMetadata {
58    fn default() -> Self {
59        Self {
60            description: "evaluation".to_string(),
61            memory_limit: None,
62            time_limit: None,
63            speculative: false,
64        }
65    }
66}
67impl TransactionalState {
68    /// Create a new transactional state with the given memory limit
69    /// # Examples
70    ///
71    /// ```
72    /// use ruchy::runtime::transaction::TransactionalState;
73    ///
74    /// let instance = TransactionalState::new();
75    /// // Verify behavior
76    /// ```
77    /// # Examples
78    ///
79    /// ```
80    /// use ruchy::runtime::transaction::TransactionalState;
81    ///
82    /// let instance = TransactionalState::new();
83    /// // Verify behavior
84    /// ```
85    /// # Examples
86    ///
87    /// ```
88    /// use ruchy::runtime::transaction::TransactionalState;
89    ///
90    /// let instance = TransactionalState::new();
91    /// // Verify behavior
92    /// ```
93    pub fn new(max_memory: usize) -> Self {
94        Self {
95            bindings: HashMap::new(),
96            binding_mutability: HashMap::new(),
97            transactions: Vec::new(),
98            next_tx_id: 1,
99            arena: TransactionalArena::new(max_memory),
100            max_depth: 100, // Prevent unbounded nesting
101        }
102    }
103    /// Begin a new transaction
104    /// # Examples
105    ///
106    /// ```
107    /// use ruchy::runtime::transaction::TransactionalState;
108    ///
109    /// let mut instance = TransactionalState::new();
110    /// let result = instance.begin_transaction();
111    /// // Verify behavior
112    /// ```
113    pub fn begin_transaction(&mut self, metadata: TransactionMetadata) -> Result<TransactionId> {
114        if self.transactions.len() >= self.max_depth {
115            return Err(anyhow!("Transaction depth limit exceeded"));
116        }
117        let id = TransactionId(self.next_tx_id);
118        self.next_tx_id += 1;
119        // Create checkpoint in arena
120        let arena_checkpoint = self.arena.checkpoint();
121        // Create transaction with current state snapshot
122        let transaction = Transaction {
123            id,
124            bindings_snapshot: self.bindings.clone(),
125            mutability_snapshot: self.binding_mutability.clone(),
126            arena_checkpoint,
127            start_time: Instant::now(),
128            metadata,
129        };
130        self.transactions.push(transaction);
131        Ok(id)
132    }
133    /// Commit the current transaction
134    /// # Examples
135    ///
136    /// ```ignore
137    /// use ruchy::runtime::transaction::commit_transaction;
138    ///
139    /// let result = commit_transaction(());
140    /// assert_eq!(result, Ok(()));
141    /// ```
142    pub fn commit_transaction(&mut self, id: TransactionId) -> Result<()> {
143        let tx = self
144            .transactions
145            .last()
146            .ok_or_else(|| anyhow!("No active transaction"))?;
147        if tx.id != id {
148            return Err(anyhow!("Transaction ID mismatch"));
149        }
150        // Commit arena changes
151        self.arena.commit()?;
152        // Remove transaction from stack
153        self.transactions.pop();
154        Ok(())
155    }
156    /// Rollback the current transaction
157    /// # Examples
158    ///
159    /// ```ignore
160    /// use ruchy::runtime::transaction::rollback_transaction;
161    ///
162    /// let result = rollback_transaction(());
163    /// assert_eq!(result, Ok(()));
164    /// ```
165    pub fn rollback_transaction(&mut self, id: TransactionId) -> Result<()> {
166        let tx = self
167            .transactions
168            .last()
169            .ok_or_else(|| anyhow!("No active transaction"))?;
170        if tx.id != id {
171            return Err(anyhow!("Transaction ID mismatch"));
172        }
173        // Restore state from snapshot
174        let tx = self.transactions.pop().unwrap();
175        self.bindings = tx.bindings_snapshot;
176        self.binding_mutability = tx.mutability_snapshot;
177        // Rollback arena
178        self.arena.rollback(tx.arena_checkpoint)?;
179        Ok(())
180    }
181    /// Check if a transaction has exceeded its limits
182    /// # Examples
183    ///
184    /// ```ignore
185    /// use ruchy::runtime::transaction::check_transaction_limits;
186    ///
187    /// let result = check_transaction_limits(());
188    /// assert_eq!(result, Ok(()));
189    /// ```
190    pub fn check_transaction_limits(&self, id: TransactionId) -> Result<()> {
191        let tx = self
192            .transactions
193            .iter()
194            .find(|t| t.id == id)
195            .ok_or_else(|| anyhow!("Transaction not found"))?;
196        // Check time limit
197        if let Some(time_limit) = tx.metadata.time_limit {
198            if tx.start_time.elapsed() > time_limit {
199                return Err(anyhow!("Transaction time limit exceeded"));
200            }
201        }
202        // Check memory limit
203        if let Some(memory_limit) = tx.metadata.memory_limit {
204            if self.arena.arena().used() > memory_limit {
205                return Err(anyhow!("Transaction memory limit exceeded"));
206            }
207        }
208        Ok(())
209    }
210    /// Get current transaction depth
211    /// # Examples
212    ///
213    /// ```ignore
214    /// use ruchy::runtime::transaction::depth;
215    ///
216    /// let result = depth(());
217    /// assert_eq!(result, Ok(()));
218    /// ```
219    pub fn depth(&self) -> usize {
220        self.transactions.len()
221    }
222    /// Get current bindings
223    /// # Examples
224    ///
225    /// ```ignore
226    /// use ruchy::runtime::transaction::bindings;
227    ///
228    /// let result = bindings(());
229    /// assert_eq!(result, Ok(()));
230    /// ```
231    pub fn bindings(&self) -> &HashMap<String, Value> {
232        &self.bindings
233    }
234    /// Get mutable bindings
235    /// # Examples
236    ///
237    /// ```ignore
238    /// use ruchy::runtime::transaction::bindings_mut;
239    ///
240    /// let result = bindings_mut(());
241    /// assert_eq!(result, Ok(()));
242    /// ```
243    pub fn bindings_mut(&mut self) -> &mut HashMap<String, Value> {
244        &mut self.bindings
245    }
246    /// Insert a binding
247    /// # Examples
248    ///
249    /// ```ignore
250    /// use ruchy::runtime::transaction::insert_binding;
251    ///
252    /// let result = insert_binding(true);
253    /// assert_eq!(result, Ok(true));
254    /// ```
255    pub fn insert_binding(&mut self, name: String, value: Value, mutable: bool) {
256        self.bindings.insert(name.clone(), value);
257        self.binding_mutability.insert(name, mutable);
258    }
259    /// Get binding mutability
260    /// # Examples
261    ///
262    /// ```ignore
263    /// use ruchy::runtime::transaction::is_mutable;
264    ///
265    /// let result = is_mutable("example");
266    /// assert_eq!(result, Ok(()));
267    /// ```
268    pub fn is_mutable(&self, name: &str) -> bool {
269        self.binding_mutability.get(name).copied().unwrap_or(false)
270    }
271    /// Clear all bindings
272    /// # Examples
273    ///
274    /// ```ignore
275    /// use ruchy::runtime::transaction::clear;
276    ///
277    /// let result = clear(());
278    /// assert_eq!(result, Ok(()));
279    /// ```
280    /// # Examples
281    ///
282    /// ```ignore
283    /// use ruchy::runtime::transaction::clear;
284    ///
285    /// let result = clear(());
286    /// assert_eq!(result, Ok(()));
287    /// ```
288    pub fn clear(&mut self) {
289        self.bindings.clear();
290        self.binding_mutability.clear();
291        self.transactions.clear();
292        self.arena.reset();
293    }
294    /// Get arena for allocation
295    ///
296    /// # Examples
297    ///
298    /// ```
299    /// use ruchy::runtime::transaction::TransactionalState;
300    ///
301    /// let state = TransactionalState::new();
302    /// let arena = state.arena();
303    /// assert!(arena.used() >= 0);
304    /// ```
305    pub fn arena(&self) -> &Arena {
306        self.arena.arena()
307    }
308    /// Get memory usage
309    ///
310    /// # Examples
311    ///
312    /// ```ignore
313    /// use ruchy::runtime::transaction::TransactionalState;
314    ///
315    /// let state = TransactionalState::new();
316    /// let used = state.memory_used();
317    /// assert!(used >= 0);
318    /// ```
319    pub fn memory_used(&self) -> usize {
320        self.arena.arena().used()
321    }
322    // SavePoint feature temporarily disabled - requires complex lifetime management
323    // /// Create a savepoint for nested transactions
324    //
325    /// # Examples
326    ///
327    /// ```ignore
328    /// use ruchy::runtime::transaction::savepoint;
329    ///
330    /// let result = savepoint(());
331    /// assert_eq!(result, Ok(()));
332    /// ```
333    pub fn savepoint(&mut self) -> Result<SavePoint> {
334        // SavePoint feature temporarily disabled - requires complex lifetime management
335        Err(anyhow!("SavePoint feature temporarily disabled"))
336    }
337}
338// ============================================================================
339// SavePoint - RAII Guard for Automatic Rollback
340// ============================================================================
341// SavePoint temporarily disabled - requires complex lifetime management
342// /// RAII guard for automatic transaction rollback
343// pub struct SavePoint {
344//     tx_id: TransactionId,
345//     state: Arc<RefCell<TransactionalState>>,
346// }
347// Placeholder for SavePoint
348pub struct SavePoint;
349// ============================================================================
350// Transaction Log for Debugging
351// ============================================================================
352/// Log entry for transaction events
353#[derive(Debug, Clone)]
354pub enum TransactionEvent {
355    Begin {
356        id: TransactionId,
357        metadata: TransactionMetadata,
358    },
359    Commit {
360        id: TransactionId,
361        duration: Duration,
362        memory_used: usize,
363    },
364    Rollback {
365        id: TransactionId,
366        reason: String,
367    },
368    BindingAdded {
369        name: String,
370        value_type: String,
371    },
372    BindingModified {
373        name: String,
374        old_type: String,
375        new_type: String,
376    },
377}
378/// Transaction log for debugging and analysis
379pub struct TransactionLog {
380    events: Vec<(Instant, TransactionEvent)>,
381    max_entries: usize,
382}
383impl TransactionLog {
384    pub fn new(max_entries: usize) -> Self {
385        Self {
386            events: Vec::new(),
387            max_entries,
388        }
389    }
390    /// # Examples
391    ///
392    /// ```
393    /// use ruchy::runtime::transaction::TransactionLog;
394    ///
395    /// let mut instance = TransactionLog::new();
396    /// let result = instance.log();
397    /// // Verify behavior
398    /// ```
399    pub fn log(&mut self, event: TransactionEvent) {
400        self.events.push((Instant::now(), event));
401        // Maintain size limit
402        if self.events.len() > self.max_entries {
403            self.events.remove(0);
404        }
405    }
406    /// # Examples
407    ///
408    /// ```ignore
409    /// use ruchy::runtime::transaction::recent_events;
410    ///
411    /// let result = recent_events(());
412    /// assert_eq!(result, Ok(()));
413    /// ```
414    pub fn recent_events(&self, count: usize) -> &[(Instant, TransactionEvent)] {
415        let start = self.events.len().saturating_sub(count);
416        &self.events[start..]
417    }
418    pub fn clear(&mut self) {
419        self.events.clear();
420    }
421}
422// ============================================================================
423// Optimistic Concurrency Control
424// ============================================================================
425/// Version number for optimistic concurrency control
426#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
427pub struct Version(u64);
428/// Versioned value for optimistic concurrency
429#[derive(Debug, Clone)]
430pub struct VersionedValue<T> {
431    pub value: T,
432    pub version: Version,
433}
434/// Multi-version concurrency control for parallel evaluation
435pub struct MVCC {
436    /// Current version
437    current_version: Version,
438    /// Versioned bindings
439    bindings: HashMap<String, Vec<VersionedValue<Value>>>,
440    /// Maximum versions to keep per binding
441    max_versions: usize,
442}
443impl MVCC {
444    pub fn new() -> Self {
445        Self {
446            current_version: Version(0),
447            bindings: HashMap::new(),
448            max_versions: 10,
449        }
450    }
451    /// Start a new read transaction
452    /// # Examples
453    ///
454    /// ```
455    /// use ruchy::runtime::transaction::MVCC;
456    ///
457    /// let mut instance = MVCC::new();
458    /// let result = instance.begin_read();
459    /// // Verify behavior
460    /// ```
461    pub fn begin_read(&self) -> Version {
462        self.current_version
463    }
464    /// Start a new write transaction
465    /// # Examples
466    ///
467    /// ```ignore
468    /// use ruchy::runtime::transaction::begin_write;
469    ///
470    /// let result = begin_write(());
471    /// assert_eq!(result, Ok(()));
472    /// ```
473    pub fn begin_write(&mut self) -> Version {
474        self.current_version.0 += 1;
475        self.current_version
476    }
477    /// Read a value at a specific version
478    /// # Examples
479    ///
480    /// ```ignore
481    /// use ruchy::runtime::transaction::read;
482    ///
483    /// let result = read("example");
484    /// assert_eq!(result, Ok(()));
485    /// ```
486    pub fn read(&self, name: &str, version: Version) -> Option<&Value> {
487        self.bindings.get(name).and_then(|versions| {
488            // Find the latest version <= requested version
489            versions
490                .iter()
491                .rev()
492                .find(|v| v.version <= version)
493                .map(|v| &v.value)
494        })
495    }
496    /// Write a value at a specific version
497    /// # Examples
498    ///
499    /// ```ignore
500    /// use ruchy::runtime::transaction::write;
501    ///
502    /// let result = write(());
503    /// assert_eq!(result, Ok(()));
504    /// ```
505    pub fn write(&mut self, name: String, value: Value, version: Version) {
506        let entry = self.bindings.entry(name).or_default();
507        // Add new version
508        entry.push(VersionedValue { value, version });
509        // Maintain version limit
510        if entry.len() > self.max_versions {
511            entry.remove(0);
512        }
513    }
514    /// Garbage collect old versions
515    /// # Examples
516    ///
517    /// ```ignore
518    /// use ruchy::runtime::transaction::gc;
519    ///
520    /// let result = gc(());
521    /// assert_eq!(result, Ok(()));
522    /// ```
523    pub fn gc(&mut self, keep_after: Version) {
524        for versions in self.bindings.values_mut() {
525            versions.retain(|v| v.version >= keep_after);
526        }
527    }
528}
529impl Default for MVCC {
530    fn default() -> Self {
531        Self::new()
532    }
533}
534#[cfg(test)]
535mod tests {
536    use super::*;
537    #[test]
538    fn test_transaction_commit() {
539        let mut state = TransactionalState::new(1024 * 1024);
540        // Add initial binding
541        state.insert_binding("x".to_string(), Value::Integer(1), false);
542        // Begin transaction
543        let tx = state
544            .begin_transaction(TransactionMetadata::default())
545            .unwrap();
546        // Modify binding
547        state.insert_binding("x".to_string(), Value::Integer(2), false);
548        state.insert_binding("y".to_string(), Value::Integer(3), false);
549        // Commit
550        state.commit_transaction(tx).unwrap();
551        // Changes should persist
552        assert_eq!(state.bindings.get("x"), Some(&Value::Integer(2)));
553        assert_eq!(state.bindings.get("y"), Some(&Value::Integer(3)));
554    }
555    #[test]
556    fn test_transaction_rollback() {
557        let mut state = TransactionalState::new(1024 * 1024);
558        // Add initial binding
559        state.insert_binding("x".to_string(), Value::Integer(1), false);
560        // Begin transaction
561        let tx = state
562            .begin_transaction(TransactionMetadata::default())
563            .unwrap();
564        // Modify binding
565        state.insert_binding("x".to_string(), Value::Integer(2), false);
566        state.insert_binding("y".to_string(), Value::Integer(3), false);
567        // Rollback
568        state.rollback_transaction(tx).unwrap();
569        // Changes should be reverted
570        assert_eq!(state.bindings.get("x"), Some(&Value::Integer(1)));
571        assert_eq!(state.bindings.get("y"), None);
572    }
573    // SavePoint test implementation
574    // #[test]
575    // fn test_savepoint() {
576    //     let mut state = TransactionalState::new(1024 * 1024);
577    //
578    //     state.insert_binding("x".to_string(), Value::Integer(1), false);
579    //
580    //     {
581    //         let sp = state.savepoint().unwrap();
582    //         state.insert_binding("x".to_string(), Value::Integer(2), false);
583    //         // SavePoint dropped here, automatic rollback
584    //     }
585    //
586    //     // Should be rolled back
587    //     assert_eq!(state.bindings.get("x"), Some(&Value::Integer(1)));
588    // }
589    #[test]
590    fn test_mvcc() {
591        let mut mvcc = MVCC::new();
592        let v1 = mvcc.begin_write();
593        mvcc.write("x".to_string(), Value::Integer(1), v1);
594        let v2 = mvcc.begin_write();
595        mvcc.write("x".to_string(), Value::Integer(2), v2);
596        // Read at different versions
597        assert_eq!(mvcc.read("x", v1), Some(&Value::Integer(1)));
598        assert_eq!(mvcc.read("x", v2), Some(&Value::Integer(2)));
599    }
600}
601#[cfg(test)]
602mod property_tests_transaction {
603    use super::*;
604    use proptest::proptest;
605
606    proptest! {
607        /// Property: TransactionalState operations never panic
608        #[test]
609        fn test_transactional_state_never_panics(size: usize) {
610            let size = size % 10_000_000;  // Limit memory size
611            let _ = TransactionalState::new(size);
612        }
613        /// Property: Transaction commit/rollback preserves invariants
614        #[test]
615        fn test_transaction_invariants(ops: Vec<u8>) {
616            let mut state = TransactionalState::new(1024 * 1024);
617            for op in ops.iter().take(100) {
618                match op % 3 {
619                    0 => {
620                        let _ = state.begin_transaction(TransactionMetadata::default());
621                    },
622                    1 => {
623                        if state.depth() > 0 {
624                            let tx = TransactionId(state.depth() as u64);
625                            let _ = state.commit_transaction(tx);
626                        }
627                    },
628                    _ => {
629                        if state.depth() > 0 {
630                            let tx = TransactionId(state.depth() as u64);
631                            let _ = state.rollback_transaction(tx);
632                        }
633                    }
634                }
635            }
636            // Invariant: depth is always non-negative (usize type)
637        }
638    }
639}