#![allow(unsafe_code)]
use std::sync::atomic::Ordering;
use crossbeam_epoch::{Atomic, Guard, Owned, Shared};
use crate::build;
use crate::config::Config;
use crate::iter;
use crate::key::Key;
use crate::node::{is_child, Node};
struct FreezeGuard<'a, 'g, K, V> {
slot: &'a Atomic<Node<K, V>>,
frozen: Shared<'g, Node<K, V>>,
original: Shared<'g, Node<K, V>>,
guard: &'g Guard,
disarmed: bool,
}
impl<K, V> Drop for FreezeGuard<'_, '_, K, V> {
fn drop(&mut self) {
if !self.disarmed {
let _ = self.slot.compare_exchange(
self.frozen,
self.original,
Ordering::AcqRel,
Ordering::Relaxed,
self.guard,
);
}
}
}
pub fn try_rebuild_subtree<K: Key, V: Clone + Send + Sync>(
parent_node: &Node<K, V>,
parent_slot_idx: usize,
config: &Config,
guard: &Guard,
) -> bool {
let state = parent_node.slot_state(parent_slot_idx);
if !is_child(state) {
return false;
}
let child_atomic = parent_node.child_atomic(parent_slot_idx);
let current = child_atomic.load(Ordering::Acquire, guard);
if current.is_null() || current.tag() != 0 {
return false;
}
let child = unsafe { current.deref() };
let frozen = current.with_tag(1);
if child_atomic
.compare_exchange(current, frozen, Ordering::AcqRel, Ordering::Acquire, guard)
.is_err()
{
return false;
}
let mut freeze_guard = FreezeGuard {
slot: child_atomic,
frozen,
original: current,
guard,
disarmed: false,
};
let pairs = iter::sorted_pairs(child, guard);
if pairs.len() <= 1 {
return false;
}
let boosted_factor = (config.expansion_factor * 1.5).min(4.0);
let boosted_config = Config {
expansion_factor: boosted_factor,
..config.clone()
};
let new_node = build::build_recursive(&pairs, &boosted_config);
let new_child = Owned::new(new_node);
match child_atomic.compare_exchange(
frozen,
new_child,
Ordering::AcqRel,
Ordering::Acquire,
guard,
) {
Ok(_) => {
freeze_guard.disarmed = true;
unsafe {
guard.defer_destroy(current);
}
guard.flush();
true
}
Err(_) => {
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::insert;
use crate::lookup;
use crate::model::LinearModel;
use crate::node::SLOT_DATA;
use crossbeam_epoch as epoch;
fn guard() -> epoch::Guard {
epoch::pin()
}
fn default_config() -> Config {
Config::default()
}
#[test]
fn rebuild_subtree_basic() {
let g = guard();
let config = Config::new().auto_rebuild(false);
let root = Node::<u64, u64>::with_capacity(LinearModel::new(0.01, 0.0), 16);
for i in 0..20u64 {
insert::insert(&root, i, &i, &config, &g);
}
let depth_before = root.max_depth(&g);
assert!(
depth_before > 2,
"setup should create depth > 2, got {depth_before}"
);
let mut rebuilt = false;
for idx in 0..root.capacity() {
if is_child(root.slot_state(idx)) {
rebuilt = try_rebuild_subtree(&root, idx, &config, &g);
if rebuilt {
break;
}
}
}
assert!(rebuilt, "should have rebuilt at least one subtree");
for i in 0..20u64 {
assert_eq!(
lookup::get(&root, &i, &g),
Some(&i),
"key {i} lost after subtree rebuild"
);
}
}
#[test]
fn rebuild_subtree_preserves_data() {
let g = guard();
let config = default_config();
let root = Node::<u64, u64>::with_capacity(LinearModel::new(0.01, 0.0), 16);
for i in 0..50u64 {
insert::insert(&root, i, &(i * 10), &config, &g);
}
for idx in 0..root.capacity() {
if is_child(root.slot_state(idx)) {
try_rebuild_subtree(&root, idx, &config, &g);
}
}
assert_eq!(root.total_keys(&g), 50);
for i in 0..50u64 {
assert_eq!(
lookup::get(&root, &i, &g),
Some(&(i * 10)),
"key {i} has wrong value after rebuild"
);
}
}
#[test]
fn rebuild_subtree_returns_false_for_data_slot() {
let g = guard();
let config = default_config();
let pairs = vec![(10u64, 100), (20, 200)];
let root = crate::build::bulk_load(&pairs, &config).unwrap();
for idx in 0..root.capacity() {
if root.slot_state(idx) == SLOT_DATA {
assert!(!try_rebuild_subtree(&root, idx, &config, &g));
return;
}
}
panic!("no data slot found");
}
#[test]
fn rebuild_subtree_empty_child() {
let g = guard();
let config = default_config();
let empty_child = Node::<u64, u64>::with_capacity(LinearModel::new(0.1, 0.0), 4);
let parent = Node::<u64, u64>::with_capacity(LinearModel::new(0.1, 0.0), 4);
parent.store_child(0, empty_child);
assert!(!try_rebuild_subtree(&parent, 0, &config, &g));
}
}