aimx/aim/
lock_manager.rs

1//! Workflow Lock Manager for Concurrent Access Control
2//!
3//! This module provides a thread-safe lock manager for controlling concurrent access
4//! to AIM workflows. It uses fine-grained locking to allow maximum concurrency while
5//! ensuring data consistency during write operations.
6//!
7//! # Overview
8//!
9//! The lock manager implements a per-workflow locking strategy that:
10//! - Allows unlimited concurrent read access to workflows
11//! - Ensures exclusive write access to individual workflows
12//! - Uses reference-based locking to prevent conflicts
13//! - Provides a global singleton instance for application-wide coordination
14//!
15//! # Concurrency Model
16//!
17//! AIM uses a Multi-Version Concurrency Control (MVCC) model where:
18//! - Readers access consistent snapshots without blocking
19//! - Writers acquire exclusive locks on specific workflows
20//! - Locks are automatically released when guard objects are dropped
21//! - Fine-grained locking minimizes contention compared to global locks
22//!
23//! # Examples
24//!
25//! ```rust
26//! use aimx::aim::get_lock_manager;
27//! use aimx::Reference;
28//!
29//! // Get the global lock manager instance
30//! let lock_manager = get_lock_manager();
31//!
32//! // Acquire a lock for a specific workflow
33//! let reference = Reference::One("my_workflow".to_string());
34//! let lock_guard = lock_manager.acquire_workflow_lock(reference);
35//!
36//! // The lock is held until `lock_guard` is dropped
37//! // Perform write operations on the workflow...
38//!
39//! // Lock is automatically released when `lock_guard` goes out of scope
40//! ```
41//!
42//! # Thread Safety
43//!
44//! The `LockManager` is designed to be used as a global singleton and is
45//! completely thread-safe. All operations use atomic reference counting
46//! and concurrent data structures to ensure safe access from multiple threads.
47//!
48//! # Implementation Details
49//!
50//! - Uses `DashMap` for lock-free concurrent access to the lock table
51//! - Employs `Arc<Mutex<()>>` for shared ownership of individual locks
52//! - Provides lazy initialization of locks on first access
53//! - Future enhancements may include file system-level locking mechanisms
54
55use dashmap::DashMap;
56use once_cell::sync::Lazy;
57use std::sync::{Arc, Mutex};
58use crate::Reference;
59
60/// Manages concurrent access to workflows through fine-grained locking
61///
62/// The `LockManager` maintains a mapping of workflow references to mutex guards,
63/// allowing for per-workflow exclusive access control. This approach maximizes
64/// concurrency by only blocking access to the specific workflow being modified,
65/// rather than using a global lock that would block all workflow operations.
66///
67/// # Design Principles
68///
69/// - **Fine-grained locking**: Each workflow has its own lock
70/// - **Lazy initialization**: Locks are created on first access
71/// - **Shared ownership**: Multiple references to the same lock using `Arc`
72/// - **Automatic cleanup**: Locks are managed by the `DashMap` and cleaned up automatically
73///
74/// # Thread Safety
75///
76/// This struct is completely thread-safe and can be safely accessed from
77/// multiple threads. The `Default` implementation is used to create the
78/// global singleton instance.
79#[derive(Debug, Default)]
80pub struct LockManager {
81    /// Maps workflow references to their corresponding mutex guards
82    ///
83    /// Uses `DashMap` for lock-free concurrent access. The value is an `Arc<Mutex<()>>`
84    /// which allows multiple owners of the same lock while ensuring mutual exclusion
85    /// when the mutex is locked.
86    workflow_locks: DashMap<Reference, Arc<Mutex<()>>>,
87}
88
89impl LockManager {
90    /// Creates a new `LockManager` instance
91    ///
92    /// Returns a new lock manager with an empty lock table. Locks will be
93    /// created lazily as workflows are accessed.
94    ///
95    /// # Examples
96    ///
97    /// ```rust
98    /// use aimx::aim::LockManager;
99    ///
100    /// let lock_manager = LockManager::new();
101    /// ```
102    pub fn new() -> Self {
103        Self::default()
104    }
105
106    /// Acquires an exclusive write lock for a single workflow by reference
107    ///
108    /// This method retrieves or creates a mutex for the specified workflow reference
109    /// and returns a clone of the `Arc<Mutex<()>>`. The caller must call `lock()`
110    /// on this mutex to actually acquire the exclusive lock.
111    ///
112    /// # Important Notes
113    ///
114    /// - The lock is not acquired by this method - it only provides access to the mutex
115    /// - Multiple calls with the same reference return the same mutex
116    /// - The lock is released when the `MutexGuard` is dropped
117    /// - For production use, ensure the reference is canonicalized to prevent lock aliasing
118    ///
119    /// # Parameters
120    ///
121    /// * `reference` - The workflow reference to lock
122    ///
123    /// # Returns
124    ///
125    /// An `Arc<Mutex<()>>` that can be used to acquire the exclusive lock
126    ///
127    /// # Examples
128    ///
129    /// ```rust
130    /// use aimx::aim::LockManager;
131    /// use aimx::Reference;
132    /// use std::sync::Arc;
133    ///
134    /// let lock_manager = LockManager::new();
135    /// let reference = Reference::One("my_workflow".to_string());
136    ///
137    /// // Get the mutex for this workflow
138    /// let mutex = lock_manager.acquire_workflow_lock(reference);
139    ///
140    /// // Actually acquire the lock
141    /// let _guard = mutex.lock().unwrap();
142    ///
143    /// // Perform exclusive operations...
144    /// // Lock is released when `_guard` is dropped
145    /// ```
146    ///
147    /// NOTE: I plan to probably add an fs lock mechanism to this at some point in the future.
148    pub fn acquire_workflow_lock(&self, reference: Reference) -> Arc<Mutex<()>> {
149        // In a real app, ensure `target_file` is canonicalized before use
150        // to prevent lock aliasing issues (e.g., `.` vs `foo/../.`).
151        self.workflow_locks
152            .entry(reference)
153            .or_insert_with(|| Arc::new(Mutex::new(())))
154            .clone()
155    }
156}
157
158/// Global singleton instance of the `LockManager`
159///
160/// Uses lazy initialization to create the lock manager on first access.
161/// This ensures the lock manager is only created when needed while providing
162/// a globally accessible instance for the entire application.
163static GLOBAL_LOCK_MANAGER: Lazy<LockManager> = Lazy::new(LockManager::new);
164
165/// Returns the global singleton instance of the `LockManager`
166///
167/// This function provides access to the application-wide lock manager that
168/// coordinates concurrent access to workflows. It uses lazy initialization
169/// to create the manager on first access.
170///
171/// # Returns
172///
173/// A static reference to the global `LockManager` instance
174///
175/// # Examples
176///
177/// ```rust
178/// use aimx::aim::get_lock_manager;
179///
180/// let lock_manager = get_lock_manager();
181/// // Use the lock manager for workflow coordination
182/// ```
183pub fn get_lock_manager() -> &'static LockManager {
184    &GLOBAL_LOCK_MANAGER
185}