Skip to main content

bimodal_array/
lib.rs

1//! `bimodal_array` provides dual-mode access to a contiguous array. Access
2//! is granted through two handler types:
3//!
4//! 1. [`ElementHandle<T>`] grants mutable access to a single element.
5//! 2. [`ArrayHandle<T>`] grants exclusive mutable access to the entire array.
6//!
7//! Handles represent the ability to *attempt* a lock. The actual lock is held
8//! by the returned guards:
9//!
10//! 1. when `ElementHandle::lock()` succeeds, it produces an [`ElementGuard<T>`]
11//! 2. when `ArrayHandle::lock()` succeeds, it produces an [`ArrayGuard<T>`].
12//!
13//! Each [`ElementHandle`] is bound to a specific element. Guards for different
14//! elements may may be held concurrently. An [`ArrayGuard<T>`] provides
15//! exclusive access to the entire array and cannot coexist with any
16//! [`ElementGuard<T>`].
17//!
18//! This pattern is useful for scatter–gather style workloads where independent
19//! workers update elements in parallel, followed by an orchestration step that
20//! processes the entire array. Internally, lock state is coordinated via a
21//! single `AtomicUsize`. As a result, `bimodal_array` may not scale well under
22//! high contention.
23//!
24//! # Examples
25//!
26//! ```no_run
27//! # use bimodal_array::bimodal_array;
28//! # use std::thread;
29//! const WORKER_COUNT: usize = 8;
30//! let data = vec![0usize; WORKER_COUNT];
31//! let (mut array_handle, element_handles) = bimodal_array(data);
32//! let threads: Vec<_> = element_handles
33//!     .into_iter()
34//!     .enumerate()
35//!     .map(|(idx, mut element)| {
36//!         thread::spawn(move || {
37//!             let mut guard = element.lock().unwrap();
38//!             *guard = 10 - idx;
39//!         })
40//!     })
41//!     .collect();
42//! threads.into_iter().for_each(|t| t.join().unwrap());
43//! let mut array_guard = array_handle.lock().unwrap();
44//! array_guard.sort();
45//! ```
46
47use core::slice;
48use std::collections::TryReserveError;
49use std::ops::Deref;
50use std::ops::DerefMut;
51use std::ptr::NonNull;
52use std::sync::atomic::AtomicUsize;
53use std::sync::atomic::Ordering;
54use std::sync::atomic::fence;
55
56/// Errors that can occur when constructing or using a `bimodal_array`.
57#[derive(Debug)]
58pub enum BimodalArrayError {
59    /// The provided collection length is not supported.
60    ///
61    /// Internally, `bimodal_array` reserves certain length values for state
62    /// encoding. Currently, a length of `usize::MAX` is rejected.
63    UnsupportedLength,
64
65    /// Allocation failed.
66    ///
67    /// This typically occurs when reserving memory for the element handle
68    /// storage. The contained [`TryReserveError`] provides additional details
69    /// about whether the failure was due to capacity overflow or allocator
70    /// refusal.
71    AllocationFailed(TryReserveError),
72
73    /// The array-wide lock could not be acquired.
74    ///
75    /// This happens when one or more [`ElementGuard<T>`] values are currently
76    /// active.
77    CouldNotAcquireArrayLock,
78
79    /// The element-level lock could not be acquired.
80    ///
81    /// This happens when an [`ArrayGuard<T>`] is currently active.
82    CouldNotAcquireElementLock,
83}
84
85/// The LockState represents how the BinomialArray is currently used
86#[repr(transparent)]
87struct LockState(AtomicUsize);
88
89/// This represents when a GLOBAL_LOCK_STATE is in effect
90const ARRAY_LOCK_STATE: usize = usize::MAX;
91
92impl LockState {
93    fn new() -> Self {
94        Self(AtomicUsize::new(0))
95    }
96
97    /// Releases the global lock that is currently in place. Should only be
98    /// called when dropping a GloablLock.
99    fn release_array_lock(&self) {
100        let old_state = self.0.fetch_xor(ARRAY_LOCK_STATE, Ordering::Release);
101        debug_assert!(
102            old_state == ARRAY_LOCK_STATE,
103            "An accessor was active when trying to release the global lock"
104        )
105    }
106
107    /// Rleases an accessor lock. Should only be called when dropping the
108    /// accessor lock.
109    fn release_element_lock(&self) {
110        let old_state = self.0.fetch_sub(1, Ordering::Release);
111        debug_assert!(
112            (old_state != ARRAY_LOCK_STATE),
113            "Global lock was in place when trying to release accessor"
114        )
115    }
116
117    /// Acquires an accessor lock. Will only work if it can successfully
118    /// increase the number of accessors and there is no Global lock in
119    /// place
120    fn acquire_element_lock(&self) -> Result<&Self, BimodalArrayError> {
121        let mut current_state = self.0.load(Ordering::Relaxed);
122        loop {
123            if current_state == ARRAY_LOCK_STATE {
124                return Err(BimodalArrayError::CouldNotAcquireElementLock);
125            }
126            match self.0.compare_exchange(
127                current_state,
128                current_state + 1,
129                Ordering::Acquire,
130                Ordering::Relaxed,
131            ) {
132                Ok(_) => return Ok(self),
133                Err(new_state) => current_state = new_state,
134            }
135        }
136    }
137
138    /// Acquires a global lock. Will only work if there are no more accessors in
139    /// place
140    fn acquire_array_lock(&self) -> Result<&Self, BimodalArrayError> {
141        match self
142            .0
143            .compare_exchange(0, ARRAY_LOCK_STATE, Ordering::Acquire, Ordering::Relaxed)
144        {
145            Ok(_) => Ok(self),
146            Err(_) => Err(BimodalArrayError::CouldNotAcquireArrayLock),
147        }
148    }
149}
150
151struct BimodalArrayInner<T> {
152    state: LockState,
153    owner_count: AtomicUsize,
154    len: usize,
155    capacity: usize,
156    data: NonNull<T>,
157}
158
159impl<T> BimodalArrayInner<T> {
160    fn new(len: usize, capacity: usize, data: NonNull<T>) -> Self {
161        let owner_count = len + 1;
162        Self {
163            state: LockState::new(),
164            owner_count: AtomicUsize::new(owner_count),
165            len,
166            capacity,
167            data,
168        }
169    }
170
171    fn to_raw_parts(&self) -> (*mut T, usize, usize) {
172        (self.data.as_ptr(), self.len, self.capacity)
173    }
174}
175
176fn inner_free<T>(inner: NonNull<BimodalArrayInner<T>>) {
177    let p = inner.as_ptr();
178    let prev = unsafe { (*p).owner_count.fetch_sub(1, Ordering::Release) };
179    if prev == 1 {
180        fence(Ordering::Acquire);
181
182        unsafe {
183            let (data_ptr, len, capacity) = (*p).to_raw_parts();
184            drop(Vec::from_raw_parts(data_ptr, len, capacity));
185            drop(Box::from_raw(p));
186        }
187    }
188}
189
190/// A handle granting access to a single element of a `bimodal_array`.
191///
192/// An `ElementHandle` represents the capability to acquire a lock for a
193/// specific element. The actual lock is held by the returned
194/// [`ElementGuard`], which provides mutable access to the element.
195///
196/// # Locking Semantics
197///
198/// - Calling [`lock`](Self::lock) attempts to acquire element-level access.
199/// - If successful, it returns an [`ElementGuard`] providing `&mut T`.
200/// - If an [`ArrayGuard`] is currently active, lock acquisition fails with
201///   [`BimodalArrayError::CouldNotAcquireElementLock`].
202///
203/// Multiple element guards for *different* elements may coexist.
204/// Element-level exclusivity is enforced through Rust's borrowing rules,
205/// as `lock` requires `&mut self`.
206pub struct ElementHandle<T> {
207    inner: NonNull<BimodalArrayInner<T>>,
208    data_ptr: NonNull<T>,
209}
210
211/// Should be able to Send when T is Send
212unsafe impl<T: Send> Send for ElementHandle<T> {}
213unsafe impl<T: Sync> Sync for ElementHandle<T> {}
214
215impl<T> ElementHandle<T> {
216    fn new(inner: NonNull<BimodalArrayInner<T>>, data_ptr: NonNull<T>) -> Self {
217        Self { inner, data_ptr }
218    }
219
220    /// Attempts to acquire mutable access to this element.
221    ///
222    /// Returns an [`ElementGuard`] on success. If an [`ArrayGuard`] is
223    /// currently active, this method returns
224    /// [`BimodalArrayError::CouldNotAcquireElementLock`].
225    ///
226    /// Lock acquisition is non-blocking.
227    pub fn lock<'a>(&'a mut self) -> Result<ElementGuard<'a, T>, BimodalArrayError> {
228        let inner = unsafe { &*self.inner.as_ptr() };
229        let state = inner.state.acquire_element_lock()?;
230        Ok(ElementGuard {
231            state,
232            data_ptr: self.data_ptr,
233        })
234    }
235}
236
237impl<T> Drop for ElementHandle<T> {
238    fn drop(&mut self) {
239        inner_free(self.inner);
240    }
241}
242
243/// A guard providing mutable access to a single element.
244///
245/// An `ElementGuard` is returned by [`ElementHandle::lock`]. While an
246/// `ElementGuard` is alive:
247///
248/// - No [`ArrayGuard<T>`] can be acquired.
249/// - No other `ElementGuard` for the same element can exist.
250///
251/// Guards for *different* elements may coexist concurrently.
252///
253/// The guard dereferences to `&mut T`, allowing direct mutation of the
254/// underlying element.
255///
256/// # RAII Semantics
257///
258/// The element-level lock is released automatically when this guard is
259/// dropped. This ensures that access is tied to the lifetime of the guard.
260///
261/// # Examples
262///
263/// ```no_run
264/// # use bimodal_array::bimodal_array;
265/// let data = vec![0];
266/// let (_, mut elements) = bimodal_array(data);
267///
268/// let mut guard = elements[0].lock().unwrap();
269/// *guard = 42;
270/// // Lock is released here when `guard` is dropped.
271/// ```
272pub struct ElementGuard<'a, T> {
273    state: &'a LockState,
274    data_ptr: NonNull<T>,
275}
276
277impl<'a, T> Deref for ElementGuard<'a, T> {
278    type Target = T;
279
280    fn deref(&self) -> &Self::Target {
281        unsafe { self.data_ptr.as_ref() }
282    }
283}
284
285impl<'a, T> AsRef<T> for ElementGuard<'a, T> {
286    fn as_ref(&self) -> &T {
287        self.deref()
288    }
289}
290
291impl<'a, T> AsMut<T> for ElementGuard<'a, T> {
292    fn as_mut(&mut self) -> &mut T {
293        self.deref_mut()
294    }
295}
296
297impl<'a, T> DerefMut for ElementGuard<'a, T> {
298    fn deref_mut(&mut self) -> &mut Self::Target {
299        unsafe { self.data_ptr.as_mut() }
300    }
301}
302
303impl<'a, T> Drop for ElementGuard<'a, T> {
304    fn drop(&mut self) {
305        self.state.release_element_lock();
306    }
307}
308
309/// A handle granting array-wide access to a `bimodal_array`.
310///
311/// An `ArrayHandle` represents the capability to acquire an exclusive lock for
312/// the entire collection. The actual lock is held by the returned
313/// [`ArrayGuard`], which provides mutable access to the full slice `&mut [T]`.
314///
315/// # Locking Semantics
316///
317/// - Calling [`lock`](Self::lock) attempts to acquire array-wide exclusive
318///   access.
319/// - If successful, it returns an [`ArrayGuard`] providing `&mut [T]`.
320/// - If one or more [`ElementGuard<T>`] values are currently active, lock
321///   acquisition fails with [`BimodalArrayError::CouldNotAcquireArrayLock`].
322///
323/// Lock acquisition is non-blocking.
324pub struct ArrayHandle<T> {
325    inner: NonNull<BimodalArrayInner<T>>,
326}
327
328unsafe impl<T: Send> Send for ArrayHandle<T> {}
329unsafe impl<T: Sync> Sync for ArrayHandle<T> {}
330
331impl<T> ArrayHandle<T> {
332    /// Attempts to acquire exclusive mutable access to the entire array.
333    ///
334    /// Returns an [`ArrayGuard`] on success. If any element guards are
335    /// currently active, this method returns
336    /// [`BimodalArrayError::CouldNotAcquireArrayLock`].
337    ///
338    /// Lock acquisition is non-blocking.
339    pub fn lock<'a>(&'a mut self) -> Result<ArrayGuard<'a, T>, BimodalArrayError> {
340        let p = self.inner.as_ptr();
341        let state = unsafe { (*p).state.acquire_array_lock()? };
342        let (data_ptr, len, _) = unsafe { (*p).to_raw_parts() };
343        let data_ptr = NonNull::new(data_ptr).unwrap();
344        Ok(ArrayGuard {
345            state,
346            data_ptr,
347            len,
348        })
349    }
350}
351
352impl<T> Drop for ArrayHandle<T> {
353    fn drop(&mut self) {
354        inner_free(self.inner);
355    }
356}
357
358/// A guard providing exclusive access to the entire array.
359///
360/// An `ArrayGuard` is returned by [`ArrayHandle::lock`]. While an
361/// `ArrayGuard` is alive:
362///
363/// - No [`ElementGuard<T>`] can be acquired.
364/// - No other `ArrayGuard` can exist.
365///
366/// The guard dereferences to `&mut [T]`, allowing full mutable access to
367/// the underlying slice.
368///
369/// # RAII Semantics
370///
371/// The array-wide lock is released automatically when this guard is dropped.
372/// This ensures that exclusivity is tied to the lifetime of the guard.
373///
374/// # Examples
375///
376/// ```no_run
377/// # use bimodal_array::bimodal_array;
378/// let data = vec![3, 1, 2];
379/// let (mut array_handle, _) = bimodal_array(data);
380///
381/// let mut guard = array_handle.lock().unwrap();
382/// guard.sort();
383/// // Lock is released here when `guard` is dropped.
384/// ```
385pub struct ArrayGuard<'a, T> {
386    state: &'a LockState,
387    data_ptr: NonNull<T>,
388    len: usize,
389}
390
391impl<'a, T> Deref for ArrayGuard<'a, T> {
392    type Target = [T];
393
394    fn deref(&self) -> &Self::Target {
395        unsafe { slice::from_raw_parts(self.data_ptr.as_ptr(), self.len) }
396    }
397}
398
399impl<'a, T> DerefMut for ArrayGuard<'a, T> {
400    fn deref_mut(&mut self) -> &mut Self::Target {
401        unsafe { slice::from_raw_parts_mut(self.data_ptr.as_ptr(), self.len) }
402    }
403}
404
405impl<'a, T> AsRef<[T]> for ArrayGuard<'a, T> {
406    fn as_ref(&self) -> &[T] {
407        self.deref()
408    }
409}
410
411impl<'a, T> AsMut<[T]> for ArrayGuard<'a, T> {
412    fn as_mut(&mut self) -> &mut [T] {
413        self.deref_mut()
414    }
415}
416
417impl<'a, T> Drop for ArrayGuard<'a, T> {
418    fn drop(&mut self) {
419        self.state.release_array_lock();
420    }
421}
422
423/// Constructs a bimodal array from an owned `Vec<T>`.
424///
425/// This is the fallible constructor. On success, it returns:
426///
427/// - an [`ArrayHandle<T>`], which can be used to acquire an [`ArrayGuard<T>`]
428///   for exclusive access to the full slice, and
429/// - a `Vec<ElementHandle<T>>`, containing one handle per element, each of
430///   which can be used to acquire an [`ElementGuard<T>`] for element-level
431///   access.
432///
433/// The returned handles enable a scatter–gather access pattern: independent
434/// workers can lock and update different elements concurrently, followed by an
435/// orchestration step that locks the entire array for consolidation.
436///
437/// # Errors
438///
439/// Returns [`BimodalArrayError::UnsupportedLength`] if the length of `data` is
440/// not supported by the internal state encoding (currently `usize::MAX`).
441///
442/// Returns [`BimodalArrayError::AllocationFailed`] if allocating fails.
443pub fn try_bimodal_array<T>(
444    data: Vec<T>,
445) -> Result<(ArrayHandle<T>, Vec<ElementHandle<T>>), BimodalArrayError> {
446    let len = data.len();
447    if len == usize::MAX {
448        return Err(BimodalArrayError::UnsupportedLength);
449    }
450    let mut element_handles = Vec::new();
451    element_handles
452        .try_reserve_exact(len)
453        .map_err(BimodalArrayError::AllocationFailed)?;
454    let (data_ptr, _len, capacity) = data.into_raw_parts();
455    let data_ptr = NonNull::new(data_ptr).unwrap();
456    let inner = Box::leak(Box::new(BimodalArrayInner::new(len, capacity, data_ptr))).into();
457    let array_handle = ArrayHandle { inner };
458    element_handles.extend((0..len).map(|idx| {
459        let elem_ptr = unsafe { data_ptr.add(idx) };
460        ElementHandle::new(inner, elem_ptr)
461    }));
462    Ok((array_handle, element_handles))
463}
464
465/// Constructs a bimodal array from an owned `Vec<T>`.
466///
467/// This is a convenience wrapper around [`try_bimodal_array`] that panics on
468/// error. Prefer [`try_bimodal_array`] if construction failure should be
469/// handled gracefully.
470///
471/// # Panics
472///
473/// Panics if [`try_bimodal_array`] returns an error (e.g. unsupported length or
474/// allocation failure).
475pub fn bimodal_array<T>(data: Vec<T>) -> (ArrayHandle<T>, Vec<ElementHandle<T>>) {
476    try_bimodal_array(data).unwrap_or_else(|e| panic!("bimodal_array construction failed: {e:?}"))
477}