aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2022-07-01 19:23:29 +0200
committerAlex Auvolat <alex@adnab.me>2022-07-01 19:23:29 +0200
commitd5272ce486075673f6f8f6ed6034b823a47b9542 (patch)
treeb352d7d2261ac843b4045094f30e9bd65f7e92b3
parent2189f8b64b635b74d92f6027e7c6f45c317c334e (diff)
downloadaerogramme-d5272ce486075673f6f8f6ed6034b823a47b9542.tar.gz
aerogramme-d5272ce486075673f6f8f6ed6034b823a47b9542.zip
Incomming locking loop
-rw-r--r--src/mail/incoming.rs177
1 files changed, 144 insertions, 33 deletions
diff --git a/src/mail/incoming.rs b/src/mail/incoming.rs
index 398f555..1d16d38 100644
--- a/src/mail/incoming.rs
+++ b/src/mail/incoming.rs
@@ -1,8 +1,10 @@
use std::collections::HashMap;
+use std::pin::Pin;
use std::sync::{Arc, Weak};
use std::time::Duration;
-use anyhow::Result;
+use anyhow::{anyhow, bail, Result};
+use futures::{future::BoxFuture, Future, FutureExt};
use k2v_client::{CausalValue, CausalityToken, K2vClient, K2vValue};
use rusoto_s3::{PutObjectRequest, S3Client, S3};
use tokio::sync::watch;
@@ -20,6 +22,12 @@ const INCOMING_PK: &str = "incoming";
const INCOMING_LOCK_SK: &str = "lock";
const INCOMING_WATCH_SK: &str = "watch";
+// When a lock is held, it is held for LOCK_DURATION (here 5 minutes)
+// It is renewed every LOCK_DURATION/3
+// If we are at 2*LOCK_DURATION/3 and haven't renewed, we assume we
+// lost the lock.
+const LOCK_DURATION: Duration = Duration::from_secs(300);
+
pub async fn incoming_mail_watch_process(
user: Weak<User>,
creds: Credentials,
@@ -124,55 +132,158 @@ async fn handle_incoming_mail(user: &Arc<User>, s3: &S3Client, inbox: &Arc<Mailb
fn k2v_lock_loop(k2v: K2vClient, pk: &'static str, sk: &'static str) -> watch::Receiver<bool> {
let (held_tx, held_rx) = watch::channel(false);
- tokio::spawn(async move {
- let _ = k2v_lock_loop_internal(k2v, pk, sk, held_tx).await;
- });
+ tokio::spawn(k2v_lock_loop_internal(k2v, pk, sk, held_tx));
held_rx
}
+#[derive(Clone, Debug)]
+enum LockState {
+ Unknown,
+ Empty,
+ Held(UniqueIdent, u64, CausalityToken),
+}
+
async fn k2v_lock_loop_internal(
k2v: K2vClient,
pk: &'static str,
sk: &'static str,
held_tx: watch::Sender<bool>,
-) -> std::result::Result<(), watch::error::SendError<bool>> {
- let pid = gen_ident();
-
- let mut state: Option<(UniqueIdent, u64, CausalityToken)> = None;
- loop {
- let held_until = match &state {
- None => None,
- Some((_holder, expiration_time, _ct)) => Some(expiration_time),
- };
-
- let now = now_msec();
- let wait_half_held_time = async {
- match held_until {
- None => futures::future::pending().await,
- Some(t) => tokio::time::sleep(Duration::from_millis((now_msec() - t) / 2)).await,
+) {
+ let (state_tx, mut state_rx) = watch::channel::<LockState>(LockState::Unknown);
+ let mut state_rx_2 = state_rx.clone();
+
+ let our_pid = gen_ident();
+
+ // Loop 1: watch state of lock in K2V, save that in corresponding watch channel
+ let watch_lock_loop: BoxFuture<Result<()>> = async {
+ let mut ct = None;
+ loop {
+ match k2v_wait_value_changed(&k2v, pk, sk, &ct).await {
+ Err(e) => {
+ error!(
+ "Error in k2v wait value changed: {} ; assuming we no longer hold lock.",
+ e
+ );
+ state_tx.send(LockState::Unknown)?;
+ tokio::time::sleep(Duration::from_secs(30)).await;
+ }
+ Ok(cv) => {
+ let mut lock_state = None;
+ for v in cv.value.iter() {
+ if let K2vValue::Value(vbytes) = v {
+ if vbytes.len() == 32 {
+ let ts = u64::from_be_bytes(vbytes[..8].try_into().unwrap());
+ let pid = UniqueIdent(vbytes[8..].try_into().unwrap());
+ if lock_state
+ .map(|(pid2, ts2)| ts > ts2 || (ts == ts2 && pid > pid2))
+ .unwrap_or(true)
+ {
+ lock_state = Some((pid, ts));
+ }
+ }
+ }
+ }
+ state_tx.send(
+ lock_state
+ .map(|(pid, ts)| LockState::Held(pid, ts, cv.causality.clone()))
+ .unwrap_or(LockState::Empty),
+ )?;
+ ct = Some(cv.causality);
+ }
}
- };
-
- unimplemented!();
+ info!("Stopping lock state watch");
+ }
+ }
+ .boxed();
+
+ // Loop 2: notify user whether we are holding the lock or not
+ let lock_notify_loop: BoxFuture<Result<()>> = async {
+ loop {
+ let now = now_msec();
+ let held_with_expiration_time = match &*state_rx.borrow_and_update() {
+ LockState::Held(pid, ts, _ct) if *pid == our_pid => {
+ let expiration_time = *ts - (LOCK_DURATION / 3).as_millis() as u64;
+ if now < expiration_time {
+ Some(expiration_time)
+ } else {
+ None
+ }
+ }
+ _ => None,
+ };
+ held_tx.send(held_with_expiration_time.is_some())?;
- /*
- tokio::select! {
- ret = k2v_wait_value_changed(&k2v, pk, sk, &state.as_ref().map(|(_, _, ct)| ct.clone())) => {
- match ret {
- Err(e) => {
- held_tx.send(false)?;
- tokio::time::sleep(Duration::from_secs(30)).await;
- continue;
+ let await_expired = async {
+ match held_with_expiration_time {
+ None => futures::future::pending().await,
+ Some(expiration_time) => {
+ tokio::time::sleep(Duration::from_millis(expiration_time - now)).await
}
- Ok(cv) => {
- unimplemented!();
+ };
+ };
+
+ tokio::select!(
+ r = state_rx.changed() => {
+ r?;
+ }
+ _ = held_tx.closed() => bail!("held_tx closed, don't need to hold lock anymore"),
+ _ = await_expired => continue,
+ );
+ }
+ }
+ .boxed();
+
+ // Loop 3: acquire lock when relevant
+ let take_lock_loop: BoxFuture<Result<()>> = async {
+ loop {
+ let now = now_msec();
+ let state: LockState = state_rx_2.borrow_and_update().clone();
+ let (acquire_at, ct) = match state {
+ LockState::Unknown => {
+ // If state of the lock is unknown, don't try to acquire
+ state_rx_2.changed().await?;
+ continue;
+ }
+ LockState::Empty => (now, None),
+ LockState::Held(pid, ts, ct) => {
+ if pid == our_pid {
+ (ts - (2 * LOCK_DURATION / 3).as_millis() as u64, Some(ct))
+ } else {
+ (ts, Some(ct))
}
}
+ };
+
+ // Wait until it is time to acquire lock
+ if acquire_at > now {
+ tokio::select!(
+ r = state_rx_2.changed() => {
+ // If lock state changed in the meantime, don't acquire and loop around
+ r?;
+ continue;
+ }
+ _ = tokio::time::sleep(Duration::from_millis(acquire_at - now)) => ()
+ );
}
+
+ // Acquire lock
+ let mut lock = vec![0u8; 32];
+ lock[..8].copy_from_slice(&u64::to_be_bytes(now_msec()));
+ lock[8..].copy_from_slice(&our_pid.0);
+ if let Err(e) = k2v.insert_item(pk, sk, lock, ct).await {
+ error!("Could not take lock: {}", e);
+ tokio::time::sleep(Duration::from_secs(30)).await;
+ }
+
+ // Wait for new information to loop back
+ state_rx_2.changed().await?;
}
- */
}
+ .boxed();
+
+ let res = futures::try_join!(watch_lock_loop, lock_notify_loop, take_lock_loop);
+ info!("lock loop exited: {:?}", res);
}
async fn k2v_wait_value_changed<'a>(