comparison OrthancFramework/UnitTestsSources/MemoryCacheTests.cpp @ 5420:d37dff2c0028 am-new-cache

Optimized the MemoryStringCache to prevent loading the same file multiple times if multiple users request the same file at the same time
author Alain Mazy <am@osimis.io>
date Mon, 13 Nov 2023 17:01:59 +0100
parents 0ea402b4d901
children 48b8dae6dc77
comparison
equal deleted inserted replaced
5419:ac4e9fb87615 5420:d37dff2c0028
31 #include "../Sources/Cache/MemoryCache.h" 31 #include "../Sources/Cache/MemoryCache.h"
32 #include "../Sources/Cache/MemoryStringCache.h" 32 #include "../Sources/Cache/MemoryStringCache.h"
33 #include "../Sources/Cache/SharedArchive.h" 33 #include "../Sources/Cache/SharedArchive.h"
34 #include "../Sources/IDynamicObject.h" 34 #include "../Sources/IDynamicObject.h"
35 #include "../Sources/Logging.h" 35 #include "../Sources/Logging.h"
36 #include "../Sources/SystemToolbox.h"
36 37
37 #include <memory> 38 #include <memory>
38 #include <algorithm> 39 #include <algorithm>
39 #include <boost/thread.hpp> 40 #include <boost/thread.hpp>
40 #include <boost/lexical_cast.hpp> 41 #include <boost/lexical_cast.hpp>
317 TEST(MemoryStringCache, Basic) 318 TEST(MemoryStringCache, Basic)
318 { 319 {
319 Orthanc::MemoryStringCache c; 320 Orthanc::MemoryStringCache c;
320 ASSERT_THROW(c.SetMaximumSize(0), Orthanc::OrthancException); 321 ASSERT_THROW(c.SetMaximumSize(0), Orthanc::OrthancException);
321 322
322 c.SetMaximumSize(2); 323 c.SetMaximumSize(3);
323 324
324 std::string v; 325 std::string v;
325 ASSERT_FALSE(c.Fetch(v, "hello")); 326 {
326 327 Orthanc::MemoryStringCache::Accessor a(c);
327 c.Add("hello", "a"); 328 ASSERT_FALSE(a.Fetch(v, "key1"));
328 ASSERT_TRUE(c.Fetch(v, "hello")); ASSERT_EQ("a", v); 329 }
329 ASSERT_FALSE(c.Fetch(v, "hello2")); 330
330 ASSERT_FALSE(c.Fetch(v, "hello3")); 331 {
331 332 Orthanc::MemoryStringCache::Accessor a(c);
332 c.Add("hello2", "b"); 333 ASSERT_FALSE(a.Fetch(v, "key1"));
333 ASSERT_TRUE(c.Fetch(v, "hello")); ASSERT_EQ("a", v); 334 a.Add("key1", "a");
334 ASSERT_TRUE(c.Fetch(v, "hello2")); ASSERT_EQ("b", v); 335 ASSERT_TRUE(a.Fetch(v, "key1"));
335 ASSERT_FALSE(c.Fetch(v, "hello3")); 336 ASSERT_EQ("a", v);
336 337
337 c.Add("hello3", "too large value"); 338 ASSERT_FALSE(a.Fetch(v, "key2"));
338 ASSERT_TRUE(c.Fetch(v, "hello")); ASSERT_EQ("a", v); 339 ASSERT_FALSE(a.Fetch(v, "key3"));
339 ASSERT_TRUE(c.Fetch(v, "hello2")); ASSERT_EQ("b", v); 340
340 ASSERT_FALSE(c.Fetch(v, "hello3")); 341 a.Add("key2", "b");
341 342 ASSERT_TRUE(a.Fetch(v, "key1"));
342 c.Add("hello3", "c"); 343 ASSERT_EQ("a", v);
343 ASSERT_FALSE(c.Fetch(v, "hello")); // Recycled 344 ASSERT_TRUE(a.Fetch(v, "key2"));
344 ASSERT_TRUE(c.Fetch(v, "hello2")); ASSERT_EQ("b", v); 345 ASSERT_EQ("b", v);
345 ASSERT_TRUE(c.Fetch(v, "hello3")); ASSERT_EQ("c", v); 346
346 } 347 a.Add("key3", "too-large-value");
347 348 ASSERT_TRUE(a.Fetch(v, "key1"));
349 ASSERT_EQ("a", v);
350 ASSERT_TRUE(a.Fetch(v, "key2"));
351 ASSERT_EQ("b", v);
352 ASSERT_FALSE(a.Fetch(v, "key3"));
353
354 a.Add("key3", "c");
355 ASSERT_TRUE(a.Fetch(v, "key2"));
356 ASSERT_EQ("b", v);
357 ASSERT_TRUE(a.Fetch(v, "key1"));
358 ASSERT_EQ("a", v);
359 ASSERT_TRUE(a.Fetch(v, "key3"));
360 ASSERT_EQ("c", v);
361
362 // adding a fourth value should remove the oldest accessed value (key2)
363 a.Add("key4", "d");
364 ASSERT_FALSE(a.Fetch(v, "key2"));
365 ASSERT_TRUE(a.Fetch(v, "key1"));
366 ASSERT_EQ("a", v);
367 ASSERT_TRUE(a.Fetch(v, "key3"));
368 ASSERT_EQ("c", v);
369 ASSERT_TRUE(a.Fetch(v, "key4"));
370 ASSERT_EQ("d", v);
371
372 }
373 }
348 374
349 TEST(MemoryStringCache, Invalidate) 375 TEST(MemoryStringCache, Invalidate)
350 { 376 {
351 Orthanc::MemoryStringCache c; 377 Orthanc::MemoryStringCache c;
352 c.Add("hello", "a"); 378 Orthanc::MemoryStringCache::Accessor a(c);
353 c.Add("hello2", "b"); 379
354 380 a.Add("hello", "a");
355 std::string v; 381 a.Add("hello2", "b");
356 ASSERT_TRUE(c.Fetch(v, "hello")); ASSERT_EQ("a", v); 382
357 ASSERT_TRUE(c.Fetch(v, "hello2")); ASSERT_EQ("b", v); 383 std::string v;
384 ASSERT_TRUE(a.Fetch(v, "hello"));
385 ASSERT_EQ("a", v);
386 ASSERT_TRUE(a.Fetch(v, "hello2"));
387 ASSERT_EQ("b", v);
358 388
359 c.Invalidate("hello"); 389 c.Invalidate("hello");
360 ASSERT_FALSE(c.Fetch(v, "hello")); 390 ASSERT_FALSE(a.Fetch(v, "hello"));
361 ASSERT_TRUE(c.Fetch(v, "hello2")); ASSERT_EQ("b", v); 391 ASSERT_TRUE(a.Fetch(v, "hello2"));
362 } 392 ASSERT_EQ("b", v);
393 }
394
395
396 static int ThreadingScenarioHappyStep = 0;
397 static Orthanc::MemoryStringCache ThreadingScenarioHappyCache;
398
399 void ThreadingScenarioHappyThread1()
400 {
401 // the first thread to call Fetch (will be in charge of adding)
402 Orthanc::MemoryStringCache::Accessor a(ThreadingScenarioHappyCache);
403 std::string v;
404
405 LOG(INFO) << "Thread1 will fetch";
406 if (!a.Fetch(v, "key1"))
407 {
408 LOG(INFO) << "Thread1 has fetch";
409 ThreadingScenarioHappyStep = 1;
410
411 // wait for the other thread to fetch too
412 while (ThreadingScenarioHappyStep < 2)
413 {
414 Orthanc::SystemToolbox::USleep(10000);
415 }
416 LOG(INFO) << "Thread1 will add after a short sleep";
417 Orthanc::SystemToolbox::USleep(100000);
418 LOG(INFO) << "Thread1 will add";
419
420 a.Add("key1", "value1");
421
422 LOG(INFO) << "Thread1 has added";
423 }
424 }
425
426 void ThreadingScenarioHappyThread2()
427 {
428 Orthanc::MemoryStringCache::Accessor a(ThreadingScenarioHappyCache);
429 std::string v;
430
431 // nobody has added key2 -> go
432 if (!a.Fetch(v, "key2"))
433 {
434 a.Add("key2", "value2");
435 }
436
437 // wait until thread 1 has completed its "Fetch" but not added yet
438 while (ThreadingScenarioHappyStep < 1)
439 {
440 Orthanc::SystemToolbox::USleep(10000);
441 }
442
443 ThreadingScenarioHappyStep = 2;
444 LOG(INFO) << "Thread2 will fetch";
445 // this should wait until thread 1 has added
446 if (!a.Fetch(v, "key1"))
447 {
448 ASSERT_FALSE(true); // this thread should not add since thread1 should have done it
449 }
450 LOG(INFO) << "Thread2 has fetched the value";
451 ASSERT_EQ("value1", v);
452 }
453
454
455 TEST(MemoryStringCache, ThreadingScenarioHappy)
456 {
457 boost::thread thread1 = boost::thread(ThreadingScenarioHappyThread1);
458 boost::thread thread2 = boost::thread(ThreadingScenarioHappyThread2);
459
460 thread1.join();
461 thread2.join();
462 }
463
464
465 static int ThreadingScenarioFailureStep = 0;
466 static Orthanc::MemoryStringCache ThreadingScenarioFailureCache;
467
468 void ThreadingScenarioFailureThread1()
469 {
470 // the first thread to call Fetch (will be in charge of adding)
471 Orthanc::MemoryStringCache::Accessor a(ThreadingScenarioFailureCache);
472 std::string v;
473
474 LOG(INFO) << "Thread1 will fetch";
475 if (!a.Fetch(v, "key1"))
476 {
477 LOG(INFO) << "Thread1 has fetch";
478 ThreadingScenarioFailureStep = 1;
479
480 // wait for the other thread to fetch too
481 while (ThreadingScenarioFailureStep < 2)
482 {
483 Orthanc::SystemToolbox::USleep(10000);
484 }
485 LOG(INFO) << "Thread1 will add after a short sleep";
486 Orthanc::SystemToolbox::USleep(100000);
487 LOG(INFO) << "Thread1 fails to add because of an error";
488 }
489 }
490
491 void ThreadingScenarioFailureThread2()
492 {
493 Orthanc::MemoryStringCache::Accessor a(ThreadingScenarioFailureCache);
494 std::string v;
495
496 // wait until thread 1 has completed its "Fetch" but not added yet
497 while (ThreadingScenarioFailureStep < 1)
498 {
499 Orthanc::SystemToolbox::USleep(10000);
500 }
501
502 ThreadingScenarioFailureStep = 2;
503 LOG(INFO) << "Thread2 will fetch and wait for thread1 to add";
504 // this should wait until thread 1 has added
505 if (!a.Fetch(v, "key1"))
506 {
507 LOG(INFO) << "Thread2 has been awaken and will add since Thread1 has failed to add";
508 a.Add("key1", "value1");
509 }
510 LOG(INFO) << "Thread2 has added the value";
511 }
512
513
514 TEST(MemoryStringCache, ThreadingScenarioFailure)
515 {
516 boost::thread thread1 = boost::thread(ThreadingScenarioFailureThread1);
517 boost::thread thread2 = boost::thread(ThreadingScenarioFailureThread2);
518
519 thread1.join();
520 thread2.join();
521 }
522
523
524 static int ThreadingScenarioInvalidateStep = 0;
525 static Orthanc::MemoryStringCache ThreadingScenarioInvalidateCache;
526
527 void ThreadingScenarioInvalidateThread1()
528 {
529 // the first thread to call Fetch (will be in charge of adding)
530 Orthanc::MemoryStringCache::Accessor a(ThreadingScenarioInvalidateCache);
531 std::string v;
532
533 LOG(INFO) << "Thread1 will fetch";
534 if (!a.Fetch(v, "key1"))
535 {
536 LOG(INFO) << "Thread1 has fetch";
537 ThreadingScenarioInvalidateStep = 1;
538
539 // wait for the other thread to fetch too
540 while (ThreadingScenarioInvalidateStep < 2)
541 {
542 Orthanc::SystemToolbox::USleep(10000);
543 }
544 LOG(INFO) << "Thread1 will invalidate after a short sleep";
545 Orthanc::SystemToolbox::USleep(100000);
546 LOG(INFO) << "Thread1 is invalidating";
547 ThreadingScenarioInvalidateCache.Invalidate("key1");
548 }
549 }
550
551 void ThreadingScenarioInvalidateThread2()
552 {
553 Orthanc::MemoryStringCache::Accessor a(ThreadingScenarioInvalidateCache);
554 std::string v;
555
556 // wait until thread 1 has completed its "Fetch" but not added yet
557 while (ThreadingScenarioInvalidateStep < 1)
558 {
559 Orthanc::SystemToolbox::USleep(10000);
560 }
561
562 ThreadingScenarioInvalidateStep = 2;
563 LOG(INFO) << "Thread2 will fetch and wait for thread1 to add";
564 // this should wait until thread 1 has added
565 if (!a.Fetch(v, "key1"))
566 {
567 LOG(INFO) << "Thread2 has been awaken because thread1 has invalidated the key";
568 }
569 }
570
571
572 TEST(MemoryStringCache, ThreadingScenarioInvalidate)
573 {
574 boost::thread thread1 = boost::thread(ThreadingScenarioInvalidateThread1);
575 boost::thread thread2 = boost::thread(ThreadingScenarioInvalidateThread2);
576
577 thread1.join();
578 thread2.join();
579 }