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> DerefMut for ElementGuard<'a, T> {
286    fn deref_mut(&mut self) -> &mut Self::Target {
287        unsafe { self.data_ptr.as_mut() }
288    }
289}
290
291impl<'a, T> Drop for ElementGuard<'a, T> {
292    fn drop(&mut self) {
293        self.state.release_element_lock();
294    }
295}
296
297/// A handle granting array-wide access to a `bimodal_array`.
298///
299/// An `ArrayHandle` represents the capability to acquire an exclusive lock for
300/// the entire collection. The actual lock is held by the returned
301/// [`ArrayGuard`], which provides mutable access to the full slice `&mut [T]`.
302///
303/// # Locking Semantics
304///
305/// - Calling [`lock`](Self::lock) attempts to acquire array-wide exclusive
306///   access.
307/// - If successful, it returns an [`ArrayGuard`] providing `&mut [T]`.
308/// - If one or more [`ElementGuard<T>`] values are currently active, lock
309///   acquisition fails with [`BimodalArrayError::CouldNotAcquireArrayLock`].
310///
311/// Lock acquisition is non-blocking.
312pub struct ArrayHandle<T> {
313    inner: NonNull<BimodalArrayInner<T>>,
314}
315
316unsafe impl<T: Send> Send for ArrayHandle<T> {}
317unsafe impl<T: Sync> Sync for ArrayHandle<T> {}
318
319impl<T> ArrayHandle<T> {
320    /// Attempts to acquire exclusive mutable access to the entire array.
321    ///
322    /// Returns an [`ArrayGuard`] on success. If any element guards are
323    /// currently active, this method returns
324    /// [`BimodalArrayError::CouldNotAcquireArrayLock`].
325    ///
326    /// Lock acquisition is non-blocking.
327    pub fn lock<'a>(&'a mut self) -> Result<ArrayGuard<'a, T>, BimodalArrayError> {
328        let p = self.inner.as_ptr();
329        let state = unsafe { (*p).state.acquire_array_lock()? };
330        let (data_ptr, len, _) = unsafe { (*p).to_raw_parts() };
331        let data_ptr = NonNull::new(data_ptr).unwrap();
332        Ok(ArrayGuard {
333            state,
334            data_ptr,
335            len,
336        })
337    }
338}
339
340impl<T> Drop for ArrayHandle<T> {
341    fn drop(&mut self) {
342        inner_free(self.inner);
343    }
344}
345
346/// A guard providing exclusive access to the entire array.
347///
348/// An `ArrayGuard` is returned by [`ArrayHandle::lock`]. While an
349/// `ArrayGuard` is alive:
350///
351/// - No [`ElementGuard<T>`] can be acquired.
352/// - No other `ArrayGuard` can exist.
353///
354/// The guard dereferences to `&mut [T]`, allowing full mutable access to
355/// the underlying slice.
356///
357/// # RAII Semantics
358///
359/// The array-wide lock is released automatically when this guard is dropped.
360/// This ensures that exclusivity is tied to the lifetime of the guard.
361///
362/// # Examples
363///
364/// ```no_run
365/// # use bimodal_array::bimodal_array;
366/// let data = vec![3, 1, 2];
367/// let (mut array_handle, _) = bimodal_array(data);
368///
369/// let mut guard = array_handle.lock().unwrap();
370/// guard.sort();
371/// // Lock is released here when `guard` is dropped.
372/// ```
373pub struct ArrayGuard<'a, T> {
374    state: &'a LockState,
375    data_ptr: NonNull<T>,
376    len: usize,
377}
378
379impl<'a, T> Deref for ArrayGuard<'a, T> {
380    type Target = [T];
381
382    fn deref(&self) -> &Self::Target {
383        unsafe { slice::from_raw_parts(self.data_ptr.as_ptr(), self.len) }
384    }
385}
386
387impl<'a, T> DerefMut for ArrayGuard<'a, T> {
388    fn deref_mut(&mut self) -> &mut Self::Target {
389        unsafe { slice::from_raw_parts_mut(self.data_ptr.as_ptr(), self.len) }
390    }
391}
392
393impl<'a, T> Drop for ArrayGuard<'a, T> {
394    fn drop(&mut self) {
395        self.state.release_array_lock();
396    }
397}
398
399/// Constructs a bimodal array from an owned `Vec<T>`.
400///
401/// This is the fallible constructor. On success, it returns:
402///
403/// - an [`ArrayHandle<T>`], which can be used to acquire an [`ArrayGuard<T>`]
404///   for exclusive access to the full slice, and
405/// - a `Vec<ElementHandle<T>>`, containing one handle per element, each of
406///   which can be used to acquire an [`ElementGuard<T>`] for element-level
407///   access.
408///
409/// The returned handles enable a scatter–gather access pattern: independent
410/// workers can lock and update different elements concurrently, followed by an
411/// orchestration step that locks the entire array for consolidation.
412///
413/// # Errors
414///
415/// Returns [`BimodalArrayError::UnsupportedLength`] if the length of `data` is
416/// not supported by the internal state encoding (currently `usize::MAX`).
417///
418/// Returns [`BimodalArrayError::AllocationFailed`] if allocating fails.
419pub fn try_bimodal_array<T>(
420    data: Vec<T>,
421) -> Result<(ArrayHandle<T>, Vec<ElementHandle<T>>), BimodalArrayError> {
422    let len = data.len();
423    if len == usize::MAX {
424        return Err(BimodalArrayError::UnsupportedLength);
425    }
426    let mut element_handles = Vec::new();
427    element_handles
428        .try_reserve_exact(len)
429        .map_err(BimodalArrayError::AllocationFailed)?;
430    let (data_ptr, _len, capacity) = data.into_raw_parts();
431    let data_ptr = NonNull::new(data_ptr).unwrap();
432    let inner = Box::leak(Box::new(BimodalArrayInner::new(len, capacity, data_ptr))).into();
433    let array_handle = ArrayHandle { inner };
434    element_handles.extend((0..len).map(|idx| {
435        let elem_ptr = unsafe { data_ptr.add(idx) };
436        ElementHandle::new(inner, elem_ptr)
437    }));
438    Ok((array_handle, element_handles))
439}
440
441/// Constructs a bimodal array from an owned `Vec<T>`.
442///
443/// This is a convenience wrapper around [`try_bimodal_array`] that panics on
444/// error. Prefer [`try_bimodal_array`] if construction failure should be
445/// handled gracefully.
446///
447/// # Panics
448///
449/// Panics if [`try_bimodal_array`] returns an error (e.g. unsupported length or
450/// allocation failure).
451pub fn bimodal_array<T>(data: Vec<T>) -> (ArrayHandle<T>, Vec<ElementHandle<T>>) {
452    try_bimodal_array(data).unwrap_or_else(|e| panic!("bimodal_array construction failed: {e:?}"))
453}