use std::io::{self, Read, Write};
use std::net::{Shutdown, TcpListener, TcpStream};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::thread::{self, JoinHandle};
use std::time::Duration;
use kevy_replicate::handshake::{encode_ack, parse_replicate_from};
use kevy_replicate::wire::{
SNAPSHOT_CHUNK_MAX, encode_snapshot_begin, encode_snapshot_chunk, encode_snapshot_end,
};
pub(crate) type SnapshotProvider = Arc<dyn Fn() -> (Vec<u8>, u64) + Send + Sync>;
use kevy_replicate::source::{FromOffset, ReplicationSource};
use kevy_resp::Argv;
pub(crate) struct ReplicaSource {
source: Arc<Mutex<ReplicationSource>>,
stop: Arc<AtomicBool>,
bound_addr: std::net::SocketAddr,
accept_join: Mutex<Option<JoinHandle<()>>>,
conn_joins: Arc<Mutex<Vec<JoinHandle<()>>>>,
}
impl ReplicaSource {
pub(crate) fn spawn(
listen_addr: &str,
backlog_bytes: usize,
snapshot: SnapshotProvider,
) -> io::Result<Self> {
let listener = TcpListener::bind(listen_addr)?;
listener.set_nonblocking(true)?;
let bound_addr = listener.local_addr()?;
let source = Arc::new(Mutex::new(ReplicationSource::new(backlog_bytes.max(64 * 1024))));
let stop = Arc::new(AtomicBool::new(false));
let conn_joins = Arc::new(Mutex::new(Vec::<JoinHandle<()>>::new()));
let source_c = Arc::clone(&source);
let stop_c = Arc::clone(&stop);
let conn_joins_c = Arc::clone(&conn_joins);
let accept_join = thread::Builder::new()
.name("kevy-embedded-writer-accept".into())
.spawn(move || run_accept_loop(listener, source_c, stop_c, conn_joins_c, snapshot))
.expect("spawn writer-accept thread");
Ok(Self {
source,
stop,
bound_addr,
accept_join: Mutex::new(Some(accept_join)),
conn_joins,
})
}
#[allow(dead_code)]
pub(crate) fn push_argv(&self, parts: &[&[u8]]) {
push_into(&self.source, parts);
}
pub(crate) fn shared_source(&self) -> Arc<Mutex<ReplicationSource>> {
Arc::clone(&self.source)
}
#[allow(dead_code)]
pub(crate) fn local_addr(&self) -> std::net::SocketAddr {
self.bound_addr
}
pub(crate) fn shutdown(&self) {
self.stop.store(true, Ordering::Relaxed);
let _ = TcpStream::connect_timeout(&self.bound_addr, Duration::from_millis(200));
if let Some(j) = self
.accept_join
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.take()
{
let _ = j.join();
}
let mut joins = self
.conn_joins
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
for j in joins.drain(..) {
let _ = j.join();
}
}
}
pub(crate) fn push_into(source: &Arc<Mutex<ReplicationSource>>, parts: &[&[u8]]) {
let mut g = source
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let mut argv = Argv::default();
for p in parts {
argv.push(p);
}
let _offset = g.push_mutation(&argv);
}
fn run_accept_loop(
listener: TcpListener,
source: Arc<Mutex<ReplicationSource>>,
stop: Arc<AtomicBool>,
conn_joins: Arc<Mutex<Vec<JoinHandle<()>>>>,
snapshot: SnapshotProvider,
) {
while !stop.load(Ordering::Relaxed) {
match listener.accept() {
Ok((stream, _peer)) => {
let source_c = Arc::clone(&source);
let stop_c = Arc::clone(&stop);
let snapshot_c = Arc::clone(&snapshot);
let join = thread::Builder::new()
.name("kevy-embedded-writer-conn".into())
.spawn(move || run_conn(stream, source_c, stop_c, snapshot_c))
.expect("spawn writer-conn thread");
conn_joins
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(join);
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
thread::sleep(Duration::from_millis(20));
}
Err(_) => {
break;
}
}
}
}
fn run_conn(
mut stream: TcpStream,
source: Arc<Mutex<ReplicationSource>>,
stop: Arc<AtomicBool>,
snapshot: SnapshotProvider,
) {
if stream.set_read_timeout(Some(Duration::from_secs(2))).is_err() {
return;
}
let from_offset = match read_handshake(&mut stream) {
Some(off) => off,
None => return,
};
if stream.set_nonblocking(false).is_err() {
return;
}
let _ = stream.set_read_timeout(None);
let needs_snapshot = {
let g = source.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
let next = g.next_offset();
(from_offset == 0 && next > 0) || g.frames_from(from_offset).is_err()
};
let from_offset = if needs_snapshot {
let (payload, ack_offset) = snapshot();
if stream.write_all(&encode_ack(ack_offset)).is_err() {
return;
}
if stream.write_all(&encode_snapshot_begin()).is_err() {
return;
}
for chunk in payload.chunks(SNAPSHOT_CHUNK_MAX) {
if stream.write_all(&encode_snapshot_chunk(chunk)).is_err() {
return;
}
}
if stream.write_all(&encode_snapshot_end(ack_offset)).is_err() {
return;
}
ack_offset
} else {
if stream.write_all(&encode_ack(from_offset)).is_err() {
return;
}
from_offset
};
let mut sent_offset = from_offset;
while !stop.load(Ordering::Relaxed) {
let next = next_frame_bytes(&source, sent_offset);
match next {
FrameStep::Send(bytes, new_off) => {
if stream.write_all(&bytes).is_err() {
break;
}
sent_offset = new_off;
}
FrameStep::CaughtUp => {
thread::sleep(Duration::from_millis(20));
}
FrameStep::TooOld | FrameStep::PeerAhead => {
let _ = stream.shutdown(Shutdown::Both);
break;
}
}
}
}
enum FrameStep {
Send(Vec<u8>, u64),
CaughtUp,
TooOld,
PeerAhead,
}
fn next_frame_bytes(source: &Arc<Mutex<ReplicationSource>>, sent_offset: u64) -> FrameStep {
let s = source
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if sent_offset >= s.next_offset() {
return FrameStep::CaughtUp;
}
match s.frames_from(sent_offset) {
Ok(mut it) => match it.next() {
Some(frame) => FrameStep::Send(frame.bytes.clone(), frame.offset.saturating_add(1)),
None => FrameStep::CaughtUp,
},
Err(FromOffset::TooOld) => FrameStep::TooOld,
Err(FromOffset::Future) => FrameStep::PeerAhead,
}
}
fn read_handshake(stream: &mut TcpStream) -> Option<u64> {
let mut buf = Vec::with_capacity(256);
let mut chunk = [0u8; 256];
loop {
let n = stream.read(&mut chunk).ok()?;
if n == 0 {
return None;
}
buf.extend_from_slice(&chunk[..n]);
if let Ok(Some((argv, _consumed))) = kevy_resp::parse_command(&buf.clone()) {
return parse_replicate_from(&argv).ok().map(|req| req.from_offset);
}
if buf.len() > 64 * 1024 {
return None;
}
}
}
pub(crate) fn freeze_and_serialize(shards: &crate::store::Shards) -> (Vec<u8>, u64) {
use crate::store::lock_write;
let guards: Vec<_> = shards.iter().map(|s| lock_write(s)).collect();
let ack = guards
.first()
.and_then(|g| g.writer_source.as_ref())
.map(|src| {
src.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.next_offset()
})
.unwrap_or(0);
let views: Vec<kevy_store::SnapshotView> =
guards.iter().map(|g| g.store.collect_snapshot()).collect();
drop(guards);
struct Multi<'v>(&'v [kevy_store::SnapshotView]);
impl kevy_persist::SnapshotSource for Multi<'_> {
fn for_each_entry(&self, mut f: impl FnMut(&[u8], &kevy_store::Value, Option<u64>)) {
for v in self.0 {
kevy_persist::SnapshotSource::for_each_entry(v, &mut f);
}
}
fn for_each_hash_ttl(&self, mut f: impl FnMut(&[u8], &[u8], u64)) {
for v in self.0 {
kevy_persist::SnapshotSource::for_each_hash_ttl(v, &mut f);
}
}
}
let mut payload = Vec::new();
let _ = kevy_persist::write_snapshot_to(&Multi(&views), &mut payload);
(payload, ack)
}