Skip to main content

starnix_core/task/
idr.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! An RCU-protected, lock-free integer ID radix tree (IDR).
6//!
7//! Maps 32-bit integer IDs to objects (`Arc<T>`), optimized for workloads with heavily
8//! contended concurrent reads and serialized mutations (such as PID tables, task registries,
9//! and descriptor tables).
10//!
11//! # Concurrency Model
12//!
13//! - **Readers (`lookup`, `iter`)**: Completely lock-free and wait-free. Traversal operates
14//!   under an [`RcuReadScope`], ensuring memory reclamation safety without blocking or
15//!   interfering with writers.
16//! - **Writers (`alloc`, `reserve_id`, `remove`)**: Mutations are serialized
17//!   via an [`IdrGuard`] acquired with [`Idr::lock`], while atomic pointer updates and memory
18//!   barriers allow concurrent readers to proceed in parallel without interruption.
19//!
20//! # Key Operations
21//!
22//! - [`Idr::lock`]: Acquires the writer lock, returning an [`IdrGuard`] for mutating operations.
23//! - [`Idr::lookup`]: Retrieves the object for a given ID without locking.
24//! - [`Idr::iter`]: Iterates over all active `(u32, &Arc<T>)` entries under an RCU scope.
25//! - [`Idr::max`]: Returns the maximal value allowed for allocation in this `Idr`.
26//! - [`Idr::set_max`]: Sets the maximal value allowed for allocation in this `Idr`.
27//! - [`IdrGuard::alloc`]: Allocates an ID using the configured allocation policy (linear from 0
28//!   or cyclic from cursor).
29//! - [`IdrGuard::reserve_id`]: Marks a specific ID as occupied without inserting an element.
30//! - [`IdrGuard::remove`]: Removes an item and restores slot availability.
31//!
32//! # Structural Architecture
33//!
34//! The tree is a 64-ary radix tree (consuming 6 bits per layer, up to 6 layers for 32-bit IDs):
35//! - Intermediate nodes (`layer > 0`) route down to child nodes.
36//! - Leaf nodes (`layer == 0`) hold concrete `Arc<T>` entries.
37//! - Each node maintains an atomic `free_bitmap` tracking capacity across its 64 sub-slots,
38//!   enabling $O(1)$ child selection and efficient subtree skipping during allocation.
39//! - The tree dynamically grows upwards in layers as ID requirements expand.
40
41use fuchsia_rcu::{RcuDroppable, RcuDroppableArc, RcuOptionBox};
42use smallvec::SmallVec;
43use starnix_rcu::RcuReadScope;
44use starnix_sync::{LockDepGuard, LockDepMutex, LockLevel};
45use std::sync::Arc;
46use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
47
48/// The number of bits consumed per layer of the tree structure.
49/// Chosen as 6 because 2^6 = 64, mapping perfectly to a 64-bit word (`u64`)
50/// used for atomic lock-free bitmaps per node.
51const BITS_PER_LEVEL: u32 = 6;
52/// The maximum number of children per node, directly derived from the bits per level.
53const NODE_CAPACITY: usize = 1 << BITS_PER_LEVEL;
54/// The bitmask used to extract the current layer's routing portion from a target ID.
55const LEVEL_MASK: u32 = (1 << BITS_PER_LEVEL) - 1;
56/// The theoretical maximum depth required to completely map a 32-bit integer space.
57const MAX_DEPTH: u32 = (32 + BITS_PER_LEVEL - 1) / BITS_PER_LEVEL;
58
59/// The allocation mode used by an [`Idr`].
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
61pub enum IdrAllocMode {
62    /// Allocates the lowest available ID starting from 0.
63    #[default]
64    Linear,
65    /// Allocates IDs sequentially starting from the previous cursor position,
66    /// wrapping around when hitting upper bounds.
67    Cyclic {
68        /// The minimum ID to allocate after wrapping around (or 0 if `None`).
69        min_after_wrap: Option<u32>,
70    },
71}
72
73/// A concurrent, lock-free radix tree mapped structure primarily employed to map 32-bit
74/// IDs to objects. Optimized for massively contended reads.
75pub struct Idr<T: RcuDroppable + Send + Sync + 'static, L: LockLevel> {
76    /// Serializes all mutating writes (allocations and removals) modifying the
77    /// tree structure. The protected `u32` value tracks the starting cursor for
78    /// cyclic allocations.
79    writer_lock: LockDepMutex<u32, L>,
80    /// The top-level entry point descending into the tree. Atomically replaced
81    /// whenever the structure grows upwards.
82    root: RcuDroppableArc<IdrNode<T>>,
83    /// The allocation mode determining whether IDs are allocated linearly or cyclically.
84    alloc_mode: IdrAllocMode,
85    /// The maximal value allowed for allocation in this `Idr`.
86    max: AtomicU32,
87}
88
89impl<T: RcuDroppable + Send + Sync + 'static, L: LockLevel> Default for Idr<T, L> {
90    fn default() -> Self {
91        Self::new(IdrAllocMode::default())
92    }
93}
94
95impl<T: RcuDroppable + Send + Sync + 'static, L: LockLevel> Idr<T, L> {
96    /// Creates a new `Idr` radix tree with the specified allocation mode.
97    pub fn new(alloc_mode: IdrAllocMode) -> Self {
98        Self {
99            writer_lock: LockDepMutex::new(0),
100            root: RcuDroppableArc::new(Arc::new(IdrNode::new(0))),
101            alloc_mode,
102            max: AtomicU32::new(u32::MAX),
103        }
104    }
105
106    /// Creates a new cyclic `Idr` radix tree with an optional minimum ID after wrapping.
107    pub fn new_cyclic(min_after_wrap: Option<u32>) -> Self {
108        Self::new(IdrAllocMode::Cyclic { min_after_wrap })
109    }
110
111    /// Returns the maximal value allowed for allocation in this `Idr`.
112    pub fn max(&self) -> u32 {
113        self.max.load(Ordering::Relaxed)
114    }
115
116    /// Sets the maximal value allowed for allocation in this `Idr`.
117    pub fn set_max(&self, max: u32) {
118        self.max.store(max, Ordering::Relaxed);
119    }
120
121    /// Acquires the writer lock, returning an [`IdrGuard`] that provides mutating operations.
122    pub fn lock(&self) -> IdrGuard<'_, T, L> {
123        IdrGuard { idr: self, cursor: self.writer_lock.lock() }
124    }
125
126    /// RCU protected lock-free lookup for readers
127    pub fn lookup(&self, id: u32, scope: &RcuReadScope) -> Option<Arc<T>> {
128        let mut current_node = self.root.as_ref(scope);
129
130        let capacity = current_node.capacity();
131        if (id as u64) >= capacity {
132            return None;
133        }
134
135        loop {
136            let index = current_node.index_for_id(id);
137
138            let entry = current_node.children[index].as_ref(scope);
139            match entry {
140                Some(IdrEntry::Leaf(arc)) => return Some(arc.clone()),
141                Some(IdrEntry::Node(next)) => {
142                    current_node = &**next;
143                }
144                None => return None,
145            }
146        }
147    }
148
149    /// Returns a lock-free RCU iterator over all entries
150    pub fn iter<'a>(&'a self, scope: &'a RcuReadScope) -> IdrIterator<'a, T> {
151        let mut stack = SmallVec::new();
152        let root = self.root.as_ref(scope);
153        stack.push((root, 0, 0));
154        IdrIterator { scope, stack }
155    }
156
157    /// Iteratively searches for the lowest available free ID >= `start_id` and <= `max_id`.
158    /// Populates `path` with the `(node, child_index)` sequence from root to leaf.
159    fn find_free_slot<'a>(
160        &self,
161        start_id: u32,
162        max_id: u32,
163        path: &mut SmallVec<[(&'a IdrNode<T>, usize); MAX_DEPTH as usize]>,
164        scope: &'a RcuReadScope,
165    ) -> Option<u32> {
166        if start_id > max_id {
167            return None;
168        }
169
170        // Traversal stack storing the path from root and unexplored sibling candidates per
171        // layer. Enables iterative backtracking without recursion or heap allocations.
172        struct StackEntry<'a, T: RcuDroppable + Send + Sync + 'static> {
173            node: &'a IdrNode<T>,
174            // Unexplored candidate slots at this level with open capacity.
175            free_bits: u64,
176            // True if slot selection at this level is restricted to indices >= start_id.
177            min_constrained: bool,
178            // True if slot selection at this level is restricted to indices <= max_id.
179            max_constrained: bool,
180            // The child slot index selected when descending to the next layer.
181            chosen_index: usize,
182        }
183        let root = self.root.as_ref(scope);
184
185        let mut stack = SmallVec::<[StackEntry<'a, T>; MAX_DEPTH as usize]>::new();
186
187        // When constrained by start_id, mask out slots below the cursor index.
188        let mut initial_free_bits = root.free_bitmap.load(Ordering::Relaxed);
189        let min_constrained = start_id > 0;
190        if min_constrained {
191            let cursor_index = root.index_for_id(start_id);
192            initial_free_bits &= !((1u64 << cursor_index) - 1);
193        }
194
195        // When constrained by max_id, mask out slots above the max index.
196        let max_constrained = (max_id as u64) < root.capacity() - 1;
197        if max_constrained {
198            let max_index = root.index_for_id(max_id);
199            let max_mask = if max_index >= 63 { !0 } else { (1u64 << (max_index + 1)) - 1 };
200            initial_free_bits &= max_mask;
201        }
202
203        // No candidate slots in [start_id, max_id] at the root level.
204        if initial_free_bits == 0 {
205            return None;
206        }
207
208        stack.push(StackEntry {
209            node: root,
210            free_bits: initial_free_bits,
211            min_constrained,
212            max_constrained,
213            chosen_index: 0,
214        });
215
216        while let Some(top) = stack.last_mut() {
217            // Backtrack to the parent layer when all candidate slots in this node are exhausted.
218            if top.free_bits == 0 {
219                stack.pop();
220                continue;
221            }
222
223            let index = top.free_bits.trailing_zeros() as usize;
224            top.free_bits &= !(1u64 << index);
225            top.chosen_index = index;
226
227            let node = top.node;
228
229            if node.layer == 0 {
230                let id = stack
231                    .iter()
232                    .fold(0u32, |acc, entry| acc | entry.node.id_for_index(entry.chosen_index));
233                path.extend(stack.into_iter().map(|entry| (entry.node, entry.chosen_index)));
234                return Some(id);
235            }
236
237            // Deeper layers stay constrained only if descending into the exact boundary slot.
238            let is_min_constrained = top.min_constrained && (index == node.index_for_id(start_id));
239            let is_max_constrained = top.max_constrained && (index == node.index_for_id(max_id));
240
241            let child = node.get_or_create_child(index, scope);
242            let mut child_free_bits = child.free_bitmap.load(Ordering::Relaxed);
243            if is_min_constrained {
244                let child_cursor = child.index_for_id(start_id);
245                child_free_bits &= !((1u64 << child_cursor) - 1);
246            }
247            if is_max_constrained {
248                let child_max = child.index_for_id(max_id);
249                let max_mask = if child_max >= 63 { !0 } else { (1u64 << (child_max + 1)) - 1 };
250                child_free_bits &= max_mask;
251            }
252
253            // Skip child subtree if constraints left no open slots.
254            if child_free_bits == 0 {
255                continue;
256            }
257
258            stack.push(StackEntry {
259                node: child,
260                free_bits: child_free_bits,
261                min_constrained: is_min_constrained,
262                max_constrained: is_max_constrained,
263                chosen_index: 0,
264            });
265        }
266
267        None
268    }
269
270    /// When a node transitions to having 0 free bits, mark it full in its parent
271    /// The caller MUST hold the `writer_lock`.
272    fn propagate_fullness(&self, path: &[(&IdrNode<T>, usize)]) {
273        for i in (0..path.len() - 1).rev() {
274            let (parent, parent_index) = &path[i];
275            if !parent.mark_allocated(*parent_index) {
276                // Parent still has other free slots. Stop propagating.
277                break;
278            }
279        }
280    }
281
282    /// When a node transitions from having 0 free bits to > 0, mark it free in its parent
283    /// The caller MUST hold the `writer_lock`.
284    fn propagate_availability(&self, path: &[(&IdrNode<T>, usize)]) {
285        for i in (0..path.len() - 1).rev() {
286            let (parent, parent_index) = &path[i];
287
288            if !parent.mark_freed(*parent_index) {
289                // Parent already had other free slots. Stop propagating.
290                break;
291            }
292        }
293    }
294
295    /// Grows the tree by adding a new layer on top of the root.
296    /// The caller MUST hold the `writer_lock`.
297    fn grow_tree_by_one_layer(&self, root_arc: &Arc<IdrNode<T>>) -> Option<Arc<IdrNode<T>>> {
298        let next_layer = root_arc.layer + 1;
299        if next_layer >= MAX_DEPTH {
300            return None;
301        }
302
303        let new_root = Arc::new(IdrNode::new(next_layer));
304        if root_arc.free_bitmap.load(Ordering::Relaxed) == 0 {
305            new_root.free_bitmap.fetch_and(!1, Ordering::Relaxed);
306        }
307        new_root.mark_present(0);
308        new_root.children[0].update(Some(IdrEntry::Node(root_arc.clone())));
309        self.root.update(new_root.clone());
310        Some(new_root)
311    }
312}
313
314/// An RAII guard representing exclusive writer access to an [`Idr`].
315///
316/// Holding this guard serializes mutations (allocations, reservations, removals)
317/// to the radix tree while allowing concurrent lock-free reads.
318pub struct IdrGuard<'a, T: RcuDroppable + Send + Sync + 'static, L: LockLevel> {
319    idr: &'a Idr<T, L>,
320    cursor: LockDepGuard<'a, u32>,
321}
322
323impl<'a, T: RcuDroppable + Send + Sync + 'static, L: LockLevel> std::ops::Deref
324    for IdrGuard<'a, T, L>
325{
326    type Target = Idr<T, L>;
327
328    fn deref(&self) -> &Self::Target {
329        self.idr
330    }
331}
332
333impl<'a, T: RcuDroppable + Send + Sync + 'static, L: LockLevel> IdrGuard<'a, T, L> {
334    /// Allocates the next available ID by calling a factory providing the newly
335    /// acquired ID.
336    ///
337    /// Depending on the `Idr` configuration, allocation will be either linear starting
338    /// from 0, or cyclic starting from the previous cursor position.
339    pub fn alloc<F>(&mut self, factory: F) -> Option<(u32, Arc<T>)>
340    where
341        F: FnOnce(u32) -> Arc<T>,
342    {
343        let max_id = self.idr.max.load(Ordering::Relaxed);
344
345        let (is_cyclic, wrap_min) = match self.idr.alloc_mode {
346            IdrAllocMode::Linear => (false, 0),
347            IdrAllocMode::Cyclic { min_after_wrap } => (true, min_after_wrap.unwrap_or(0)),
348        };
349
350        let (start_id, wrapped) = if is_cyclic {
351            if *self.cursor > max_id { (wrap_min, true) } else { (*self.cursor, false) }
352        } else {
353            (0, false)
354        };
355
356        if start_id > max_id {
357            return None;
358        }
359
360        let mut root_arc = self.idr.root.to_arc();
361
362        // Ensure the tree is large enough:
363        // 1. If the root free bitmap is 0, the entire current tree capacity is exhausted,
364        //    requiring a new root layer on top if capacity <= max_id.
365        // 2. If `start_id` exceeds current tree capacity (e.g. after wrapping or initial placement),
366        //    grow the tree until capacity covers `start_id` or until MAX_DEPTH is reached.
367        while (start_id as u64) >= root_arc.capacity()
368            || (root_arc.free_bitmap.load(Ordering::Relaxed) == 0
369                && root_arc.capacity() <= (max_id as u64))
370        {
371            if let Some(new_root) = self.idr.grow_tree_by_one_layer(&root_arc) {
372                root_arc = new_root;
373            } else {
374                // Tree reached maximum allowable depth and cannot grow further.
375                return None;
376            }
377        }
378
379        let scope = RcuReadScope::new();
380        let mut path = SmallVec::<[(&IdrNode<T>, usize); MAX_DEPTH as usize]>::new();
381        // First attempt: search for a free slot >= `start_id` and <= `max_id`.
382        let id_opt = self.idr.find_free_slot(start_id, max_id, &mut path, &scope).or_else(|| {
383            // If cyclic allocation failed to find a slot between `start_id`
384            // and `max_id`, wrap around to search from `wrap_min` up to `max_id`.
385            if is_cyclic && !wrapped && start_id > wrap_min && wrap_min <= max_id {
386                path.clear();
387                self.idr.find_free_slot(wrap_min, max_id, &mut path, &scope)
388            } else {
389                None
390            }
391        });
392
393        let id = id_opt?;
394        let (leaf_node, leaf_index) = path.last().expect("path should not be empty");
395        let leaf_index = *leaf_index;
396        let item = factory(id);
397
398        // Install the newly constructed leaf item at the target slot.
399        leaf_node.children[leaf_index].update(Some(IdrEntry::Leaf(item.clone())));
400        leaf_node.mark_present(leaf_index);
401
402        // Mark the leaf slot as allocated in its free bitmap. If this clears the final free bit
403        // in the leaf node, propagate the full state up the ancestor chain via `propagate_fullness`.
404        if leaf_node.mark_allocated(leaf_index) {
405            self.idr.propagate_fullness(&path);
406        }
407
408        // For cyclic allocations, advance the cursor to `id + 1`, wrapping to `wrap_min` when
409        // exceeding `max_id` or on u32 overflow.
410        if is_cyclic {
411            let next_cursor = id.wrapping_add(1);
412            *self.cursor = if next_cursor > max_id || (next_cursor == 0 && wrap_min > 0) {
413                wrap_min
414            } else {
415                next_cursor
416            };
417        }
418        Some((id, item))
419    }
420
421    /// Marks a specific ID as unavailable so the allocator will never return it.
422    /// Does not populate the tree with an item.
423    pub fn reserve_id(&mut self, id: u32) {
424        let mut root_arc = self.idr.root.to_arc();
425
426        loop {
427            let capacity = root_arc.capacity();
428            if (id as u64) < capacity {
429                break;
430            }
431            if let Some(new_root) = self.idr.grow_tree_by_one_layer(&root_arc) {
432                root_arc = new_root;
433            } else {
434                return; // Exceeded max depth
435            }
436        }
437
438        let scope = RcuReadScope::new();
439        let mut current_node = root_arc.as_ref();
440        let mut path = SmallVec::<[(&IdrNode<T>, usize); MAX_DEPTH as usize]>::new();
441
442        loop {
443            let index = current_node.index_for_id(id);
444
445            path.push((current_node, index));
446
447            // Reached a leaf node. Claim the slot.
448            if current_node.layer == 0 {
449                // If it was previously free, and this clears the last free bit, propagate fullness
450                if current_node.mark_allocated(index) {
451                    self.idr.propagate_fullness(&path);
452                }
453                return;
454            }
455
456            // Descend to the next layer.
457            current_node = current_node.get_or_create_child(index, &scope);
458        }
459    }
460
461    /// Removes an item by ID.
462    pub fn remove(&mut self, id: u32) {
463        let scope = RcuReadScope::new();
464
465        let mut current_node = self.idr.root.as_ref(&scope);
466
467        if (id as u64) >= current_node.capacity() {
468            return;
469        }
470
471        let mut path = SmallVec::<[(&IdrNode<T>, usize); MAX_DEPTH as usize]>::new();
472
473        loop {
474            let index = current_node.index_for_id(id);
475
476            path.push((current_node, index));
477
478            let Some(child) = current_node.children[index].as_ref(&scope) else {
479                // Nothing to remove
480                return;
481            };
482
483            if current_node.layer == 0 {
484                current_node.children[index].update(None);
485                current_node.mark_absent(index);
486                if current_node.mark_freed(index) {
487                    self.idr.propagate_availability(&path);
488                }
489                return;
490            } else {
491                current_node = match child {
492                    IdrEntry::Node(n) => &**n,
493                    _ => unreachable!("Tree corruption: expected a Node entry here"),
494                };
495            }
496        }
497    }
498
499    /// Returns the current cursor position for cyclic allocations.
500    #[cfg(test)]
501    fn cursor(&self) -> u32 {
502        *self.cursor
503    }
504
505    /// Sets the cursor position for cyclic allocations.
506    #[cfg(test)]
507    fn set_cursor(&mut self, cursor: u32) {
508        *self.cursor = cursor;
509    }
510}
511
512/// A lock-free iterator traversing the allocated elements inside the radix tree
513/// under an automated RCU read scope, operating without acquiring any thread locks.
514pub struct IdrIterator<'a, T: RcuDroppable + Send + Sync + 'static> {
515    /// The ambient read scope keeping the traversed tree nodes pinned in memory.
516    scope: &'a RcuReadScope,
517    /// The traversal stack keeping track of the current path, the next index, and
518    /// the accumulated ID prefix.
519    stack: SmallVec<[(&'a IdrNode<T>, usize, u32); MAX_DEPTH as usize]>,
520}
521
522impl<'a, T: RcuDroppable + Send + Sync + 'static> Iterator for IdrIterator<'a, T> {
523    type Item = (u32, &'a Arc<T>);
524
525    fn next(&mut self) -> Option<Self::Item> {
526        while let Some((node, index, id_base)) = self.stack.pop() {
527            let presence = node.presence_bitmap.load(Ordering::Relaxed);
528
529            let mask = if index >= NODE_CAPACITY { 0 } else { !((1u64 << index) - 1) };
530            let remaining = presence & mask;
531
532            if remaining == 0 {
533                continue;
534            }
535
536            let next_bit = remaining.trailing_zeros() as usize;
537
538            self.stack.push((node, next_bit + 1, id_base));
539
540            let entry_opt = node.children[next_bit].as_ref(self.scope);
541            if let Some(entry) = entry_opt {
542                let child_id = id_base | node.id_for_index(next_bit);
543
544                match entry {
545                    IdrEntry::Node(child_arc) => {
546                        self.stack.push((child_arc.as_ref(), 0, child_id));
547                    }
548                    IdrEntry::Leaf(arc) => {
549                        return Some((child_id, arc));
550                    }
551                }
552            }
553        }
554        None
555    }
556}
557
558/// Represents a single slot within an `IdrNode`'s capability array.
559#[derive(Debug)]
560enum IdrEntry<T: RcuDroppable + Send + Sync + 'static> {
561    /// An intermediate branch pointing to the next layer down the tree structure.
562    Node(Arc<IdrNode<T>>),
563    /// A concrete element residing at the bottom layer.
564    Leaf(Arc<T>),
565}
566
567// SAFETY: All variants contain only types that are `RcuDroppable` (`Arc<IdrNode<T>>` and `Arc<T>`).
568// A manual implementation is necessary because deriving `RcuDroppable` on both types triggers a
569// recursive evaluation overflow (Rust issue #26925) due to mutual recursion with `IdrNode`.
570unsafe impl<T: RcuDroppable + Send + Sync + 'static> RcuDroppable for IdrEntry<T> {}
571
572#[derive(Debug, RcuDroppable)]
573struct IdrNode<T: RcuDroppable + Send + Sync + 'static> {
574    /// The structural height of this node in the tree. Leaf nodes holding concrete
575    /// elements sit at layer 0. Intermediate branches exist at layers > 0.
576    layer: u32,
577
578    /// Tracks allocation capacity across the 64 sub-slots. A `1` bit indicates the
579    /// corresponding slot (or its sub-branch) still has open IDs available. A `0`
580    /// bit signifies the branch or leaf is at 100% capacity (or reserved).
581    free_bitmap: AtomicU64,
582
583    /// Tracks structural instantiation of the 64 sub-slots. A `1` bit indicates an
584    /// intermediate branch node has physically been allocated into memory. Used strictly
585    /// by the lock-free iterator to gracefully skip over uninstantiated memory gaps.
586    presence_bitmap: AtomicU64,
587
588    /// The contiguous memory array branching off this node, storing inner `IdrNode`
589    /// branches (when layer > 0) or underlying `Arc<T>` leaf items (when layer == 0).
590    children: [RcuOptionBox<IdrEntry<T>>; NODE_CAPACITY],
591}
592
593impl<T: RcuDroppable + Send + Sync + 'static> Default for IdrNode<T> {
594    fn default() -> Self {
595        Self::new(0)
596    }
597}
598
599impl<T: RcuDroppable + Send + Sync + 'static> IdrNode<T> {
600    fn new(layer: u32) -> Self {
601        let children = std::array::from_fn(|_| RcuOptionBox::new(None));
602        Self {
603            layer,
604            free_bitmap: AtomicU64::new(!0), // all 1s means all free
605            presence_bitmap: AtomicU64::new(0),
606            children,
607        }
608    }
609
610    /// Computes the total capacity underneath this specific node.
611    /// Layer 0 nodes possess a capacity of exactly 64. Each higher layer multiplies it by 64.
612    #[inline]
613    fn capacity(&self) -> u64 {
614        1u64 << (BITS_PER_LEVEL * (self.layer + 1))
615    }
616
617    /// Compute the index of the child of this node that contains `id`.
618    #[inline]
619    fn index_for_id(&self, id: u32) -> usize {
620        ((id >> (self.layer * BITS_PER_LEVEL)) & LEVEL_MASK) as usize
621    }
622
623    /// Compute the contribution to the final value of the `index` child of this node.
624    /// Utilized by the iterator to reconstruct numeric IDs.
625    #[inline]
626    fn id_for_index(&self, index: usize) -> u32 {
627        (index as u32) << (self.layer * BITS_PER_LEVEL)
628    }
629
630    /// Clears the `index` free bit.
631    /// Returns true if this transition caused the node to become completely full (0).
632    #[inline]
633    fn mark_allocated(&self, index: usize) -> bool {
634        let old_free = self.free_bitmap.fetch_and(!(1 << index), Ordering::Relaxed);
635        old_free == (1 << index)
636    }
637
638    /// Adds the `index` free bit.
639    /// Returns true if this transition caused the node to transition from completely
640    /// full (0) to having capacity.
641    #[inline]
642    fn mark_freed(&self, index: usize) -> bool {
643        let old_free = self.free_bitmap.fetch_or(1 << index, Ordering::Relaxed);
644        old_free == 0
645    }
646
647    /// Marks the `index` slot as populated with a branch or leaf.
648    #[inline]
649    fn mark_present(&self, index: usize) {
650        self.presence_bitmap.fetch_or(1 << index, Ordering::Relaxed);
651    }
652
653    /// Marks the `index` slot as physically empty/removed.
654    #[inline]
655    fn mark_absent(&self, index: usize) {
656        self.presence_bitmap.fetch_and(!(1 << index), Ordering::Relaxed);
657    }
658
659    /// Descends into the specific child branch index. If the branch is currently
660    /// entirely empty and uninstantiated, it constructs the next layer.
661    /// Must never be called on layer 0.
662    fn get_or_create_child<'a>(&'a self, index: usize, scope: &'a RcuReadScope) -> &'a IdrNode<T> {
663        debug_assert!(self.layer > 0);
664        if let Some(IdrEntry::Node(n)) = self.children[index].as_ref(scope) {
665            return &**n;
666        }
667
668        let new_node = Arc::new(IdrNode::new(self.layer - 1));
669        self.children[index].update(Some(IdrEntry::Node(new_node)));
670        self.mark_present(index);
671        match self.children[index].as_ref(scope).unwrap() {
672            IdrEntry::Node(n) => &**n,
673            _ => unreachable!("Tree corruption: expected a Node entry here"),
674        }
675    }
676}
677
678#[cfg(test)]
679mod tests {
680    use super::*;
681    use starnix_sync::lock_ordering;
682    use std::sync::Arc;
683    use std::sync::atomic::{AtomicBool, Ordering};
684
685    lock_ordering! {
686        Terminal(TestIdrLock),
687    }
688
689    type Idr<T> = super::Idr<T, TestIdrLock>;
690
691    #[derive(RcuDroppable)]
692    struct MockItem {
693        value: u32,
694    }
695
696    #[fuchsia::test]
697    fn test_basic_alloc_lookup() {
698        let idr = Idr::default();
699        assert_eq!(idr.alloc_mode, IdrAllocMode::Linear);
700
701        let (id1, _item1) = idr.lock().alloc(|id| Arc::new(MockItem { value: id * 10 })).unwrap();
702        assert_eq!(id1, 0);
703
704        let (id2, _item2) = idr.lock().alloc(|id| Arc::new(MockItem { value: id * 10 })).unwrap();
705        assert_eq!(id2, 1);
706
707        let scope = RcuReadScope::new();
708        let lookup1 = idr.lookup(0, &scope).unwrap();
709        assert_eq!(lookup1.value, 0);
710
711        let lookup2 = idr.lookup(1, &scope).unwrap();
712        assert_eq!(lookup2.value, 10);
713
714        idr.lock().remove(0);
715        assert!(idr.lookup(0, &scope).is_none());
716
717        // Second item remains completely untouched and safely addressable
718        let lookup_still_there = idr.lookup(1, &scope).unwrap();
719        assert_eq!(lookup_still_there.value, 10);
720    }
721
722    #[fuchsia::test]
723    fn test_tree_growth() {
724        let idr = Idr::default();
725        let mut _items = Vec::new();
726
727        // NODE_CAPACITY is natively 64. Allocating 150 structurally guarantees forcing the tree to
728        // grow at least once.
729        for i in 0..150 {
730            let (id, item) = idr.lock().alloc(|id| Arc::new(MockItem { value: id })).unwrap();
731            _items.push(item);
732            assert_eq!(id, i);
733        }
734
735        let scope = RcuReadScope::new();
736        for i in 0..150 {
737            let item = idr.lookup(i, &scope).unwrap();
738            assert_eq!(item.value, i);
739        }
740    }
741
742    #[fuchsia::test]
743    fn test_alloc_cyclic() {
744        let idr = Idr::new_cyclic(None);
745        let mut _items = Vec::new();
746
747        for i in 0..30 {
748            let (id, item) = idr.lock().alloc(|id| Arc::new(MockItem { value: id })).unwrap();
749            _items.push(item);
750            assert_eq!(id, i);
751        }
752
753        // Manually bump the cursor.
754        idr.lock().set_cursor(100);
755
756        // Allocation formally continues from new cursor.
757        let (id, item1) = idr.lock().alloc(|id| Arc::new(MockItem { value: id })).unwrap();
758        _items.push(item1);
759        assert_eq!(id, 100);
760
761        let (id, item2) = idr.lock().alloc(|id| Arc::new(MockItem { value: id })).unwrap();
762        _items.push(item2);
763        assert_eq!(id, 101);
764
765        let scope = RcuReadScope::new();
766        assert!(idr.lookup(30, &scope).is_none());
767        assert_eq!(idr.lookup(100, &scope).unwrap().value, 100);
768    }
769
770    #[fuchsia::test]
771    fn test_alloc_cyclic_start_exceeds_capacity() {
772        let idr = Idr::new_cyclic(None);
773        let mut _items = Vec::new();
774
775        // Immediately bump start_id above the initial layer 0 capacity (64)
776        idr.lock().set_cursor(256);
777
778        let (id, item) = idr.lock().alloc(|id| Arc::new(MockItem { value: id })).unwrap();
779        _items.push(item);
780
781        // It should have assigned exactly 256, successfully growing the tree.
782        assert_eq!(id, 256);
783    }
784
785    #[fuchsia::test]
786    fn test_reserve_id() {
787        let idr = Idr::new_cyclic(None);
788        let mut _items = Vec::new();
789
790        // Reserve an ID within initial layer
791        idr.lock().reserve_id(10);
792
793        let (id1, item1) = idr.lock().alloc(|id| Arc::new(MockItem { value: id })).unwrap();
794        _items.push(item1);
795        assert_eq!(id1, 0);
796
797        // Reserve an ID requiring tree growth
798        idr.lock().reserve_id(200);
799
800        // Fill up to 10
801        let mut allocated_10 = false;
802        for _ in 1..15 {
803            let (id, item) = idr.lock().alloc(|id| Arc::new(MockItem { value: id })).unwrap();
804            _items.push(item);
805            if id == 10 {
806                allocated_10 = true;
807            }
808        }
809        assert!(!allocated_10, "ID 10 was allocated despite being reserved");
810
811        // Verify ID 200 is skipped when allocating near it
812        idr.lock().set_cursor(199);
813
814        let mut allocated_200 = false;
815        for _ in 0..5 {
816            let (id, item) = idr.lock().alloc(|id| Arc::new(MockItem { value: id })).unwrap();
817            _items.push(item);
818            if id == 200 {
819                allocated_200 = true;
820            }
821        }
822        assert!(!allocated_200, "ID 200 was allocated despite being reserved");
823
824        let scope = RcuReadScope::new();
825        // Lookup of reserved IDs should return None
826        assert!(idr.lookup(10, &scope).is_none());
827        assert!(idr.lookup(200, &scope).is_none());
828
829        // Remove should not panic or corrupt it
830        idr.lock().remove(10);
831        idr.lock().remove(200);
832        assert!(idr.lookup(10, &scope).is_none());
833    }
834
835    #[fuchsia::test]
836    fn test_iter_and_remove() {
837        let idr = Idr::default();
838        let mut _items = Vec::new();
839
840        // Assigned cleanly to 0
841        _items.push(idr.lock().alloc(|_| Arc::new(MockItem { value: 10 })).unwrap().1);
842        // Assigned cleanly to 1
843        _items.push(idr.lock().alloc(|_| Arc::new(MockItem { value: 20 })).unwrap().1);
844        // Assigned cleanly to 2
845        _items.push(idr.lock().alloc(|_| Arc::new(MockItem { value: 30 })).unwrap().1);
846
847        // Eliminate middle ID freeing slot 1.
848        idr.lock().remove(1);
849
850        let scope = RcuReadScope::new();
851        let mut iter = idr.iter(&scope);
852
853        let next_a = iter.next();
854        let (id_a, item_a) = next_a.unwrap();
855        assert_eq!(id_a, 0);
856        assert_eq!(item_a.value, 10);
857
858        let (id_b, item_b) = iter.next().unwrap();
859        assert_eq!(id_b, 2);
860        assert_eq!(item_b.value, 30);
861
862        assert!(iter.next().is_none());
863    }
864
865    #[fuchsia::test]
866    fn test_iter_minimal() {
867        let idr = Idr::default();
868        let mut _items = Vec::new();
869        _items.push(idr.lock().alloc(|value| Arc::new(MockItem { value })).unwrap().1);
870        let scope = RcuReadScope::new();
871        let mut iter = idr.iter(&scope);
872        let next_a = iter.next();
873        assert!(next_a.is_some(), "iter.next() returned None!");
874    }
875
876    #[fuchsia::test]
877    fn test_alloc_cyclic_with_gaps() {
878        let idr = Idr::new_cyclic(None);
879        let mut _items = Vec::new();
880
881        // Allocate 100 items (0..99) across layer 0 and layer 1.
882        for i in 0..100 {
883            let (id, item) = idr.lock().alloc(|id| Arc::new(MockItem { value: id })).unwrap();
884            assert_eq!(id, i);
885            _items.push(item);
886        }
887
888        // Create holes in child 0 (0..63) below 50, but keep 50..63 allocated.
889        for id in 40..50 {
890            idr.lock().remove(id);
891        }
892
893        // Advance cursor to 50.
894        idr.lock().set_cursor(50);
895
896        // Cyclic allocation should find next free slot >= 50, which is slot 100 in child 1,
897        // rather than prematurely wrapping to 40 in child 0.
898        let (id_100, item_100) = idr.lock().alloc(|id| Arc::new(MockItem { value: id })).unwrap();
899        assert_eq!(id_100, 100);
900        _items.push(item_100);
901
902        // Subsequent allocations should continue forward monotonically across child boundaries (101..130).
903        for expected in 101..130 {
904            let (id, item) = idr.lock().alloc(|id| Arc::new(MockItem { value: id })).unwrap();
905            assert_eq!(id, expected);
906            _items.push(item);
907        }
908    }
909
910    #[fuchsia::test]
911    fn test_concurrent_readers_and_writers() {
912        let idr = Arc::new(Idr::new_cyclic(None));
913        let running = Arc::new(AtomicBool::new(true));
914
915        // Pre-populate some items
916        for _i in 0..50 {
917            idr.lock().alloc(|id| Arc::new(MockItem { value: id })).unwrap();
918        }
919
920        let mut reader_handles = Vec::new();
921        for _ in 0..4 {
922            let idr_clone = Arc::clone(&idr);
923            let running_clone = Arc::clone(&running);
924            reader_handles.push(std::thread::spawn(move || {
925                while running_clone.load(Ordering::Relaxed) {
926                    let scope = RcuReadScope::new();
927                    // Random lookups
928                    for id in 0..100 {
929                        if let Some(item) = idr_clone.lookup(id, &scope) {
930                            assert_eq!(item.value, id);
931                        }
932                    }
933
934                    // Iterator traversal
935                    let iter = idr_clone.iter(&scope);
936                    for (id, item) in iter {
937                        assert_eq!(item.value, id);
938                    }
939                }
940            }));
941        }
942
943        let running_clone = Arc::clone(&running);
944        let rcu_advancer = std::thread::spawn(move || {
945            while running_clone.load(Ordering::Relaxed) {
946                fuchsia_rcu::rcu_run_callbacks();
947                std::thread::sleep(std::time::Duration::from_millis(1));
948            }
949        });
950
951        // Writer thread performs allocations and deletions
952        let idr_clone = Arc::clone(&idr);
953        let writer_handle = std::thread::spawn(move || {
954            for _ in 0..200 {
955                let (id, _) =
956                    idr_clone.lock().alloc(|id| Arc::new(MockItem { value: id })).unwrap();
957                if id > 50 && id % 3 == 0 {
958                    idr_clone.lock().remove(id);
959                }
960            }
961        });
962
963        writer_handle.join().unwrap();
964        running.store(false, Ordering::Relaxed);
965        rcu_advancer.join().unwrap();
966
967        for handle in reader_handles {
968            handle.join().unwrap();
969        }
970    }
971
972    #[fuchsia::test]
973    fn test_u32_max_wrap_around() {
974        let idr = Idr::<MockItem>::new_cyclic(None);
975        idr.lock().set_cursor(u32::MAX);
976
977        // First allocation is at u32::MAX
978        let (id1, _item1) = idr.lock().alloc(|id| Arc::new(MockItem { value: id })).unwrap();
979        assert_eq!(id1, u32::MAX);
980
981        // Next allocation correctly wraps around to 0
982        let (id2, _item2) = idr.lock().alloc(|id| Arc::new(MockItem { value: id })).unwrap();
983        assert_eq!(id2, 0);
984
985        // Validating they are safely addressable
986        let scope = RcuReadScope::new();
987        assert_eq!(idr.lookup(u32::MAX, &scope).unwrap().value, u32::MAX);
988        assert_eq!(idr.lookup(0, &scope).unwrap().value, 0);
989    }
990
991    #[fuchsia::test]
992    fn test_overflow_bug_at_u32_max() {
993        let idr = Idr::<MockItem>::new_cyclic(None);
994
995        // Reserve 0 so that if the allocator wraps around safely,
996        // it assigns 1 (acting as a dual check).
997        idr.lock().reserve_id(0);
998
999        // Reserving `u32::MAX` is required to trigger this bug. If `u32::MAX` is free, an
1000        // allocation starting at `u32::MAX` simply takes it, and the cursor wraps cleanly to `0`.
1001        // By making it occupied, `find_free_slot` descends to the bottom of
1002        // subtree 3, discovers there is no space left at or above the cursor,
1003        // and backtracks all the way up to layer 5.
1004        // If layer 5 is not properly constrained by the maximal value, this backtracking
1005        // causes the allocator to erroneously spill over into the invalid index 4.
1006        idr.lock().reserve_id(u32::MAX);
1007
1008        idr.lock().set_cursor(u32::MAX);
1009
1010        let (id, _) = idr.lock().alloc(|id| Arc::new(MockItem { value: id })).unwrap();
1011        assert_eq!(id, 1);
1012
1013        let scope = RcuReadScope::new();
1014        assert!(idr.lookup(1, &scope).is_some());
1015    }
1016
1017    #[fuchsia::test]
1018    fn test_lock_held_across_operations() {
1019        let idr = Idr::<MockItem>::default();
1020
1021        // Acquire the lock outside the class and perform multiple operations while holding it.
1022        let mut guard = idr.lock();
1023        assert_eq!(guard.cursor(), 0);
1024
1025        guard.reserve_id(0);
1026        let (id1, item1) = guard.alloc(|value| Arc::new(MockItem { value })).unwrap();
1027        assert_eq!(id1, 1);
1028        assert_eq!(item1.value, 1);
1029
1030        let (id2, item2) = guard.alloc(|value| Arc::new(MockItem { value })).unwrap();
1031        assert_eq!(id2, 2);
1032        assert_eq!(item2.value, 2);
1033
1034        guard.remove(1);
1035
1036        // The slot for ID 1 is now available again.
1037        let (id3, item3) = guard.alloc(|value| Arc::new(MockItem { value })).unwrap();
1038        assert_eq!(id3, 1);
1039        assert_eq!(item3.value, 1);
1040
1041        // Readers can still access items through Deref on the lock.
1042        let scope = RcuReadScope::new();
1043        assert_eq!(guard.lookup(1, &scope).unwrap().value, 1);
1044        assert_eq!(guard.lookup(2, &scope).unwrap().value, 2);
1045
1046        // Allocating the next lowest available slot yields 3.
1047        let (id4, _) = guard.alloc(|value| Arc::new(MockItem { value })).unwrap();
1048        assert_eq!(id4, 3);
1049
1050        guard.set_cursor(50);
1051        assert_eq!(guard.cursor(), 50);
1052    }
1053
1054    #[fuchsia::test]
1055    fn test_alloc_cyclic_min_after_wrap() {
1056        let idr = Idr::<MockItem>::new_cyclic(Some(2));
1057        assert_eq!(idr.alloc_mode, IdrAllocMode::Cyclic { min_after_wrap: Some(2) });
1058        let mut guard = idr.lock();
1059
1060        // Allocate slots 0, 1, 2, 3, 4.
1061        let (id0, _) = guard.alloc(|value| Arc::new(MockItem { value })).unwrap();
1062        assert_eq!(id0, 0);
1063        let (id1, _) = guard.alloc(|value| Arc::new(MockItem { value })).unwrap();
1064        assert_eq!(id1, 1);
1065        let (id2, _) = guard.alloc(|value| Arc::new(MockItem { value })).unwrap();
1066        assert_eq!(id2, 2);
1067        let (id3, _) = guard.alloc(|value| Arc::new(MockItem { value })).unwrap();
1068        assert_eq!(id3, 3);
1069        let (id4, _) = guard.alloc(|value| Arc::new(MockItem { value })).unwrap();
1070        assert_eq!(id4, 4);
1071
1072        // Free 0 and 1 so slots 0 and 1 become free in the tree.
1073        guard.remove(0);
1074        guard.remove(1);
1075
1076        // Reserve slots 10..64 within initial layer 0 (capacity 64).
1077        for i in 10..64 {
1078            guard.reserve_id(i);
1079        }
1080
1081        // Advance cursor to 10.
1082        guard.set_cursor(10);
1083
1084        // Since slots 10..64 are reserved and cursor is 10, searching >= 10 fails.
1085        // It wraps around. With min_after_wrap = Some(2), it must skip free slots 0 and 1,
1086        // and find the next free slot >= 2, which is slot 5.
1087        let (id5, _) = guard.alloc(|value| Arc::new(MockItem { value })).unwrap();
1088        assert_eq!(id5, 5);
1089
1090        // Allocate remaining slots up to 9.
1091        for expected in 6..10 {
1092            let (id, _) = guard.alloc(|value| Arc::new(MockItem { value })).unwrap();
1093            assert_eq!(id, expected);
1094        }
1095
1096        // Now slots 2..64 are all occupied (allocated or reserved).
1097        // Slots 0 and 1 remain free.
1098        // Attempting another allocation with min_after_wrap = Some(2) must return None,
1099        // because all slots >= 2 are full.
1100        assert!(guard.alloc(|value| Arc::new(MockItem { value })).is_none());
1101    }
1102
1103    #[fuchsia::test]
1104    fn test_alloc_cyclic_wrap_without_min_after_wrap() {
1105        // For an IDR configured without min_after_wrap, wrapping allows allocating slots 0 and 1.
1106        let idr = Idr::<MockItem>::new_cyclic(None);
1107        let mut guard = idr.lock();
1108        let (z0, _) = guard.alloc(|value| Arc::new(MockItem { value })).unwrap();
1109        assert_eq!(z0, 0);
1110        let (z1, _) = guard.alloc(|value| Arc::new(MockItem { value })).unwrap();
1111        assert_eq!(z1, 1);
1112        guard.remove(0);
1113        guard.remove(1);
1114        guard.set_cursor(10);
1115        for i in 10..64 {
1116            guard.reserve_id(i);
1117        }
1118        let (id0_after, _) = guard.alloc(|value| Arc::new(MockItem { value })).unwrap();
1119        assert_eq!(id0_after, 0);
1120
1121        let (id1_after, _) = guard.alloc(|value| Arc::new(MockItem { value })).unwrap();
1122        assert_eq!(id1_after, 1);
1123    }
1124
1125    #[fuchsia::test]
1126    fn test_alloc_cyclic_min_after_wrap_at_u32_max() {
1127        let idr = Idr::<MockItem>::new_cyclic(Some(2));
1128        let mut guard = idr.lock();
1129
1130        // Slots 0, 1, 2 are all free. Set cursor to u32::MAX.
1131        guard.set_cursor(u32::MAX);
1132
1133        // Allocate u32::MAX.
1134        let (id_max, _) = guard.alloc(|value| Arc::new(MockItem { value })).unwrap();
1135        assert_eq!(id_max, u32::MAX);
1136
1137        // Cursor should wrap to min_after_wrap (2), not 0.
1138        assert_eq!(guard.cursor(), 2);
1139
1140        // Next allocation starts at cursor (2), skipping slots 0 and 1.
1141        let (id2, _) = guard.alloc(|value| Arc::new(MockItem { value })).unwrap();
1142        assert_eq!(id2, 2);
1143        assert_eq!(guard.cursor(), 3);
1144
1145        let scope = RcuReadScope::new();
1146        assert!(guard.lookup(0, &scope).is_none());
1147        assert!(guard.lookup(1, &scope).is_none());
1148        assert!(guard.lookup(2, &scope).is_some());
1149        assert!(guard.lookup(u32::MAX, &scope).is_some());
1150    }
1151
1152    #[fuchsia::test]
1153    fn test_linear_alloc_max() {
1154        let idr = Idr::<MockItem>::default();
1155        idr.set_max(3);
1156        assert_eq!(idr.max(), 3);
1157
1158        let mut guard = idr.lock();
1159        assert_eq!(guard.alloc(|value| Arc::new(MockItem { value })).unwrap().0, 0);
1160        assert_eq!(guard.alloc(|value| Arc::new(MockItem { value })).unwrap().0, 1);
1161        assert_eq!(guard.alloc(|value| Arc::new(MockItem { value })).unwrap().0, 2);
1162        assert_eq!(guard.alloc(|value| Arc::new(MockItem { value })).unwrap().0, 3);
1163
1164        // All IDs <= max are allocated; subsequent allocation fails.
1165        assert!(guard.alloc(|value| Arc::new(MockItem { value })).is_none());
1166
1167        // Free ID 1 below max.
1168        guard.remove(1);
1169        assert_eq!(guard.alloc(|value| Arc::new(MockItem { value })).unwrap().0, 1);
1170        assert!(guard.alloc(|value| Arc::new(MockItem { value })).is_none());
1171    }
1172
1173    #[fuchsia::test]
1174    fn test_update_max() {
1175        let idr = Idr::<MockItem>::default();
1176        idr.set_max(2);
1177        let mut guard = idr.lock();
1178
1179        assert_eq!(guard.alloc(|value| Arc::new(MockItem { value })).unwrap().0, 0);
1180        assert_eq!(guard.alloc(|value| Arc::new(MockItem { value })).unwrap().0, 1);
1181        assert_eq!(guard.alloc(|value| Arc::new(MockItem { value })).unwrap().0, 2);
1182        assert!(guard.alloc(|value| Arc::new(MockItem { value })).is_none());
1183
1184        // Increase maximal value to 5.
1185        guard.set_max(5);
1186        assert_eq!(guard.max(), 5);
1187
1188        assert_eq!(guard.alloc(|value| Arc::new(MockItem { value })).unwrap().0, 3);
1189        assert_eq!(guard.alloc(|value| Arc::new(MockItem { value })).unwrap().0, 4);
1190        assert_eq!(guard.alloc(|value| Arc::new(MockItem { value })).unwrap().0, 5);
1191        assert!(guard.alloc(|value| Arc::new(MockItem { value })).is_none());
1192
1193        // Lower maximal value to 4. Existing ID 5 remains readable.
1194        guard.set_max(4);
1195        let scope = RcuReadScope::new();
1196        assert!(guard.lookup(5, &scope).is_some());
1197
1198        // Removing 5 does not allow reallocating it because 5 > max (4).
1199        guard.remove(5);
1200        assert!(guard.alloc(|value| Arc::new(MockItem { value })).is_none());
1201
1202        // Removing 2 allows reallocating it because 2 <= max (4).
1203        guard.remove(2);
1204        assert_eq!(guard.alloc(|value| Arc::new(MockItem { value })).unwrap().0, 2);
1205        assert!(guard.alloc(|value| Arc::new(MockItem { value })).is_none());
1206    }
1207
1208    #[fuchsia::test]
1209    fn test_cyclic_alloc_max_wrapping() {
1210        let idr = Idr::<MockItem>::new_cyclic(Some(2));
1211        idr.set_max(5);
1212        let mut guard = idr.lock();
1213
1214        // Initial sequential allocations.
1215        for expected in 0..=5 {
1216            assert_eq!(guard.alloc(|value| Arc::new(MockItem { value })).unwrap().0, expected);
1217        }
1218
1219        // Cursor should wrap to min_after_wrap (2).
1220        assert_eq!(guard.cursor(), 2);
1221
1222        // Slots 2..=5 are full, so allocation returns None.
1223        assert!(guard.alloc(|value| Arc::new(MockItem { value })).is_none());
1224
1225        // Free slot 3. Allocation should reuse it and advance cursor to 4.
1226        guard.remove(3);
1227        assert_eq!(guard.alloc(|value| Arc::new(MockItem { value })).unwrap().0, 3);
1228        assert_eq!(guard.cursor(), 4);
1229
1230        // Free slots 0 and 1. Allocation returns None because wrapping stays >= 2.
1231        guard.remove(0);
1232        guard.remove(1);
1233        assert!(guard.alloc(|value| Arc::new(MockItem { value })).is_none());
1234    }
1235
1236    #[fuchsia::test]
1237    fn test_cyclic_cursor_above_new_max() {
1238        let idr = Idr::<MockItem>::new_cyclic(None);
1239        let mut guard = idr.lock();
1240
1241        // Advance cursor to 80 and allocate.
1242        guard.set_cursor(80);
1243        assert_eq!(guard.alloc(|value| Arc::new(MockItem { value })).unwrap().0, 80);
1244        assert_eq!(guard.cursor(), 81);
1245
1246        // Lower max below current cursor.
1247        guard.set_max(50);
1248
1249        // Next allocation resets cursor to wrap_min (0) and allocates slot 0.
1250        assert_eq!(guard.alloc(|value| Arc::new(MockItem { value })).unwrap().0, 0);
1251        assert_eq!(guard.cursor(), 1);
1252    }
1253
1254    #[fuchsia::test]
1255    fn test_max_across_tree_layers() {
1256        // 70 exceeds layer 0 capacity (64), requiring the tree to grow to layer 1.
1257        let idr = Idr::<MockItem>::default();
1258        idr.set_max(70);
1259        let mut guard = idr.lock();
1260
1261        for expected in 0..=70 {
1262            assert_eq!(guard.alloc(|value| Arc::new(MockItem { value })).unwrap().0, expected);
1263        }
1264
1265        // Exhausted up to 70.
1266        assert!(guard.alloc(|value| Arc::new(MockItem { value })).is_none());
1267
1268        // Update maximal value to 75.
1269        guard.set_max(75);
1270        for expected in 71..=75 {
1271            assert_eq!(guard.alloc(|value| Arc::new(MockItem { value })).unwrap().0, expected);
1272        }
1273        assert!(guard.alloc(|value| Arc::new(MockItem { value })).is_none());
1274    }
1275
1276    #[fuchsia::test]
1277    fn test_max_zero() {
1278        let idr = Idr::<MockItem>::default();
1279        idr.set_max(0);
1280        let mut guard = idr.lock();
1281
1282        assert_eq!(guard.alloc(|value| Arc::new(MockItem { value })).unwrap().0, 0);
1283        assert!(guard.alloc(|value| Arc::new(MockItem { value })).is_none());
1284    }
1285}