@@ -209,3 +209,368 @@ macro_rules! make_thread_local_readhandle_cache {
209209 }
210210 } ;
211211}
212+
213+ #[ cfg( test) ]
214+ mod tests {
215+ #![ allow( clippy:: collapsible_if) ]
216+
217+ use super :: * ;
218+ use left_right:: { Absorb , ReadHandleFactory , WriteHandle } ;
219+ use serial_test:: serial;
220+ use std:: sync:: Mutex ;
221+ // Our left-right protected struct
222+ #[ derive( Debug , Clone ) ]
223+ struct TestStruct {
224+ id : u64 ,
225+ data : String ,
226+ }
227+ impl TestStruct {
228+ fn new ( id : u64 , data : & str ) -> Self {
229+ Self {
230+ id,
231+ data : data. to_string ( ) ,
232+ }
233+ }
234+ }
235+ // Implement identity for TestStruct
236+ impl Identity < u64 > for TestStruct {
237+ fn identity ( & self ) -> u64 {
238+ self . id
239+ }
240+ }
241+
242+ // Dummy implementation of Absorb for TestStruct
243+ #[ derive( Debug ) ]
244+ enum TestStructChange {
245+ Update ( String ) ,
246+ }
247+ impl Absorb < TestStructChange > for TestStruct {
248+ fn absorb_first ( & mut self , op : & mut TestStructChange , _other : & Self ) {
249+ match op {
250+ TestStructChange :: Update ( data) => {
251+ self . data = data. clone ( ) ;
252+ }
253+ }
254+ }
255+ fn sync_with ( & mut self , first : & Self ) {
256+ * self = first. clone ( ) ;
257+ }
258+ }
259+
260+ // create local cache
261+ make_thread_local_readhandle_cache ! ( TEST_CACHE , u64 , TestStruct ) ;
262+
263+ // ReadHandle "owner" implementing ReadHandleProvider
264+ #[ derive( Debug ) ]
265+ struct TestProviderEntry < TestStruct : Absorb < TestStructChange > , TestStructChange > {
266+ id : u64 ,
267+ factory : ReadHandleFactory < TestStruct > ,
268+ // writer owning the TestStruct. We use option to easily drop it
269+ // and Mutex to make the provider Sync.
270+ writer : Option < Mutex < WriteHandle < TestStruct , TestStructChange > > > ,
271+ }
272+ impl TestProviderEntry < TestStruct , TestStructChange > {
273+ fn new (
274+ id : u64 ,
275+ factory : ReadHandleFactory < TestStruct > ,
276+ writer : Option < Mutex < WriteHandle < TestStruct , TestStructChange > > > ,
277+ ) -> Self {
278+ Self {
279+ id,
280+ factory,
281+ writer,
282+ }
283+ }
284+ }
285+ #[ derive( Debug ) ]
286+ struct TestProvider {
287+ data : HashMap < u64 , TestProviderEntry < TestStruct , TestStructChange > > ,
288+ version : u64 ,
289+ }
290+ impl TestProvider {
291+ fn new ( ) -> Self {
292+ Self {
293+ data : HashMap :: new ( ) ,
294+ version : 0 ,
295+ }
296+ }
297+ fn add_object ( & mut self , key : u64 , identity : u64 ) {
298+ if key != identity {
299+ let entry = self . data . get ( & identity) . unwrap ( ) ;
300+ let new = TestProviderEntry :: new ( identity, entry. factory . clone ( ) , None ) ;
301+ self . data . insert ( key, new) ;
302+ } else {
303+ let object = TestStruct :: new ( identity, "unset" ) ;
304+ let ( w, r) = left_right:: new_from_empty ( object) ;
305+ let entry = TestProviderEntry :: new ( identity, r. factory ( ) , Some ( Mutex :: new ( w) ) ) ;
306+ self . data . insert ( key, entry) ;
307+ let stored = self . data . get ( & key) . unwrap ( ) ;
308+ assert_eq ! ( stored. id, identity) ;
309+ }
310+ self . version = self . version . wrapping_add ( 1 ) ;
311+ }
312+ fn mod_object ( & mut self , key : u64 , data : & str ) {
313+ if let Some ( object) = self . data . get_mut ( & key) {
314+ if let Some ( writer_lock) = & mut object. writer {
315+ #[ allow( clippy:: mut_mutex_lock) ] // lock exists just to make provider Sync
316+ let mut writer = writer_lock. lock ( ) . unwrap ( ) ;
317+ writer. append ( TestStructChange :: Update ( data. to_owned ( ) ) ) ;
318+ writer. publish ( ) ;
319+ }
320+ }
321+ }
322+ fn drop_writer ( & mut self , key : u64 ) {
323+ if let Some ( object) = self . data . get_mut ( & key) {
324+ let x = object. writer . take ( ) ;
325+ drop ( x) ;
326+ self . version = self . version . wrapping_add ( 1 ) ;
327+ }
328+ }
329+ }
330+
331+ // Implement trait ReadHandleProvider
332+ impl ReadHandleProvider for TestProvider {
333+ type Data = TestStruct ;
334+ type Key = u64 ;
335+ fn get_version ( & self ) -> u64 {
336+ self . version
337+ }
338+ fn get_factory (
339+ & self ,
340+ key : & Self :: Key ,
341+ ) -> Option < ( & ReadHandleFactory < Self :: Data > , Self :: Key , u64 ) > {
342+ self . data
343+ . get ( key)
344+ . map ( |entry| ( & entry. factory , entry. id , self . version ) )
345+ }
346+ fn get_identity ( & self , key : & Self :: Key ) -> Option < Self :: Key > {
347+ self . data . get ( key) . map ( |entry| entry. id )
348+ }
349+ }
350+
351+ #[ serial]
352+ #[ test]
353+ fn test_readhandle_cache_basic ( ) {
354+ // start fresh
355+ ReadHandleCache :: purge ( & TEST_CACHE ) ;
356+
357+ // build provider
358+ let mut provider = TestProvider :: new ( ) ;
359+ provider. add_object ( 1 , 1 ) ;
360+ provider. add_object ( 2 , 2 ) ;
361+ provider. mod_object ( 1 , "object-1" ) ;
362+ provider. mod_object ( 2 , "object-2" ) ;
363+
364+ // add alias for 1
365+ provider. add_object ( 6000 , 1 ) ;
366+
367+ {
368+ let key = 1 ;
369+ println ! ( "Test: Access to object with key {key}" ) ;
370+ let h = ReadHandleCache :: get_reader ( & TEST_CACHE , key, & provider) . unwrap ( ) ;
371+ let x = h. enter ( ) . unwrap ( ) ;
372+ let obj = x. as_ref ( ) ;
373+ assert_eq ! ( obj. id, 1 ) ;
374+ assert_eq ! ( obj. data, "object-1" ) ;
375+ }
376+
377+ {
378+ let key = 2 ;
379+ println ! ( "Test: Access to object with key {key}" ) ;
380+ let h = ReadHandleCache :: get_reader ( & TEST_CACHE , key, & provider) . unwrap ( ) ;
381+ let x = h. enter ( ) . unwrap ( ) ;
382+ let obj = x. as_ref ( ) ;
383+ assert_eq ! ( obj. id, 2 ) ;
384+ assert_eq ! ( obj. data, "object-2" ) ;
385+ }
386+
387+ {
388+ // 6000 is alias for 1: should access object with id 1
389+ let key = 6000 ;
390+ println ! ( "Test: Access to object with key {key}" ) ;
391+ let h = ReadHandleCache :: get_reader ( & TEST_CACHE , key, & provider) . unwrap ( ) ;
392+ let x = h. enter ( ) . unwrap ( ) ;
393+ let obj = x. as_ref ( ) ;
394+ assert_eq ! ( obj. id, 1 ) ;
395+ assert_eq ! ( obj. data, "object-1" ) ;
396+ }
397+
398+ TEST_CACHE . with ( |x| {
399+ let x = x. handles . borrow ( ) ;
400+ assert ! ( x. contains_key( & 6000 ) ) ;
401+ assert ! ( x. contains_key( & 1 ) ) ;
402+ assert ! ( x. contains_key( & 2 ) ) ;
403+ assert_eq ! ( x. len( ) , 3 ) ;
404+ println ! ( "Test: cache contains entries" ) ;
405+ } ) ;
406+
407+ println ! ( "Change: Let 6000 be a key for 2 instead of 1" ) ;
408+ provider. add_object ( 6000 , 2 ) ;
409+ provider. mod_object ( 2 , "object-2-modified" ) ;
410+ {
411+ let key = 6000 ;
412+ let h = ReadHandleCache :: get_reader ( & TEST_CACHE , key, & provider) . unwrap ( ) ;
413+ let x = h. enter ( ) . unwrap ( ) ;
414+ let obj = x. as_ref ( ) ;
415+ assert_eq ! ( obj. id, 2 ) ;
416+ assert_eq ! ( obj. data, "object-2-modified" ) ;
417+ }
418+ {
419+ let key = 6000 ;
420+ println ! ( "Test: Access to object with key {key}" ) ;
421+ let h = ReadHandleCache :: get_reader ( & TEST_CACHE , key, & provider) . unwrap ( ) ;
422+ let x = h. enter ( ) . unwrap ( ) ;
423+ let obj = x. as_ref ( ) ;
424+ assert_eq ! ( obj. id, 2 ) ;
425+ assert_eq ! ( obj. data, "object-2-modified" ) ;
426+ }
427+
428+ println ! ( "Change: drop data for object 1. It should not be accessible" ) ;
429+ provider. drop_writer ( 1 ) ;
430+ {
431+ let key = 1 ;
432+ println ! ( "Test: Access to object with key {key}" ) ;
433+ let h = ReadHandleCache :: get_reader ( & TEST_CACHE , key, & provider) ;
434+ assert ! ( h. is_err_and( |e| e == ReadHandleCacheError :: NotAccessible ( key) ) ) ;
435+ }
436+
437+ println ! ( "Change: drop data for object 2: should not be accessible by keys 2 and 6000" ) ;
438+ provider. drop_writer ( 2 ) ;
439+ {
440+ let key = 2 ;
441+ println ! ( "Test: Access to object with key {key}" ) ;
442+ let h = ReadHandleCache :: get_reader ( & TEST_CACHE , key, & provider) ;
443+ assert ! ( h. is_err_and( |e| e == ReadHandleCacheError :: NotAccessible ( key) ) ) ;
444+ }
445+ {
446+ let key = 6000 ;
447+ println ! ( "Test: Access to object with key {key}" ) ;
448+ let h = ReadHandleCache :: get_reader ( & TEST_CACHE , key, & provider) ;
449+ assert ! ( h. is_err_and( |e| e == ReadHandleCacheError :: NotAccessible ( key) ) ) ;
450+ }
451+
452+ // ensure cache is clean
453+ TEST_CACHE . with ( |x| {
454+ let x = x. handles . borrow ( ) ;
455+ assert ! ( !x. contains_key( & 6000 ) ) ;
456+ assert ! ( !x. contains_key( & 1 ) ) ;
457+ assert ! ( !x. contains_key( & 2 ) ) ;
458+ assert ! ( x. is_empty( ) ) ;
459+ println ! ( "Test: cache is empty" ) ;
460+ } ) ;
461+ }
462+
463+ #[ serial]
464+ #[ test]
465+ fn test_readhandle_cache_multi_invalidation ( ) {
466+ // start fresh
467+ ReadHandleCache :: purge ( & TEST_CACHE ) ;
468+
469+ const NUM_ALIASES : u64 = 10 ;
470+
471+ // build provider
472+ let mut provider = TestProvider :: new ( ) ;
473+ provider. add_object ( 1 , 1 ) ;
474+ provider. mod_object ( 1 , "object-1" ) ;
475+
476+ // add aliases
477+ for k in 1 ..=NUM_ALIASES {
478+ let alias = 100 + k;
479+ provider. add_object ( alias, 1 ) ;
480+ }
481+
482+ // query for identity and all aliases
483+ ReadHandleCache :: get_reader ( & TEST_CACHE , 1 , & provider) . unwrap ( ) ;
484+ for k in 1 ..=NUM_ALIASES {
485+ let alias = 100 + k;
486+ ReadHandleCache :: get_reader ( & TEST_CACHE , alias, & provider) . unwrap ( ) ;
487+ }
488+
489+ // cache should contain NUM_ALIASES + 1 entries
490+ TEST_CACHE
491+ . with ( |cache| assert_eq ! ( cache. handles. borrow( ) . len( ) as u64 , ( NUM_ALIASES + 1u64 ) ) ) ;
492+
493+ provider. drop_writer ( 1 ) ;
494+
495+ // do single query for identity
496+ let h = ReadHandleCache :: get_reader ( & TEST_CACHE , 1 , & provider) ;
497+ assert ! ( h. is_err_and( |e| e == ReadHandleCacheError :: NotAccessible ( 1 ) ) ) ;
498+
499+ // all entries should have been invalidated
500+ TEST_CACHE . with ( |cache| assert ! ( cache. handles. borrow( ) . is_empty( ) ) ) ;
501+
502+ // querying again with key = identity should fail
503+ let h = ReadHandleCache :: get_reader ( & TEST_CACHE , 1 , & provider) ;
504+ assert ! ( h. is_err_and( |e| e == ReadHandleCacheError :: NotAccessible ( 1 ) ) ) ;
505+
506+ // querying again with aliases should fail too. Aliases are 100 + k k=1..=NUM_ALIASES
507+ let alias = 100 + 1 ;
508+ let h = ReadHandleCache :: get_reader ( & TEST_CACHE , alias, & provider) ;
509+ assert ! ( h. is_err_and( |e| e == ReadHandleCacheError :: NotAccessible ( alias) ) ) ;
510+ }
511+
512+ #[ serial]
513+ #[ test]
514+ fn test_readhandle_cache ( ) {
515+ // start fresh
516+ ReadHandleCache :: purge ( & TEST_CACHE ) ;
517+
518+ // build provider and populate it
519+ const NUM_HANDLES : u64 = 1000 ;
520+ let mut provider = TestProvider :: new ( ) ;
521+ for id in 0 ..=NUM_HANDLES {
522+ provider. add_object ( id, id) ;
523+ provider. mod_object ( id, format ! ( "object-id-{id}" ) . as_ref ( ) ) ;
524+ provider. add_object ( id + NUM_HANDLES + 1 , id) ;
525+ }
526+
527+ // access all objects by id
528+ for id in 0 ..=NUM_HANDLES {
529+ let h = ReadHandleCache :: get_reader ( & TEST_CACHE , id, & provider) . unwrap ( ) ;
530+ let x = h. enter ( ) . unwrap ( ) ;
531+ let obj = x. as_ref ( ) ;
532+ assert_eq ! ( obj. id, id) ;
533+ }
534+
535+ // access all objects by alias
536+ for id in 0 ..=NUM_HANDLES {
537+ let alias = id + NUM_HANDLES + 1 ;
538+ let h = ReadHandleCache :: get_reader ( & TEST_CACHE , alias, & provider) . unwrap ( ) ;
539+ let x = h. enter ( ) . unwrap ( ) ;
540+ let obj = x. as_ref ( ) ;
541+ assert_eq ! ( obj. id, id) ;
542+ }
543+
544+ // modify all objects and replace all aliases
545+ for id in 0 ..=NUM_HANDLES {
546+ let alias = 2 * NUM_HANDLES + 1 - id;
547+ provider. mod_object ( id, format ! ( "modified-id-{id}" ) . as_ref ( ) ) ;
548+ provider. add_object ( alias, id) ;
549+ }
550+
551+ // access objects with re-assigned aliases
552+ for id in 0 ..=NUM_HANDLES {
553+ let alias = 2 * NUM_HANDLES + 1 - id;
554+ let h = ReadHandleCache :: get_reader ( & TEST_CACHE , alias, & provider) . unwrap ( ) ;
555+ let x = h. enter ( ) . unwrap ( ) ;
556+ let obj = x. as_ref ( ) ;
557+ assert_eq ! ( obj. id, id) ;
558+ assert_eq ! ( obj. data, format!( "modified-id-{id}" ) ) ;
559+ }
560+
561+ // invalidate all writers
562+ for id in 0 ..=NUM_HANDLES {
563+ provider. drop_writer ( id) ;
564+ }
565+
566+ // access objects from alias: none should be accessible
567+ for id in 0 ..=NUM_HANDLES {
568+ let alias = 2 * NUM_HANDLES + 1 - id;
569+ let h = ReadHandleCache :: get_reader ( & TEST_CACHE , alias, & provider) ;
570+ assert ! ( h. is_err_and( |e| e == ReadHandleCacheError :: NotAccessible ( alias) ) ) ;
571+ }
572+
573+ // all handles from cache should have been removed as we looked up them all
574+ TEST_CACHE . with ( |cache| assert ! ( cache. handles. borrow( ) . is_empty( ) ) ) ;
575+ }
576+ }
0 commit comments