From 3b603906ebee7d3141706cdb78be3763ca9f389c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Ochagav=C3=ADa?= Date: Wed, 20 Dec 2023 14:48:50 +0100 Subject: [PATCH] Toggle event processing through the filesystem See the readme for details on how to use the feature --- ci-bench-runner/Cargo.lock | 239 +++++++++++++++++++++++++---- ci-bench-runner/Cargo.toml | 2 + ci-bench-runner/src/event_queue.rs | 119 +++++++++++++- ci-bench-runner/src/lib.rs | 6 +- ci-bench-runner/src/test/mod.rs | 85 ++++++++++ readme.md | 4 + 6 files changed, 422 insertions(+), 33 deletions(-) diff --git a/ci-bench-runner/Cargo.lock b/ci-bench-runner/Cargo.lock index f91b801..7688c88 100644 --- a/ci-bench-runner/Cargo.lock +++ b/ci-bench-runner/Cargo.lock @@ -383,7 +383,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -399,6 +399,8 @@ dependencies = [ "hmac", "hyper", "jsonwebtoken", + "libc", + "notify", "octocrab", "reqwest", "sentry", @@ -478,6 +480,16 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4939f9ed1444bd8c896d37f3090012fa6e7834fe84ef8c9daa166109515732f9" +[[package]] +name = "crossbeam-channel" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c3242926edf34aec4ac3a77108ad4854bffaa2e4ddc1824124ce59231302d5" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.8" @@ -490,9 +502,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" dependencies = [ "cfg-if", ] @@ -656,7 +668,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -667,7 +679,7 @@ checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ "cfg-if", "home", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -700,6 +712,18 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "filetime" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.52.0", +] + [[package]] name = "finl_unicode" version = "1.2.0" @@ -732,6 +756,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "futures" version = "0.3.29" @@ -1018,7 +1051,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1194,6 +1227,26 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "instant" version = "0.1.12" @@ -1257,6 +1310,26 @@ dependencies = [ "simple_asn1", ] +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1268,9 +1341,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.149" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "libm" @@ -1371,8 +1444,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", + "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1385,6 +1459,25 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.4.1", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "walkdir", + "windows-sys 0.48.0", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1577,7 +1670,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -1960,7 +2053,7 @@ dependencies = [ "libc", "spin 0.9.8", "untrusted", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2008,7 +2101,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2066,13 +2159,22 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2429,7 +2531,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2743,7 +2845,7 @@ dependencies = [ "fastrand 2.0.1", "redox_syscall", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2834,7 +2936,7 @@ dependencies = [ "pin-project-lite", "socket2 0.5.5", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3197,6 +3299,16 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -3335,6 +3447,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -3347,7 +3468,7 @@ version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -3356,7 +3477,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -3365,13 +3495,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -3380,42 +3525,84 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winreg" version = "0.50.0" @@ -3423,7 +3610,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] diff --git a/ci-bench-runner/Cargo.toml b/ci-bench-runner/Cargo.toml index 18bff07..0b0bec8 100644 --- a/ci-bench-runner/Cargo.toml +++ b/ci-bench-runner/Cargo.toml @@ -12,6 +12,7 @@ hex = "0.4.3" hmac = "0.12.1" hyper = { version = "0.14.27", default-features = false } jsonwebtoken = "9.1.0" +notify = "6.1.1" octocrab = "0.32.0" sentry = { version = "0.31.7", features = ["tracing", "ureq", "rustls"], default-features = false } sentry-tracing = "0.31.7" @@ -29,5 +30,6 @@ uuid = { version = "1.4.1", features = ["v4", "serde"] } [dev-dependencies] ctor = "0.2.5" +libc = "0.2.151" reqwest = { version = "0.11.22", default-features = false, features = ["rustls-tls-webpki-roots"] } wiremock = "0.5.19" diff --git a/ci-bench-runner/src/event_queue.rs b/ci-bench-runner/src/event_queue.rs index ec8926e..5478a30 100644 --- a/ci-bench-runner/src/event_queue.rs +++ b/ci-bench-runner/src/event_queue.rs @@ -1,9 +1,11 @@ use std::fmt::{Debug, Formatter}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use std::time::Duration; +use anyhow::Context; use axum::body::Bytes; +use notify::{EventKind, RecommendedWatcher, RecursiveMode, Watcher}; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; @@ -25,6 +27,11 @@ pub struct EventQueue { active_job_id: Arc>>, /// A sender indicating that a new event has been enqueued event_enqueued_tx: UnboundedSender<()>, + /// Keeps track of whether incoming events should be processed. + /// + /// Note: when event processing gets disabled, we still let the currently active job run to + /// completion. + process_events_toggler: ProcessEventsToggler, /// Database handle, used to persist events and recover in case of crashes db: Db, /// Bencher.dev client @@ -42,22 +49,24 @@ impl EventQueue { db: Db, bench_runner: Arc, octocrab: CachedOctocrab, - ) -> Self { + ) -> anyhow::Result { let (worker_tx, event_enqueued_rx) = tokio::sync::mpsc::unbounded_channel(); let queue = Self { active_job_id: Arc::new(Mutex::new(None)), event_enqueued_tx: worker_tx, + process_events_toggler: ProcessEventsToggler::new() + .context("failed to initialize ProcessEventsToggler")?, db, bencher_dev: config.bencher.clone().map(BencherDev::new), }; - queue.start_and_supervise_queue_processing( + Ok(queue.start_and_supervise_queue_processing( event_enqueued_rx, config, bench_runner, octocrab, - ) + )) } /// Starts and supervises the background queue processing task @@ -71,11 +80,14 @@ impl EventQueue { let active_job_id = self.active_job_id.clone(); let queue = self.clone(); let event_enqueued_rx = Arc::new(tokio::sync::Mutex::new(event_enqueued_rx)); + let toggler = self.process_events_toggler.clone(); + tokio::spawn(async move { loop { let background_task = queue.process_queued_events_in_background( event_enqueued_rx.clone(), config.clone(), + toggler.clone(), bench_runner.clone(), octocrab.clone(), ); @@ -118,6 +130,7 @@ impl EventQueue { &self, event_enqueued_rx: Arc>>, config: Arc, + mut toggler: ProcessEventsToggler, bench_runner: Arc, octocrab: CachedOctocrab, ) -> JoinHandle> { @@ -140,7 +153,10 @@ impl EventQueue { break; } - // Now get it from the database + // Postpone event processing if requested + toggler.wait_for_processing_enabled().await; + + // Get the next event from the database let event = db.next_queued_event().await?; let Some(github_event) = AllowedEvent::from_event_string(&event.event) else { @@ -222,6 +238,16 @@ impl EventQueue { Ok(Some(event_id)) } + /// Returns the active job's id, if there is an active job + pub fn active_job_id(&self) -> Option { + *self.active_job_id.lock().unwrap() + } + + /// Returns whether event processing is currently enabled + pub fn event_processing_enabled(&self) -> bool { + self.process_events_toggler.processing_enabled() + } + /// Returns a user-facing view of the given job id, or `None` if the job could not be found pub async fn job_view(&self, job_id: Uuid) -> anyhow::Result> { let Some(job) = self.db.maybe_job(job_id).await? else { @@ -320,3 +346,86 @@ pub enum JobStatus { Success, Failure, } + +/// Watches the filesystem to toggle event processing. +/// +/// Event processing is enabled by default, but can be disabled by creating a file called `pause` +/// in the program's working directory. If the file is present upon startup or gets created while +/// the application runs, processing will be disabled until the file is deleted. +struct ProcessEventsToggler { + /// Filesystem watcher + watcher: Arc, + /// Receiver tracking the current state of the toggle (enabled / disabled) + processing_enabled_rx: tokio::sync::watch::Receiver, +} + +impl ProcessEventsToggler { + fn new() -> anyhow::Result { + let pause_file_path = Path::new("pause"); + let process_events = pause_file_path.try_exists().ok() != Some(true); + let (processing_enabled_tx, processing_enabled_rx) = + tokio::sync::watch::channel(process_events); + + let mut watcher = + notify::recommended_watcher(move |r: notify::Result| match r { + Ok(event) => { + if event + .paths + .iter() + .any(|p| p.file_name() == Some(pause_file_path.as_os_str())) + { + match event.kind { + EventKind::Create(_) => { + info!("event processing disabled"); + processing_enabled_tx.send_replace(false); + } + EventKind::Remove(_) => { + info!("event processing enabled"); + processing_enabled_tx.send_replace(true); + } + _ => {} + } + } + } + Err(e) => error!("error watching pause file: {:?}", e), + })?; + + let watched_path = Path::new("."); + info!( + "watching for file creation/deletion under {}", + watched_path.canonicalize()?.display() + ); + watcher.watch(watched_path, RecursiveMode::NonRecursive)?; + + Ok(ProcessEventsToggler { + watcher: Arc::new(watcher), + processing_enabled_rx, + }) + } + + fn processing_enabled(&self) -> bool { + *self.processing_enabled_rx.borrow() + } + + fn processing_disabled(&self) -> bool { + !self.processing_enabled() + } + + async fn wait_for_processing_enabled(&mut self) { + if self.processing_disabled() { + info!("event handling postponed until processing gets enabled"); + } + + self.processing_enabled_rx.wait_for(|&b| b).await.unwrap(); + assert!(*self.processing_enabled_rx.borrow()); + } +} + +impl Clone for ProcessEventsToggler { + fn clone(&self) -> Self { + Self { + watcher: self.watcher.clone(), + processing_enabled_rx: self.processing_enabled_rx.clone(), + } + } +} diff --git a/ci-bench-runner/src/lib.rs b/ci-bench-runner/src/lib.rs index af4149b..8867ec0 100644 --- a/ci-bench-runner/src/lib.rs +++ b/ci-bench-runner/src/lib.rs @@ -104,7 +104,7 @@ pub async fn server( // Set up dependencies let octocrab = CachedOctocrab::new(&config).await?; let db = Db::with_connection(sqlite); - let event_queue = EventQueue::new(config.clone(), db.clone(), bench_runner, octocrab); + let event_queue = EventQueue::new(config.clone(), db.clone(), bench_runner, octocrab)?; // Create the application's state, accessible when handling requests let state = Arc::new(AppState { @@ -134,10 +134,12 @@ pub async fn server( } /// Returns git commit information about the binary that is currently deployed -async fn get_server_info() -> Json { +async fn get_server_info(State(state): State>) -> Json { Json(json!({ "git_commit_sha": env!("GIT_HEAD_SHA").to_string(), "git_commit_message": env!("GIT_HEAD_COMMIT_MESSAGE").to_string(), + "active_job_id": state.event_queue.active_job_id(), + "event_processing_enabled": state.event_queue.event_processing_enabled(), })) } diff --git a/ci-bench-runner/src/test/mod.rs b/ci-bench-runner/src/test/mod.rs index e7dec3f..1a0d5aa 100644 --- a/ci-bench-runner/src/test/mod.rs +++ b/ci-bench-runner/src/test/mod.rs @@ -1,4 +1,5 @@ use std::fs; +use std::fs::File; use std::path::Path; use std::sync::Arc; use std::time::Duration; @@ -258,6 +259,79 @@ async fn test_issue_comment_happy_path() { mock_github.server.verify().await; } +#[cfg(target_os = "linux")] +#[tokio::test] +async fn test_issue_comment_postponed_processing() { + let tempdir = tempfile::tempdir().unwrap(); + let client = reqwest::Client::default(); + + // This is necessary to create a "pause" file without interfering with other tests + // See https://stackoverflow.com/a/73867506/2110623 for details on `unshare` + unsafe { libc::unshare(libc::CLONE_FS) }; + std::env::set_current_dir(tempdir.path()).unwrap(); + + // Mock HTTP responses from GitHub + let mock_github = MockGitHub::start().await; + let _get_pr = mock_github.mock_get_pr().await; + let _post_comment = mock_github.mock_post_comment().await; + let update_status = mock_github.mock_post_status().await; + + // Run the job server + let server = TestServer::start(&mock_github).await; + + // Sanity check: event processing is enabled + let response = get_info(&client, &server.base_url).await; + assert_eq!( + response.get("event_processing_enabled"), + Some(&serde_json::Value::Bool(true)) + ); + + // Disable event processing + let pause_file_path = tempdir.path().join("pause"); + File::create(&pause_file_path).unwrap(); + + // Post the webhook event + let event = webhook::comment("@rustls-benchmarking bench", "created", "OWNER"); + post_webhook( + &client, + &server.base_url, + &server.config.webhook_secret, + event, + "issue_comment", + ) + .await; + + // Ensure there is no active job after one second and event processing is disabled + tokio::time::sleep(Duration::from_secs(1)).await; + let response = get_info(&client, &server.base_url).await; + assert_eq!( + response.get("active_job_id"), + Some(&serde_json::Value::Null) + ); + assert_eq!( + response.get("event_processing_enabled"), + Some(&serde_json::Value::Bool(false)) + ); + + // Re-enable event processing + fs::remove_file(&pause_file_path).unwrap(); + + // Wait for our mock endpoints to have been called + tokio::time::timeout(Duration::from_secs(5), update_status.wait_until_satisfied()) + .await + .ok(); + + // Assert that the mocks were used and report any errors + mock_github.server.verify().await; + + // Sanity check: event processing is enabled + let response = get_info(&client, &server.base_url).await; + assert_eq!( + response.get("event_processing_enabled"), + Some(&serde_json::Value::Bool(true)) + ); +} + #[tokio::test] async fn test_pr_opened_happy_path_with_comment_reuse() { // Mock HTTP responses from GitHub @@ -685,6 +759,17 @@ async fn post_webhook( assert_eq!(response.status(), StatusCode::OK); } +async fn get_info(client: &reqwest::Client, base_url: &str) -> serde_json::Value { + client + .get(format!("{base_url}/info")) + .send() + .await + .unwrap() + .json() + .await + .unwrap() +} + async fn ensure_webhook_handled(server: &TestServer) { tokio::time::sleep(Duration::from_secs(1)).await; let events = server.db.queued_events().await.unwrap(); diff --git a/readme.md b/readme.md index 03043fc..b876dcb 100644 --- a/readme.md +++ b/readme.md @@ -64,6 +64,10 @@ The following features are supported: body. This can be used as a fallback mechanism when the triggers mentioned above are not enough. - Report comparison results in a comment to the relevant PR, reusing the same comment when new results are available. +- Pause event processing by creating a file called `pause` in the application's working directory. +- Show information about the application through the `/info` endpoint. Includes the hash of the + deployed commit, the id of the active job (if any) and whether event processing is currently + enabled. Interesting ideas for later: