use super::*; use dever_runtime::{ database::{Database, Options}, task::{Limits, Runtime}, }; #[test] #[ignore = "requires explicit isolated config/setting.json database.postgres_test"] fn postgres_native_job_renews_short_lease_during_business_transaction() { let directory = Directory::new(); let schema = super::super::database::postgres::Schema::new( &directory.0, "dever_native_job_heartbeat", "CREATE SCHEMA dever_native_job_heartbeat", "DROP SCHEMA dever_native_job_heartbeat CASCADE", ); let project = Project::load(&fixture().with_file_name("job")).unwrap(); let program = project.check().unwrap(); assert_eq!(program.models[0].table, "demo_queue_model"); let binary = build::build( &program, &project.sources, "main", &directory.0.join("heartbeat"), ) .unwrap(); let mut connection = schema.connection.clone(); connection["max_connections"] = 4.into(); std::fs::create_dir(directory.0.join("config")).unwrap(); std::fs::write( directory.0.join("config/setting.json"), serde_json::to_vec(&serde_json::json!({ "database":{"default":connection}, "job":{"workers":2,"poll_ms":10,"lease_ms":200,"retry_base_ms":10,"retry_max_ms":100}, "runtime":{"shutdown_ms":1000} })) .unwrap(), ) .unwrap(); let run = |name: &str, input: &str| { let result = execute(&binary, &format!("demo.queue.{name}"), input); assert!( result.status.success(), "{}", String::from_utf8_lossy(&result.stderr) ); serde_json::from_slice::(&result.stdout).unwrap()["data"].clone() }; run("submit", r#"{"name":"slow","key":"heartbeat"}"#); let runtime = Runtime::new(Limits::default()).unwrap(); let database = runtime.run(async |scope| { let options: Options = serde_json::from_value(connection).unwrap(); let database = Database::open(&directory.0, options, scope).await.unwrap(); database.executor().await.unwrap().initialize(scope, "CREATE FUNCTION slow_job() RETURNS trigger LANGUAGE plpgsql AS 'BEGIN PERFORM pg_sleep(0.6); RETURN NEW; END'; CREATE TRIGGER slow_job BEFORE INSERT ON demo_queue_model FOR EACH ROW EXECUTE FUNCTION slow_job()").await.unwrap(); database }).unwrap(); let started = Instant::now(); let mut worker = Server(Command::new(&binary).spawn().unwrap()); while run("count", "{}") != 1 { assert!( worker.0.try_wait().unwrap().is_none(), "worker exited before completion" ); assert!( started.elapsed() < Duration::from_secs(5), "short lease job did not complete" ); std::thread::sleep(Duration::from_millis(20)); } assert!(started.elapsed() >= Duration::from_millis(500)); runtime .run(async |scope| { let rows = database .executor() .await .unwrap() .query( scope, "SELECT attempt,state FROM _dever_job", vec![], 1, |row| Ok((row.get::<_, i64>(0)?, row.get::<_, String>(1)?)), ) .await .unwrap(); assert_eq!(rows, vec![(1, "completed".into())]); }) .unwrap(); assert!( Command::new("sh") .args(["-c", "kill -TERM \"$1\"", "--", &worker.0.id().to_string()]) .status() .unwrap() .success() ); assert!(worker.0.wait().unwrap().success()); database.close(); }