use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU64, Ordering};
use std::thread::JoinHandle;
use std::time::{Duration, Instant};
use parking_lot::Mutex;
use crate::protocol::FrameType;
use crate::protocol::command::{catch_payload, rewrite_payload, transform_payload};
use super::correlation::PendingEntry;
use super::counters::Counters;
use super::reconcile::DesiredState;
use super::restart::{self, Cause, Outcome, Pending, RestartWatch};
use super::slot::TransportSlot;
use super::write_frame;
const KEEPALIVE_STOP_POLL: Duration = Duration::from_millis(20);
pub(crate) struct KeepaliveCtx {
pub(crate) transport: Arc<TransportSlot>,
pub(crate) write_lock: Arc<Mutex<()>>,
pub(crate) seq: Arc<AtomicU8>,
pub(crate) counters: Arc<Counters>,
pub(crate) desired: Arc<Mutex<DesiredState>>,
pub(crate) catch_lock: Arc<Mutex<()>>,
pub(crate) stop: Arc<AtomicBool>,
pub(crate) cadence: Duration,
pub(crate) pending: Arc<Mutex<HashMap<u8, PendingEntry>>>,
pub(crate) query_gen: Arc<AtomicU64>,
pub(crate) restart: Arc<RestartWatch>,
}
pub(crate) fn spawn_keepalive(ctx: KeepaliveCtx) -> JoinHandle<()> {
std::thread::Builder::new()
.name("medius-keepalive".into())
.spawn(move || keepalive_loop(ctx))
.expect("spawn medius-keepalive thread")
}
fn keepalive_loop(ctx: KeepaliveCtx) {
let mut tick_at = Instant::now() + ctx.cadence;
let mut pending: Option<Pending> = None;
loop {
if ctx.stop.load(Ordering::SeqCst) {
return;
}
let now = Instant::now();
if ctx.restart.take_owed() {
pending = Some(Pending::new(Cause::Restart, now));
}
if let Some(p) = pending.as_mut()
&& now >= p.next
{
match restart::step(&ctx, p.cause) {
Outcome::Done => pending = None,
Outcome::Again => *p = Pending::new(Cause::Restart, Instant::now()),
Outcome::Wait => p.wait(Instant::now()),
}
continue;
}
let idle = ctx.desired.lock().is_idle();
if now < tick_at {
if pending.is_none()
&& !idle
&& ctx.restart.expecting_represent(now)
&& restart::session_released(&ctx)
{
ctx.restart.begin_release();
pending = Some(Pending::new(Cause::Released, Instant::now()));
continue;
}
std::thread::sleep(KEEPALIVE_STOP_POLL.min(tick_at - now));
continue;
}
tick_at = now + ctx.cadence;
if idle {
continue;
}
if pending.is_none() && restart::session_released(&ctx) {
ctx.restart.begin_release();
pending = Some(Pending::new(Cause::Released, Instant::now()));
continue;
}
if ctx.desired.lock().clip_ring_held() {
restart::read_ring(&ctx);
}
let _serial = ctx.catch_lock.lock();
let (catch, rewrites, transforms) = {
let d = ctx.desired.lock();
(d.catch(), d.held_rewrites(), d.held_transforms())
};
for f in catch.values() {
let seq = ctx.seq.fetch_add(1, Ordering::Relaxed);
let (class, id) = f.wire();
let _ = write_frame(
&ctx.transport,
&ctx.write_lock,
&ctx.counters,
seq,
FrameType::Catch,
&catch_payload(class, id, f.direction().as_u8(), 1, f.capture().as_u8()),
);
}
for r in rewrites {
let seq = ctx.seq.fetch_add(1, Ordering::Relaxed);
let _ = write_frame(
&ctx.transport,
&ctx.write_lock,
&ctx.counters,
seq,
FrameType::Rewrite,
&rewrite_payload(
r.class,
r.id,
r.direction,
1,
r.action,
r.offset,
&r.match_bytes,
&r.mask,
&r.payload,
),
);
}
for t in transforms {
let seq = ctx.seq.fetch_add(1, Ordering::Relaxed);
let _ = write_frame(
&ctx.transport,
&ctx.write_lock,
&ctx.counters,
seq,
FrameType::Transform,
&transform_payload(t.op, t.sclass, t.sid, t.dclass, t.did, 1),
);
}
}
}