Skip to content

Commit 2d50c2a

Browse files
Merge pull request #23 from thunderstore-io/fix-cache-updates
Fix cache invalidation when on-disk index updates
2 parents fdf985e + 0592e21 commit 2d50c2a

File tree

4 files changed

+57
-9
lines changed

4 files changed

+57
-9
lines changed

src/package/index.rs

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use std::borrow::Borrow;
22
use std::collections::HashMap;
33
use std::fs::{self, File};
4+
use std::mem;
45
use std::path::Path;
56
use std::str::FromStr;
7+
use std::sync::Mutex;
68

79
use chrono::NaiveDateTime;
810
use futures_util::StreamExt;
@@ -19,6 +21,10 @@ use crate::ts::package_reference::PackageReference;
1921
use crate::ts::version::Version;
2022
use crate::util::file;
2123

24+
// Memory cache for the package index. We set this when we initially load the index lookup table
25+
// so we don't need to query the disk, however we must also be able to update it when necessary.
26+
static INDEX_CACHE: OnceCell<Mutex<PackageIndex>> = OnceCell::new();
27+
2228
#[derive(Serialize, Deserialize)]
2329
struct IndexHeader {
2430
update_time: NaiveDateTime,
@@ -33,6 +39,7 @@ struct IndexHeader {
3339
/// 3. The index. This contains a series of newline-delimited json strings, unparsed and unserialized.
3440
#[derive(Debug)]
3541
pub struct PackageIndex {
42+
update_time: NaiveDateTime,
3643
lookup: Vec<LookupTableEntry>,
3744

3845
strict_lookup: HashMap<String, usize>,
@@ -146,16 +153,15 @@ impl PackageIndex {
146153
}
147154

148155
/// Open and serialize the on-disk index, retrieving a fresh copy if it doesn't already exist.
149-
pub async fn open(tcli_home: &Path) -> Result<&PackageIndex, Error> {
156+
pub async fn open(tcli_home: &Path) -> Result<&Mutex<PackageIndex>, Error> {
150157
// Sync the index before we open it if it's in an invalid state.
151158
if !is_index_valid(tcli_home) {
152159
PackageIndex::sync(tcli_home).await?;
153160
}
154161

155162
// Maintain a cached version of the index so subsequent calls don't trigger a complete reload.
156-
static CACHE: OnceCell<PackageIndex> = OnceCell::new();
157-
if let Some(index) = CACHE.get() {
158-
return Ok(index);
163+
if let Some(index) = INDEX_CACHE.get() {
164+
return Ok(&index);
159165
}
160166

161167
let index_dir = tcli_home.join("index");
@@ -180,16 +186,23 @@ impl PackageIndex {
180186
}
181187

182188
let index_file = File::open(index_dir.join("index.json"))?;
189+
let header: IndexHeader = {
190+
let header = fs::read_to_string(index_dir.join("header.json"))?;
191+
serde_json::from_str(&header)
192+
}?;
183193

184194
let index = PackageIndex {
195+
update_time: header.update_time,
185196
lookup: entries,
186197
loose_lookup: loose,
187198
strict_lookup: strict,
188199
index_file,
189200
};
190-
CACHE.set(index).unwrap();
191201

192-
Ok(CACHE.get().unwrap())
202+
// Set or otherwise update the memory cache.
203+
hydrate_cache(index);
204+
205+
Ok(&INDEX_CACHE.get().unwrap())
193206
}
194207

195208
/// Get a package which matches the given package reference.
@@ -233,7 +246,7 @@ impl PackageIndex {
233246
}
234247

235248
/// Determine if the index is in a valid state or not.
236-
pub fn is_index_valid(tcli_home: &Path) -> bool {
249+
fn is_index_valid(tcli_home: &Path) -> bool {
237250
let index_dir = tcli_home.join("index");
238251

239252
let lookup = index_dir.join("lookup.json");
@@ -242,3 +255,36 @@ pub fn is_index_valid(tcli_home: &Path) -> bool {
242255

243256
index_dir.exists() && lookup.exists() && index.exists() && header.exists()
244257
}
258+
259+
/// Determine if the in-memory cache is older than the index on disk.
260+
fn is_cache_expired(tcli_home: &Path) -> bool {
261+
let index_dir = tcli_home.join("index");
262+
let Ok(header) = fs::read_to_string(index_dir.join("header.json")) else {
263+
warn!("Failed to read from index header, invalidating the cache.");
264+
return true;
265+
};
266+
267+
let Ok(header) = serde_json::from_str::<IndexHeader>(&header) else {
268+
return true;
269+
};
270+
271+
if let Some(index) = INDEX_CACHE.get() {
272+
let index = index.lock().unwrap();
273+
index.update_time != header.update_time
274+
} else {
275+
true
276+
}
277+
}
278+
279+
/// Init the cache or hydrate it with updated data.
280+
fn hydrate_cache(index: PackageIndex) {
281+
// If the cache hasn't set, do so and stop.
282+
if INDEX_CACHE.get().is_none() {
283+
INDEX_CACHE.get_or_init(|| Mutex::new(index));
284+
return;
285+
};
286+
287+
// Otherwise update the cache.
288+
let cache = INDEX_CACHE.get().unwrap();
289+
let _ = mem::replace(&mut *cache.lock().unwrap(), index);
290+
}

src/package/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ impl Package {
107107
let ident = ident.borrow();
108108

109109
let index = PackageIndex::open(&TCLI_HOME).await?;
110-
let package = index.get_package(ident).unwrap();
110+
let package = index.lock().unwrap().get_package(ident).unwrap();
111111

112112
Ok(Package {
113113
identifier: ident.clone(),

src/package/resolver.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@ pub async fn resolve_packages(packages: Vec<PackageReference>) -> Result<Depende
225225

226226
while let Some(package_ident) = iter_queue.pop_front() {
227227
let package = package_index
228+
.lock()
229+
.unwrap()
228230
.get_package(package_ident.as_ref())
229231
.unwrap_or_else(|| panic!("{} does not exist in the index.", package_ident));
230232

src/server/method/package.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ impl PackageMethod {
3232
match self {
3333
Self::GetMetadata(data) => {
3434
let index = PackageIndex::open(&TCLI_HOME).await?;
35-
let package = index.get_package(&data.package).unwrap();
35+
let package = index.lock().unwrap().get_package(&data.package).unwrap();
3636
rt.send(Response::data_ok(Id::String("diowadaw".into()), package));
3737
}
3838
Self::IsCached(data) => {

0 commit comments

Comments
 (0)