use std::sync::Arc;
use std::time::Duration;
use anyhow::Result;
use derive_builder::Builder;
use futures::StreamExt;
use crate::InstanceId;
use crate::pubsub::{StubBus, Subscriber, Subscription};
use kvbm_logical::blocks::BlockMetadata;
use kvbm_logical::events::{
BatchingConfig, EventsManager, KvbmCacheEvents, KvbmCacheEventsPublisher,
};
use kvbm_logical::manager::BlockManager;
use super::managers::TestManagerBuilder;
#[derive(Builder)]
#[builder(setter(into, strip_option), build_fn(skip), pattern = "owned")]
#[allow(dead_code)] pub struct EventsPipelineConfig {
#[builder(default)]
instance_id: Option<InstanceId>,
#[builder(default = "Duration::from_millis(50)")]
batching_window: Duration,
#[builder(default = "\"kvbm.events\".to_string()")]
subject: String,
}
impl EventsPipelineConfigBuilder {
pub async fn build_async(self) -> Result<EventsPipelineFixture> {
let instance_id = self
.instance_id
.flatten()
.unwrap_or_else(InstanceId::new_v4);
let batching_window = self.batching_window.unwrap_or(Duration::from_millis(50));
let subject = self.subject.unwrap_or_else(|| "kvbm.events".to_string());
let events_manager = Arc::new(EventsManager::builder().build());
let bus = StubBus::default();
let publisher_arc = Arc::new(bus.publisher());
let subscriber = bus.subscriber();
let subscription = subscriber.subscribe(&subject).await?;
let publisher = KvbmCacheEventsPublisher::builder()
.instance_id(instance_id.as_u128())
.event_stream(events_manager.subscribe())
.publisher(publisher_arc)
.batching_config(BatchingConfig::default().with_window(batching_window))
.subject(&subject)
.build()?;
Ok(EventsPipelineFixture {
events_manager,
subscription,
publisher,
bus,
instance_id,
subject,
})
}
}
pub struct EventsPipelineFixture {
pub events_manager: Arc<EventsManager>,
pub subscription: Subscription,
#[allow(dead_code)]
publisher: KvbmCacheEventsPublisher,
#[allow(dead_code)]
bus: StubBus,
pub instance_id: InstanceId,
pub subject: String,
}
impl EventsPipelineFixture {
pub fn builder() -> EventsPipelineConfigBuilder {
EventsPipelineConfigBuilder::default()
}
pub fn create_manager<M: BlockMetadata>(
&self,
block_count: usize,
block_size: usize,
) -> BlockManager<M> {
TestManagerBuilder::<M>::new()
.block_count(block_count)
.block_size(block_size)
.events_manager(self.events_manager.clone())
.build()
}
pub async fn receive_batch(&mut self, timeout: Duration) -> Option<KvbmCacheEvents> {
match tokio::time::timeout(timeout, self.subscription.next()).await {
Ok(Some(msg)) => rmp_serde::from_slice(&msg.payload).ok(),
_ => None,
}
}
pub async fn receive_batch_default(&mut self) -> Option<KvbmCacheEvents> {
self.receive_batch(Duration::from_millis(500)).await
}
pub async fn flush_and_receive(
&mut self,
batching_window: Duration,
) -> Option<KvbmCacheEvents> {
tokio::time::sleep(batching_window + Duration::from_millis(50)).await;
self.receive_batch(Duration::from_millis(500)).await
}
pub fn events_manager(&self) -> &Arc<EventsManager> {
&self.events_manager
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::time::Duration;
use futures::StreamExt;
use super::super::managers::TestManagerBuilder;
use super::super::token_blocks;
use crate::G1;
use crate::pubsub::{StubBus, Subscriber};
use kvbm_logical::events::{
BatchingConfig, EventsManager, KvCacheEvents, KvbmCacheEvents, KvbmCacheEventsPublisher,
PowerOfTwoPolicy,
};
#[tokio::test]
async fn test_full_events_pipeline_with_block_manager() {
let events_manager = Arc::new(EventsManager::builder().build());
let block_count = 100;
let block_size = 4;
let manager = TestManagerBuilder::<G1>::new()
.block_count(block_count)
.block_size(block_size)
.events_manager(events_manager.clone())
.build();
let bus = StubBus::default();
let publisher = Arc::new(bus.publisher());
let subscriber = bus.subscriber();
let mut subscription = subscriber
.subscribe("kvbm.events")
.await
.expect("Should subscribe");
let _events_publisher = KvbmCacheEventsPublisher::builder()
.instance_id(12345)
.event_stream(events_manager.subscribe())
.publisher(publisher)
.batching_config(BatchingConfig::default().with_window(Duration::from_millis(50)))
.subject("kvbm.events")
.build()
.expect("Should build publisher");
let num_blocks = 5;
let token_sequence = token_blocks::create_token_sequence(num_blocks, block_size, 0);
let allocated_blocks = manager
.allocate_blocks(num_blocks)
.expect("Should allocate blocks");
let complete_blocks: Vec<_> = allocated_blocks
.into_iter()
.zip(token_sequence.blocks())
.map(|(block, token_block)| block.complete(token_block).expect("Should complete"))
.collect();
let _immutable_blocks = manager.register_blocks(complete_blocks);
tokio::time::sleep(Duration::from_millis(100)).await;
let msg = tokio::time::timeout(Duration::from_millis(500), subscription.next())
.await
.expect("Should receive within timeout")
.expect("Should have message");
let batch: KvbmCacheEvents =
rmp_serde::from_slice(&msg.payload).expect("Should deserialize");
assert_eq!(batch.instance_id, 12345);
match &batch.events {
KvCacheEvents::Create(hashes) => {
assert_eq!(hashes.len(), num_blocks);
for i in 1..hashes.len() {
assert!(
hashes[i - 1].position() <= hashes[i].position(),
"Create events should be sorted ascending"
);
}
}
KvCacheEvents::Remove(_) => panic!("Expected Create events, got Remove"),
KvCacheEvents::Shutdown => panic!("Expected Create events, got Shutdown"),
}
}
#[tokio::test]
async fn test_events_with_power_of_two_policy() {
let events_manager = Arc::new(
EventsManager::builder()
.policy(Arc::new(PowerOfTwoPolicy::new()))
.build(),
);
let block_size = 4;
let manager = TestManagerBuilder::<G1>::new()
.block_count(100)
.block_size(block_size)
.events_manager(events_manager.clone())
.build();
let bus = StubBus::default();
let publisher = Arc::new(bus.publisher());
let subscriber = bus.subscriber();
let mut subscription = subscriber
.subscribe("kvbm.events")
.await
.expect("Should subscribe");
let _events_publisher = KvbmCacheEventsPublisher::builder()
.instance_id(12345)
.event_stream(events_manager.subscribe())
.publisher(publisher)
.batching_config(BatchingConfig::default().with_window(Duration::from_millis(50)))
.subject("kvbm.events")
.build()
.expect("Should build publisher");
let num_blocks = 32;
let token_sequence = token_blocks::create_token_sequence(num_blocks, block_size, 0);
let allocated_blocks = manager
.allocate_blocks(num_blocks)
.expect("Should allocate blocks");
let complete_blocks: Vec<_> = allocated_blocks
.into_iter()
.zip(token_sequence.blocks())
.map(|(block, token_block)| block.complete(token_block).expect("Should complete"))
.collect();
let _immutable_blocks = manager.register_blocks(complete_blocks);
tokio::time::sleep(Duration::from_millis(100)).await;
let msg = tokio::time::timeout(Duration::from_millis(500), subscription.next())
.await
.expect("Should receive within timeout")
.expect("Should have message");
let batch: KvbmCacheEvents =
rmp_serde::from_slice(&msg.payload).expect("Should deserialize");
match &batch.events {
KvCacheEvents::Create(hashes) => {
for hash in hashes {
let pos = hash.position();
assert!(
pos.is_power_of_two(),
"Position {} should be power of 2",
pos
);
}
}
KvCacheEvents::Remove(_) => panic!("Expected Create events"),
KvCacheEvents::Shutdown => panic!("Expected Create events"),
}
}
#[tokio::test]
async fn test_remove_events_on_pool_eviction() {
let events_manager = Arc::new(EventsManager::builder().build());
let block_count = 10;
let block_size = 4;
let manager = TestManagerBuilder::<G1>::new()
.block_count(block_count)
.block_size(block_size)
.events_manager(events_manager.clone())
.build();
let bus = StubBus::default();
let publisher = Arc::new(bus.publisher());
let subscriber = bus.subscriber();
let mut subscription = subscriber
.subscribe("kvbm.events")
.await
.expect("Should subscribe");
let _events_publisher = KvbmCacheEventsPublisher::builder()
.instance_id(12345)
.event_stream(events_manager.subscribe())
.publisher(publisher)
.batching_config(BatchingConfig::default().with_window(Duration::from_millis(50)))
.subject("kvbm.events")
.build()
.expect("Should build publisher");
let first_batch_size = block_count;
let token_sequence1 = token_blocks::create_token_sequence(first_batch_size, block_size, 0);
let allocated_blocks = manager
.allocate_blocks(first_batch_size)
.expect("Should allocate blocks");
let complete_blocks: Vec<_> = allocated_blocks
.into_iter()
.zip(token_sequence1.blocks())
.map(|(block, token_block)| block.complete(token_block).expect("Should complete"))
.collect();
let _first_batch = manager.register_blocks(complete_blocks);
tokio::time::sleep(Duration::from_millis(100)).await;
let msg = tokio::time::timeout(Duration::from_millis(500), subscription.next())
.await
.expect("Should receive Create batch")
.expect("Should have message");
let batch: KvbmCacheEvents = rmp_serde::from_slice(&msg.payload).unwrap();
assert!(
matches!(batch.events, KvCacheEvents::Create(ref h) if h.len() == first_batch_size)
);
drop(_first_batch);
let second_batch_size = block_count;
let token_sequence2 =
token_blocks::create_token_sequence(second_batch_size, block_size, 1000);
let allocated_blocks = manager
.allocate_blocks(second_batch_size)
.expect("Should allocate blocks for second batch");
let complete_blocks: Vec<_> = allocated_blocks
.into_iter()
.zip(token_sequence2.blocks())
.map(|(block, token_block)| block.complete(token_block).expect("Should complete"))
.collect();
let _second_batch = manager.register_blocks(complete_blocks);
tokio::time::sleep(Duration::from_millis(100)).await;
let mut received_creates = 0;
let mut received_removes = 0;
while let Ok(Some(msg)) =
tokio::time::timeout(Duration::from_millis(200), subscription.next()).await
{
let batch: KvbmCacheEvents = rmp_serde::from_slice(&msg.payload).unwrap();
match batch.events {
KvCacheEvents::Create(hashes) => received_creates += hashes.len(),
KvCacheEvents::Remove(hashes) => received_removes += hashes.len(),
KvCacheEvents::Shutdown => {} }
}
assert_eq!(
received_creates, second_batch_size,
"Should receive Create events for second batch"
);
assert_eq!(
received_removes, first_batch_size,
"Should receive Remove events for evicted blocks"
);
}
}