## Coverage Report

#### Created: 2025-09-08 21:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /home/noah/src/ruchy/src/runtime/transaction.rs

    Line
    Count
    Source
    1
    //! Transactional state management for REPL evaluation
    2
    //!
    3
    //! Provides atomic evaluation with rollback capability for safe experimentation.
    4
    5
    use anyhow::{Result, anyhow};
    6
    use std::collections::HashMap;
    7
    use std::time::{Duration, Instant};
    8
    9
    use crate::runtime::repl::Value;
    10
    use crate::runtime::safe_arena::{TransactionalArena, SafeArena as Arena};
    11
    12
    // ============================================================================
    13
    // Transactional REPL State
    14
    // ============================================================================
    15
    16
    /// Transaction ID for tracking evaluation transactions
    17
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    18
    pub struct TransactionId(pub u64);
    19
    20
    /// Transactional wrapper for REPL state with O(1) checkpoint/rollback
    21
    pub struct TransactionalState {
    22
        /// Current bindings
    23
        bindings: HashMap<String, Value>,
    24
        /// Binding mutability tracking
    25
        binding_mutability: HashMap<String, bool>,
    26
        /// Transaction stack
    27
        transactions: Vec<Transaction>,
    28
        /// Next transaction ID
    29
        next_tx_id: u64,
    30
        /// Arena for memory allocation
    31
        arena: TransactionalArena,
    32
        /// Maximum transaction depth
    33
        max_depth: usize,
    34
    }
    35
    36
    /// A single transaction in the stack
    37
    #[derive(Debug, Clone)]
    38
    struct Transaction {
    39
        id: TransactionId,
    40
        /// Snapshot of bindings at transaction start
    41
        bindings_snapshot: HashMap<String, Value>,
    42
        /// Snapshot of mutability at transaction start
    43
        mutability_snapshot: HashMap<String, bool>,
    44
        /// Arena checkpoint
    45
        arena_checkpoint: usize,
    46
        /// Start time for timeout tracking
    47
        start_time: Instant,
    48
        /// Transaction metadata
    49
        metadata: TransactionMetadata,
    50
    }
    51
    52
    /// Metadata about a transaction
    53
    #[derive(Debug, Clone)]
    54
    pub struct TransactionMetadata {
    55
        /// Description of the transaction
    56
        pub description: String,
    57
        /// Memory limit for this transaction
    58
        pub memory_limit: Option<usize>,
    59
        /// Time limit for this transaction
    60
        pub time_limit: Option<Duration>,
    61
        /// Whether this is a speculative evaluation
    62
        pub speculative: bool,
    63
    }
    64
    65
    impl Default for TransactionMetadata {
    66
    0
        fn default() -> Self {
    67
    0
            Self {
    68
    0
                description: "evaluation".to_string(),
    69
    0
                memory_limit: None,
    70
    0
                time_limit: None,
    71
    0
                speculative: false,
    72
    0
            }
    73
    0
        }
    74
    }
    75
    76
    impl TransactionalState {
    77
        /// Create a new transactional state with the given memory limit
    78
    0
        pub fn new(max_memory: usize) -> Self {
    79
    0
            Self {
    80
    0
                bindings: HashMap::new(),
    81
    0
                binding_mutability: HashMap::new(),
    82
    0
                transactions: Vec::new(),
    83
    0
                next_tx_id: 1,
    84
    0
                arena: TransactionalArena::new(max_memory),
    85
    0
                max_depth: 100, // Prevent unbounded nesting
    86
    0
            }
    87
    0
        }
    88
    89
        /// Begin a new transaction
    90
    0
        pub fn begin_transaction(&mut self, metadata: TransactionMetadata) -> Result<TransactionId> {
    91
    0
            if self.transactions.len() >= self.max_depth {
    92
    0
                return Err(anyhow!("Transaction depth limit exceeded"));
    93
    0
            }
    94
    95
    0
            let id = TransactionId(self.next_tx_id);
    96
    0
            self.next_tx_id += 1;
    97
    98
            // Create checkpoint in arena
    99
    0
            let arena_checkpoint = self.arena.checkpoint();
    100
    101
            // Create transaction with current state snapshot
    102
    0
            let transaction = Transaction {
    103
    0
                id,
    104
    0
                bindings_snapshot: self.bindings.clone(),
    105
    0
                mutability_snapshot: self.binding_mutability.clone(),
    106
    0
                arena_checkpoint,
    107
    0
                start_time: Instant::now(),
    108
    0
                metadata,
    109
    0
            };
    110
    111
    0
            self.transactions.push(transaction);
    112
    0
            Ok(id)
    113
    0
        }
    114
    115
        /// Commit the current transaction
    116
    0
        pub fn commit_transaction(&mut self, id: TransactionId) -> Result<()> {
    117
    0
            let tx = self.transactions.last()
    118
    0
                .ok_or_else(|| anyhow!("No active transaction"))?;
    119
    120
    0
            if tx.id != id {
    121
    0
                return Err(anyhow!("Transaction ID mismatch"));
    122
    0
            }
    123
    124
            // Commit arena changes
    125
    0
            self.arena.commit()?;
    126
    127
            // Remove transaction from stack
    128
    0
            self.transactions.pop();
    129
    130
    0
            Ok(())
    131
    0
        }
    132
    133
        /// Rollback the current transaction
    134
    0
        pub fn rollback_transaction(&mut self, id: TransactionId) -> Result<()> {
    135
    0
            let tx = self.transactions.last()
    136
    0
                .ok_or_else(|| anyhow!("No active transaction"))?;
    137
    138
    0
            if tx.id != id {
    139
    0
                return Err(anyhow!("Transaction ID mismatch"));
    140
    0
            }
    141
    142
            // Restore state from snapshot
    143
    0
            let tx = self.transactions.pop().unwrap();
    144
    0
            self.bindings = tx.bindings_snapshot;
    145
    0
            self.binding_mutability = tx.mutability_snapshot;
    146
    147
            // Rollback arena
    148
    0
            self.arena.rollback(tx.arena_checkpoint)?;
    149
    150
    0
            Ok(())
    151
    0
        }
    152
    153
        /// Check if a transaction has exceeded its limits
    154
    0
        pub fn check_transaction_limits(&self, id: TransactionId) -> Result<()> {
    155
    0
            let tx = self.transactions.iter()
    156
    0
                .find(|t| t.id == id)
    157
    0
                .ok_or_else(|| anyhow!("Transaction not found"))?;
    158
    159
            // Check time limit
    160
    0
            if let Some(time_limit) = tx.metadata.time_limit {
    161
    0
                if tx.start_time.elapsed() > time_limit {
    162
    0
                    return Err(anyhow!("Transaction time limit exceeded"));
    163
    0
                }
    164
    0
            }
    165
    166
            // Check memory limit
    167
    0
            if let Some(memory_limit) = tx.metadata.memory_limit {
    168
    0
                if self.arena.arena().used() > memory_limit {
    169
    0
                    return Err(anyhow!("Transaction memory limit exceeded"));
    170
    0
                }
    171
    0
            }
    172
    173
    0
            Ok(())
    174
    0
        }
    175
    176
        /// Get current transaction depth
    177
    0
        pub fn depth(&self) -> usize {
    178
    0
            self.transactions.len()
    179
    0
        }
    180
    181
        /// Get current bindings
    182
    0
        pub fn bindings(&self) -> &HashMap<String, Value> {
    183
    0
            &self.bindings
    184
    0
        }
    185
    186
        /// Get mutable bindings
    187
    0
        pub fn bindings_mut(&mut self) -> &mut HashMap<String, Value> {
    188
    0
            &mut self.bindings
    189
    0
        }
    190
    191
        /// Insert a binding
    192
    0
        pub fn insert_binding(&mut self, name: String, value: Value, mutable: bool) {
    193
    0
            self.bindings.insert(name.clone(), value);
    194
    0
            self.binding_mutability.insert(name, mutable);
    195
    0
        }
    196
    197
        /// Get binding mutability
    198
    0
        pub fn is_mutable(&self, name: &str) -> bool {
    199
    0
            self.binding_mutability.get(name).copied().unwrap_or(false)
    200
    0
        }
    201
    202
        /// Clear all bindings
    203
    0
        pub fn clear(&mut self) {
    204
    0
            self.bindings.clear();
    205
    0
            self.binding_mutability.clear();
    206
    0
            self.transactions.clear();
    207
    0
            self.arena.reset();
    208
    0
        }
    209
    210
        /// Get arena for allocation
    211
    0
        pub fn arena(&self) -> &Arena {
    212
    0
            self.arena.arena()
    213
    0
        }
    214
    215
        /// Get memory usage
    216
    0
        pub fn memory_used(&self) -> usize {
    217
    0
            self.arena.arena().used()
    218
    0
        }
    219
    220
        // SavePoint feature temporarily disabled - requires complex lifetime management
    221
        // /// Create a savepoint for nested transactions
    222
        // pub fn savepoint(&mut self) -> Result<SavePoint> {
    223
        //     let tx_id = self.begin_transaction(TransactionMetadata {
    224
        //         description: "savepoint".to_string(),
    225
        //         speculative: true,
    226
        //         ..Default::default()
    227
        //     })?;
    228
        //
    229
        //     Ok(SavePoint {
    230
        //         tx_id,
    231
        //         state: Rc::new(RefCell::new(*self)),
    232
        //     })
    233
        // }
    234
    }
    235
    236
    // ============================================================================
    237
    // SavePoint - RAII Guard for Automatic Rollback
    238
    // ============================================================================
    239
    240
    // SavePoint temporarily disabled - requires complex lifetime management
    241
    // /// RAII guard for automatic transaction rollback
    242
    // pub struct SavePoint {
    243
    //     tx_id: TransactionId,
    244
    //     state: Rc<RefCell<TransactionalState>>,
    245
    // }
    246
    247
    // Placeholder for SavePoint
    248
    pub struct SavePoint;
    249
    250
    // ============================================================================
    251
    // Transaction Log for Debugging
    252
    // ============================================================================
    253
    254
    /// Log entry for transaction events
    255
    #[derive(Debug, Clone)]
    256
    pub enum TransactionEvent {
    257
        Begin {
    258
            id: TransactionId,
    259
            metadata: TransactionMetadata,
    260
        },
    261
        Commit {
    262
            id: TransactionId,
    263
            duration: Duration,
    264
            memory_used: usize,
    265
        },
    266
        Rollback {
    267
            id: TransactionId,
    268
            reason: String,
    269
        },
    270
        BindingAdded {
    271
            name: String,
    272
            value_type: String,
    273
        },
    274
        BindingModified {
    275
            name: String,
    276
            old_type: String,
    277
            new_type: String,
    278
        },
    279
    }
    280
    281
    /// Transaction log for debugging and analysis
    282
    pub struct TransactionLog {
    283
        events: Vec<(Instant, TransactionEvent)>,
    284
        max_entries: usize,
    285
    }
    286
    287
    impl TransactionLog {
    288
    0
        pub fn new(max_entries: usize) -> Self {
    289
    0
            Self {
    290
    0
                events: Vec::new(),
    291
    0
                max_entries,
    292
    0
            }
    293
    0
        }
    294
    295
    0
        pub fn log(&mut self, event: TransactionEvent) {
    296
    0
            self.events.push((Instant::now(), event));
    297
    298
            // Maintain size limit
    299
    0
            if self.events.len() > self.max_entries {
    300
    0
                self.events.remove(0);
    301
    0
            }
    302
    0
        }
    303
    304
    0
        pub fn recent_events(&self, count: usize) -> &[(Instant, TransactionEvent)] {
    305
    0
            let start = self.events.len().saturating_sub(count);
    306
    0
            &self.events[start..]
    307
    0
        }
    308
    309
    0
        pub fn clear(&mut self) {
    310
    0
            self.events.clear();
    311
    0
        }
    312
    }
    313
    314
    // ============================================================================
    315
    // Optimistic Concurrency Control
    316
    // ============================================================================
    317
    318
    /// Version number for optimistic concurrency control
    319
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
    320
    pub struct Version(u64);
    321
    322
    /// Versioned value for optimistic concurrency
    323
    #[derive(Debug, Clone)]
    324
    pub struct VersionedValue<T> {
    325
        pub value: T,
    326
        pub version: Version,
    327
    }
    328
    329
    /// Multi-version concurrency control for parallel evaluation
    330
    pub struct MVCC {
    331
        /// Current version
    332
        current_version: Version,
    333
        /// Versioned bindings
    334
        bindings: HashMap<String, Vec<VersionedValue<Value>>>,
    335
        /// Maximum versions to keep per binding
    336
        max_versions: usize,
    337
    }
    338
    339
    impl MVCC {
    340
    0
        pub fn new() -> Self {
    341
    0
            Self {
    342
    0
                current_version: Version(0),
    343
    0
                bindings: HashMap::new(),
    344
    0
                max_versions: 10,
    345
    0
            }
    346
    0
        }
    347
    348
        /// Start a new read transaction
    349
    0
        pub fn begin_read(&self) -> Version {
    350
    0
            self.current_version
    351
    0
        }
    352
    353
        /// Start a new write transaction
    354
    0
        pub fn begin_write(&mut self) -> Version {
    355
    0
            self.current_version.0 += 1;
    356
    0
            self.current_version
    357
    0
        }
    358
    359
        /// Read a value at a specific version
    360
    0
        pub fn read(&self, name: &str, version: Version) -> Option<&Value> {
    361
    0
            self.bindings.get(name).and_then(|versions| {
    362
                // Find the latest version <= requested version
    363
    0
                versions.iter()
    364
    0
                    .rev()
    365
    0
                    .find(|v| v.version <= version)
    366
    0
                    .map(|v| &v.value)
    367
    0
            })
    368
    0
        }
    369
    370
        /// Write a value at a specific version
    371
    0
        pub fn write(&mut self, name: String, value: Value, version: Version) {
    372
    0
            let entry = self.bindings.entry(name).or_default();
    373
    374
            // Add new version
    375
    0
            entry.push(VersionedValue { value, version });
    376
    377
            // Maintain version limit
    378
    0
            if entry.len() > self.max_versions {
    379
    0
                entry.remove(0);
    380
    0
            }
    381
    0
        }
    382
    383
        /// Garbage collect old versions
    384
    0
        pub fn gc(&mut self, keep_after: Version) {
    385
    0
            for versions in self.bindings.values_mut() {
    386
    0
                versions.retain(|v| v.version >= keep_after);
    387
            }
    388
    0
        }
    389
    }
    390
    391
    impl Default for MVCC {
    392
    0
        fn default() -> Self {
    393
    0
            Self::new()
    394
    0
        }
    395
    }
    396
    397
    #[cfg(test)]
    398
    mod tests {
    399
        use super::*;
    400
    401
        #[test]
    402
        fn test_transaction_commit() {
    403
            let mut state = TransactionalState::new(1024 * 1024);
    404
    405
            // Add initial binding
    406
            state.insert_binding("x".to_string(), Value::Int(1), false);
    407
    408
            // Begin transaction
    409
            let tx = state.begin_transaction(TransactionMetadata::default()).unwrap();
    410
    411
            // Modify binding
    412
            state.insert_binding("x".to_string(), Value::Int(2), false);
    413
            state.insert_binding("y".to_string(), Value::Int(3), false);
    414
    415
            // Commit
    416
            state.commit_transaction(tx).unwrap();
    417
    418
            // Changes should persist
    419
            assert_eq!(state.bindings.get("x"), Some(&Value::Int(2)));
    420
            assert_eq!(state.bindings.get("y"), Some(&Value::Int(3)));
    421
        }
    422
    423
        #[test]
    424
        fn test_transaction_rollback() {
    425
            let mut state = TransactionalState::new(1024 * 1024);
    426
    427
            // Add initial binding
    428
            state.insert_binding("x".to_string(), Value::Int(1), false);
    429
    430
            // Begin transaction
    431
            let tx = state.begin_transaction(TransactionMetadata::default()).unwrap();
    432
    433
            // Modify binding
    434
            state.insert_binding("x".to_string(), Value::Int(2), false);
    435
            state.insert_binding("y".to_string(), Value::Int(3), false);
    436
    437
            // Rollback
    438
            state.rollback_transaction(tx).unwrap();
    439
    440
            // Changes should be reverted
    441
            assert_eq!(state.bindings.get("x"), Some(&Value::Int(1)));
    442
            assert_eq!(state.bindings.get("y"), None);
    443
        }
    444
    445
        // SavePoint test disabled - feature temporarily disabled
    446
        // #[test]
    447
        // fn test_savepoint() {
    448
        //     let mut state = TransactionalState::new(1024 * 1024);
    449
        //
    450
        //     state.insert_binding("x".to_string(), Value::Int(1), false);
    451
        //
    452
        //     {
    453
        //         let sp = state.savepoint().unwrap();
    454
        //         state.insert_binding("x".to_string(), Value::Int(2), false);
    455
        //         // SavePoint dropped here, automatic rollback
    456
        //     }
    457
        //
    458
        //     // Should be rolled back
    459
        //     assert_eq!(state.bindings.get("x"), Some(&Value::Int(1)));
    460
        // }
    461
    462
        #[test]
    463
        fn test_mvcc() {
    464
            let mut mvcc = MVCC::new();
    465
    466
            let v1 = mvcc.begin_write();
    467
            mvcc.write("x".to_string(), Value::Int(1), v1);
    468
    469
            let v2 = mvcc.begin_write();
    470
            mvcc.write("x".to_string(), Value::Int(2), v2);
    471
    472
            // Read at different versions
    473
            assert_eq!(mvcc.read("x", v1), Some(&Value::Int(1)));
    474
            assert_eq!(mvcc.read("x", v2), Some(&Value::Int(2)));
    475
        }
    476
    }