1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
#![doc = include_str!("../README.md")]
use std::fmt;
use self::{config::ConfigPrivate, control::PageControl, key::PageNo, page::Page};
mod config;
mod control;
mod handles;
mod key;
mod loom;
mod page;
mod slot;
pub use self::{
config::{Config, DefaultConfig},
handles::{BorrowedEntry, Iter, OwnedEntry, VacantEntry},
key::Key,
};
// === Idr ===
/// An IDR (IDentifier Resolver) provides a way to efficiently and concurrently
/// map integer IDs to references to objects. It's particularly useful in
/// scenarios where you need to quickly find objects based on their ID. This
/// structure is designed to be highly efficient in terms of both speed and
/// memory usage.
pub struct Idr<T, C = DefaultConfig> {
// TODO: flatten
pages: Box<[Page<T, C>]>,
// Used to synchronize page allocations.
page_control: PageControl,
}
impl<T: 'static> Default for Idr<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: 'static, C: Config> Idr<T, C> {
/// The number of bits in each key which are used by the IDR.
///
/// If other data is packed into the keys returned by [`Idr::insert()`],
/// user code is free to use any bits higher than the `USED_BITS`-th bit.
///
/// This is determined by the [`Config`] type that configures the IDR's
/// parameters. By default, all bits are used; this can be changed by
/// overriding the [`Config::RESERVED_BITS`] constant.
pub const USED_BITS: u32 = C::USED_BITS;
/// Returns a new IDR with the provided configuration parameters.
pub fn new() -> Self {
// Perform compile-time postmono checks.
assert!(C::ENSURE_VALID);
Self {
pages: (0..C::MAX_PAGES).map(PageNo::new).map(Page::new).collect(),
page_control: PageControl::default(),
}
}
/// Inserts a value into the IDR, returning the key at which that
/// value was inserted. This key can then be used to access the entry.
///
/// This method is, usually, lock-free. However, it can block if a new page
/// should be allocated. Thus, it can block max [`Config::MAX_PAGES`] times.
/// Once allocated, the page is never deallocated until the IDR is dropped.
///
/// Returns `None` if there is no more space in the IDR,
/// and no items can be added until some are removed.
///
/// # Panics
///
/// If a new page should be allocated, but the allocator fails.
///
/// # Example
///
/// ```
/// use idr_ebr::{Idr, EbrGuard};
///
/// let idr = Idr::default();
/// let key = idr.insert("foo").unwrap();
/// assert_eq!(idr.get(key, &EbrGuard::new()).unwrap(), "foo");
/// ```
#[inline]
pub fn insert(&self, value: T) -> Option<Key> {
self.vacant_entry().map(|entry| {
let key = entry.key();
entry.insert(value);
key
})
}
/// Returns a handle to a vacant entry allowing for further manipulation.
///
/// This method is, usually, lock-free. However, it can block if a new page
/// should be allocated. Thus, it can block max [`Config::MAX_PAGES`] times.
/// Once allocated, the page is never deallocated until the IDR is dropped.
///
/// This method is useful when creating values that must contain their
/// IDR key. The returned [`VacantEntry`] reserves a slot in the IDR and
/// is able to return the key of the entry.
///
/// Returns `None` if there is no more space in the IDR,
/// and no items can be added until some are removed.
///
/// # Panics
///
/// If a new page should be allocated, but the allocator fails.
///
/// # Example
///
/// ```
/// use idr_ebr::{Idr, EbrGuard};
///
/// let idr = Idr::default();
///
/// let key = {
/// let entry = idr.vacant_entry().unwrap();
/// let key = entry.key();
/// entry.insert((key, "foo"));
/// key
/// };
///
/// assert_eq!(idr.get(key, &EbrGuard::new()).unwrap().0, key);
/// assert_eq!(idr.get(key, &EbrGuard::new()).unwrap().1, "foo");
/// ```
#[inline]
pub fn vacant_entry(&self) -> Option<VacantEntry<'_, T, C>> {
self.page_control.choose(&self.pages, |page| {
page.reserve(&self.page_control)
.map(|(key, slot)| VacantEntry::new(page, slot, key))
})
}
/// Removes the entry at the given key in the IDR, returning `true` if a
/// value was present at the moment of the removal.
///
/// This method is lock-free.
///
/// The removed entry becomes unreachable for getting instantly,
/// but it still can be accessed using existing handles.
///
/// An object behind the entry is not actually dropped until all handles are
/// dropped and EBR garbage is cleaned up.
///
/// # Example
///
/// ```
/// use idr_ebr::{Idr, EbrGuard};
///
/// let idr = Idr::default();
/// let key = idr.insert("foo").unwrap();
///
/// let guard = EbrGuard::new();
/// let entry = idr.get(key, &guard).unwrap();
///
/// // Remove the entry from the IDR.
/// assert!(idr.remove(key));
///
/// // Repeat removal will return false.
/// assert!(!idr.remove(key));
///
/// // Now, the entry is unrechable using IDR.
/// assert!(!idr.contains(key));
///
/// // However, it still can be accessed using the handle.
/// assert_eq!(entry, "foo");
///
/// // An object behind the entry is not dropped until all handles are dropped.
/// // The real destruction of the object can be delayed according to EBR.
/// drop(guard);
/// ```
#[inline]
pub fn remove(&self, key: Key) -> bool {
let page_no = key.page_no::<C>();
self.pages
.get(page_no.to_usize())
.map_or(false, |page| page.remove(key))
}
/// Returns a borrowed handle to the entry associated with the given key,
/// or `None` if the IDR contains no entry for the given key.
///
/// This method is wait-free.
///
/// While the handle exists, it indicates to the IDR that the entry the
/// handle references is currently being accessed. If the entry is
/// removed from the IDR while a handle exists, it's still accessible via
/// the handle.
///
/// This method **doesn't modify memory**, thus it creates no contention on
/// it at all. This is the whole point of the EBR pattern and the reason
/// why it's used here.
///
/// The returned handle cannot be send to another thread.
/// Also, it means it cannot be hold over `.await` points.
///
/// # Example
///
/// ```
/// use idr_ebr::{Idr, EbrGuard, Key};
///
/// let idr = Idr::default();
/// let key = idr.insert("foo").unwrap();
///
/// let guard = EbrGuard::new();
/// let entry = idr.get(key, &guard).unwrap();
/// assert_eq!(entry, "foo");
///
/// // If the entry is removed, the handle is still valid.
/// assert!(idr.remove(key));
/// assert_eq!(entry, "foo");
///
/// // Getting entry for an unknown key produces None.
/// assert!(idr.get(Key::try_from(12345).unwrap(), &guard).is_none());
/// ```
#[inline]
pub fn get<'g>(&self, key: Key, guard: &'g EbrGuard) -> Option<BorrowedEntry<'g, T>> {
let page_no = key.page_no::<C>();
let page = self.pages.get(page_no.to_usize())?;
page.get(key, guard)
}
/// Returns a owned handle to the entry associated with the given key,
/// or `None` if the IDR contains no entry for the given key.
///
/// This method is lock-free.
///
/// While the handle exists, it indicates to the IDR that the entry the
/// handle references is currently being accessed. If the entry is
/// removed from the IDR while a handle exists, it's still accessible via
/// the handle.
///
/// Unlike [`Idr::get()`], which borrows the IDR, this method holds a strong
/// reference to the object itself:
/// * It modify the memory and, therefore, creates contention on it.
/// * The IDR can be dropped while the handle exists.
/// * It can be send to another thread.
/// * It can be hold over `.await` points.
///
/// # Example
///
/// ```
/// use idr_ebr::Idr;
///
/// let idr = Idr::default();
/// let key = idr.insert("foo").unwrap();
///
/// let entry = idr.get_owned(key).unwrap();
///
/// // The IDR can be dropped.
/// drop(idr);
///
/// // The handle can be send to another thread.
/// std::thread::spawn(move || {
/// assert_eq!(entry, "foo");
/// }).join().unwrap();
/// ```
#[inline]
pub fn get_owned(&self, key: Key) -> Option<OwnedEntry<T>> {
self.get(key, &EbrGuard::new()).map(BorrowedEntry::to_owned)
}
/// Returns `true` if the IDR contains an entry for the given key.
///
/// This method is wait-free.
///
/// # Example
///
/// ```
/// use idr_ebr::Idr;
///
/// let idr = Idr::default();
///
/// let key = idr.insert("foo").unwrap();
/// assert!(idr.contains(key));
///
/// idr.remove(key);
/// assert!(!idr.contains(key));
/// ```
#[inline]
pub fn contains(&self, key: Key) -> bool {
self.get(key, &EbrGuard::new()).is_some()
}
/// Returns a fused iterator over all occupied entries in the IDR.
/// An order of iteration is not guaranteed. Added during iteration entries
/// can be observed via the iterator, but it depends on the current position
/// of the iterator.
///
/// This method is wait-free and [`Iter::next()`] is also wait-free.
///
/// While the handle exists, it indicates to the IDR that the entry the
/// handle references is currently being accessed. If the entry is
/// removed from the IDR while a handle exists, it's still accessible via
/// the handle.
///
/// This method **doesn't modify memory**, thus it creates no contention on
/// it at all. This is the whole point of the EBR pattern and the reason
/// why it's used here.
///
/// The returned iterator cannot be send to another thread.
/// Also, it means it cannot be hold over `.await` points.
///
/// # Example
///
/// ```
/// use idr_ebr::{Idr, EbrGuard};
///
/// let idr = Idr::default();
/// let foo_key = idr.insert("foo").unwrap();
/// let bar_key = idr.insert("bar").unwrap();
///
/// let guard = EbrGuard::new();
/// let mut iter = idr.iter(&guard);
///
/// let (key, entry) = iter.next().unwrap();
/// assert_eq!(key, foo_key);
/// assert_eq!(entry, "foo");
///
/// let (key, entry) = iter.next().unwrap();
/// assert_eq!(key, bar_key);
/// assert_eq!(entry, "bar");
///
/// let baz_key = idr.insert("baz").unwrap();
/// let (key, entry) = iter.next().unwrap();
/// assert_eq!(key, baz_key);
/// assert_eq!(entry, "baz");
/// ```
#[inline]
pub fn iter<'g>(&self, guard: &'g EbrGuard) -> Iter<'g, '_, T, C> {
Iter::new(&self.pages, guard)
}
}
impl<T, C: Config> fmt::Debug for Idr<T, C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Idr")
.field("allocated_pages", &self.page_control.allocated())
.field("config", &C::debug())
.finish_non_exhaustive()
}
}
// === EbrGuard ===
/// [`EbrGuard`] allows to access entries of [`Idr`].
///
/// Wraps [`sdd::Guard`] in order to avoid potential breaking changes.
#[derive(Default)]
#[must_use]
pub struct EbrGuard(sdd::Guard);
impl EbrGuard {
/// Creates a new [`EbrGuard`].
///
/// # Panics
///
/// The maximum number of [`EbrGuard`] instances in a thread is limited to
/// `u32::MAX`; a thread panics when the number of [`EbrGuard`]
/// instances in the thread exceeds the limit.
///
/// # Examples
///
/// ```
/// use idr_ebr::EbrGuard;
///
/// let guard = EbrGuard::new();
/// ```
#[inline]
pub fn new() -> Self {
Self(sdd::Guard::new())
}
}
impl fmt::Debug for EbrGuard {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EbrGuard").finish()
}
}