Mercurial > hg > orthanc
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 } |