aboutsummaryrefslogtreecommitdiff
path: root/src/modules
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules')
-rw-r--r--src/modules/blog/module.rs60
1 files changed, 47 insertions, 13 deletions
diff --git a/src/modules/blog/module.rs b/src/modules/blog/module.rs
index b1ca8c6..e3a78ef 100644
--- a/src/modules/blog/module.rs
+++ b/src/modules/blog/module.rs
@@ -10,7 +10,10 @@ use {
url::ROOT_GEMINI_URL,
xml::{Item as XmlItem, Writer as XmlWriter},
},
- std::sync::{LazyLock, Mutex, RwLock},
+ std::sync::{
+ LazyLock, Mutex, RwLock,
+ atomic::{AtomicBool, Ordering},
+ },
};
pub static POSTS: LazyLock<Mutex<Vec<Post>>> =
@@ -19,12 +22,11 @@ static BLOG_CATEGORIES: LazyLock<RwLock<Vec<BlogCategory>>> =
LazyLock::new(|| RwLock::new(Vec::new()));
static BLOG_POSTS: LazyLock<RwLock<Vec<BlogPost>>> =
LazyLock::new(|| RwLock::new(Vec::new()));
+static REFRESH_IN_PROGRESS: AtomicBool = AtomicBool::new(false);
#[allow(clippy::too_many_lines)]
pub fn module(router: &mut windmark::router::Router) {
- std::thread::spawn(fetch_from_notion)
- .join()
- .expect("initial Notion fetch failed");
+ trigger_background_refresh("initial");
track_mount(router, "/blog", "Fuwn's blogs", |context| {
if should_trigger_manual_refresh(context.url.query()) {
trigger_manual_refresh();
@@ -126,13 +128,37 @@ fn should_trigger_manual_refresh(query: Option<&str>) -> bool {
})
}
-fn trigger_manual_refresh() {
+fn begin_refresh() -> bool {
+ REFRESH_IN_PROGRESS
+ .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
+ .is_ok()
+}
+
+fn finish_refresh() { REFRESH_IN_PROGRESS.store(false, Ordering::Release); }
+
+fn refresh_once(refresh_origin: &str) {
+ if !begin_refresh() {
+ info!(
+ "{refresh_origin} Notion refresh skipped because another refresh is already in progress"
+ );
+
+ return;
+ }
+
match std::panic::catch_unwind(fetch_from_notion) {
- Ok(()) => info!("manually refreshed blog data from Notion"),
- Err(_) => warn!("manual Notion refresh failed"),
+ Ok(()) => info!("{refresh_origin} refreshed blog data from Notion"),
+ Err(_) => warn!("{refresh_origin} Notion refresh failed"),
}
+
+ finish_refresh();
+}
+
+fn trigger_background_refresh(refresh_origin: &'static str) {
+ std::thread::spawn(move || refresh_once(refresh_origin));
}
+fn trigger_manual_refresh() { trigger_background_refresh("manual"); }
+
fn construct_header(post: &BlogPost) -> String {
let title_line = format!("# {}", post.title);
let has_metadata = post.author.is_some()
@@ -453,18 +479,16 @@ pub fn refresh_loop() {
loop {
std::thread::sleep(std::time::Duration::from_secs(refresh_interval_seconds));
- match std::panic::catch_unwind(fetch_from_notion) {
- Ok(()) => info!("refreshed blog data from Notion"),
- Err(_) => warn!("failed to refresh blog data from Notion"),
- }
+ refresh_once("scheduled");
}
}
#[cfg(test)]
mod tests {
use super::{
- build_global_posts, build_rss_feed, find_visible_post_by_slug,
- render_blog_page, should_trigger_manual_refresh, slugify,
+ begin_refresh, build_global_posts, build_rss_feed,
+ find_visible_post_by_slug, finish_refresh, render_blog_page,
+ should_trigger_manual_refresh, slugify,
visible_posts_for_blog, BLOG_CATEGORIES, BLOG_POSTS,
};
use crate::modules::blog::config::{BlogCategory, BlogPost};
@@ -751,4 +775,14 @@ mod tests {
assert!(!should_trigger_manual_refresh(Some("foo=bar")));
assert!(!should_trigger_manual_refresh(None));
}
+
+ #[test]
+ fn manual_refresh_guard_prevents_parallel_refreshes() {
+ finish_refresh();
+ assert!(begin_refresh());
+ assert!(!begin_refresh());
+ finish_refresh();
+ assert!(begin_refresh());
+ finish_refresh();
+ }
}