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::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) -> 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))
.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<()>>>>,
) {
while !stop.load(Ordering::Relaxed) {
match listener.accept() {
Ok((stream, _peer)) => {
let source_c = Arc::clone(&source);
let stop_c = Arc::clone(&stop);
let join = thread::Builder::new()
.name("kevy-embedded-writer-conn".into())
.spawn(move || run_conn(stream, source_c, stop_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>) {
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.write_all(&encode_ack(from_offset)).is_err() {
return;
}
let mut sent_offset = from_offset;
if stream.set_nonblocking(false).is_err() {
return;
}
let _ = stream.set_read_timeout(None);
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;
}
}
}