use std::{
collections::HashMap,
hash::{BuildHasherDefault, Hasher},
sync::{
atomic::{AtomicI32, AtomicU64, Ordering},
Arc, Weak,
},
};
use dashmap::DashMap;
use once_cell::sync::Lazy;
use parking_lot::RwLock as ParkingLotRwLock;
use tracing::debug;
use super::{
common::{MatchResult, TenantId},
RadixTree,
};
pub type TokenId = u32;
pub const PAGE_SIZE: usize = 16;
pub type TokenPageKey = [TokenId; PAGE_SIZE];
type NodeRef = Arc<Node>;
const ROOT_SHARD_COUNT: usize = 32;
const NODE_SHARD_COUNT: usize = 4;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum EvictionPolicy {
#[default]
Lru,
Lfu,
Fifo,
Mru,
Filo,
Priority,
}
#[inline]
fn align_to_page(len: usize) -> usize {
(len / PAGE_SIZE) * PAGE_SIZE
}
#[inline]
fn make_page_key(tokens: &[TokenId]) -> TokenPageKey {
debug_assert!(tokens.len() >= PAGE_SIZE);
let mut key = [0u32; PAGE_SIZE];
key.copy_from_slice(&tokens[..PAGE_SIZE]);
key
}
#[derive(Default)]
struct TokenPageHasher(u64);
impl Hasher for TokenPageHasher {
#[inline(always)]
fn finish(&self) -> u64 {
self.0
}
#[inline(always)]
fn write(&mut self, bytes: &[u8]) {
for chunk in bytes.chunks(4) {
if chunk.len() == 4 {
let val = u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
self.0 = self
.0
.wrapping_add(val as u64)
.wrapping_mul(0x517cc1b727220a95);
}
}
}
}
type TokenPageHasherBuilder = BuildHasherDefault<TokenPageHasher>;
#[inline]
fn new_children_map() -> DashMap<TokenPageKey, NodeRef, TokenPageHasherBuilder> {
DashMap::with_hasher_and_shard_amount(TokenPageHasherBuilder::default(), NODE_SHARD_COUNT)
}
#[inline]
fn new_tenant_map() -> DashMap<TenantId, u64> {
DashMap::with_shard_amount(NODE_SHARD_COUNT)
}
#[derive(Debug, Clone)]
pub struct PrefixMatchResult {
pub tenant: TenantId,
pub matched_token_count: usize,
pub input_token_count: usize,
}
impl MatchResult for PrefixMatchResult {
fn tenant(&self) -> &TenantId {
&self.tenant
}
fn matched_count(&self) -> usize {
self.matched_token_count
}
fn input_count(&self) -> usize {
self.input_token_count
}
}
static TENANT_INTERN_POOL: Lazy<DashMap<Arc<str>, ()>> = Lazy::new(DashMap::new);
fn intern_tenant(tenant: &str) -> TenantId {
if let Some(entry) = TENANT_INTERN_POOL.get(tenant) {
return Arc::clone(entry.key());
}
let interned: Arc<str> = Arc::from(tenant);
TENANT_INTERN_POOL.insert(Arc::clone(&interned), ());
interned
}
static GLOBAL_TIMESTAMP: AtomicU64 = AtomicU64::new(0);
fn next_timestamp() -> u64 {
GLOBAL_TIMESTAMP.fetch_add(1, Ordering::Relaxed)
}
struct Node {
tokens: ParkingLotRwLock<Vec<TokenId>>,
children: DashMap<TokenPageKey, NodeRef, TokenPageHasherBuilder>,
tenant_last_access_time: DashMap<TenantId, u64>,
last_tenant: ParkingLotRwLock<Option<TenantId>>,
parent: ParkingLotRwLock<Weak<Node>>,
page_key: ParkingLotRwLock<Option<TokenPageKey>>,
hit_count: AtomicU64,
creation_time: u64,
priority: AtomicI32,
}
impl Node {
fn new(tokens: Vec<TokenId>) -> Self {
Self {
tokens: ParkingLotRwLock::new(tokens),
children: new_children_map(),
tenant_last_access_time: new_tenant_map(),
last_tenant: ParkingLotRwLock::new(None),
parent: ParkingLotRwLock::new(Weak::new()),
page_key: ParkingLotRwLock::new(None),
hit_count: AtomicU64::new(0),
creation_time: next_timestamp(),
priority: AtomicI32::new(0),
}
}
#[allow(dead_code)] fn new_with_priority(tokens: Vec<TokenId>, priority: i32) -> Self {
Self {
tokens: ParkingLotRwLock::new(tokens),
children: new_children_map(),
tenant_last_access_time: new_tenant_map(),
last_tenant: ParkingLotRwLock::new(None),
parent: ParkingLotRwLock::new(Weak::new()),
page_key: ParkingLotRwLock::new(None),
hit_count: AtomicU64::new(0),
creation_time: next_timestamp(),
priority: AtomicI32::new(priority),
}
}
fn new_root() -> Self {
Self {
tokens: ParkingLotRwLock::new(Vec::new()),
children: DashMap::with_hasher_and_shard_amount(
TokenPageHasherBuilder::default(),
ROOT_SHARD_COUNT,
),
tenant_last_access_time: DashMap::with_shard_amount(ROOT_SHARD_COUNT),
last_tenant: ParkingLotRwLock::new(None),
parent: ParkingLotRwLock::new(Weak::new()),
page_key: ParkingLotRwLock::new(None),
hit_count: AtomicU64::new(0),
creation_time: 0, priority: AtomicI32::new(i32::MIN), }
}
fn set_parent(&self, parent: &NodeRef, key: TokenPageKey) {
*self.parent.write() = Arc::downgrade(parent);
*self.page_key.write() = Some(key);
}
fn is_empty(&self) -> bool {
self.tenant_last_access_time.is_empty() && self.children.is_empty()
}
fn get_any_tenant(&self) -> Option<TenantId> {
let guard = self.last_tenant.read();
if let Some(ref tenant) = *guard {
if self.tenant_last_access_time.contains_key(tenant.as_ref()) {
return Some(Arc::clone(tenant));
}
}
drop(guard);
self.tenant_last_access_time
.iter()
.next()
.map(|entry| Arc::clone(entry.key()))
}
fn touch_tenant(&self, tenant: &TenantId, track_lfu: bool) {
let ts = next_timestamp();
if track_lfu {
self.hit_count.fetch_add(1, Ordering::Relaxed);
}
if let Some(mut entry) = self.tenant_last_access_time.get_mut(tenant.as_ref()) {
*entry = ts;
} else {
self.tenant_last_access_time.insert(Arc::clone(tenant), ts);
}
if ts & 0xF == 0 {
if let Some(mut guard) = self.last_tenant.try_write() {
*guard = Some(Arc::clone(tenant));
}
}
}
#[allow(dead_code)] fn update_priority(&self, new_priority: i32) {
self.priority.fetch_max(new_priority, Ordering::Relaxed);
}
}
pub struct TokenTree {
root: NodeRef,
tenant_token_count: DashMap<TenantId, usize>,
eviction_policy: EvictionPolicy,
page_size: usize,
}
impl std::fmt::Debug for TokenTree {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TokenTree")
.field("tenant_count", &self.tenant_token_count.len())
.field("eviction_policy", &self.eviction_policy)
.field("page_size", &self.page_size)
.finish_non_exhaustive()
}
}
impl Default for TokenTree {
fn default() -> Self {
Self::new()
}
}
impl TokenTree {
pub fn new() -> Self {
Self::with_config(PAGE_SIZE, EvictionPolicy::default())
}
pub fn with_policy(policy: EvictionPolicy) -> Self {
Self::with_config(PAGE_SIZE, policy)
}
pub fn with_config(page_size: usize, policy: EvictionPolicy) -> Self {
assert_eq!(
page_size, PAGE_SIZE,
"TokenTree currently only supports page_size={} (compile-time limitation). \
Got page_size={}. Future versions may support configurable page sizes.",
PAGE_SIZE, page_size
);
Self {
root: Arc::new(Node::new_root()),
tenant_token_count: DashMap::with_shard_amount(ROOT_SHARD_COUNT),
eviction_policy: policy,
page_size,
}
}
pub fn eviction_policy(&self) -> EvictionPolicy {
self.eviction_policy
}
pub fn page_size(&self) -> usize {
self.page_size
}
pub fn insert_tokens(&self, tokens: &[TokenId], tenant: &str) {
let aligned_len = align_to_page(tokens.len());
if aligned_len == 0 {
return;
}
let tokens = &tokens[..aligned_len];
let tenant_id = intern_tenant(tenant);
self.root
.tenant_last_access_time
.entry(Arc::clone(&tenant_id))
.or_insert(0);
self.tenant_token_count
.entry(Arc::clone(&tenant_id))
.or_insert(0);
let mut remaining = tokens;
let mut current = Arc::clone(&self.root);
let mut tokens_added = 0usize;
let track_lfu = self.eviction_policy == EvictionPolicy::Lfu;
enum InsertStep {
Done(usize),
Continue { next: NodeRef, advance: usize },
}
while remaining.len() >= PAGE_SIZE {
let page_key = make_page_key(remaining);
let step = match current.children.entry(page_key) {
dashmap::mapref::entry::Entry::Vacant(entry) => {
let new_node = Arc::new(Node::new(remaining.to_vec()));
new_node.set_parent(¤t, page_key);
new_node.touch_tenant(&tenant_id, track_lfu);
entry.insert(new_node);
InsertStep::Done(remaining.len())
}
dashmap::mapref::entry::Entry::Occupied(mut entry) => {
let child = Arc::clone(entry.get());
let child_tokens = child.tokens.read();
let child_len = child_tokens.len();
let common_len = remaining
.iter()
.zip(child_tokens.iter())
.take_while(|(a, b)| a == b)
.count();
let common_len = align_to_page(common_len);
if common_len == 0 {
drop(child_tokens);
InsertStep::Done(0)
} else if common_len == child_len {
drop(child_tokens);
child.touch_tenant(&tenant_id, track_lfu);
InsertStep::Continue {
next: child,
advance: common_len,
}
} else if common_len >= remaining.len() {
let common_len = align_to_page(remaining.len());
let prefix_tokens: Vec<TokenId> = child_tokens[..common_len].to_vec();
let suffix_page_key = make_page_key(&child_tokens[common_len..]);
let tenant_already_owned = child
.tenant_last_access_time
.contains_key(tenant_id.as_ref());
drop(child_tokens);
let mut child_tokens_write = child.tokens.write();
let suffix_tokens: Vec<TokenId> = child_tokens_write[common_len..].to_vec();
*child_tokens_write = suffix_tokens;
drop(child_tokens_write);
let intermediate_node = Arc::new(Node {
tokens: ParkingLotRwLock::new(prefix_tokens),
children: new_children_map(),
tenant_last_access_time: child.tenant_last_access_time.clone(),
last_tenant: ParkingLotRwLock::new(child.last_tenant.read().clone()),
parent: ParkingLotRwLock::new(Arc::downgrade(¤t)),
page_key: ParkingLotRwLock::new(Some(page_key)),
hit_count: AtomicU64::new(child.hit_count.load(Ordering::Relaxed)),
creation_time: child.creation_time,
priority: AtomicI32::new(child.priority.load(Ordering::Relaxed)),
});
child.set_parent(&intermediate_node, suffix_page_key);
intermediate_node
.children
.insert(suffix_page_key, Arc::clone(&child));
entry.insert(intermediate_node.clone());
intermediate_node.touch_tenant(&tenant_id, track_lfu);
let new_tokens = if tenant_already_owned { 0 } else { common_len };
InsertStep::Done(new_tokens)
} else {
let prefix_tokens: Vec<TokenId> = child_tokens[..common_len].to_vec();
let child_suffix_page_key = make_page_key(&child_tokens[common_len..]);
let tenant_already_owned = child
.tenant_last_access_time
.contains_key(tenant_id.as_ref());
drop(child_tokens);
let mut child_tokens_write = child.tokens.write();
let child_suffix: Vec<TokenId> = child_tokens_write[common_len..].to_vec();
*child_tokens_write = child_suffix;
drop(child_tokens_write);
let intermediate_node = Arc::new(Node {
tokens: ParkingLotRwLock::new(prefix_tokens),
children: new_children_map(),
tenant_last_access_time: child.tenant_last_access_time.clone(),
last_tenant: ParkingLotRwLock::new(child.last_tenant.read().clone()),
parent: ParkingLotRwLock::new(Arc::downgrade(¤t)),
page_key: ParkingLotRwLock::new(Some(page_key)),
hit_count: AtomicU64::new(child.hit_count.load(Ordering::Relaxed)),
creation_time: child.creation_time,
priority: AtomicI32::new(child.priority.load(Ordering::Relaxed)),
});
child.set_parent(&intermediate_node, child_suffix_page_key);
intermediate_node
.children
.insert(child_suffix_page_key, Arc::clone(&child));
let new_remaining = &remaining[common_len..];
let new_branch_tokens = if new_remaining.len() >= PAGE_SIZE {
let new_node = Arc::new(Node::new(new_remaining.to_vec()));
let new_page_key = make_page_key(new_remaining);
new_node.set_parent(&intermediate_node, new_page_key);
new_node.touch_tenant(&tenant_id, track_lfu);
intermediate_node.children.insert(new_page_key, new_node);
new_remaining.len()
} else {
0
};
entry.insert(intermediate_node.clone());
intermediate_node.touch_tenant(&tenant_id, track_lfu);
let common_tokens = if tenant_already_owned { 0 } else { common_len };
InsertStep::Done(new_branch_tokens + common_tokens)
}
}
};
match step {
InsertStep::Done(added) => {
tokens_added += added;
break;
}
InsertStep::Continue { next, advance } => {
tokens_added += advance;
remaining = &remaining[advance..];
current = next;
}
}
}
if tokens_added > 0 {
self.tenant_token_count
.entry(tenant_id)
.and_modify(|c| *c += tokens_added)
.or_insert(tokens_added);
}
}
pub fn match_prefix_with_counts(&self, tokens: &[TokenId]) -> PrefixMatchResult {
let input_token_count = tokens.len();
let aligned_len = align_to_page(tokens.len());
if aligned_len == 0 {
return PrefixMatchResult {
tenant: self
.root
.get_any_tenant()
.unwrap_or_else(|| Arc::from("empty")),
matched_token_count: 0,
input_token_count,
};
}
let tokens = &tokens[..aligned_len];
let mut matched_tokens = 0;
let mut last_tenant: Option<TenantId> = None;
let mut remaining = tokens;
let mut current = Arc::clone(&self.root);
let track_lfu = self.eviction_policy == EvictionPolicy::Lfu;
enum MatchStep {
Done,
Continue {
next: NodeRef,
advance: usize,
tenant: Option<TenantId>,
},
PartialMatch {
matched: usize,
tenant: Option<TenantId>,
},
}
while remaining.len() >= PAGE_SIZE {
let page_key = make_page_key(remaining);
let step = match current.children.get(&page_key) {
None => MatchStep::Done,
Some(child_ref) => {
let child = Arc::clone(child_ref.value());
drop(child_ref);
let child_tokens = child.tokens.read();
let match_len = remaining
.iter()
.zip(child_tokens.iter())
.take_while(|(a, b)| a == b)
.count();
let match_len = align_to_page(match_len);
if match_len == 0 {
MatchStep::Done
} else {
let tenant = child.get_any_tenant();
if tenant.is_none() {
MatchStep::Done
} else {
if let Some(ref t) = tenant {
child.touch_tenant(t, track_lfu);
}
if match_len < child_tokens.len() {
MatchStep::PartialMatch {
matched: match_len,
tenant,
}
} else {
drop(child_tokens);
MatchStep::Continue {
next: child,
advance: match_len,
tenant,
}
}
}
}
}
};
match step {
MatchStep::Done => break,
MatchStep::PartialMatch { matched, tenant } => {
matched_tokens += matched;
if let Some(t) = tenant {
last_tenant = Some(t);
}
break;
}
MatchStep::Continue {
next,
advance,
tenant,
} => {
matched_tokens += advance;
if let Some(t) = tenant {
last_tenant = Some(t);
}
remaining = &remaining[advance..];
current = next;
}
}
}
PrefixMatchResult {
tenant: last_tenant.unwrap_or_else(|| Arc::from("empty")),
matched_token_count: matched_tokens,
input_token_count,
}
}
pub fn prefix_match_legacy(&self, tokens: &[TokenId]) -> (Vec<TokenId>, String) {
let result = self.match_prefix_with_counts(tokens);
let matched: Vec<TokenId> = tokens[..result.matched_token_count].to_vec();
(matched, result.tenant.to_string())
}
#[allow(dead_code)]
pub fn get_tenant_token_counts(&self) -> HashMap<String, usize> {
self.tenant_token_count
.iter()
.map(|entry| (entry.key().to_string(), *entry.value()))
.collect()
}
fn compute_eviction_priority(&self, node: &Node, last_access_time: u64) -> (i64, u64) {
match self.eviction_policy {
EvictionPolicy::Lru => {
(last_access_time as i64, 0)
}
EvictionPolicy::Lfu => {
let hit_count = node.hit_count.load(Ordering::Relaxed);
(hit_count as i64, last_access_time)
}
EvictionPolicy::Fifo => {
(node.creation_time as i64, 0)
}
EvictionPolicy::Mru => {
((last_access_time as i64).wrapping_neg(), 0)
}
EvictionPolicy::Filo => {
((node.creation_time as i64).wrapping_neg(), 0)
}
EvictionPolicy::Priority => {
let priority = node.priority.load(Ordering::Relaxed);
(priority as i64, last_access_time)
}
}
}
pub fn evict_tenant(&self, tenant: &TenantId, max_tokens: usize) {
use std::{cmp::Reverse, collections::BinaryHeap};
let current_count = self
.tenant_token_count
.get(tenant.as_ref())
.map(|v| *v)
.unwrap_or(0);
if current_count <= max_tokens {
return;
}
let to_evict = current_count - max_tokens;
let mut evicted = 0;
let mut leaves: Vec<(NodeRef, u64)> = Vec::new();
self.collect_tenant_leaves(&self.root, tenant, &mut leaves);
let mut heap: BinaryHeap<Reverse<((i64, u64), usize)>> = BinaryHeap::new();
let mut leaf_data: Vec<NodeRef> = Vec::with_capacity(leaves.len());
for (node, ts) in leaves.drain(..) {
let priority = self.compute_eviction_priority(&node, ts);
let idx = leaf_data.len();
leaf_data.push(node);
heap.push(Reverse((priority, idx)));
}
while evicted < to_evict {
let Some(Reverse((_, idx))) = heap.pop() else {
break;
};
let node = &leaf_data[idx];
let (node_tokens, parent_became_leaf) = self.remove_tenant_and_cleanup(node, tenant);
if node_tokens > 0 {
evicted += node_tokens;
if let Some((parent_node, parent_ts)) = parent_became_leaf {
let priority = self.compute_eviction_priority(&parent_node, parent_ts);
let new_idx = leaf_data.len();
leaf_data.push(parent_node);
heap.push(Reverse((priority, new_idx)));
}
}
}
if let Some(mut count) = self.tenant_token_count.get_mut(tenant.as_ref()) {
*count = count.saturating_sub(evicted);
}
debug!(
tenant = %tenant.as_ref(),
evicted = evicted,
remaining = current_count.saturating_sub(evicted),
policy = ?self.eviction_policy,
"Evicted tokens from tenant (leaf-first)"
);
}
fn collect_tenant_leaves(
&self,
node: &NodeRef,
tenant_id: &TenantId,
result: &mut Vec<(NodeRef, u64)>,
) {
let is_root = Arc::ptr_eq(node, &self.root);
let node_has_tenant = !is_root
&& node
.tenant_last_access_time
.contains_key(tenant_id.as_ref());
let mut any_child_has_tenant = false;
for child_entry in node.children.iter() {
let child = child_entry.value();
if child
.tenant_last_access_time
.contains_key(tenant_id.as_ref())
{
any_child_has_tenant = true;
}
self.collect_tenant_leaves(child, tenant_id, result);
}
if node_has_tenant && !any_child_has_tenant {
if let Some(ts) = node.tenant_last_access_time.get(tenant_id.as_ref()) {
result.push((Arc::clone(node), *ts));
}
}
}
fn is_tenant_leaf(&self, node: &NodeRef, tenant_id: &TenantId) -> bool {
if !node
.tenant_last_access_time
.contains_key(tenant_id.as_ref())
{
return false;
}
for child_entry in node.children.iter() {
if child_entry
.value()
.tenant_last_access_time
.contains_key(tenant_id.as_ref())
{
return false;
}
}
true
}
fn remove_tenant_and_cleanup(
&self,
node: &NodeRef,
tenant_id: &TenantId,
) -> (usize, Option<(NodeRef, u64)>) {
if node
.tenant_last_access_time
.remove(tenant_id.as_ref())
.is_none()
{
return (0, None);
}
let node_tokens = node.tokens.read().len();
let parent_leaf_info = self.cleanup_empty_ancestors_with_parent(node, tenant_id);
(node_tokens, parent_leaf_info)
}
fn cleanup_empty_ancestors_with_parent(
&self,
node: &NodeRef,
tenant_id: &TenantId,
) -> Option<(NodeRef, u64)> {
let mut current = Arc::clone(node);
let mut parent_leaf_info: Option<(NodeRef, u64)> = None;
loop {
if !current.is_empty() {
if parent_leaf_info.is_none() && self.is_tenant_leaf(¤t, tenant_id) {
if let Some(ts) = current.tenant_last_access_time.get(tenant_id.as_ref()) {
parent_leaf_info = Some((Arc::clone(¤t), *ts));
}
}
break;
}
let parent_weak = current.parent.read();
let Some(parent) = parent_weak.upgrade() else {
break;
};
drop(parent_weak);
let page_key_guard = current.page_key.read();
let Some(page_key) = *page_key_guard else {
break;
};
drop(page_key_guard);
parent.children.remove(&page_key);
if parent_leaf_info.is_none() && self.is_tenant_leaf(&parent, tenant_id) {
if let Some(ts) = parent.tenant_last_access_time.get(tenant_id.as_ref()) {
parent_leaf_info = Some((Arc::clone(&parent), *ts));
}
}
current = parent;
}
parent_leaf_info
}
pub fn tenant_token_size(&self, tenant: &TenantId) -> usize {
self.tenant_token_count
.get(tenant.as_ref())
.map(|v| *v)
.unwrap_or(0)
}
pub fn clear(&self) {
self.root.children.clear();
self.root.tenant_last_access_time.clear();
self.tenant_token_count.clear();
}
pub fn evict_tenant_by_size(&self, max_size: usize) {
let tenants_to_evict: Vec<TenantId> = self
.tenant_token_count
.iter()
.filter(|entry| *entry.value() > max_size)
.map(|entry| Arc::clone(entry.key()))
.collect();
for tenant_id in tenants_to_evict {
self.evict_tenant(&tenant_id, max_size);
}
}
}
impl RadixTree for TokenTree {
type Key = [TokenId];
type MatchResult = PrefixMatchResult;
fn insert(&self, key: &Self::Key, tenant: &str) {
self.insert_tokens(key, tenant);
}
fn prefix_match(&self, key: &Self::Key) -> Option<TenantId> {
let result = self.match_prefix_with_counts(key);
if result.matched_token_count > 0 {
Some(result.tenant)
} else {
None
}
}
fn prefix_match_with_counts(&self, key: &Self::Key) -> Self::MatchResult {
self.match_prefix_with_counts(key)
}
fn evict(&self, tenant: &TenantId, max_units: usize) {
self.evict_tenant(tenant, max_units);
}
fn tenant_size(&self, tenant: &TenantId) -> usize {
self.tenant_token_size(tenant)
}
fn reset(&self) {
self.clear();
}
}
#[cfg(test)]
mod tests {
use std::{sync::Arc, thread};
use super::*;
fn make_tokens(base: u32, pages: usize) -> Vec<TokenId> {
(0..(pages * PAGE_SIZE)).map(|i| base + i as u32).collect()
}
#[test]
fn test_basic_insert_match() {
let tree = TokenTree::new();
let tokens = make_tokens(1, 2);
tree.insert_tokens(&tokens, "tenant1");
let result = tree.match_prefix_with_counts(&tokens);
assert_eq!(result.matched_token_count, 32);
assert_eq!(result.tenant.as_ref(), "tenant1");
let first_page = make_tokens(1, 1);
let result = tree.match_prefix_with_counts(&first_page);
assert_eq!(result.matched_token_count, PAGE_SIZE);
assert_eq!(result.tenant.as_ref(), "tenant1");
let mut extended = tokens.clone();
extended.extend([100, 101, 102, 103, 104]);
let result = tree.match_prefix_with_counts(&extended);
assert_eq!(result.matched_token_count, 32);
}
#[test]
fn test_short_sequences_skipped() {
let tree = TokenTree::new();
tree.insert_tokens(&[1, 2, 3, 4, 5], "tenant1");
let counts = tree.get_tenant_token_counts();
assert!(counts.is_empty() || counts.get("tenant1").copied().unwrap_or(0) == 0);
let result = tree.match_prefix_with_counts(&[1, 2, 3, 4, 5]);
assert_eq!(result.matched_token_count, 0);
assert_eq!(result.input_token_count, 5);
}
#[test]
fn test_multiple_tenants() {
let tree = TokenTree::new();
let tokens = make_tokens(1, 1);
tree.insert_tokens(&tokens, "tenant1");
tree.insert_tokens(&tokens, "tenant2");
let result = tree.match_prefix_with_counts(&tokens);
assert_eq!(result.matched_token_count, PAGE_SIZE);
assert!(result.tenant.as_ref() == "tenant1" || result.tenant.as_ref() == "tenant2");
}
#[test]
fn test_prefix_split() {
let tree = TokenTree::new();
let long_tokens = make_tokens(1, 3);
tree.insert_tokens(&long_tokens, "tenant1");
let short_tokens = make_tokens(1, 1);
tree.insert_tokens(&short_tokens, "tenant2");
let result = tree.match_prefix_with_counts(&short_tokens);
assert_eq!(result.matched_token_count, PAGE_SIZE);
let result = tree.match_prefix_with_counts(&long_tokens);
assert_eq!(result.matched_token_count, 3 * PAGE_SIZE);
assert_eq!(result.tenant.as_ref(), "tenant1");
}
#[test]
fn test_empty_input() {
let tree = TokenTree::new();
let tokens = make_tokens(1, 1);
tree.insert_tokens(&tokens, "tenant1");
let result = tree.match_prefix_with_counts(&[]);
assert_eq!(result.matched_token_count, 0);
assert_eq!(result.input_token_count, 0);
}
#[test]
fn test_no_match() {
let tree = TokenTree::new();
let tokens = make_tokens(1, 1);
tree.insert_tokens(&tokens, "tenant1");
let other = make_tokens(1000, 1);
let result = tree.match_prefix_with_counts(&other);
assert_eq!(result.matched_token_count, 0);
}
#[test]
fn test_eviction() {
let tree = TokenTree::new();
let tokens1 = make_tokens(1, 2);
let tokens2 = make_tokens(1, 3);
tree.insert_tokens(&tokens1, "tenant1");
tree.insert_tokens(&tokens2, "tenant1");
let counts = tree.get_tenant_token_counts();
assert!(counts.get("tenant1").unwrap() > &0);
tree.evict(&TenantId::from("tenant1"), 0);
let new_counts = tree.get_tenant_token_counts();
let new_count = new_counts.get("tenant1").copied().unwrap_or(0);
assert!(new_count < *counts.get("tenant1").unwrap());
}
#[test]
fn test_concurrent_insert_match() {
let tree = Arc::new(TokenTree::new());
let mut handles = vec![];
for i in 0..4 {
let tree = Arc::clone(&tree);
handles.push(thread::spawn(move || {
for j in 0..100 {
let base = (i * 1000000 + j * 1000) as u32;
let tokens = make_tokens(base, 2);
tree.insert_tokens(&tokens, &format!("tenant{}", i));
}
}));
}
for i in 0..4 {
let tree = Arc::clone(&tree);
handles.push(thread::spawn(move || {
for j in 0..100 {
let base = (i * 1000000 + j * 1000) as u32;
let tokens = make_tokens(base, 2);
let _ = tree.match_prefix_with_counts(&tokens);
}
}));
}
for handle in handles {
handle.join().unwrap();
}
}
#[test]
fn test_prefix_match_with_counts() {
let tree = TokenTree::new();
let tokens = make_tokens(1, 2);
tree.insert_tokens(&tokens, "tenant1");
let result = tree.match_prefix_with_counts(&tokens);
assert_eq!(result.matched_token_count, 2 * PAGE_SIZE);
assert_eq!(result.input_token_count, 2 * PAGE_SIZE);
let first_page = make_tokens(1, 1);
let result = tree.match_prefix_with_counts(&first_page);
assert_eq!(result.matched_token_count, PAGE_SIZE);
assert_eq!(result.input_token_count, PAGE_SIZE);
let extended = make_tokens(1, 3);
let result = tree.match_prefix_with_counts(&extended);
assert_eq!(result.matched_token_count, 2 * PAGE_SIZE);
assert_eq!(result.input_token_count, 3 * PAGE_SIZE);
}
#[test]
fn test_disjoint_paths() {
let tree = TokenTree::new();
let tokens1 = make_tokens(1, 1);
let tokens2 = make_tokens(1000, 1);
let tokens3 = make_tokens(2000, 1);
tree.insert_tokens(&tokens1, "tenant1");
tree.insert_tokens(&tokens2, "tenant2");
tree.insert_tokens(&tokens3, "tenant3");
let result = tree.match_prefix_with_counts(&tokens1);
assert_eq!(result.matched_token_count, PAGE_SIZE);
assert_eq!(result.tenant.as_ref(), "tenant1");
let result = tree.match_prefix_with_counts(&tokens2);
assert_eq!(result.matched_token_count, PAGE_SIZE);
assert_eq!(result.tenant.as_ref(), "tenant2");
let result = tree.match_prefix_with_counts(&tokens3);
assert_eq!(result.matched_token_count, PAGE_SIZE);
assert_eq!(result.tenant.as_ref(), "tenant3");
}
#[test]
fn test_branching_paths() {
let tree = TokenTree::new();
let mut tokens1 = make_tokens(1, 1);
tokens1.extend(make_tokens(100, 1));
let mut tokens2 = make_tokens(1, 1);
tokens2.extend(make_tokens(200, 1));
let mut tokens3 = make_tokens(1, 1);
tokens3.extend(make_tokens(300, 1));
tree.insert_tokens(&tokens1, "tenant1");
tree.insert_tokens(&tokens2, "tenant2");
tree.insert_tokens(&tokens3, "tenant3");
let result = tree.match_prefix_with_counts(&tokens1);
assert_eq!(result.matched_token_count, 2 * PAGE_SIZE);
assert_eq!(result.tenant.as_ref(), "tenant1");
let result = tree.match_prefix_with_counts(&tokens2);
assert_eq!(result.matched_token_count, 2 * PAGE_SIZE);
assert_eq!(result.tenant.as_ref(), "tenant2");
let first_page = make_tokens(1, 1);
let result = tree.match_prefix_with_counts(&first_page);
assert_eq!(result.matched_token_count, PAGE_SIZE);
}
#[test]
fn test_radix_tree_trait() {
let tree = TokenTree::new();
let tokens = make_tokens(1, 2);
RadixTree::insert(&tree, &tokens, "tenant1");
let tenant = RadixTree::prefix_match(&tree, &tokens);
assert!(tenant.is_some());
assert_eq!(tenant.unwrap().as_ref(), "tenant1");
let extended = make_tokens(1, 3);
let result = RadixTree::prefix_match_with_counts(&tree, &extended);
assert_eq!(result.matched_count(), 2 * PAGE_SIZE);
assert_eq!(result.input_count(), 3 * PAGE_SIZE);
assert!(RadixTree::tenant_size(&tree, &TenantId::from("tenant1")) > 0);
}
#[test]
fn test_clear() {
let tree = TokenTree::new();
let tokens1 = make_tokens(1, 1);
let tokens2 = make_tokens(1000, 1);
tree.insert_tokens(&tokens1, "tenant1");
tree.insert_tokens(&tokens2, "tenant2");
assert!(!tree.get_tenant_token_counts().is_empty());
tree.clear();
assert!(tree.get_tenant_token_counts().is_empty());
let result = tree.match_prefix_with_counts(&tokens1);
assert_eq!(result.matched_token_count, 0);
}
#[test]
fn test_tenant_token_count() {
let tree = TokenTree::new();
let tokens1 = make_tokens(1, 2);
let tokens2 = make_tokens(1, 3);
let tokens3 = make_tokens(1000, 1);
tree.insert_tokens(&tokens1, "tenant1");
tree.insert_tokens(&tokens2, "tenant1");
tree.insert_tokens(&tokens3, "tenant2");
let tenant1_id: TenantId = Arc::from("tenant1");
let tenant2_id: TenantId = Arc::from("tenant2");
assert!(tree.tenant_token_size(&tenant1_id) >= PAGE_SIZE);
assert!(tree.tenant_token_size(&tenant2_id) >= PAGE_SIZE);
let counts = tree.get_tenant_token_counts();
assert!(counts.contains_key("tenant1"));
assert!(counts.contains_key("tenant2"));
}
#[test]
fn test_cold_start() {
let tree = TokenTree::new();
let result = tree.match_prefix_with_counts(&[1, 2, 3, 4, 5]);
assert_eq!(result.matched_token_count, 0);
assert_eq!(result.input_token_count, 5);
let tokens = make_tokens(1, 1);
let result = tree.match_prefix_with_counts(&tokens);
assert_eq!(result.matched_token_count, 0);
assert_eq!(result.input_token_count, PAGE_SIZE);
}
#[test]
fn test_exact_match_seq() {
let tree = TokenTree::new();
for i in 0..100 {
let base = (i * 1000) as u32;
let tokens = make_tokens(base, 2);
tree.insert_tokens(&tokens, &format!("tenant{}", i));
}
for i in 0..100 {
let base = (i * 1000) as u32;
let tokens = make_tokens(base, 2);
let result = tree.match_prefix_with_counts(&tokens);
assert_eq!(result.matched_token_count, 2 * PAGE_SIZE);
assert_eq!(result.tenant.as_ref(), &format!("tenant{}", i));
}
}
#[test]
fn test_exact_match_concurrent() {
let tree = Arc::new(TokenTree::new());
let num_threads = 8;
let entries_per_thread = 100;
let mut handles = vec![];
for t in 0..num_threads {
let tree = Arc::clone(&tree);
handles.push(thread::spawn(move || {
for i in 0..entries_per_thread {
let base = (t * 1000000 + i * 1000) as u32;
let tokens = make_tokens(base, 2);
tree.insert_tokens(&tokens, &format!("tenant{}", t));
}
}));
}
for handle in handles {
handle.join().unwrap();
}
let mut handles = vec![];
for t in 0..num_threads {
let tree = Arc::clone(&tree);
handles.push(thread::spawn(move || {
for i in 0..entries_per_thread {
let base = (t * 1000000 + i * 1000) as u32;
let tokens = make_tokens(base, 2);
let result = tree.match_prefix_with_counts(&tokens);
assert_eq!(result.matched_token_count, 2 * PAGE_SIZE);
assert_eq!(result.tenant.as_ref(), &format!("tenant{}", t));
}
}));
}
for handle in handles {
handle.join().unwrap();
}
}
#[test]
fn test_partial_match_concurrent() {
let tree = Arc::new(TokenTree::new());
let num_threads = 8;
let entries_per_thread = 100;
let mut handles = vec![];
for t in 0..num_threads {
let tree = Arc::clone(&tree);
handles.push(thread::spawn(move || {
for i in 0..entries_per_thread {
let base = (t * 1000000 + i * 1000) as u32;
let tokens = make_tokens(base, 3);
tree.insert_tokens(&tokens, &format!("tenant{}", t));
}
}));
}
for handle in handles {
handle.join().unwrap();
}
let mut handles = vec![];
for t in 0..num_threads {
let tree = Arc::clone(&tree);
handles.push(thread::spawn(move || {
for i in 0..entries_per_thread {
let base = (t * 1000000 + i * 1000) as u32;
let partial = make_tokens(base, 1);
let result = tree.match_prefix_with_counts(&partial);
assert_eq!(result.matched_token_count, PAGE_SIZE);
}
}));
}
for handle in handles {
handle.join().unwrap();
}
}
#[test]
fn test_group_prefix_insert_match_concurrent() {
let tree = Arc::new(TokenTree::new());
let num_threads = 8;
let common_prefix = make_tokens(100, 1);
let mut handles = vec![];
for t in 0..num_threads {
let tree = Arc::clone(&tree);
let prefix = common_prefix.clone();
handles.push(thread::spawn(move || {
for i in 0..50 {
let mut tokens = prefix.clone();
let suffix = make_tokens((t * 10000 + i * 100) as u32, 1);
tokens.extend(suffix);
tree.insert_tokens(&tokens, &format!("tenant{}", t));
}
}));
}
for handle in handles {
handle.join().unwrap();
}
let result = tree.match_prefix_with_counts(&common_prefix);
assert_eq!(result.matched_token_count, PAGE_SIZE);
}
#[test]
fn test_mixed_concurrent_insert_match() {
let tree = Arc::new(TokenTree::new());
let num_threads = 4;
for i in 0..100 {
let base = (i * 1000) as u32;
let tokens = make_tokens(base, 2);
tree.insert_tokens(&tokens, &format!("initial{}", i));
}
let mut handles = vec![];
for t in 0..num_threads {
let tree = Arc::clone(&tree);
handles.push(thread::spawn(move || {
for i in 0..100 {
let base = (10000000 + t * 100000 + i * 1000) as u32;
let tokens = make_tokens(base, 2);
tree.insert_tokens(&tokens, &format!("new_tenant{}", t));
}
}));
}
for t in 0..num_threads {
let tree = Arc::clone(&tree);
handles.push(thread::spawn(move || {
for _ in 0..100 {
let base = ((t * 10) * 1000) as u32;
let tokens = make_tokens(base, 2);
let result = tree.match_prefix_with_counts(&tokens);
assert!(result.matched_token_count > 0);
}
}));
}
for handle in handles {
handle.join().unwrap();
}
}
#[test]
fn test_simple_eviction() {
let tree = TokenTree::new();
let tokens1 = make_tokens(1, 2);
let tokens2 = make_tokens(1000, 2);
tree.insert_tokens(&tokens1, "tenant1");
tree.insert_tokens(&tokens2, "tenant2");
let tenant1_id: TenantId = Arc::from("tenant1");
tree.evict_tenant(&tenant1_id, 0);
let result = tree.match_prefix_with_counts(&tokens2);
assert_eq!(result.matched_token_count, 2 * PAGE_SIZE);
assert_eq!(result.tenant.as_ref(), "tenant2");
}
#[test]
fn test_advanced_eviction() {
let tree = TokenTree::new();
let mut tokens1 = make_tokens(1, 1);
tokens1.extend(make_tokens(100, 1));
let mut tokens2 = make_tokens(1, 1);
tokens2.extend(make_tokens(200, 1));
let mut tokens3 = make_tokens(1, 1);
tokens3.extend(make_tokens(300, 1));
tree.insert_tokens(&tokens1, "tenant1");
tree.insert_tokens(&tokens2, "tenant1");
tree.insert_tokens(&tokens3, "tenant1");
let tenant1_id: TenantId = Arc::from("tenant1");
let initial_size = tree.tenant_token_size(&tenant1_id);
tree.evict_tenant(&tenant1_id, initial_size / 2);
let after_size = tree.tenant_token_size(&tenant1_id);
assert!(after_size <= initial_size);
}
#[test]
fn test_concurrent_operations_with_eviction() {
let tree = Arc::new(TokenTree::new());
let num_threads = 4;
for i in 0..100 {
let base = (i * 1000) as u32;
let tokens = make_tokens(base, 2);
tree.insert_tokens(&tokens, &format!("tenant{}", i % 4));
}
let mut handles = vec![];
for t in 0..num_threads {
let tree = Arc::clone(&tree);
handles.push(thread::spawn(move || {
for i in 0..50 {
let base = (10000000 + t * 100000 + i * 1000) as u32;
let tokens = make_tokens(base, 2);
tree.insert_tokens(&tokens, &format!("tenant{}", t));
}
}));
}
for t in 0..num_threads {
let tree = Arc::clone(&tree);
handles.push(thread::spawn(move || {
let tenant_id: TenantId = Arc::from(format!("tenant{}", t));
for _ in 0..10 {
tree.evict_tenant(&tenant_id, 50);
thread::sleep(std::time::Duration::from_millis(1));
}
}));
}
for _ in 0..num_threads {
let tree = Arc::clone(&tree);
handles.push(thread::spawn(move || {
for i in 0..50 {
let base = (i * 1000) as u32;
let tokens = make_tokens(base, 1);
let _ = tree.match_prefix_with_counts(&tokens);
}
}));
}
for handle in handles {
handle.join().unwrap();
}
}
#[test]
fn test_get_used_size_per_tenant() {
let tree = TokenTree::new();
let tokens1 = make_tokens(1, 2);
let tokens2 = make_tokens(1, 3);
let tokens3 = make_tokens(1000, 1);
tree.insert_tokens(&tokens1, "tenant1");
tree.insert_tokens(&tokens2, "tenant1");
tree.insert_tokens(&tokens3, "tenant2");
let counts = tree.get_tenant_token_counts();
assert!(counts.contains_key("tenant1"));
assert!(counts.contains_key("tenant2"));
assert!(*counts.get("tenant1").unwrap() >= PAGE_SIZE);
assert!(*counts.get("tenant2").unwrap() >= PAGE_SIZE);
}
#[test]
fn test_prefix_match_tenant() {
let tree = TokenTree::new();
let tokens = make_tokens(1, 2);
tree.insert_tokens(&tokens, "tenant1");
tree.insert_tokens(&tokens, "tenant2");
let result = tree.match_prefix_with_counts(&tokens);
assert_eq!(result.matched_token_count, 2 * PAGE_SIZE);
assert!(result.tenant.as_ref() == "tenant1" || result.tenant.as_ref() == "tenant2");
}
#[test]
fn test_simple_tenant_eviction() {
let tree = TokenTree::new();
let tokens1 = make_tokens(1, 2);
let tokens2 = make_tokens(1000, 2);
tree.insert_tokens(&tokens1, "tenant1");
tree.insert_tokens(&tokens2, "tenant2");
let tenant1_id: TenantId = Arc::from("tenant1");
tree.evict_tenant(&tenant1_id, 0);
let result = tree.match_prefix_with_counts(&tokens2);
assert_eq!(result.matched_token_count, 2 * PAGE_SIZE);
assert_eq!(result.tenant.as_ref(), "tenant2");
}
#[test]
fn test_complex_tenant_eviction() {
let tree = TokenTree::new();
let mut tokens1 = make_tokens(1, 1);
tokens1.extend(make_tokens(100, 1));
let mut tokens2 = make_tokens(1, 1);
tokens2.extend(make_tokens(200, 1));
let mut tokens3 = make_tokens(1, 1);
tokens3.extend(make_tokens(300, 1));
tree.insert_tokens(&tokens1, "tenant1");
tree.insert_tokens(&tokens2, "tenant2");
tree.insert_tokens(&tokens3, "tenant1");
let tenant1_id: TenantId = Arc::from("tenant1");
tree.evict_tenant(&tenant1_id, 0);
let result = tree.match_prefix_with_counts(&tokens2);
assert_eq!(result.matched_token_count, 2 * PAGE_SIZE);
assert_eq!(result.tenant.as_ref(), "tenant2");
}
#[test]
fn test_empty_node_cleanup_after_eviction() {
let tree = TokenTree::new();
let tokens1 = make_tokens(500, 2); tree.insert_tokens(&tokens1, "tenant1");
assert!(
!tree.root.children.is_empty(),
"Tree should have children after insert"
);
let tenant1_id: TenantId = Arc::from("tenant1");
tree.evict_tenant(&tenant1_id, 0);
assert!(
tree.root.children.is_empty(),
"Empty nodes should be removed after tenant eviction"
);
assert_eq!(tree.tenant_token_size(&tenant1_id), 0);
}
#[test]
fn test_partial_cleanup_shared_nodes() {
let tree = TokenTree::new();
let tokens = make_tokens(100, 2);
tree.insert_tokens(&tokens, "tenant1");
tree.insert_tokens(&tokens, "tenant2");
let tenant1_id: TenantId = Arc::from("tenant1");
tree.evict_tenant(&tenant1_id, 0);
assert!(
!tree.root.children.is_empty(),
"Shared nodes should NOT be removed when other tenants exist"
);
let result = tree.match_prefix_with_counts(&tokens);
assert_eq!(result.matched_token_count, 2 * PAGE_SIZE);
assert_eq!(result.tenant.as_ref(), "tenant2");
}
#[test]
fn test_leaf_first_eviction() {
let tree = TokenTree::new();
let path_abc = make_tokens(1, 3); tree.insert_tokens(&path_abc, "tenant1");
let path_a = make_tokens(1, 1); tree.insert_tokens(&path_a, "tenant1");
let initial_count = tree.tenant_token_size(&Arc::from("tenant1"));
assert_eq!(initial_count, 3 * PAGE_SIZE);
let tenant1_id: TenantId = Arc::from("tenant1");
tree.evict_tenant(&tenant1_id, 2 * PAGE_SIZE);
let result = tree.match_prefix_with_counts(&path_abc);
assert!(
result.matched_token_count <= 2 * PAGE_SIZE,
"Expected at most 2 pages matched after evicting leaf, got {}",
result.matched_token_count / PAGE_SIZE
);
let result = tree.match_prefix_with_counts(&path_a);
assert_eq!(
result.matched_token_count, PAGE_SIZE,
"Shared prefix A should still be cached"
);
tree.evict_tenant(&tenant1_id, PAGE_SIZE);
let result = tree.match_prefix_with_counts(&path_abc);
assert!(
result.matched_token_count <= PAGE_SIZE,
"Expected at most 1 page matched after evicting B"
);
let result = tree.match_prefix_with_counts(&path_a);
assert_eq!(
result.matched_token_count, PAGE_SIZE,
"Root prefix A should survive longest"
);
}
#[test]
fn test_single_page_operations() {
let tree = TokenTree::new();
let tokens1 = make_tokens(1, 1);
let tokens2 = make_tokens(1000, 1);
let tokens3 = make_tokens(2000, 1);
tree.insert_tokens(&tokens1, "tenant1");
tree.insert_tokens(&tokens2, "tenant2");
tree.insert_tokens(&tokens3, "tenant3");
let result = tree.match_prefix_with_counts(&tokens1);
assert_eq!(result.matched_token_count, PAGE_SIZE);
assert_eq!(result.tenant.as_ref(), "tenant1");
let result = tree.match_prefix_with_counts(&tokens2);
assert_eq!(result.matched_token_count, PAGE_SIZE);
assert_eq!(result.tenant.as_ref(), "tenant2");
let result = tree.match_prefix_with_counts(&tokens3);
assert_eq!(result.matched_token_count, PAGE_SIZE);
assert_eq!(result.tenant.as_ref(), "tenant3");
}
#[test]
fn test_prefix_is_subset_of_existing() {
let tree = TokenTree::new();
let long_tokens = make_tokens(1, 3);
tree.insert_tokens(&long_tokens, "tenant1");
let short_tokens = make_tokens(1, 1);
tree.insert_tokens(&short_tokens, "tenant2");
let result = tree.match_prefix_with_counts(&short_tokens);
assert_eq!(result.matched_token_count, PAGE_SIZE);
let result = tree.match_prefix_with_counts(&long_tokens);
assert_eq!(result.matched_token_count, 3 * PAGE_SIZE);
assert_eq!(result.tenant.as_ref(), "tenant1");
}
#[test]
fn test_existing_is_prefix_of_new() {
let tree = TokenTree::new();
let short_tokens = make_tokens(1, 1);
tree.insert_tokens(&short_tokens, "tenant1");
let long_tokens = make_tokens(1, 3);
tree.insert_tokens(&long_tokens, "tenant2");
let result = tree.match_prefix_with_counts(&short_tokens);
assert_eq!(result.matched_token_count, PAGE_SIZE);
let result = tree.match_prefix_with_counts(&long_tokens);
assert_eq!(result.matched_token_count, 3 * PAGE_SIZE);
assert_eq!(result.tenant.as_ref(), "tenant2");
}
#[test]
fn test_prefix_match_with_counts_accuracy() {
let tree = TokenTree::new();
let tokens = make_tokens(1, 4);
tree.insert_tokens(&tokens, "tenant1");
let result = tree.match_prefix_with_counts(&tokens);
assert_eq!(result.matched_token_count, 4 * PAGE_SIZE);
assert_eq!(result.input_token_count, 4 * PAGE_SIZE);
let partial = make_tokens(1, 2);
let result = tree.match_prefix_with_counts(&partial);
assert_eq!(result.matched_token_count, 2 * PAGE_SIZE);
assert_eq!(result.input_token_count, 2 * PAGE_SIZE);
let extended = make_tokens(1, 6);
let result = tree.match_prefix_with_counts(&extended);
assert_eq!(result.matched_token_count, 4 * PAGE_SIZE);
assert_eq!(result.input_token_count, 6 * PAGE_SIZE);
}
#[test]
fn test_split_at_page_boundary() {
let tree = TokenTree::new();
let long_tokens = make_tokens(1, 3);
tree.insert_tokens(&long_tokens, "tenant1");
let short_tokens = make_tokens(1, 1);
tree.insert_tokens(&short_tokens, "tenant2");
let result = tree.match_prefix_with_counts(&short_tokens);
assert_eq!(result.matched_token_count, PAGE_SIZE);
let result = tree.match_prefix_with_counts(&long_tokens);
assert_eq!(result.matched_token_count, 3 * PAGE_SIZE);
assert_eq!(result.tenant.as_ref(), "tenant1");
}
#[test]
fn test_multiple_splits_same_path() {
let tree = TokenTree::new();
let tokens4 = make_tokens(1, 4);
tree.insert_tokens(&tokens4, "tenant1");
let tokens3 = make_tokens(1, 3);
tree.insert_tokens(&tokens3, "tenant2");
let tokens2 = make_tokens(1, 2);
tree.insert_tokens(&tokens2, "tenant3");
let tokens1 = make_tokens(1, 1);
tree.insert_tokens(&tokens1, "tenant4");
let result = tree.match_prefix_with_counts(&tokens1);
assert_eq!(result.matched_token_count, PAGE_SIZE);
let result = tree.match_prefix_with_counts(&tokens2);
assert_eq!(result.matched_token_count, 2 * PAGE_SIZE);
let result = tree.match_prefix_with_counts(&tokens3);
assert_eq!(result.matched_token_count, 3 * PAGE_SIZE);
let result = tree.match_prefix_with_counts(&tokens4);
assert_eq!(result.matched_token_count, 4 * PAGE_SIZE);
}
#[test]
fn test_high_contention_same_prefix() {
let tree = Arc::new(TokenTree::new());
let prefix = make_tokens(100, 1);
let num_threads = 16;
let mut handles = vec![];
for t in 0..num_threads {
let tree = Arc::clone(&tree);
let p = prefix.clone();
handles.push(thread::spawn(move || {
for i in 0..100 {
let mut tokens = p.clone();
let suffix = make_tokens((t * 10000 + i * 100) as u32, 1);
tokens.extend(suffix);
tree.insert_tokens(&tokens, &format!("tenant{}", t));
}
}));
}
for handle in handles {
handle.join().unwrap();
}
let result = tree.match_prefix_with_counts(&prefix);
assert_eq!(result.matched_token_count, PAGE_SIZE);
}
#[test]
fn test_rapid_insert_remove_cycles() {
let tree = Arc::new(TokenTree::new());
let num_threads = 4;
let mut handles = vec![];
for t in 0..num_threads {
let tree = Arc::clone(&tree);
handles.push(thread::spawn(move || {
let tenant_id: TenantId = Arc::from(format!("tenant{}", t));
for cycle in 0..10 {
for i in 0..20 {
let base = (t * 10000000 + cycle * 100000 + i * 1000) as u32;
let tokens = make_tokens(base, 2);
tree.insert_tokens(&tokens, &format!("tenant{}", t));
}
tree.evict_tenant(&tenant_id, 10);
}
}));
}
for handle in handles {
handle.join().unwrap();
}
}
#[test]
fn test_eviction_empty_tree() {
let tree = TokenTree::new();
let tenant_id: TenantId = Arc::from("nonexistent");
tree.evict_tenant(&tenant_id, 0);
tree.evict_tenant(&tenant_id, 100);
}
#[test]
fn test_eviction_zero_max_size() {
let tree = TokenTree::new();
let tokens1 = make_tokens(1, 2);
let tokens2 = make_tokens(1000, 2);
tree.insert_tokens(&tokens1, "tenant1");
tree.insert_tokens(&tokens2, "tenant1");
let tenant_id: TenantId = Arc::from("tenant1");
tree.evict_tenant(&tenant_id, 0);
let size = tree.tenant_token_size(&tenant_id);
assert!(size < 4 * PAGE_SIZE);
}
#[test]
fn test_eviction_single_tenant_all_entries() {
let tree = TokenTree::new();
for i in 0..10 {
let base = (i * 1000) as u32;
let tokens = make_tokens(base, 2);
tree.insert_tokens(&tokens, "tenant1");
}
let tenant_id: TenantId = Arc::from("tenant1");
let initial_size = tree.tenant_token_size(&tenant_id);
assert!(initial_size > 0);
tree.evict_tenant(&tenant_id, 0);
let final_size = tree.tenant_token_size(&tenant_id);
assert!(final_size < initial_size);
}
#[test]
fn test_last_tenant_cache_update() {
let tree = TokenTree::new();
let tokens = make_tokens(1, 1);
tree.insert_tokens(&tokens, "tenant1");
tree.insert_tokens(&tokens, "tenant2");
let result1 = tree.match_prefix_with_counts(&tokens);
let first_tenant = result1.tenant.clone();
let result2 = tree.match_prefix_with_counts(&tokens);
assert_eq!(result2.tenant, first_tenant);
}
#[test]
fn test_stale_cache_after_tenant_removal() {
let tree = TokenTree::new();
let tokens = make_tokens(1, 2);
tree.insert_tokens(&tokens, "tenant1");
tree.insert_tokens(&tokens, "tenant2");
let _ = tree.match_prefix_with_counts(&tokens);
let tenant1_id: TenantId = Arc::from("tenant1");
tree.evict_tenant(&tenant1_id, 0);
let result = tree.match_prefix_with_counts(&tokens);
assert_eq!(result.matched_token_count, 2 * PAGE_SIZE);
}
#[test]
fn test_token_count_consistency_after_operations() {
let tree = TokenTree::new();
let tokens1 = make_tokens(1, 3);
let tokens2 = make_tokens(1, 5);
tree.insert_tokens(&tokens1, "tenant1");
tree.insert_tokens(&tokens2, "tenant1");
let tenant1_id: TenantId = Arc::from("tenant1");
let count1 = tree.tenant_token_size(&tenant1_id);
assert!(count1 >= PAGE_SIZE);
tree.evict_tenant(&tenant1_id, count1 / 2);
let count2 = tree.tenant_token_size(&tenant1_id);
assert!(count2 <= count1);
let tokens3 = make_tokens(2000, 2);
tree.insert_tokens(&tokens3, "tenant1");
let count3 = tree.tenant_token_size(&tenant1_id);
assert!(count3 >= count2);
}
#[test]
fn test_tree_structure_integrity_after_stress() {
let tree = Arc::new(TokenTree::new());
let num_threads = 8;
let mut handles = vec![];
for t in 0..num_threads {
let tree = Arc::clone(&tree);
handles.push(thread::spawn(move || {
for i in 0..200 {
let base = (t * 10000000 + i * 1000) as u32;
let tokens = make_tokens(base, 2);
tree.insert_tokens(&tokens, &format!("tenant{}", t));
}
}));
}
for handle in handles {
handle.join().unwrap();
}
for t in 0..num_threads {
for i in 0..10 {
let base = (t * 10000000 + i * 1000) as u32;
let tokens = make_tokens(base, 2);
let result = tree.match_prefix_with_counts(&tokens);
assert_eq!(result.matched_token_count, 2 * PAGE_SIZE);
}
}
}
#[test]
fn test_very_long_sequences() {
let tree = TokenTree::new();
let long_seq = make_tokens(1, 64);
tree.insert_tokens(&long_seq, "tenant1");
let result = tree.match_prefix_with_counts(&long_seq);
assert_eq!(result.matched_token_count, 64 * PAGE_SIZE);
assert_eq!(result.tenant.as_ref(), "tenant1");
let prefix = make_tokens(1, 32);
let result = tree.match_prefix_with_counts(&prefix);
assert_eq!(result.matched_token_count, 32 * PAGE_SIZE);
}
#[test]
fn test_many_tenants_same_path() {
let tree = TokenTree::new();
let tokens = make_tokens(1, 2);
for i in 0..100 {
tree.insert_tokens(&tokens, &format!("tenant{}", i));
}
let result = tree.match_prefix_with_counts(&tokens);
assert_eq!(result.matched_token_count, 2 * PAGE_SIZE);
let counts = tree.get_tenant_token_counts();
assert!(!counts.is_empty()); }
#[test]
fn test_token_id_edge_values() {
let tree = TokenTree::new();
let mut zeros_page: Vec<TokenId> = (0..PAGE_SIZE as u32).collect();
zeros_page[0] = 0;
tree.insert_tokens(&zeros_page, "tenant1");
let mut max_page: Vec<TokenId> = (0..PAGE_SIZE as u32).collect();
max_page[0] = u32::MAX;
tree.insert_tokens(&max_page, "tenant2");
let mut mixed_page: Vec<TokenId> = (0..PAGE_SIZE as u32).collect();
mixed_page[0] = 0;
mixed_page[1] = u32::MAX;
tree.insert_tokens(&mixed_page, "tenant3");
let (matched, tenant) = tree.prefix_match_legacy(&zeros_page);
assert_eq!(matched.len(), PAGE_SIZE);
assert!(tenant == "tenant1" || tenant == "tenant3");
let (matched, tenant) = tree.prefix_match_legacy(&max_page);
assert_eq!(matched.len(), PAGE_SIZE);
assert_eq!(tenant, "tenant2");
}
#[test]
fn test_hit_ratio_calculation() {
use crate::MatchResult;
let tree = TokenTree::new();
let one_page = make_tokens(1, 1); tree.insert_tokens(&one_page, "tenant1");
let result = tree.match_prefix_with_counts(&one_page);
assert!((result.hit_ratio() - 1.0).abs() < 0.001);
let two_pages = make_tokens(1, 2);
let result = tree.match_prefix_with_counts(&two_pages);
assert!((result.hit_ratio() - 0.5).abs() < 0.001);
let no_match = make_tokens(1000, 1);
let result = tree.match_prefix_with_counts(&no_match);
assert_eq!(result.hit_ratio(), 0.0);
}
#[test]
fn test_reset_via_trait() {
use crate::RadixTree;
let tree = TokenTree::new();
tree.insert_tokens(&make_tokens(1, 1), "tenant1");
tree.insert_tokens(&make_tokens(100, 1), "tenant2");
assert!(!tree.get_tenant_token_counts().is_empty());
RadixTree::reset(&tree);
assert!(tree.get_tenant_token_counts().is_empty());
}
#[test]
fn test_eviction_policy_lru() {
let tree = TokenTree::with_policy(EvictionPolicy::Lru);
let tokens1 = make_tokens(1, 1);
let tokens2 = make_tokens(100, 1);
let tokens3 = make_tokens(200, 1);
tree.insert_tokens(&tokens1, "tenant1"); tree.insert_tokens(&tokens2, "tenant1");
tree.insert_tokens(&tokens3, "tenant1");
let _ = tree.match_prefix_with_counts(&tokens2);
tree.evict_tenant(&Arc::from("tenant1"), 2 * PAGE_SIZE);
let result = tree.match_prefix_with_counts(&tokens1);
assert_eq!(
result.matched_token_count, 0,
"tokens1 should be evicted (oldest)"
);
let result = tree.match_prefix_with_counts(&tokens2);
assert_eq!(
result.matched_token_count, PAGE_SIZE,
"tokens2 should remain"
);
let result = tree.match_prefix_with_counts(&tokens3);
assert_eq!(
result.matched_token_count, PAGE_SIZE,
"tokens3 should remain"
);
}
#[test]
fn test_eviction_policy_mru() {
let tree = TokenTree::with_policy(EvictionPolicy::Mru);
let tokens1 = make_tokens(1, 1);
let tokens2 = make_tokens(100, 1);
let tokens3 = make_tokens(200, 1);
tree.insert_tokens(&tokens1, "tenant1"); tree.insert_tokens(&tokens2, "tenant1");
tree.insert_tokens(&tokens3, "tenant1");
tree.evict_tenant(&Arc::from("tenant1"), 2 * PAGE_SIZE);
let result = tree.match_prefix_with_counts(&tokens3);
assert_eq!(
result.matched_token_count, 0,
"tokens3 should be evicted (newest)"
);
let result = tree.match_prefix_with_counts(&tokens1);
assert_eq!(
result.matched_token_count, PAGE_SIZE,
"tokens1 should remain"
);
}
#[test]
fn test_eviction_policy_fifo() {
let tree = TokenTree::with_policy(EvictionPolicy::Fifo);
let tokens1 = make_tokens(1, 1);
let tokens2 = make_tokens(100, 1);
let tokens3 = make_tokens(200, 1);
tree.insert_tokens(&tokens1, "tenant1"); tree.insert_tokens(&tokens2, "tenant1");
tree.insert_tokens(&tokens3, "tenant1");
for _ in 0..10 {
let _ = tree.match_prefix_with_counts(&tokens1);
}
tree.evict_tenant(&Arc::from("tenant1"), 2 * PAGE_SIZE);
let result = tree.match_prefix_with_counts(&tokens1);
assert_eq!(
result.matched_token_count, 0,
"tokens1 should be evicted (oldest creation)"
);
}
#[test]
fn test_eviction_policy_filo() {
let tree = TokenTree::with_policy(EvictionPolicy::Filo);
let tokens1 = make_tokens(1, 1);
let tokens2 = make_tokens(100, 1);
let tokens3 = make_tokens(200, 1);
tree.insert_tokens(&tokens1, "tenant1"); tree.insert_tokens(&tokens2, "tenant1");
tree.insert_tokens(&tokens3, "tenant1");
tree.evict_tenant(&Arc::from("tenant1"), 2 * PAGE_SIZE);
let result = tree.match_prefix_with_counts(&tokens3);
assert_eq!(
result.matched_token_count, 0,
"tokens3 should be evicted (newest creation)"
);
let result = tree.match_prefix_with_counts(&tokens1);
assert_eq!(
result.matched_token_count, PAGE_SIZE,
"tokens1 should remain"
);
}
#[test]
fn test_eviction_policy_lfu() {
let tree = TokenTree::with_policy(EvictionPolicy::Lfu);
let tokens1 = make_tokens(1, 1);
let tokens2 = make_tokens(100, 1);
let tokens3 = make_tokens(200, 1);
tree.insert_tokens(&tokens1, "tenant1");
tree.insert_tokens(&tokens2, "tenant1");
tree.insert_tokens(&tokens3, "tenant1");
for _ in 0..20 {
let _ = tree.match_prefix_with_counts(&tokens1);
}
for _ in 0..5 {
let _ = tree.match_prefix_with_counts(&tokens3);
}
tree.evict_tenant(&Arc::from("tenant1"), 2 * PAGE_SIZE);
let result = tree.match_prefix_with_counts(&tokens2);
assert_eq!(
result.matched_token_count, 0,
"tokens2 should be evicted (lowest hit count)"
);
let result = tree.match_prefix_with_counts(&tokens1);
assert_eq!(
result.matched_token_count, PAGE_SIZE,
"tokens1 should remain (high hit count)"
);
}
#[test]
fn test_eviction_policy_enum_default() {
assert_eq!(EvictionPolicy::default(), EvictionPolicy::Lru);
}
#[test]
fn test_tree_with_policy_getter() {
let tree_lru = TokenTree::with_policy(EvictionPolicy::Lru);
assert_eq!(tree_lru.eviction_policy(), EvictionPolicy::Lru);
let tree_lfu = TokenTree::with_policy(EvictionPolicy::Lfu);
assert_eq!(tree_lfu.eviction_policy(), EvictionPolicy::Lfu);
let tree_fifo = TokenTree::with_policy(EvictionPolicy::Fifo);
assert_eq!(tree_fifo.eviction_policy(), EvictionPolicy::Fifo);
}
#[test]
fn test_tree_with_config() {
let tree = TokenTree::with_config(PAGE_SIZE, EvictionPolicy::Lfu);
assert_eq!(tree.page_size(), PAGE_SIZE);
assert_eq!(tree.eviction_policy(), EvictionPolicy::Lfu);
let tree_default = TokenTree::new();
assert_eq!(tree_default.page_size(), 16);
assert_eq!(tree_default.eviction_policy(), EvictionPolicy::Lru);
let tree_policy = TokenTree::with_policy(EvictionPolicy::Fifo);
assert_eq!(tree_policy.page_size(), PAGE_SIZE);
assert_eq!(tree_policy.eviction_policy(), EvictionPolicy::Fifo);
}
#[test]
#[should_panic(expected = "TokenTree currently only supports page_size=16")]
fn test_tree_with_config_invalid_page_size() {
let _tree = TokenTree::with_config(32, EvictionPolicy::Lru);
}
}