20
20
const { AdvancedBase } = require ( "@heyputer/putility" ) ;
21
21
const { FileTracker } = require ( "./FileTracker" ) ;
22
22
const { pausing_tee } = require ( "../../util/streamutil" ) ;
23
+ const putility = require ( "@heyputer/putility" ) ;
24
+ const { EWMA } = require ( "../../util/opmath" ) ;
23
25
24
26
/**
25
27
* FileCacheService
@@ -71,6 +73,11 @@ class FileCacheService extends AdvancedBase {
71
73
this . precache = new Map ( ) ;
72
74
this . uid_to_tracker = new Map ( ) ;
73
75
76
+ this . cache_hit_rate = new EWMA ( {
77
+ initial : 0.5 ,
78
+ alpha : 0.2 ,
79
+ } ) ;
80
+
74
81
this . init ( ) ;
75
82
76
83
this . _register_commands ( services . get ( 'commands' ) ) ;
@@ -139,7 +146,12 @@ class FileCacheService extends AdvancedBase {
139
146
* @param {string } uid - The unique identifier of the file.
140
147
* @returns {string } The full path where the file is stored on disk.
141
148
*/
142
- async try_get ( fsNode , opt_log ) {
149
+ async try_get ( fsNode , opt_log ) {
150
+ const result = await this . try_get_ ( fsNode , opt_log ) ;
151
+ this . cache_hit_rate . put ( result ? 1 : 0 ) ;
152
+ return result ;
153
+ }
154
+ async try_get_ ( fsNode , opt_log ) {
143
155
const tracker = this . uid_to_tracker . get ( await fsNode . get ( 'uid' ) ) ;
144
156
145
157
if ( ! tracker ) {
@@ -153,6 +165,27 @@ class FileCacheService extends AdvancedBase {
153
165
154
166
tracker . touch ( ) ;
155
167
168
+ // If the file is in pending, that means it's currenty being read
169
+ // for cache entry, so we wait for it to be ready.
170
+ if ( tracker . phase === FileTracker . PHASE_PENDING ) {
171
+ Promise . race ( [
172
+ tracker . p_ready ,
173
+ new Promise ( resolve => setTimeout ( resolve , 2000 ) )
174
+ ] ) ;
175
+ }
176
+
177
+ // If the file is still in pending it means we waited too long;
178
+ // it's possible that reading the file failed is is delayed.
179
+ if ( tracker . phase === FileTracker . PHASE_PENDING ) {
180
+ return this . _get_path ( await fsNode . get ( 'uid' ) ) ;
181
+ }
182
+
183
+ // Since we waited for the file to be ready, it's not impossible
184
+ // that it was evicted in the meantime; just very unlikely.
185
+ if ( tracker . phase === FileTracker . PHASE_GONE ) {
186
+ return null ;
187
+ }
188
+
156
189
if ( tracker . phase === FileTracker . PHASE_PRECACHE ) {
157
190
if ( opt_log ) opt_log . info ( 'obtained from precache' ) ;
158
191
return this . precache . get ( await fsNode . get ( 'uid' ) ) ;
@@ -218,6 +251,7 @@ class FileCacheService extends AdvancedBase {
218
251
// Add file tracker
219
252
const tracker = new FileTracker ( { key, size } ) ;
220
253
this . uid_to_tracker . set ( key , tracker ) ;
254
+ tracker . p_ready = new putility . libs . promise . TeePromise ( ) ;
221
255
tracker . touch ( ) ;
222
256
223
257
@@ -236,6 +270,7 @@ class FileCacheService extends AdvancedBase {
236
270
await this . _precache_make_room ( size ) ;
237
271
this . precache . set ( key , data ) ;
238
272
tracker . phase = FileTracker . PHASE_PRECACHE ;
273
+ tracker . p_ready . resolve ( ) ;
239
274
} ) ( )
240
275
241
276
return { cached : true , stream : replace_stream } ;
@@ -395,6 +430,12 @@ class FileCacheService extends AdvancedBase {
395
430
396
431
log . log ( JSON . stringify ( status , null , 2 ) ) ;
397
432
}
433
+ } ,
434
+ {
435
+ id : 'hitrate' ,
436
+ handler : async ( args , log ) => {
437
+ log . log ( this . cache_hit_rate . get ( ) ) ;
438
+ }
398
439
}
399
440
] ) ;
400
441
}
0 commit comments