use std::{
collections::HashMap,
sync::{
atomic::{AtomicU64, Ordering},
Arc,
},
thread,
time::Duration,
};
use anyhow::Error;
use thingvellir::{
service_builder, Commit, CommitToUpstream, DataCommitRequest, DataLoadRequest,
LoadFromUpstream, MutableServiceHandle, ServiceData, ShardShutdownStats, ShutdownResult,
};
#[derive(Clone, Default, Debug)]
struct CountersUpstream {
num_commits: Arc<AtomicU64>,
upstream_data: HashMap<u64, CounterData>,
}
#[derive(Clone, Default, Copy, Debug)]
struct CounterData {
count: u64,
}
impl ServiceData for CounterData {}
impl CommitToUpstream<u64, CounterData> for CountersUpstream {
fn commit(&mut self, request: DataCommitRequest<u64, CounterData>) {
println!(
"[COMMIT TO UPSTREAM] key {} committed with count {}",
request.key(),
request.data().count
);
self.upstream_data.insert(*request.key(), *request.data());
self.num_commits.fetch_add(1, Ordering::SeqCst);
request.resolve()
}
}
impl LoadFromUpstream<u64, CounterData> for CountersUpstream {
fn load(&mut self, request: DataLoadRequest<u64, CounterData>) {
if let Some(value) = self.upstream_data.get(request.key()) {
println!(
"[LOAD FROM UPSTREAM] key {} loaded with count {}",
request.key(),
value.count
);
request.resolve(*value);
} else {
println!(
"[LOAD FROM UPSTREAM] key {} not found upstream. returning count of 0",
request.key()
);
request.resolve(CounterData { count: 0 })
}
}
}
#[tokio::main]
async fn main() -> Result<(), Error> {
async fn increment_counter(
key: u64,
service: &mut MutableServiceHandle<u64, CounterData>,
) -> Option<u64> {
let exec = service.execute_mut(key, move |counter_data| {
println!(
"Counter {} was {} and is being changed to {}",
key,
counter_data.count,
counter_data.count + 1
);
counter_data.count += 1;
Commit::default(counter_data.count)
});
exec.await.ok()
}
async fn load_counter(
key: u64,
service: &mut MutableServiceHandle<u64, CounterData>,
) -> Option<u64> {
let exec = service.execute(key, |x| x.count);
exec.await.ok()
}
let num_commits = Arc::new(AtomicU64::new(0));
let (mut service, shutdown) = service_builder::<u64, CounterData>(1000).build_mutable(
CountersUpstream {
num_commits: Arc::clone(&num_commits),
upstream_data: HashMap::new(),
},
thingvellir::DefaultCommitPolicy::Within(Duration::from_secs(3)),
);
for key in 1..4 {
for _ in 1..501 {
increment_counter(key, &mut service).await;
}
}
for key in 1..4 {
assert_eq!(
load_counter(key, &mut service)
.await
.unwrap_or_else(|| panic!("No count for key {key}")),
500
);
}
println!("Sleeping for 5 seconds");
thread::sleep(Duration::from_secs(5));
let num_commits_unwrapped = num_commits.load(Ordering::SeqCst);
assert_eq!(num_commits_unwrapped, 3);
for key in 1..4 {
for _ in 1..501 {
increment_counter(key, &mut service).await;
}
}
for key in 1..4 {
assert_eq!(
load_counter(key, &mut service)
.await
.unwrap_or_else(|| panic!("No count for key {key}")),
1000
);
}
let shutdown_result = shutdown.gracefully_shutdown().join().await;
match shutdown_result {
ShutdownResult::GracefullyShutdown(vec) => {
let mut final_stats = ShardShutdownStats::default();
for stats in vec {
final_stats.initial_commits_enqueued += stats.initial_commits_enqueued;
final_stats.commits_completed += stats.commits_completed;
final_stats.loads_completed += stats.loads_completed;
}
println!("Graceful shutdown stats: {:?}", final_stats);
assert_eq!(final_stats.initial_commits_enqueued, 3);
assert_eq!(final_stats.loads_completed, 0);
assert_eq!(final_stats.commits_completed, 3);
}
ShutdownResult::HardShutdown => unreachable!(), ShutdownResult::AlreadyShutdown => unreachable!(), }
let num_commits_unwrapped = num_commits.load(Ordering::SeqCst);
assert_eq!(num_commits_unwrapped, 6);
Ok(())
}