1
1
use std:: borrow:: Borrow ;
2
2
use std:: collections:: HashMap ;
3
3
use std:: fs:: { self , File } ;
4
+ use std:: mem;
4
5
use std:: path:: Path ;
5
6
use std:: str:: FromStr ;
7
+ use std:: sync:: Mutex ;
6
8
7
9
use chrono:: NaiveDateTime ;
8
10
use futures_util:: StreamExt ;
@@ -19,6 +21,10 @@ use crate::ts::package_reference::PackageReference;
19
21
use crate :: ts:: version:: Version ;
20
22
use crate :: util:: file;
21
23
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
+
22
28
#[ derive( Serialize , Deserialize ) ]
23
29
struct IndexHeader {
24
30
update_time : NaiveDateTime ,
@@ -33,6 +39,7 @@ struct IndexHeader {
33
39
/// 3. The index. This contains a series of newline-delimited json strings, unparsed and unserialized.
34
40
#[ derive( Debug ) ]
35
41
pub struct PackageIndex {
42
+ update_time : NaiveDateTime ,
36
43
lookup : Vec < LookupTableEntry > ,
37
44
38
45
strict_lookup : HashMap < String , usize > ,
@@ -146,16 +153,15 @@ impl PackageIndex {
146
153
}
147
154
148
155
/// 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 > {
150
157
// Sync the index before we open it if it's in an invalid state.
151
158
if !is_index_valid ( tcli_home) {
152
159
PackageIndex :: sync ( tcli_home) . await ?;
153
160
}
154
161
155
162
// 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) ;
159
165
}
160
166
161
167
let index_dir = tcli_home. join ( "index" ) ;
@@ -180,16 +186,23 @@ impl PackageIndex {
180
186
}
181
187
182
188
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
+ } ?;
183
193
184
194
let index = PackageIndex {
195
+ update_time : header. update_time ,
185
196
lookup : entries,
186
197
loose_lookup : loose,
187
198
strict_lookup : strict,
188
199
index_file,
189
200
} ;
190
- CACHE . set ( index) . unwrap ( ) ;
191
201
192
- Ok ( CACHE . get ( ) . unwrap ( ) )
202
+ // Set or otherwise update the memory cache.
203
+ hydrate_cache ( index) ;
204
+
205
+ Ok ( & INDEX_CACHE . get ( ) . unwrap ( ) )
193
206
}
194
207
195
208
/// Get a package which matches the given package reference.
@@ -233,7 +246,7 @@ impl PackageIndex {
233
246
}
234
247
235
248
/// 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 {
237
250
let index_dir = tcli_home. join ( "index" ) ;
238
251
239
252
let lookup = index_dir. join ( "lookup.json" ) ;
@@ -242,3 +255,36 @@ pub fn is_index_valid(tcli_home: &Path) -> bool {
242
255
243
256
index_dir. exists ( ) && lookup. exists ( ) && index. exists ( ) && header. exists ( )
244
257
}
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
+ }
0 commit comments