comparison OrthancServer/Sources/ServerContext.cpp @ 4457:789676a8c96a

Refactoring and improvements to the cache of DICOM files in ServerContext
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 19 Jan 2021 19:05:04 +0100
parents d9473bd5ed43
children 6831de40acd9
comparison
equal deleted inserted replaced
4456:3e4f7b7840f0 4457:789676a8c96a
56 #include "StorageCommitmentReports.h" 56 #include "StorageCommitmentReports.h"
57 57
58 #include <dcmtk/dcmdata/dcfilefo.h> 58 #include <dcmtk/dcmdata/dcfilefo.h>
59 59
60 60
61 61 static size_t DICOM_CACHE_SIZE = 128 * 1024 * 1024; // 128 MB
62 #define ENABLE_DICOM_CACHE 1 62
63
64 static const size_t DICOM_CACHE_SIZE = 2;
65 63
66 /** 64 /**
67 * IMPORTANT: We make the assumption that the same instance of 65 * IMPORTANT: We make the assumption that the same instance of
68 * FileStorage can be accessed from multiple threads. This seems OK 66 * FileStorage can be accessed from multiple threads. This seems OK
69 * since the filesystem implements the required locking mechanisms, 67 * since the filesystem implements the required locking mechanisms,
270 } 268 }
271 } 269 }
272 } 270 }
273 271
274 272
273 void ServerContext::PublishDicomCacheMetrics()
274 {
275 metricsRegistry_->SetValue("orthanc_dicom_cache_size",
276 static_cast<float>(dicomCache_.GetCurrentSize()) / static_cast<float>(1024 * 1024));
277 metricsRegistry_->SetValue("orthanc_dicom_cache_count",
278 static_cast<float>(dicomCache_.GetNumberOfItems()));
279 }
280
281
275 ServerContext::ServerContext(IDatabaseWrapper& database, 282 ServerContext::ServerContext(IDatabaseWrapper& database,
276 IStorageArea& area, 283 IStorageArea& area,
277 bool unitTesting, 284 bool unitTesting,
278 size_t maxCompletedJobs) : 285 size_t maxCompletedJobs) :
279 index_(*this, database, (unitTesting ? 20 : 500)), 286 index_(*this, database, (unitTesting ? 20 : 500)),
280 area_(area), 287 area_(area),
281 compressionEnabled_(false), 288 compressionEnabled_(false),
282 storeMD5_(true), 289 storeMD5_(true),
283 provider_(*this), 290 largeDicomThrottler_(1),
284 dicomCache_(provider_, DICOM_CACHE_SIZE), 291 dicomCache_(DICOM_CACHE_SIZE),
285 mainLua_(*this), 292 mainLua_(*this),
286 filterLua_(*this), 293 filterLua_(*this),
287 luaListener_(*this), 294 luaListener_(*this),
288 jobsEngine_(maxCompletedJobs), 295 jobsEngine_(maxCompletedJobs),
289 #if ORTHANC_ENABLE_PLUGINS == 1 296 #if ORTHANC_ENABLE_PLUGINS == 1
526 { 533 {
527 LOG(INFO) << "An incoming instance has been discarded by the filter"; 534 LOG(INFO) << "An incoming instance has been discarded by the filter";
528 return StoreStatus_FilteredOut; 535 return StoreStatus_FilteredOut;
529 } 536 }
530 537
531 { 538 // Remove the file from the DicomCache (useful if
532 // Remove the file from the DicomCache (useful if 539 // "OverwriteInstances" is set to "true")
533 // "OverwriteInstances" is set to "true") 540 dicomCache_.Invalidate(resultPublicId);
534 boost::mutex::scoped_lock lock(dicomCacheMutex_); 541 PublishDicomCacheMetrics();
535 dicomCache_.Invalidate(resultPublicId);
536 }
537 542
538 // TODO Should we use "gzip" instead? 543 // TODO Should we use "gzip" instead?
539 CompressionType compression = (compressionEnabled_ ? CompressionType_ZlibWithSize : CompressionType_None); 544 CompressionType compression = (compressionEnabled_ ? CompressionType_ZlibWithSize : CompressionType_None);
540 545
541 FileInfo dicomInfo = accessor.Write(dicom.GetBufferData(), dicom.GetBufferSize(), 546 FileInfo dicomInfo = accessor.Write(dicom.GetBufferData(), dicom.GetBufferSize(),
876 StorageAccessor accessor(area_, GetMetricsRegistry()); 881 StorageAccessor accessor(area_, GetMetricsRegistry());
877 accessor.Read(result, attachment); 882 accessor.Read(result, attachment);
878 } 883 }
879 884
880 885
881 IDynamicObject* ServerContext::DicomCacheProvider::Provide(const std::string& instancePublicId) 886 ServerContext::DicomCacheLocker::DicomCacheLocker(ServerContext& context,
882 { 887 const std::string& instancePublicId) :
883 std::string content; 888 context_(context),
884 context_.ReadDicom(content, instancePublicId); 889 instancePublicId_(instancePublicId)
885 return new ParsedDicomFile(content); 890 {
886 } 891 accessor_.reset(new ParsedDicomCache::Accessor(context_.dicomCache_, instancePublicId));
887 892
888 893 if (!accessor_->IsValid())
889 ServerContext::DicomCacheLocker::DicomCacheLocker(ServerContext& that, 894 {
890 const std::string& instancePublicId) : 895 accessor_.reset(NULL);
891 that_(that), 896
892 lock_(that_.dicomCacheMutex_) 897 // Throttle to avoid loading several large DICOM files simultaneously
893 { 898 largeDicomLocker_.reset(new Semaphore::Locker(context.largeDicomThrottler_));
894 #if ENABLE_DICOM_CACHE == 0 899
895 static std::unique_ptr<IDynamicObject> p; 900 std::string content;
896 p.reset(that_.provider_.Provide(instancePublicId)); 901 context_.ReadDicom(content, instancePublicId);
897 dicom_ = dynamic_cast<ParsedDicomFile*>(p.get()); 902
898 #else 903 // Release the throttle if loading "small" DICOM files (under
899 dicom_ = &dynamic_cast<ParsedDicomFile&>(that_.dicomCache_.Access(instancePublicId)); 904 // 50MB, which is an arbitrary value)
900 #endif 905 if (content.size() < 50 * 1024 * 1024)
906 {
907 largeDicomLocker_.reset(NULL);
908 }
909
910 dicom_.reset(new ParsedDicomFile(content));
911 dicomSize_ = content.size();
912 }
913
914 assert(accessor_.get() != NULL ||
915 dicom_.get() != NULL);
901 } 916 }
902 917
903 918
904 ServerContext::DicomCacheLocker::~DicomCacheLocker() 919 ServerContext::DicomCacheLocker::~DicomCacheLocker()
905 { 920 {
906 } 921 if (dicom_.get() != NULL)
907 922 {
908 923 try
924 {
925 context_.dicomCache_.Acquire(instancePublicId_, dicom_.release(), dicomSize_);
926 context_.PublishDicomCacheMetrics();
927 }
928 catch (OrthancException&)
929 {
930 }
931 }
932 }
933
934
935 ParsedDicomFile& ServerContext::DicomCacheLocker::GetDicom() const
936 {
937 if (dicom_.get() != NULL)
938 {
939 return *dicom_;
940 }
941 else
942 {
943 assert(accessor_.get() != NULL);
944 return accessor_->GetDicom();
945 }
946 }
947
948
909 void ServerContext::SetStoreMD5ForAttachments(bool storeMD5) 949 void ServerContext::SetStoreMD5ForAttachments(bool storeMD5)
910 { 950 {
911 LOG(INFO) << "Storing MD5 for attachments: " << (storeMD5 ? "yes" : "no"); 951 LOG(INFO) << "Storing MD5 for attachments: " << (storeMD5 ? "yes" : "no");
912 storeMD5_ = storeMD5; 952 storeMD5_ = storeMD5;
913 } 953 }
944 ResourceType expectedType) 984 ResourceType expectedType)
945 { 985 {
946 if (expectedType == ResourceType_Instance) 986 if (expectedType == ResourceType_Instance)
947 { 987 {
948 // remove the file from the DicomCache 988 // remove the file from the DicomCache
949 boost::mutex::scoped_lock lock(dicomCacheMutex_);
950 dicomCache_.Invalidate(uuid); 989 dicomCache_.Invalidate(uuid);
990 PublishDicomCacheMetrics();
951 } 991 }
952 992
953 return index_.DeleteResource(target, uuid, expectedType); 993 return index_.DeleteResource(target, uuid, expectedType);
954 } 994 }
955 995
956 996
957 void ServerContext::SignalChange(const ServerIndexChange& change) 997 void ServerContext::SignalChange(const ServerIndexChange& change)
958 { 998 {
999 if (change.GetResourceType() == ResourceType_Instance &&
1000 change.GetChangeType() == ChangeType_Deleted)
1001 {
1002 dicomCache_.Invalidate(change.GetPublicId());
1003 PublishDicomCacheMetrics();
1004 }
1005
959 pendingChanges_.Enqueue(change.Clone()); 1006 pendingChanges_.Enqueue(change.Clone());
960 } 1007 }
961 1008
962 1009
963 #if ORTHANC_ENABLE_PLUGINS == 1 1010 #if ORTHANC_ENABLE_PLUGINS == 1