use super::handler::AckResult;
use super::lease_state::{LeaseEvent, LeaseOptions, LeaseState};
use super::leaser::Leaser;
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel};
use tokio::task::JoinHandle;
pub(super) struct LeaseLoop {
pub(super) handle: JoinHandle<()>,
pub(super) message_tx: UnboundedSender<String>,
pub(super) ack_tx: UnboundedSender<AckResult>,
}
impl LeaseLoop {
pub(super) fn new<L>(leaser: L, options: LeaseOptions) -> Self
where
L: Leaser + Clone + Send + 'static,
{
let (message_tx, mut message_rx) = unbounded_channel();
let (ack_tx, mut ack_rx) = unbounded_channel();
let mut state = LeaseState::new(leaser, options);
let handle = tokio::spawn(async move {
loop {
tokio::select! {
biased;
event = state.next_event() => {
match event {
LeaseEvent::Flush => state.flush().await,
LeaseEvent::Extend => state.extend().await,
}
},
message = message_rx.recv() => {
match message {
None => break shutdown(state, ack_rx).await,
Some(ack_id) => state.add(ack_id),
}
},
ack_id = ack_rx.recv() => {
match ack_id {
None => break,
Some(AckResult::Ack(ack_id)) => state.ack(ack_id),
Some(AckResult::Nack(ack_id)) => state.nack(ack_id),
}
},
}
}
});
LeaseLoop {
handle,
message_tx,
ack_tx,
}
}
}
async fn shutdown<L>(mut state: LeaseState<L>, mut ack_rx: UnboundedReceiver<AckResult>)
where
L: Leaser + Clone + Send + 'static,
{
while let Ok(r) = ack_rx.try_recv() {
if let AckResult::Ack(ack_id) = r {
state.ack(ack_id);
}
}
state.shutdown().await;
}
#[cfg(test)]
mod tests {
use super::super::lease_state::tests::{sorted, test_id, test_ids};
use super::super::leaser::tests::MockLeaser;
use super::*;
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::time::{Duration, Instant};
#[tokio::test(start_paused = true)]
async fn flush_acks_nacks_on_interval() -> anyhow::Result<()> {
const FLUSH_PERIOD: Duration = Duration::from_secs(1);
const FLUSH_START: Duration = Duration::from_millis(200);
let mock = Arc::new(Mutex::new(MockLeaser::new()));
let options = LeaseOptions {
flush_period: FLUSH_PERIOD,
flush_start: FLUSH_START,
extend_start: Duration::from_secs(900),
..Default::default()
};
let lease_loop = LeaseLoop::new(mock.clone(), options);
tokio::task::yield_now().await;
for i in 0..30 {
lease_loop.message_tx.send(test_id(i))?;
}
for i in 0..10 {
lease_loop.ack_tx.send(AckResult::Ack(test_id(i)))?;
}
mock.lock().await.checkpoint();
{
mock.lock()
.await
.expect_ack()
.times(1)
.withf(|v| sorted(v) == test_ids(0..10))
.returning(move |_| ());
tokio::time::advance(FLUSH_START).await;
tokio::task::yield_now().await;
mock.lock().await.checkpoint();
}
for i in 10..20 {
lease_loop.ack_tx.send(AckResult::Nack(test_id(i)))?;
}
{
mock.lock()
.await
.expect_nack()
.times(1)
.withf(|v| sorted(v) == test_ids(10..20))
.returning(|_| ());
tokio::time::advance(FLUSH_PERIOD).await;
tokio::task::yield_now().await;
mock.lock().await.checkpoint();
}
for i in 20..25 {
lease_loop.ack_tx.send(AckResult::Ack(test_id(i)))?;
}
for i in 25..30 {
lease_loop.ack_tx.send(AckResult::Nack(test_id(i)))?;
}
{
mock.lock()
.await
.expect_ack()
.times(1)
.withf(|v| sorted(v) == test_ids(20..25))
.returning(move |_| ());
mock.lock()
.await
.expect_nack()
.times(1)
.withf(|v| sorted(v) == test_ids(25..30))
.returning(|_| ());
tokio::time::advance(FLUSH_PERIOD).await;
tokio::task::yield_now().await;
mock.lock().await.checkpoint();
}
Ok(())
}
#[tokio::test(start_paused = true)]
async fn deadline_interval() -> anyhow::Result<()> {
const EXTEND_PERIOD: Duration = Duration::from_secs(1);
const EXTEND_START: Duration = Duration::from_millis(200);
let mock = Arc::new(Mutex::new(MockLeaser::new()));
let options = LeaseOptions {
flush_start: Duration::from_secs(900),
extend_period: EXTEND_PERIOD,
extend_start: EXTEND_START,
..Default::default()
};
let lease_loop = LeaseLoop::new(mock.clone(), options);
tokio::task::yield_now().await;
for i in 0..30 {
lease_loop.message_tx.send(test_id(i))?;
}
mock.lock().await.checkpoint();
{
mock.lock()
.await
.expect_extend()
.times(1)
.withf(|v| sorted(v) == test_ids(0..30))
.returning(move |_| ());
tokio::time::advance(EXTEND_START).await;
tokio::task::yield_now().await;
mock.lock().await.checkpoint();
}
for i in 0..10 {
lease_loop.ack_tx.send(AckResult::Ack(test_id(i)))?;
}
{
mock.lock()
.await
.expect_extend()
.times(1)
.withf(|v| sorted(v) == test_ids(10..30))
.returning(|_| ());
tokio::time::advance(EXTEND_PERIOD).await;
tokio::task::yield_now().await;
mock.lock().await.checkpoint();
}
Ok(())
}
#[tokio::test(start_paused = true)]
async fn drop_does_not_wait_for_pending_operations() -> anyhow::Result<()> {
let start = Instant::now();
let mock = MockLeaser::new();
let lease_loop = LeaseLoop::new(Arc::new(mock), LeaseOptions::default());
tokio::task::yield_now().await;
for i in 0..30 {
lease_loop.message_tx.send(test_id(i))?;
}
for i in 0..10 {
lease_loop.ack_tx.send(AckResult::Ack(test_id(i)))?;
}
drop(lease_loop);
assert_eq!(start.elapsed(), Duration::ZERO);
Ok(())
}
#[tokio::test(start_paused = true)]
async fn close_waits_for_flush() -> anyhow::Result<()> {
const EXPECTED_SLEEP: Duration = Duration::from_millis(100);
let start = Instant::now();
#[derive(Clone)]
struct FakeLeaser;
#[async_trait::async_trait]
impl Leaser for FakeLeaser {
async fn ack(&self, mut ack_ids: Vec<String>) {
ack_ids.sort();
assert_eq!(ack_ids, test_ids(0..10));
tokio::time::sleep(EXPECTED_SLEEP).await;
}
async fn nack(&self, mut ack_ids: Vec<String>) {
ack_ids.sort();
assert_eq!(ack_ids, test_ids(10..30));
}
async fn extend(&self, _ack_ids: Vec<String>) {}
}
let lease_loop = LeaseLoop::new(FakeLeaser, LeaseOptions::default());
for i in 0..30 {
lease_loop.message_tx.send(test_id(i))?;
}
for i in 0..10 {
lease_loop.ack_tx.send(AckResult::Ack(test_id(i)))?;
}
drop(lease_loop.message_tx);
lease_loop.handle.await?;
assert_eq!(start.elapsed(), EXPECTED_SLEEP);
Ok(())
}
#[tokio::test(start_paused = true)]
async fn no_add_and_ack_race() -> anyhow::Result<()> {
for _ in 0..1000 {
let mock = Arc::new(Mutex::new(MockLeaser::new()));
let options = LeaseOptions {
flush_start: Duration::from_millis(100),
extend_start: Duration::from_millis(200),
..Default::default()
};
let lease_loop = LeaseLoop::new(mock.clone(), options);
tokio::task::yield_now().await;
lease_loop.message_tx.send(test_id(1))?;
lease_loop.ack_tx.send(AckResult::Ack(test_id(1)))?;
{
mock.lock()
.await
.expect_ack()
.times(1)
.withf(|v| *v == vec![test_id(1)])
.returning(|_| ());
tokio::time::advance(Duration::from_millis(100)).await;
tokio::task::yield_now().await;
mock.lock().await.checkpoint();
}
{
mock.lock().await.expect_extend().times(0);
tokio::time::advance(Duration::from_millis(100)).await;
tokio::task::yield_now().await;
mock.lock().await.checkpoint();
}
}
Ok(())
}
}