use std::future::Future;
use super::{AnchorCapableSyncIndexer, AnchorRef, AnchorTask, KvRouterError, ShardSizeSnapshot};
use crate::indexer::{KvIndexerInterface, ThreadPoolIndexer};
use crate::protocols::*;
pub trait AsyncShardHandle: Send + Sync + 'static {
fn apply_event(&self, event: RouterEvent) -> impl Future<Output = ()> + Send;
fn enqueue_anchor(
&self,
worker: WorkerWithDpRank,
anchor: AnchorTask,
) -> Result<(), KvRouterError>;
fn find_matches_from_anchor<'a>(
&'a self,
anchor: AnchorRef,
suffix: &'a [LocalBlockHash],
) -> impl Future<Output = Result<OverlapScores, KvRouterError>> + Send + 'a;
fn remove_worker(&self, worker_id: WorkerId) -> impl Future<Output = ()> + Send;
fn remove_worker_dp_rank(
&self,
worker_id: WorkerId,
dp_rank: DpRank,
) -> impl Future<Output = ()> + Send;
fn dump_events(&self) -> impl Future<Output = Result<Vec<RouterEvent>, KvRouterError>> + Send;
fn shard_sizes(&self) -> impl Future<Output = ShardSizeSnapshot> + Send;
fn flush(&self) -> impl Future<Output = usize> + Send;
fn shutdown(&self);
fn node_edge_lengths(&self) -> Vec<usize>;
}
impl<T: AnchorCapableSyncIndexer> AsyncShardHandle for ThreadPoolIndexer<T> {
async fn apply_event(&self, event: RouterEvent) {
KvIndexerInterface::apply_event(self, event).await;
}
fn enqueue_anchor(
&self,
worker: WorkerWithDpRank,
anchor: AnchorTask,
) -> Result<(), KvRouterError> {
ThreadPoolIndexer::enqueue_anchor(self, worker, anchor)
}
async fn find_matches_from_anchor<'a>(
&'a self,
anchor: AnchorRef,
suffix: &'a [LocalBlockHash],
) -> Result<OverlapScores, KvRouterError> {
self.backend().find_matches_from_anchor(anchor, suffix)
}
async fn remove_worker(&self, worker_id: WorkerId) {
KvIndexerInterface::remove_worker(self, worker_id).await;
}
async fn remove_worker_dp_rank(&self, worker_id: WorkerId, dp_rank: DpRank) {
KvIndexerInterface::remove_worker_dp_rank(self, worker_id, dp_rank).await;
}
async fn dump_events(&self) -> Result<Vec<RouterEvent>, KvRouterError> {
KvIndexerInterface::dump_events(self).await
}
async fn shard_sizes(&self) -> ShardSizeSnapshot {
KvIndexerInterface::shard_sizes(self)
.await
.into_iter()
.next()
.unwrap_or(ShardSizeSnapshot {
shard_idx: 0,
worker_count: 0,
block_count: 0,
node_count: 0,
})
}
async fn flush(&self) -> usize {
KvIndexerInterface::flush(self).await
}
fn shutdown(&self) {
KvIndexerInterface::shutdown(self);
}
fn node_edge_lengths(&self) -> Vec<usize> {
KvIndexerInterface::node_edge_lengths(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::indexer::concurrent_radix_tree_compressed::ConcurrentRadixTreeCompressed;
use crate::test_utils::router_event;
type TestTPI = ThreadPoolIndexer<ConcurrentRadixTreeCompressed>;
fn make_tpi() -> TestTPI {
ThreadPoolIndexer::new(ConcurrentRadixTreeCompressed::new(), 2, 32)
}
fn store_event_for(worker_id: u64, dp_rank: u32, values: &[u64]) -> RouterEvent {
let locals: Vec<LocalBlockHash> = values.iter().copied().map(LocalBlockHash).collect();
let seq_hashes = crate::protocols::compute_seq_hash_for_block(&locals);
router_event(
worker_id,
0,
dp_rank,
KvCacheEventData::Stored(KvCacheStoreData {
parent_hash: None,
start_position: None,
blocks: locals
.iter()
.zip(seq_hashes.iter())
.map(|(&th, &sh)| KvCacheStoredBlockData {
tokens_hash: th,
block_hash: ExternalSequenceBlockHash(sh),
mm_extra_info: None,
})
.collect(),
}),
)
}
#[tokio::test]
async fn remove_worker_dp_rank_preserves_sibling_dp_ranks() {
let tpi = make_tpi();
AsyncShardHandle::apply_event(&tpi, store_event_for(7, 0, &[1, 2, 3])).await;
AsyncShardHandle::apply_event(&tpi, store_event_for(7, 1, &[4, 5, 6])).await;
AsyncShardHandle::flush(&tpi).await;
let before = AsyncShardHandle::dump_events(&tpi).await.unwrap();
assert!(
before.iter().any(|e| e.event.dp_rank == 0),
"dp_rank=0 should have events before removal"
);
assert!(
before.iter().any(|e| e.event.dp_rank == 1),
"dp_rank=1 should have events before removal"
);
AsyncShardHandle::remove_worker_dp_rank(&tpi, 7, 0).await;
AsyncShardHandle::flush(&tpi).await;
let after = AsyncShardHandle::dump_events(&tpi).await.unwrap();
assert!(
!after.iter().any(|e| e.event.dp_rank == 0),
"dp_rank=0 should be gone after remove_worker_dp_rank"
);
assert!(
after.iter().any(|e| e.event.dp_rank == 1),
"dp_rank=1 should still be present after removing dp_rank=0 only"
);
}
}