# HG changeset patch # User Alain Mazy # Date 1660060658 -7200 # Node ID e95fadefeb720b8bf62afe04659c4c2ef124457c # Parent e69a3ff39bc5fb3c3e62b6e4c8d3e9cc78652375 new MaximumStorageMode configuration diff -r e69a3ff39bc5 -r e95fadefeb72 NEWS --- a/NEWS Mon Aug 08 12:42:48 2022 +0200 +++ b/NEWS Tue Aug 09 17:57:38 2022 +0200 @@ -5,6 +5,9 @@ ------- * Added support for RGBA64 images in tools/create-dicom and /preview +* New configuration "MaximumStorageMode" to choose between recyling of + old patients (default behavior) and rejection of new incoming data when + the MaximumStorageSize has been reached. Bug Fixes --------- diff -r e69a3ff39bc5 -r e95fadefeb72 OrthancServer/Resources/Configuration.json --- a/OrthancServer/Resources/Configuration.json Mon Aug 08 12:42:48 2022 +0200 +++ b/OrthancServer/Resources/Configuration.json Tue Aug 09 17:57:38 2022 +0200 @@ -41,6 +41,16 @@ // of patients) "MaximumPatientCount" : 0, + // Action to take when the maximum storage is reached. + // By default, the patients are recycled ("Recycle" mode). + // In "Reject" mode, the sender will receive a 0xA700 DIMSE status code + // if the instance was sent through C-Store, a 507 HTTP status code + // if using the Rest API and a 0xA700 Failure reason when using + // DicomWeb Stow-RS + // Allowed values: "Recycle", "Reject" + // (new in Orthanc 1.11.2) + "MaximumStorageMode" : "Recycle", + // Maximum size of the storage cache in MB. The storage cache // is stored in RAM and contains a copy of recently accessed // files (written or read). A value of "0" indicates the cache diff -r e69a3ff39bc5 -r e95fadefeb72 OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp --- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Mon Aug 08 12:42:48 2022 +0200 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Tue Aug 09 17:57:38 2022 +0200 @@ -2773,10 +2773,8 @@ } - static bool IsRecyclingNeeded(IDatabaseWrapper::ITransaction& transaction, - uint64_t maximumStorageSize, - unsigned int maximumPatients, - uint64_t addedInstanceSize) + bool StatelessDatabaseOperations::ReadWriteTransaction::HasReachedMaxStorageSize(uint64_t maximumStorageSize, + uint64_t addedInstanceSize) { if (maximumStorageSize != 0) { @@ -2788,24 +2786,35 @@ boost::lexical_cast(maximumStorageSize)); } - if (transaction.IsDiskSizeAbove(maximumStorageSize - addedInstanceSize)) - { - return true; - } - } - - if (maximumPatients != 0) - { - uint64_t patientCount = transaction.GetResourcesCount(ResourceType_Patient); - if (patientCount > maximumPatients) + if (transaction_.IsDiskSizeAbove(maximumStorageSize - addedInstanceSize)) { return true; } } return false; + } + + bool StatelessDatabaseOperations::ReadWriteTransaction::HasReachedMaxPatientCount(unsigned int maximumPatientCount, + const std::string& patientId) + { + if (maximumPatientCount != 0) + { + uint64_t patientCount = transaction_.GetResourcesCount(ResourceType_Patient); // at this time, the new patient has already been added (as part of the transaction) + return patientCount > maximumPatientCount; + } + + return false; } + bool StatelessDatabaseOperations::ReadWriteTransaction::IsRecyclingNeeded(uint64_t maximumStorageSize, + unsigned int maximumPatients, + uint64_t addedInstanceSize, + const std::string& newPatientId) + { + return HasReachedMaxStorageSize(maximumStorageSize, addedInstanceSize) + || HasReachedMaxPatientCount(maximumPatients, newPatientId); + } void StatelessDatabaseOperations::ReadWriteTransaction::Recycle(uint64_t maximumStorageSize, unsigned int maximumPatients, @@ -2814,7 +2823,7 @@ { // TODO - Performance: Avoid calls to "IsRecyclingNeeded()" - if (IsRecyclingNeeded(transaction_, maximumStorageSize, maximumPatients, addedInstanceSize)) + if (IsRecyclingNeeded(maximumStorageSize, maximumPatients, addedInstanceSize, newPatientId)) { // Check whether other DICOM instances from this patient are // already stored @@ -2854,7 +2863,7 @@ LOG(TRACE) << "Recycling one patient"; transaction_.DeleteResource(patientToRecycle); - if (!IsRecyclingNeeded(transaction_, maximumStorageSize, maximumPatients, addedInstanceSize)) + if (!IsRecyclingNeeded(maximumStorageSize, maximumPatients, addedInstanceSize, newPatientId)) { // OK, we're done return; @@ -2864,14 +2873,15 @@ } - void StatelessDatabaseOperations::StandaloneRecycling(uint64_t maximumStorageSize, + void StatelessDatabaseOperations::StandaloneRecycling(MaxStorageMode maximumStorageMode, + uint64_t maximumStorageSize, unsigned int maximumPatientCount) { class Operations : public IReadWriteOperations { private: - uint64_t maximumStorageSize_; - unsigned int maximumPatientCount_; + uint64_t maximumStorageSize_; + unsigned int maximumPatientCount_; public: Operations(uint64_t maximumStorageSize, @@ -2887,8 +2897,8 @@ } }; - if (maximumStorageSize != 0 || - maximumPatientCount != 0) + if (maximumStorageMode == MaxStorageMode_Recycle + && (maximumStorageSize != 0 || maximumPatientCount != 0)) { Operations operations(maximumStorageSize, maximumPatientCount); Apply(operations); @@ -2906,6 +2916,7 @@ DicomTransferSyntax transferSyntax, bool hasPixelDataOffset, uint64_t pixelDataOffset, + MaxStorageMode maximumStorageMode, uint64_t maximumStorageSize, unsigned int maximumPatients, bool isReconstruct) @@ -2924,6 +2935,7 @@ DicomTransferSyntax transferSyntax_; bool hasPixelDataOffset_; uint64_t pixelDataOffset_; + MaxStorageMode maximumStorageMode_; uint64_t maximumStorageSize_; unsigned int maximumPatientCount_; bool isReconstruct_; @@ -3026,6 +3038,7 @@ DicomTransferSyntax transferSyntax, bool hasPixelDataOffset, uint64_t pixelDataOffset, + MaxStorageMode maximumStorageMode, uint64_t maximumStorageSize, unsigned int maximumPatientCount, bool isReconstruct) : @@ -3040,6 +3053,7 @@ transferSyntax_(transferSyntax), hasPixelDataOffset_(hasPixelDataOffset), pixelDataOffset_(pixelDataOffset), + maximumStorageMode_(maximumStorageMode), maximumStorageSize_(maximumStorageSize), maximumPatientCount_(maximumPatientCount), isReconstruct_(isReconstruct) @@ -3134,8 +3148,24 @@ if (!isReconstruct_) // reconstruction should not affect recycling { - transaction.Recycle(maximumStorageSize_, maximumPatientCount_, - instanceSize, hashPatient_ /* don't consider the current patient for recycling */); + if (maximumStorageMode_ == MaxStorageMode_Reject) + { + if (transaction.HasReachedMaxStorageSize(maximumStorageSize_, instanceSize)) + { + storeStatus_ = StoreStatus_StorageFull; + throw OrthancException(ErrorCode_FullStorage, HttpStatus_507_InsufficientStorage, "Maximum storage size reached"); // throw to cancel the transaction + } + if (transaction.HasReachedMaxPatientCount(maximumPatientCount_, hashPatient_)) + { + storeStatus_ = StoreStatus_StorageFull; + throw OrthancException(ErrorCode_FullStorage, HttpStatus_507_InsufficientStorage, "Maximum patient count reached"); // throw to cancel the transaction + } + } + else + { + transaction.Recycle(maximumStorageSize_, maximumPatientCount_, + instanceSize, hashPatient_ /* don't consider the current patient for recycling */); + } } // Attach the files to the newly created instance @@ -3349,7 +3379,7 @@ Operations operations(instanceMetadata, dicomSummary, attachments, metadata, origin, overwrite, hasTransferSyntax, transferSyntax, hasPixelDataOffset, - pixelDataOffset, maximumStorageSize, maximumPatients, isReconstruct); + pixelDataOffset, maximumStorageMode, maximumStorageSize, maximumPatients, isReconstruct); Apply(operations); return operations.GetStoreStatus(); } diff -r e69a3ff39bc5 -r e95fadefeb72 OrthancServer/Sources/Database/StatelessDatabaseOperations.h --- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.h Mon Aug 08 12:42:48 2022 +0200 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.h Tue Aug 09 17:57:38 2022 +0200 @@ -411,6 +411,17 @@ unsigned int maximumPatients, uint64_t addedInstanceSize, const std::string& newPatientId); + + bool HasReachedMaxStorageSize(uint64_t maximumStorageSize, + uint64_t addedInstanceSize); + + bool HasReachedMaxPatientCount(unsigned int maximumPatientCount, + const std::string& patientId); + + bool IsRecyclingNeeded(uint64_t maximumStorageSize, + unsigned int maximumPatients, + uint64_t addedInstanceSize, + const std::string& newPatientId); }; @@ -457,7 +468,8 @@ IReadWriteOperations* writeOperations); protected: - void StandaloneRecycling(uint64_t maximumStorageSize, + void StandaloneRecycling(MaxStorageMode maximumStorageMode, + uint64_t maximumStorageSize, unsigned int maximumPatientCount); public: @@ -658,6 +670,7 @@ DicomTransferSyntax transferSyntax, bool hasPixelDataOffset, uint64_t pixelDataOffset, + MaxStorageMode maximumStorageMode, uint64_t maximumStorageSize, unsigned int maximumPatients, bool isReconstruct); diff -r e69a3ff39bc5 -r e95fadefeb72 OrthancServer/Sources/ServerEnumerations.cpp --- a/OrthancServer/Sources/ServerEnumerations.cpp Mon Aug 08 12:42:48 2022 +0200 +++ b/OrthancServer/Sources/ServerEnumerations.cpp Tue Aug 09 17:57:38 2022 +0200 @@ -218,6 +218,24 @@ return mode == FindStorageAccessMode_DiskOnLookupAndAnswer; } + MaxStorageMode StringToMaxStorageMode(const std::string& value) + { + if (value == "Recycle") + { + return MaxStorageMode_Recycle; + } + else if (value == "Reject") + { + return MaxStorageMode_Reject; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange, + "Configuration option \"MaxStorageMode\" " + "should be \"Recycle\" or \"Reject\": " + value); + } + } + BuiltinDecoderTranscoderOrder StringToBuiltinDecoderTranscoderOrder(const std::string& value) { if (value == "Before") diff -r e69a3ff39bc5 -r e95fadefeb72 OrthancServer/Sources/ServerEnumerations.h --- a/OrthancServer/Sources/ServerEnumerations.h Mon Aug 08 12:42:48 2022 +0200 +++ b/OrthancServer/Sources/ServerEnumerations.h Tue Aug 09 17:57:38 2022 +0200 @@ -49,7 +49,14 @@ StoreStatus_Success, StoreStatus_AlreadyStored, StoreStatus_Failure, - StoreStatus_FilteredOut // Removed by NewInstanceFilter or ReceivedInstanceCallback + StoreStatus_FilteredOut, // Removed by NewInstanceFilter or ReceivedInstanceCallback + StoreStatus_StorageFull // new in Orthanc 1.11.2 + }; + + enum MaxStorageMode + { + MaxStorageMode_Recycle, + MaxStorageMode_Reject }; enum DicomTagType @@ -217,6 +224,8 @@ FindStorageAccessMode StringToFindStorageAccessMode(const std::string& str); + MaxStorageMode StringToMaxStorageMode(const std::string& str); + bool IsStorageAccessAllowedForAnswers(FindStorageAccessMode mode); bool IsStorageAccessAllowedForLookup(FindStorageAccessMode mode); diff -r e69a3ff39bc5 -r e95fadefeb72 OrthancServer/Sources/ServerIndex.cpp --- a/OrthancServer/Sources/ServerIndex.cpp Mon Aug 08 12:42:48 2022 +0200 +++ b/OrthancServer/Sources/ServerIndex.cpp Tue Aug 09 17:57:38 2022 +0200 @@ -321,6 +321,7 @@ unsigned int threadSleepGranularityMilliseconds) : StatelessDatabaseOperations(db), done_(false), + maximumStorageMode_(MaxStorageMode_Recycle), maximumStorageSize_(0), maximumPatients_(0) { @@ -328,7 +329,7 @@ // Initial recycling if the parameters have changed since the last // execution of Orthanc - StandaloneRecycling(maximumStorageSize_, maximumPatients_); + StandaloneRecycling(maximumStorageMode_, maximumStorageSize_, maximumPatients_); if (HasFlushToDisk()) { @@ -385,7 +386,7 @@ } } - StandaloneRecycling(maximumStorageSize_, maximumPatients_); + StandaloneRecycling(maximumStorageMode_, maximumStorageSize_, maximumPatients_); } @@ -405,9 +406,27 @@ } } - StandaloneRecycling(maximumStorageSize_, maximumPatients_); + StandaloneRecycling(maximumStorageMode_, maximumStorageSize_, maximumPatients_); } + void ServerIndex::SetMaximumStorageMode(MaxStorageMode mode) + { + { + boost::mutex::scoped_lock lock(monitoringMutex_); + maximumStorageMode_ = mode; + + if (mode == MaxStorageMode_Recycle) + { + LOG(WARNING) << "Maximum Storage mode: Recycle"; + } + else + { + LOG(WARNING) << "Maximum Storage mode: Reject"; + } + } + + StandaloneRecycling(maximumStorageMode_, maximumStorageSize_, maximumPatients_); + } void ServerIndex::UnstableResourcesMonitorThread(ServerIndex* that, unsigned int threadSleepGranularityMilliseconds) @@ -523,16 +542,18 @@ { uint64_t maximumStorageSize; unsigned int maximumPatients; + MaxStorageMode maximumStorageMode; { boost::mutex::scoped_lock lock(monitoringMutex_); maximumStorageSize = maximumStorageSize_; maximumPatients = maximumPatients_; + maximumStorageMode = maximumStorageMode_; } return StatelessDatabaseOperations::Store( instanceMetadata, dicomSummary, attachments, metadata, origin, overwrite, hasTransferSyntax, - transferSyntax, hasPixelDataOffset, pixelDataOffset, maximumStorageSize, maximumPatients, isReconstruct); + transferSyntax, hasPixelDataOffset, pixelDataOffset, maximumStorageMode, maximumStorageSize, maximumPatients, isReconstruct); } diff -r e69a3ff39bc5 -r e95fadefeb72 OrthancServer/Sources/ServerIndex.h --- a/OrthancServer/Sources/ServerIndex.h Mon Aug 08 12:42:48 2022 +0200 +++ b/OrthancServer/Sources/ServerIndex.h Tue Aug 09 17:57:38 2022 +0200 @@ -45,8 +45,9 @@ LeastRecentlyUsedIndex unstableResources_; - uint64_t maximumStorageSize_; - unsigned int maximumPatients_; + MaxStorageMode maximumStorageMode_; + uint64_t maximumStorageSize_; + unsigned int maximumPatients_; static void FlushThread(ServerIndex* that, unsigned int threadSleep); @@ -75,6 +76,8 @@ // "count == 0" means no limit on the number of patients void SetMaximumPatientCount(unsigned int count); + void SetMaximumStorageMode(MaxStorageMode mode); + StoreStatus Store(std::map& instanceMetadata, const DicomMap& dicomSummary, const Attachments& attachments, diff -r e69a3ff39bc5 -r e95fadefeb72 OrthancServer/Sources/main.cpp --- a/OrthancServer/Sources/main.cpp Mon Aug 08 12:42:48 2022 +0200 +++ b/OrthancServer/Sources/main.cpp Tue Aug 09 17:57:38 2022 +0200 @@ -43,6 +43,7 @@ #include "OrthancMoveRequestHandler.h" #include "OrthancWebDav.h" #include "ServerContext.h" +#include "ServerEnumerations.h" #include "ServerJobs/StorageCommitmentScpJob.h" #include "ServerToolbox.h" #include "StorageCommitmentReports.h" @@ -1564,6 +1565,16 @@ try { + std::string mode = lock.GetConfiguration().GetStringParameter("MaximumStorageMode", "Recycle"); + context.GetIndex().SetMaximumStorageMode(StringToMaxStorageMode(mode)); + } + catch (...) + { + context.GetIndex().SetMaximumStorageMode(MaxStorageMode_Recycle); + } + + try + { uint64_t size = lock.GetConfiguration().GetUnsignedIntegerParameter("MaximumStorageCacheSize", 128); context.SetMaximumStorageCacheSize(size * 1024 * 1024); }