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}