My personal site cherry.computer
htmx tailwind axum askama

Avoid contention causing multiple requests to API

If multiple tasks see that the scrobble cache is out of date
simultaneously, they will all requests write access concurrently. If
there is no check after they have been given the lock that another task
hasn't already got there first then multiple tasks could make the same
call to the last.fm API, writing and re-writing over a perfectly fresh
value.

+21 -10
+21 -10
server/src/scrobble_monitor.rs
··· 29 29 } 30 30 31 31 pub async fn get_scrobble(&mut self) -> anyhow::Result<String> { 32 + let is_fresh = |fetch_time: &Instant| fetch_time.elapsed() < Duration::from_secs(30); 33 + 32 34 if let Some(scrobble) = &*self.last_scrobble.read().await { 33 - if scrobble.fetch_time.elapsed() < Duration::from_secs(30) { 35 + if is_fresh(&scrobble.fetch_time) { 34 36 tracing::debug!("returning recently fetched scrobble data"); 35 37 return Ok(scrobble.data.clone()); 36 38 } 37 39 } 38 40 39 - tracing::debug!("fetching new scrobble data"); 40 - // try and prevent multiple handlers calling the API at the same time 41 - let mut write_guard = self.last_scrobble.write().await; 42 - let latest = self.fetch_scrobble().await?; 43 - *write_guard = Some(Scrobble { 44 - data: latest.clone(), 45 - fetch_time: Instant::now(), 46 - }); 47 - Ok(latest) 41 + let mut last_scrobble = self.last_scrobble.write().await; 42 + match &*last_scrobble { 43 + // make sure another task hasn't fetched the new data first after we 44 + // both waited for write access 45 + Some(scrobble) if is_fresh(&scrobble.fetch_time) => { 46 + tracing::debug!("returning (very) recently fetched scrobble data"); 47 + Ok(scrobble.data.clone()) 48 + } 49 + _ => { 50 + tracing::debug!("fetching new scrobble data"); 51 + let latest = self.fetch_scrobble().await?; 52 + *last_scrobble = Some(Scrobble { 53 + data: latest.clone(), 54 + fetch_time: Instant::now(), 55 + }); 56 + Ok(latest) 57 + } 58 + } 48 59 } 49 60 50 61 async fn fetch_scrobble(&self) -> anyhow::Result<String> {