My personal site cherry.computer
htmx tailwind axum askama

feat: add try_get_scrobble method

This allows us to immediately return cached scrobbles in the rendered
HTML if available, but never waste any time waiting for the last.fm API
to give us new data if the cache is stale. The 200ms timeout that was
previously in place was never long enough for the last.fm API to return
so it was just an unnecessary delay on response times.

cherry.computer 30a3bedf bf758043

verified
+22 -21
+6 -17
server/src/index.rs
··· 1 1 use crate::{scrobble::ScrobblesTemplate, scrobble_monitor::ScrobbleMonitor}; 2 2 3 - use std::time::Duration; 4 - 5 3 use askama::Template; 6 4 7 5 #[derive(Template, Debug, Clone)] ··· 11 9 } 12 10 13 11 pub async fn get_index(mut monitor: ScrobbleMonitor) -> RootTemplate { 14 - let scrobbles_template = tokio::time::timeout( 15 - Duration::from_millis(200), 16 - tokio::spawn(async move { monitor.get_scrobble().await }), 17 - ) 18 - .await; 12 + let scrobbles_template = monitor.try_get_scrobble(); 13 + if scrobbles_template.is_none() { 14 + // start fetching scrobble so we can send a fresh response to the client ASAP 15 + tokio::spawn(async move { monitor.get_scrobble().await }); 16 + } 19 17 20 18 RootTemplate { 21 - scrobble: scrobbles_template 22 - .map_err(|_| tracing::debug!("last.fm request took too long")) 23 - .and_then(|scrobble| { 24 - scrobble 25 - .map_err(|err| tracing::error!(?err, "Panicked when trying to get scrobble")) 26 - }) 27 - .and_then(|scrobble| { 28 - scrobble.map_err(|err| tracing::warn!(?err, "Failed to get scrobble")) 29 - }) 30 - .ok(), 19 + scrobble: scrobbles_template, 31 20 } 32 21 }
+16 -4
server/src/scrobble_monitor.rs
··· 14 14 fetch_time: Instant, 15 15 } 16 16 17 + impl CachedScrobble { 18 + fn is_fresh(&self) -> bool { 19 + self.fetch_time.elapsed() < Duration::from_secs(30) 20 + } 21 + } 22 + 17 23 #[derive(Debug, Clone)] 18 24 pub struct ScrobbleMonitor { 19 25 client: Client, ··· 30 36 } 31 37 } 32 38 33 - pub async fn get_scrobble(&mut self) -> anyhow::Result<ScrobblesTemplate> { 34 - let is_fresh = |fetch_time: &Instant| fetch_time.elapsed() < Duration::from_secs(30); 39 + pub fn try_get_scrobble(&self) -> Option<ScrobblesTemplate> { 40 + let scrobble = &*self.last_scrobble.try_read().ok()?; 41 + scrobble 42 + .as_ref() 43 + .filter(|scrobble| scrobble.is_fresh()) 44 + .map(|scrobble| scrobble.data.clone()) 45 + } 35 46 47 + pub async fn get_scrobble(&mut self) -> anyhow::Result<ScrobblesTemplate> { 36 48 if let Some(scrobble) = &*self.last_scrobble.read().await { 37 - if is_fresh(&scrobble.fetch_time) { 49 + if scrobble.is_fresh() { 38 50 tracing::debug!("returning recently fetched scrobble data"); 39 51 return Ok(scrobble.data.clone()); 40 52 } ··· 44 56 match &*last_scrobble { 45 57 // make sure another task hasn't fetched the new data first after we 46 58 // both waited for write access 47 - Some(scrobble) if is_fresh(&scrobble.fetch_time) => { 59 + Some(scrobble) if scrobble.is_fresh() => { 48 60 tracing::debug!("returning (very) recently fetched scrobble data"); 49 61 Ok(scrobble.data.clone()) 50 62 }