@@ -440,6 +440,107 @@ describe("TaskHistoryStore", () => {
440440
441441 expect ( store . get ( "gone-task" ) ) . toBeUndefined ( )
442442 } )
443+
444+ it ( "waits for an in-flight write before refreshing the cache" , async ( ) => {
445+ await store . initialize ( )
446+
447+ const item = makeHistoryItem ( { id : "invalidate-locked" , tokensIn : 100 } )
448+ await store . upsert ( item )
449+
450+ let signalWriteStarted ! : ( ) => void
451+ const writeStarted = new Promise < void > ( ( resolve ) => {
452+ signalWriteStarted = resolve
453+ } )
454+ let releaseWrite ! : ( ) => void
455+ const writeCanFinish = new Promise < void > ( ( resolve ) => {
456+ releaseWrite = resolve
457+ } )
458+ let releaseStaleRead ! : ( ) => void
459+ const staleReadCanFinish = new Promise < void > ( ( resolve ) => {
460+ releaseStaleRead = resolve
461+ } )
462+ let writeReleased = false
463+
464+ const storeAny = store as any
465+ const originalWriteTaskFile = storeAny . writeTaskFile . bind ( store )
466+ const originalReadTaskFile = storeAny . readTaskFile . bind ( store )
467+ vi . spyOn ( storeAny , "writeTaskFile" ) . mockImplementation ( async ( ...args : unknown [ ] ) => {
468+ const next = args [ 0 ] as HistoryItem
469+ if ( next . id === item . id && next . tokensIn === 999 ) {
470+ signalWriteStarted ( )
471+ await writeCanFinish
472+ }
473+ return originalWriteTaskFile ( ...args )
474+ } )
475+ vi . spyOn ( storeAny , "readTaskFile" ) . mockImplementation ( async ( ...args : unknown [ ] ) => {
476+ if ( args [ 0 ] === item . id && ! writeReleased ) {
477+ await staleReadCanFinish
478+ return item
479+ }
480+ return originalReadTaskFile ( ...args )
481+ } )
482+
483+ const write = store . upsert ( { ...item , tokensIn : 999 } )
484+ await writeStarted
485+ const invalidation = store . invalidate ( item . id )
486+
487+ writeReleased = true
488+ releaseWrite ( )
489+ await write
490+ releaseStaleRead ( )
491+ await invalidation
492+
493+ expect ( store . get ( item . id ) ?. tokensIn ) . toBe ( 999 )
494+ } )
495+ } )
496+
497+ describe ( "invalidateAll()" , ( ) => {
498+ it ( "waits for an in-flight write before clearing the cache" , async ( ) => {
499+ const onWrite = vi . fn ( ) . mockResolvedValue ( undefined )
500+ store = new TaskHistoryStore ( tmpDir , { onWrite } )
501+ await store . initialize ( )
502+
503+ const first = makeHistoryItem ( { id : "invalidate-all-first" , ts : 1000 , tokensIn : 100 } )
504+ const second = makeHistoryItem ( { id : "invalidate-all-second" , ts : 2000 } )
505+ await store . upsert ( first )
506+ await store . upsert ( second )
507+ onWrite . mockClear ( )
508+
509+ let signalWriteStarted ! : ( ) => void
510+ const writeStarted = new Promise < void > ( ( resolve ) => {
511+ signalWriteStarted = resolve
512+ } )
513+ let releaseWrite ! : ( ) => void
514+ const writeCanFinish = new Promise < void > ( ( resolve ) => {
515+ releaseWrite = resolve
516+ } )
517+
518+ const storeAny = store as any
519+ const originalWriteTaskFile = storeAny . writeTaskFile . bind ( store )
520+ vi . spyOn ( storeAny , "writeTaskFile" ) . mockImplementation ( async ( ...args : unknown [ ] ) => {
521+ const item = args [ 0 ] as HistoryItem
522+ if ( item . id === first . id && item . tokensIn === 999 ) {
523+ signalWriteStarted ( )
524+ await writeCanFinish
525+ }
526+ return originalWriteTaskFile ( ...args )
527+ } )
528+
529+ const write = store . upsert ( { ...first , tokensIn : 999 } )
530+ await writeStarted
531+ const invalidation = store . invalidateAll ( )
532+
533+ releaseWrite ( )
534+ await write
535+ await invalidation
536+
537+ expect ( onWrite ) . toHaveBeenCalledTimes ( 1 )
538+ expect ( onWrite . mock . calls [ 0 ] [ 0 ] . map ( ( item : HistoryItem ) => item . id ) . sort ( ) ) . toEqual ( [
539+ "invalidate-all-first" ,
540+ "invalidate-all-second" ,
541+ ] )
542+ expect ( store . getAll ( ) ) . toEqual ( [ ] )
543+ } )
443544 } )
444545
445546 describe ( "atomicUpdatePair()" , ( ) => {
0 commit comments