use std::{
panic::{self, AssertUnwindSafe},
sync::Arc,
thread,
time::{Duration, Instant},
};
use parking_lot::{Condvar, Mutex};
use super::{
vector_manager::{INDEX_SIZE_BYTES, VectorManager},
vector_types::VectorSetFlags,
};
#[derive(Default)]
pub struct CleanupGate {
paused: Mutex<bool>,
signal: Condvar,
}
impl CleanupGate {
pub fn new() -> Self {
Self::default()
}
pub fn wait(&self) {
let mut paused = self.paused.lock();
while *paused {
self.signal.wait(&mut paused);
}
}
pub fn is_paused(&self) -> bool {
*self.paused.lock()
}
fn set_paused(&self, value: bool) {
let mut paused = self.paused.lock();
*paused = value;
if !value {
self.signal.notify_all();
}
}
}
#[derive(Default)]
pub struct CleanupRuntime {
running: Mutex<usize>,
signal: Condvar,
}
impl CleanupRuntime {
pub fn new() -> Self {
Self::default()
}
fn on_start(&self) {
*self.running.lock() += 1;
}
fn on_stop(&self) {
let mut running = self.running.lock();
*running = running.saturating_sub(1);
if *running == 0 {
self.signal.notify_all();
}
}
pub fn is_quiescent(&self) -> bool {
*self.running.lock() == 0
}
}
impl VectorManager {
pub fn on_exception(&self, context: u64, error: &str) {
log::error!("During Vector Set cleanup for context {context}: {error}");
}
pub fn on_start(&self) {
self.cleanup_runtime.on_start();
}
pub fn on_stop(&self) {
self.cleanup_runtime.on_stop();
}
pub fn run_cleanup_task_async(self: &Arc<Self>) {
let manager = Arc::clone(self);
let _ = thread::Builder::new()
.name("vector-cleanup".into())
.spawn(move || {
manager.on_start();
let channel = &manager.cleanup_task_channel;
while channel.wait_to_read(250) {
manager.cleanup_gate.wait();
let Some(context) = channel.try_read() else {
continue;
};
if let Err(e) = panic::catch_unwind(AssertUnwindSafe(|| {
manager.process_cleanup(context);
})) {
manager.on_exception(context, &format!("panic: {e:?}"));
}
}
manager.on_stop();
})
.expect("清理线程创建失败");
}
pub fn run_request_cleanup_task_async(self: &Arc<Self>) {
let manager = Arc::clone(self);
let _ = thread::Builder::new()
.name("vector-request-cleanup".into())
.spawn(move || {
manager.on_start();
let channel = &manager.request_cleanup_task_channel;
while channel.wait_to_read(250) {
let Some(context) = channel.try_read() else {
continue;
};
manager.process_request_cleanup(context);
}
manager.on_stop();
})
.expect("请求清理线程创建失败");
}
pub fn run_request_drop_task_async(self: &Arc<Self>) {
let manager = Arc::clone(self);
let _ = thread::Builder::new()
.name("vector-request-drop".into())
.spawn(move || {
manager.on_start();
let channel = &manager.request_drop_task_channel;
while channel.wait_to_read(250) {
if channel.try_read().is_none() {
continue;
}
channel.drain_pending();
manager.process_request_drop_once();
}
manager.on_stop();
})
.expect("请求丢弃线程创建失败");
}
pub fn pause_cleanup_async(&self) {
self.cleanup_gate.set_paused(true);
}
pub fn resume_cleanup(&self) {
self.cleanup_gate.set_paused(false);
}
pub fn drop_requested(&self, key: &[u8]) -> bool {
self.requested_drops.lock().contains_key(key)
}
pub fn wait_for_disk_ann_index_drop(&self, key: &[u8]) {
while self.drop_requested(key) {
thread::yield_now();
}
}
pub fn wait_for_quiescence(&self, timeout_ms: u64) -> bool {
let deadline = Instant::now() + Duration::from_millis(timeout_ms);
let mut running = self.cleanup_runtime.running.lock();
while *running > 0 {
let remain = deadline.saturating_duration_since(Instant::now());
if remain.is_zero() {
return false;
}
self.cleanup_runtime.signal.wait_for(&mut running, remain);
}
true
}
pub fn vector_set_potentially_deleted(&self, key: &[u8], value: &[u8]) {
let Some(index) = super::vector_manager__index::Index::from_bytes(value) else {
log::error!(
"Unexpected index size on Vector Set during compaction, {} != {}",
value.len(),
INDEX_SIZE_BYTES
);
return;
};
if index.flags.contains(VectorSetFlags::SUPPRESS_CLEANUP) {
return;
}
self
.potentially_deleted
.lock()
.insert(key.to_vec(), index.context);
}
pub fn checkpoint_completed(&self) {
let entries: Vec<(Vec<u8>, u64)> = self.potentially_deleted.lock().drain().collect();
for (key, context) in entries {
let needs_delete = match self.read_stored_index(&key) {
None => true,
Some(bytes) => super::vector_manager__index::Index::from_bytes(&bytes)
.is_none_or(|live| live.context != context),
};
if needs_delete && !self.request_cleanup_task_channel.try_publish(context) {
log::warn!("Could not request delete of abandoned Vector Set");
}
}
}
pub fn queue_cleanups(&self) -> usize {
let metas = self.context_metadatas.lock();
let mut queued = 0;
for (i, meta) in metas.iter().enumerate() {
if let Some(contexts) = meta.get_need_cleanup() {
let offset = Self::offset_for_context_metadata(i);
for ctx in contexts {
if self
.cleanup_task_channel
.try_publish(offset + u64::from(ctx))
{
queued += 1;
}
}
}
}
queued
}
pub fn process_cleanup(&self, context: u64) {
let (context_index, context_value) = Self::decompose_context(context);
let mut metas = self.context_metadatas.lock();
let Some(meta) = metas.get_mut(context_index) else {
return;
};
let allow_zero = context_index != 0;
if meta.is_cleaning_up(allow_zero, context_value) {
meta.finished_cleaning_up(allow_zero, context_value);
drop(metas);
self.dirty_context_metadatas.lock().insert(context_index);
self.update_context_metadata();
}
}
pub fn process_request_cleanup(&self, context: u64) {
self.service.drop_index(context);
let (context_index, context_value) = Self::decompose_context(context);
let mut metas = self.context_metadatas.lock();
if let Some(meta) = metas.get_mut(context_index) {
let allow_zero = context_index != 0;
if meta.is_in_use(allow_zero, context_value)
&& !meta.is_cleaning_up(allow_zero, context_value)
{
meta.mark_cleaning_up(allow_zero, context_value);
drop(metas);
self.dirty_context_metadatas.lock().insert(context_index);
self.update_context_metadata();
let _ = self.cleanup_task_channel.try_publish(context);
}
}
}
fn perform_drop(&self, context: u64) {
self.service.drop_index(context);
}
pub fn process_request_drop_once(&self) {
let keys: Vec<Vec<u8>> = self.requested_drops.lock().keys().cloned().collect();
for key in keys {
if let Some(context) = self.requested_drops.lock().remove(&key) {
let _guard = self.vector_set_locks.acquire_exclusive(&key);
self.perform_drop(context);
}
}
}
}
#[cfg(test)]
mod tests {
use super::{
super::vector_manager::{CONTEXT_METADATA_SIZE, VectorManager, VectorManagerOptions},
*,
};
#[test]
fn gate_pauses_and_resumes() {
let gate = CleanupGate::new();
assert!(!gate.is_paused());
gate.wait();
gate.set_paused(true);
assert!(gate.is_paused());
gate.set_paused(false);
gate.wait();
assert!(!gate.is_paused());
}
#[test]
fn quiescence_lifecycle() {
let manager = VectorManager::new(VectorManagerOptions {
is_enabled: true,
..Default::default()
});
assert!(manager.cleanup_runtime.is_quiescent());
manager.on_start();
assert!(!manager.wait_for_quiescence(10));
manager.on_stop();
assert!(manager.wait_for_quiescence(10));
}
#[test]
fn request_cleanup_to_cleanup_pipeline() {
let manager = VectorManager::new(VectorManagerOptions {
is_enabled: true,
..Default::default()
});
let context = manager.next_vector_set_context(2).unwrap();
assert!(manager.get_context_state(context).0);
manager.process_request_cleanup(context);
let (in_use, cleaning_up, _) = manager.get_context_state(context);
assert!(in_use && cleaning_up);
assert_eq!(manager.cleanup_task_channel.try_read(), Some(context));
manager.process_cleanup(context);
assert_eq!(manager.get_context_state(context), (false, false, false));
}
#[test]
fn queue_cleanups_walks_metadata() {
let manager = VectorManager::new(VectorManagerOptions {
is_enabled: true,
..Default::default()
});
let context = manager.next_vector_set_context(3).unwrap();
{
let (context_index, context_value) = VectorManager::decompose_context(context);
let mut metas = manager.context_metadatas.lock();
metas[context_index].mark_cleaning_up(context_index != 0, context_value);
}
assert_eq!(manager.queue_cleanups(), 1);
assert_eq!(manager.cleanup_task_channel.try_read(), Some(context));
assert_eq!(manager.queue_cleanups(), 1);
manager.process_cleanup(context);
assert_eq!(manager.queue_cleanups(), 0);
}
#[test]
fn drop_request_wait_and_checkpoint_flow() {
let manager = VectorManager::new(VectorManagerOptions {
is_enabled: true,
..Default::default()
});
manager.service.create_index(
9,
1,
0,
super::super::vector_types::VectorQuantType::NoQuant,
8,
2,
super::super::vector_types::VectorDistanceMetricType::L2,
);
let key = b"drop-me".to_vec();
let record = super::super::vector_manager__index::Index {
context: 9,
index_ptr: 1,
dimensions: 1,
reduce_dims: 0,
num_links: 2,
build_exploration_factor: 8,
quant_type: super::super::vector_types::VectorQuantType::NoQuant,
distance_metric: super::super::vector_types::VectorDistanceMetricType::L2,
flags: super::VectorSetFlags::NONE,
};
manager.vector_set_potentially_deleted(&key, &record.to_bytes());
assert!(manager.potentially_deleted.lock().contains_key(&key));
manager.pause_cleanup_async();
assert!(manager.cleanup_gate.is_paused());
manager.requested_drops.lock().insert(key.clone(), 9);
assert!(manager.drop_requested(&key));
manager.process_request_drop_once();
manager.wait_for_disk_ann_index_drop(&key);
assert_eq!(manager.service.card(9), 0);
manager.write_stored_index(&key, &record.to_bytes());
manager.checkpoint_completed();
assert!(manager.potentially_deleted.lock().is_empty());
assert!(!manager.request_cleanup_task_channel.has_pending());
manager.vector_set_potentially_deleted(&key, &record.to_bytes());
manager.remove_stored_index(&key);
manager.checkpoint_completed();
assert_eq!(manager.request_cleanup_task_channel.try_read(), Some(9));
manager.on_exception(0, "synthetic");
}
#[test]
fn context_metadata_size_alias_matches() {
assert_eq!(CONTEXT_METADATA_SIZE, 160);
assert_eq!(super::INDEX_SIZE_BYTES, 56);
}
}