use crate::Commands;
use crate::blocked::unix_now_ms;
use crate::message::{Agg, Inbound, Part};
use crate::replication::ReplicaState;
use crate::shard::Shard;
pub(crate) const WAIT_HARD_CAP_MS: u64 = 60_000;
pub(crate) struct ReplWaiter {
origin: usize,
conn: u64,
seq: u64,
deadline_ms: u64,
kind: ReplWaiterKind,
}
#[derive(Clone, Copy)]
pub(crate) enum ReplWaiterKind {
AckCount { target_offset: u64, need: u32 },
Applied { min_offset: u64 },
}
fn effective_deadline(timeout_ms: u64) -> u64 {
let t = if timeout_ms == 0 { WAIT_HARD_CAP_MS } else { timeout_ms.min(WAIT_HARD_CAP_MS) };
unix_now_ms().saturating_add(t)
}
impl<C: Commands> Shard<C> {
pub(crate) fn start_repl_wait(
&mut self,
conn_id: u64,
seq: u64,
need: u32,
timeout_ms: u64,
) {
let deadline_ms = effective_deadline(timeout_ms);
self.flush_requests();
self.push_pending_slot(conn_id, self.nshards as u32, Agg::MinInt(i64::MAX), false);
let me = self.id;
for s in 0..self.nshards {
if s == me {
self.arm_repl_wait(me, conn_id, seq, need, deadline_ms);
} else {
self.send_to(
s,
Inbound::ReplWaitArm { origin: me, conn: conn_id, seq, need, deadline_ms },
);
}
}
}
pub(crate) fn start_repl_barrier(
&mut self,
conn_id: u64,
seq: u64,
offsets: Vec<u64>,
timeout_ms: u64,
miss: Vec<u8>,
) {
if offsets.len() != self.nshards {
let err = format!(
"-ERR REPL.WAIT token has {} (gen, offset) pair(s) but this server has {} \
shard(s); take the token from this server's primary with REPL.TOKEN\r\n",
offsets.len(),
self.nshards,
);
self.push_pending_slot(conn_id, 1, Agg::First(None), false);
self.fold(
conn_id,
seq,
Part::Reply(crate::message::SmallReply::from_vec(err.into_bytes())),
);
return;
}
let deadline_ms = effective_deadline(timeout_ms);
self.push_pending_slot(
conn_id,
self.nshards as u32,
Agg::ReplBarrier { ok: true, miss },
false,
);
let me = self.id;
for (s, min_offset) in offsets.into_iter().enumerate() {
if s == me {
self.arm_repl_apply(me, conn_id, seq, min_offset, deadline_ms);
} else {
self.send_to(
s,
Inbound::ReplApplyArm {
origin: me,
conn: conn_id,
seq,
min_offset,
deadline_ms,
},
);
}
}
}
pub(crate) fn arm_repl_wait(
&mut self,
origin: usize,
conn: u64,
seq: u64,
need: u32,
deadline_ms: u64,
) {
let target_offset = self
.replicate
.as_ref()
.map_or(0, |f| f.source().next_offset());
let n = self.repl_ack_count(target_offset);
if n >= i64::from(need) {
self.repl_waiter_reply(origin, conn, seq, n);
return;
}
self.repl_waiters.push(ReplWaiter {
origin,
conn,
seq,
deadline_ms,
kind: ReplWaiterKind::AckCount { target_offset, need },
});
}
pub(crate) fn arm_repl_apply(
&mut self,
origin: usize,
conn: u64,
seq: u64,
min_offset: u64,
deadline_ms: u64,
) {
if self.replica_applied_next >= min_offset {
self.repl_waiter_reply(origin, conn, seq, 1);
return;
}
self.repl_waiters.push(ReplWaiter {
origin,
conn,
seq,
deadline_ms,
kind: ReplWaiterKind::Applied { min_offset },
});
}
fn repl_ack_count(&self, target_offset: u64) -> i64 {
self.slots
.iter()
.filter(|s| s.acked_offset >= target_offset && self.replica_conn_is_live(&s.id))
.count() as i64
}
fn replica_conn_is_live(&self, id: &str) -> bool {
self.replicas.iter().any(|c| match &c.state {
ReplicaState::AckSent { replica_id, .. }
| ReplicaState::Streaming { replica_id, .. }
| ReplicaState::SnapshotShipping { replica_id, .. } => replica_id == id,
_ => false,
})
}
pub(crate) fn check_repl_ack_waiters(&mut self) {
if self.repl_waiters.is_empty() {
return;
}
let mut done: Vec<(usize, u64, u64, i64)> = Vec::new();
{
let slots = &self.slots;
let replicas = &self.replicas;
self.repl_waiters.retain(|w| {
if let ReplWaiterKind::AckCount { target_offset, need } = w.kind {
let n = slots
.iter()
.filter(|s| {
s.acked_offset >= target_offset
&& replicas
.iter()
.any(|c| replica_conn_id(c) == Some(s.id.as_str()))
})
.count() as i64;
if n >= i64::from(need) {
done.push((w.origin, w.conn, w.seq, n));
return false;
}
}
true
});
}
for (origin, conn, seq, n) in done {
self.repl_waiter_reply(origin, conn, seq, n);
}
}
pub(crate) fn check_repl_apply_waiters(&mut self) {
if self.repl_waiters.is_empty() {
return;
}
let applied = self.replica_applied_next;
let mut done: Vec<(usize, u64, u64)> = Vec::new();
self.repl_waiters.retain(|w| {
if let ReplWaiterKind::Applied { min_offset } = w.kind
&& applied >= min_offset
{
done.push((w.origin, w.conn, w.seq));
return false;
}
true
});
for (origin, conn, seq) in done {
self.repl_waiter_reply(origin, conn, seq, 1);
}
}
pub(crate) fn tick_repl_waiters(&mut self) {
if self.repl_waiters.is_empty() {
return;
}
let now_ms = unix_now_ms();
let waiters = std::mem::take(&mut self.repl_waiters);
let (expired, keep): (Vec<_>, Vec<_>) =
waiters.into_iter().partition(|w| w.deadline_ms <= now_ms);
self.repl_waiters = keep;
for w in expired {
let n = match w.kind {
ReplWaiterKind::AckCount { target_offset, .. } => {
self.repl_ack_count(target_offset)
}
ReplWaiterKind::Applied { .. } => 0,
};
self.repl_waiter_reply(w.origin, w.conn, w.seq, n);
}
}
pub(crate) fn repl_waiter_reply(&mut self, origin: usize, conn: u64, seq: u64, n: i64) {
if origin == self.id {
self.fold(conn, seq, Part::Int(n));
self.mark_pending_write_dirty(conn);
} else {
self.send_to(origin, Inbound::ReplDone { conn, seq, n });
}
}
}
fn replica_conn_id(c: &crate::replication::ReplicaConn) -> Option<&str> {
match &c.state {
ReplicaState::AckSent { replica_id, .. }
| ReplicaState::Streaming { replica_id, .. }
| ReplicaState::SnapshotShipping { replica_id, .. } => Some(replica_id),
_ => None,
}
}