use std::net::{IpAddr, SocketAddr};
use std::sync::Arc;
use std::time::Duration;
use reconcile::{replicated_map::Config, InMemoryNetwork, ReadReplicaMap, ReplicatedMap};
async fn wait_until<F: FnMut() -> bool>(mut f: F) -> bool {
for _ in 0..200 {
tokio::time::sleep(Duration::from_millis(10)).await;
if f() {
return true;
}
}
false
}
macro_rules! assert_until {
( $x:expr ) => {
assert!(wait_until(|| $x).await, stringify!($x))
};
}
#[tokio::test(flavor = "multi_thread")]
async fn read_replica_converges_with_dated_store() {
let port = 8086;
let net = "127.0.0.1/8".parse().unwrap();
let dated_addr = "127.0.0.90".parse().unwrap();
let read_replica_addr = "127.0.0.91".parse().unwrap();
let dated = ReplicatedMap::<String, String>::new(
Config::default()
.with_port(port)
.with_listen_addr(dated_addr)
.with_net(net)
.with_insecure_no_key(),
)
.await
.expect("bind failed");
let read_replica = ReadReplicaMap::<String, String>::new(
Config::default()
.with_port(port)
.with_listen_addr(read_replica_addr)
.with_net(net)
.with_insecure_no_key(),
)
.await
.expect("bind failed")
.with_seed(dated_addr);
for i in 0..50 {
dated.insert(format!("k{i:02}"), format!("v{i:02}"));
}
dated.insert("doomed".to_string(), "to be deleted".to_string());
let dated_task = tokio::spawn(dated.clone().run());
let read_replica_task = tokio::spawn(read_replica.clone().run());
assert_until!(read_replica.get(&"k00".to_string()).as_deref() == Some(&"v00".to_string()));
assert_until!(read_replica.get(&"k49".to_string()).as_deref() == Some(&"v49".to_string()));
assert_until!(
read_replica.get(&"doomed".to_string()).as_deref() == Some(&"to be deleted".to_string())
);
assert_until!(read_replica.fingerprint(..) == dated.value_fingerprint(..));
assert_eq!(read_replica.len(), 51);
dated.insert("late".to_string(), "arrival".to_string());
assert_until!(read_replica.get(&"late".to_string()).as_deref() == Some(&"arrival".to_string()));
dated.remove(&"doomed".to_string());
assert_until!(read_replica.get(&"doomed".to_string()).is_none());
assert_until!(read_replica.fingerprint(..) == dated.value_fingerprint(..));
dated_task.abort();
read_replica_task.abort();
}
#[tokio::test(flavor = "multi_thread")]
async fn read_replica_does_not_block_tombstone_gc() {
let port = 8087;
let net = "127.0.0.1/8".parse().unwrap();
let dated_addr = "127.0.0.92".parse().unwrap();
let read_replica_addr = "127.0.0.93".parse().unwrap();
let dated = ReplicatedMap::<i32, i32>::new(
Config::default()
.with_port(port)
.with_listen_addr(dated_addr)
.with_net(net)
.with_insecure_no_key(),
)
.await
.expect("bind failed")
.with_tombstone_timeout(Duration::from_millis(50));
let read_replica = ReadReplicaMap::<i32, i32>::new(
Config::default()
.with_port(port)
.with_listen_addr(read_replica_addr)
.with_net(net)
.with_insecure_no_key(),
)
.await
.expect("bind failed")
.with_seed(dated_addr);
dated.insert(1, 11);
dated.insert(2, 22);
let dated_task = tokio::spawn(dated.clone().run());
let read_replica_task = tokio::spawn(read_replica.clone().run());
assert_until!(read_replica.get(&1).as_deref() == Some(&11));
assert_until!(read_replica.get(&2).as_deref() == Some(&22));
dated.remove(&1);
let with_tombstone = dated.fingerprint(..);
assert_until!(dated.fingerprint(..) != with_tombstone);
assert_eq!(dated.get(&2).as_deref(), Some(&22));
dated_task.abort();
read_replica_task.abort();
}
#[tokio::test(flavor = "multi_thread")]
async fn read_replica_converges_with_dated_store_over_in_memory_transport() {
let network = InMemoryNetwork::new();
let port = 5300u16;
let net = "127.0.0.1/8".parse().unwrap();
let dated_ip: IpAddr = "127.0.0.94".parse().unwrap();
let read_replica_ip: IpAddr = "127.0.0.95".parse().unwrap();
let config = |ip: IpAddr| {
Config::default()
.with_port(port)
.with_listen_addr(ip)
.with_net(net)
.with_insecure_no_key()
};
let dated = ReplicatedMap::<String, String>::new_with_transport(
config(dated_ip),
Arc::new(network.bind(SocketAddr::new(dated_ip, port))),
);
let read_replica = ReadReplicaMap::<String, String>::new_with_transport(
config(read_replica_ip),
Arc::new(network.bind(SocketAddr::new(read_replica_ip, port))),
)
.with_seed(dated_ip);
for i in 0..50 {
dated.insert(format!("k{i:02}"), format!("v{i:02}"));
}
dated.insert("doomed".to_string(), "to be deleted".to_string());
let dated_task = tokio::spawn(dated.clone().run());
let read_replica_task = tokio::spawn(read_replica.clone().run());
assert_until!(read_replica.get(&"k00".to_string()).as_deref() == Some(&"v00".to_string()));
assert_until!(read_replica.get(&"k49".to_string()).as_deref() == Some(&"v49".to_string()));
assert_until!(
read_replica.get(&"doomed".to_string()).as_deref() == Some(&"to be deleted".to_string())
);
assert_until!(read_replica.fingerprint(..) == dated.value_fingerprint(..));
assert_eq!(read_replica.len(), 51);
dated.insert("late".to_string(), "arrival".to_string());
assert_until!(read_replica.get(&"late".to_string()).as_deref() == Some(&"arrival".to_string()));
dated.remove(&"doomed".to_string());
assert_until!(read_replica.get(&"doomed".to_string()).is_none());
assert!(!read_replica.contains_key(&"doomed".to_string()));
assert_until!(read_replica.fingerprint(..) == dated.value_fingerprint(..));
dated_task.abort();
read_replica_task.abort();
}