use super::*; use std::sync::Arc; async fn locked_claim(case: &Case, scope: &Scope<'_>) -> (Executor, Executor, Claim, bool) { let mut owner = case.executor(scope).await; let waiting = case.executor(scope).await; job::enqueue(&mut owner, scope, submission("locked"), NOW) .await .unwrap(); let claim = job::claim(&mut owner, scope, NOW, 100) .await .unwrap() .unwrap(); let transaction = owner.begin(scope).await.unwrap(); owner .query( scope, "SELECT id FROM _dever_job WHERE id=$1 FOR UPDATE", vec![Parameter::Text(claim.id.clone())], 1, |row| row.get::<_, String>(0), ) .await .unwrap(); (owner, waiting, claim, transaction) } #[test] #[ignore = "requires explicit isolated database.postgres_test"] fn lease_expiration_after_row_lock_wait_rolls_back_business_writes() { fixture(async |case, scope| { let (mut owner, mut waiting, claim, transaction) = locked_claim(case, scope).await; waiting .initialize(scope, "CREATE TABLE business(id BIGINT PRIMARY KEY)") .await .unwrap(); let business = waiting.begin(scope).await.unwrap(); waiting .execute(scope, "INSERT INTO business VALUES (1)", vec![]) .await .unwrap(); let (result, ()) = tokio::join!(job::complete(&mut waiting, scope, &claim, NOW + 1), async { tokio::time::sleep(Duration::from_millis(250)).await; owner.finish(scope, transaction, true).await.unwrap(); },); assert!( result.is_err(), "lease checked before the row lock was acquired" ); assert!(waiting.finish(scope, business, true).await.is_err()); assert_eq!(case.count(scope, "SELECT COUNT(*) FROM business").await, 0); let retry = job::claim(&mut waiting, scope, NOW + 1000, 1000) .await .unwrap() .unwrap(); assert_ne!(retry.token, claim.token); assert!( job::complete(&mut waiting, scope, &claim, NOW + 1001) .await .is_err() ); job::complete(&mut waiting, scope, &retry, NOW + 1001) .await .unwrap(); }); } #[test] #[ignore = "requires explicit isolated database.postgres_test"] fn claim_clock_includes_table_lock_wait() { fixture(async |case, scope| { let mut owner = case.executor(scope).await; let mut waiting = case.executor(scope).await; job::enqueue(&mut owner, scope, submission("wait"), NOW) .await .unwrap(); let transaction = owner.begin(scope).await.unwrap(); owner .initialize(scope, "LOCK TABLE _dever_job IN ACCESS EXCLUSIVE MODE") .await .unwrap(); let (result, ()) = tokio::join!(job::claim(&mut waiting, scope, NOW, 100), async { tokio::time::sleep(Duration::from_millis(250)).await; owner.finish(scope, transaction, true).await.unwrap(); },); assert!(result.unwrap().unwrap().lease_until >= NOW + 300); }); } #[test] #[ignore = "requires explicit isolated database.postgres_test"] fn cancellation_discards_waiting_transaction_and_preserves_recovery() { fixture(async |case, scope| { let (mut owner, mut waiting, claim, transaction) = locked_claim(case, scope).await; let claim = Arc::new(claim); let copy = claim.clone(); let (started, ready) = tokio::sync::oneshot::channel(); let task = scope .spawn(move |child| async move { started.send(()).unwrap(); job::complete(&mut waiting, &child, ©, NOW + 1).await }) .unwrap(); ready.await.unwrap(); tokio::time::sleep(Duration::from_millis(50)).await; assert!(task.stop().await.unwrap().is_none()); owner.finish(scope, transaction, true).await.unwrap(); let mut replacement = case.executor(scope).await; let next = job::claim(&mut replacement, scope, NOW + 1000, 1000) .await .unwrap() .unwrap(); assert_eq!(next.id, claim.id); assert_ne!(next.token, claim.token); job::complete(&mut replacement, scope, &next, NOW + 1001) .await .unwrap(); }); }