# HG changeset patch # User Sebastien Jodogne # Date 1614710219 -3600 # Node ID 350a22c094f2efbcba6dea48658bcc7a0e524298 # Parent 9c0cff7a6ca2e5e3b5deeefb95f01d1025158940 testing replay of transactions diff -r 9c0cff7a6ca2 -r 350a22c094f2 OrthancFramework/Resources/CodeGeneration/ErrorCodes.json --- a/OrthancFramework/Resources/CodeGeneration/ErrorCodes.json Tue Mar 02 16:51:19 2021 +0100 +++ b/OrthancFramework/Resources/CodeGeneration/ErrorCodes.json Tue Mar 02 19:36:59 2021 +0100 @@ -232,6 +232,12 @@ "HttpStatus" : 416, "Name": "BadRange", "Description": "Incorrect range request" + }, + { + "Code": 42, + "HttpStatus": 503, + "Name": "DatabaseCannotSerialize", + "Description": "Database could not serialize access due to concurrent update, the transaction should be retried" }, diff -r 9c0cff7a6ca2 -r 350a22c094f2 OrthancFramework/Sources/Enumerations.cpp --- a/OrthancFramework/Sources/Enumerations.cpp Tue Mar 02 16:51:19 2021 +0100 +++ b/OrthancFramework/Sources/Enumerations.cpp Tue Mar 02 19:36:59 2021 +0100 @@ -187,6 +187,9 @@ case ErrorCode_BadRange: return "Incorrect range request"; + case ErrorCode_DatabaseCannotSerialize: + return "Database could not serialize access due to concurrent update, the transaction should be retried"; + case ErrorCode_SQLiteNotOpened: return "SQLite: The database is not opened"; @@ -2141,6 +2144,9 @@ case ErrorCode_BadRange: return HttpStatus_416_RequestedRangeNotSatisfiable; + case ErrorCode_DatabaseCannotSerialize: + return HttpStatus_503_ServiceUnavailable; + case ErrorCode_CreateDicomNotString: return HttpStatus_400_BadRequest; diff -r 9c0cff7a6ca2 -r 350a22c094f2 OrthancFramework/Sources/Enumerations.h --- a/OrthancFramework/Sources/Enumerations.h Tue Mar 02 16:51:19 2021 +0100 +++ b/OrthancFramework/Sources/Enumerations.h Tue Mar 02 19:36:59 2021 +0100 @@ -135,6 +135,7 @@ ErrorCode_SslInitialization = 39 /*!< Cannot initialize SSL encryption, check out your certificates */, ErrorCode_DiscontinuedAbi = 40 /*!< Calling a function that has been removed from the Orthanc Framework */, ErrorCode_BadRange = 41 /*!< Incorrect range request */, + ErrorCode_DatabaseCannotSerialize = 42 /*!< Database could not serialize access due to concurrent update, the transaction should be retried */, ErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, ErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, ErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, diff -r 9c0cff7a6ca2 -r 350a22c094f2 OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h --- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Tue Mar 02 16:51:19 2021 +0100 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Tue Mar 02 19:36:59 2021 +0100 @@ -239,6 +239,7 @@ OrthancPluginErrorCode_SslInitialization = 39 /*!< Cannot initialize SSL encryption, check out your certificates */, OrthancPluginErrorCode_DiscontinuedAbi = 40 /*!< Calling a function that has been removed from the Orthanc Framework */, OrthancPluginErrorCode_BadRange = 41 /*!< Incorrect range request */, + OrthancPluginErrorCode_DatabaseCannotSerialize = 42 /*!< Database could not serialize access due to concurrent update, the transaction should be retried */, OrthancPluginErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, OrthancPluginErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, OrthancPluginErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, diff -r 9c0cff7a6ca2 -r 350a22c094f2 OrthancServer/Sources/LuaScripting.cpp --- a/OrthancServer/Sources/LuaScripting.cpp Tue Mar 02 16:51:19 2021 +0100 +++ b/OrthancServer/Sources/LuaScripting.cpp Tue Mar 02 19:36:59 2021 +0100 @@ -126,6 +126,65 @@ private: ServerIndexChange change_; + class GetInfoOperations : public ServerIndex::IReadOnlyOperations + { + private: + const ServerIndexChange& change_; + bool ok_; + Json::Value tags_; + std::map metadata_; + + public: + GetInfoOperations(const ServerIndexChange& change) : + change_(change), + ok_(false) + { + } + + virtual void Apply(ServerIndex::ReadOnlyTransaction& transaction) ORTHANC_OVERRIDE + { + if (transaction.LookupResource(tags_, change_.GetPublicId(), change_.GetResourceType())) + { + transaction.GetAllMetadata(metadata_, change_.GetPublicId(), change_.GetResourceType()); + ok_ = true; + } + } + + void CallLua(LuaScripting& that, + const char* name) const + { + if (ok_) + { + Json::Value formattedMetadata = Json::objectValue; + + for (std::map::const_iterator + it = metadata_.begin(); it != metadata_.end(); ++it) + { + std::string key = EnumerationToString(it->first); + formattedMetadata[key] = it->second; + } + + { + LuaScripting::Lock lock(that); + + if (lock.GetLua().IsExistingFunction(name)) + { + that.InitializeJob(); + + LuaFunctionCall call(lock.GetLua(), name); + call.PushString(change_.GetPublicId()); + call.PushJson(tags_["MainDicomTags"]); + call.PushJson(formattedMetadata); + call.Execute(); + + that.SubmitJob(); + } + } + } + } + }; + + public: explicit StableResourceEvent(const ServerIndexChange& change) : change_(change) @@ -164,39 +223,9 @@ } } - Json::Value tags; - - if (that.context_.GetIndex().LookupResource(tags, change_.GetPublicId(), change_.GetResourceType())) - { - std::map metadata; - that.context_.GetIndex().GetAllMetadata(metadata, change_.GetPublicId(), change_.GetResourceType()); - - Json::Value formattedMetadata = Json::objectValue; - - for (std::map::const_iterator - it = metadata.begin(); it != metadata.end(); ++it) - { - std::string key = EnumerationToString(it->first); - formattedMetadata[key] = it->second; - } - - { - LuaScripting::Lock lock(that); - - if (lock.GetLua().IsExistingFunction(name)) - { - that.InitializeJob(); - - LuaFunctionCall call(lock.GetLua(), name); - call.PushString(change_.GetPublicId()); - call.PushJson(tags["MainDicomTags"]); - call.PushJson(formattedMetadata); - call.Execute(); - - that.SubmitJob(); - } - } - } + GetInfoOperations operations(change_); + that.context_.GetIndex().Apply(operations); + operations.CallLua(that, name); } }; diff -r 9c0cff7a6ca2 -r 350a22c094f2 OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Tue Mar 02 16:51:19 2021 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Tue Mar 02 19:36:59 2021 +0100 @@ -168,10 +168,11 @@ { if (expand) { - Json::Value item; - if (index.LookupResource(item, *resource, level)) + ServerIndex::ExpandResourceOperation operation(*resource, level); + index.Apply(operation); + if (operation.IsFound()) { - answer.append(item); + answer.append(operation.GetResource()); } } else @@ -255,11 +256,13 @@ .SetHttpGetSample(GetDocumentationSampleResource(resourceType), true); return; } - - Json::Value result; - if (OrthancRestApi::GetIndex(call).LookupResource(result, call.GetUriComponent("id", ""), resourceType)) + + ServerIndex::ExpandResourceOperation operation(call.GetUriComponent("id", ""), resourceType); + OrthancRestApi::GetIndex(call).Apply(operation); + + if (operation.IsFound()) { - call.GetOutput().AnswerJson(result); + call.GetOutput().AnswerJson(operation.GetResource()); } } @@ -1414,13 +1417,36 @@ return; } - std::map metadata; - assert(!call.GetFullUri().empty()); - const std::string publicId = call.GetUriComponent("id", ""); - const ResourceType level = StringToResourceType(call.GetFullUri() [0].c_str()); - - OrthancRestApi::GetIndex(call).GetAllMetadata(metadata, publicId, level); + + typedef std::map Metadata; + + Metadata metadata; + + class Operation : public ServerIndex::IReadOnlyOperations + { + private: + Metadata& metadata_; + std::string publicId_; + ResourceType level_; + + public: + Operation(Metadata& metadata, + const RestApiGetCall& call) : + metadata_(metadata), + publicId_(call.GetUriComponent("id", "")), + level_(StringToResourceType(call.GetFullUri() [0].c_str())) + { + } + + virtual void Apply(ServerIndex::ReadOnlyTransaction& transaction) ORTHANC_OVERRIDE + { + transaction.GetAllMetadata(metadata_, publicId_, level_); + } + }; + + Operation operation(metadata, call); + OrthancRestApi::GetIndex(call).Apply(operation); Json::Value result; @@ -1428,8 +1454,7 @@ { result = Json::objectValue; - for (std::map::const_iterator - it = metadata.begin(); it != metadata.end(); ++it) + for (Metadata::const_iterator it = metadata.begin(); it != metadata.end(); ++it) { std::string key = EnumerationToString(it->first); result[key] = it->second; @@ -1439,8 +1464,7 @@ { result = Json::arrayValue; - for (std::map::const_iterator - it = metadata.begin(); it != metadata.end(); ++it) + for (Metadata::const_iterator it = metadata.begin(); it != metadata.end(); ++it) { result.append(EnumerationToString(it->first)); } @@ -2544,11 +2568,12 @@ for (std::list::const_iterator it = a.begin(); it != a.end(); ++it) { - Json::Value item; - - if (OrthancRestApi::GetIndex(call).LookupResource(item, *it, end)) + ServerIndex::ExpandResourceOperation operation(*it, end); + OrthancRestApi::GetIndex(call).Apply(operation); + + if (operation.IsFound()) { - result.append(item); + result.append(operation.GetResource()); } } @@ -2654,10 +2679,12 @@ assert(currentType == end); - Json::Value result; - if (index.LookupResource(result, current, end)) + ServerIndex::ExpandResourceOperation operation(current, end); + OrthancRestApi::GetIndex(call).Apply(operation); + + if (operation.IsFound()) { - call.GetOutput().AnswerJson(result); + call.GetOutput().AnswerJson(operation.GetResource()); } } diff -r 9c0cff7a6ca2 -r 350a22c094f2 OrthancServer/Sources/OrthancWebDav.cpp --- a/OrthancServer/Sources/OrthancWebDav.cpp Tue Mar 02 16:51:19 2021 +0100 +++ b/OrthancServer/Sources/OrthancWebDav.cpp Tue Mar 02 19:36:59 2021 +0100 @@ -268,8 +268,10 @@ const DicomMap& mainDicomTags, const Json::Value* dicomAsJson /* unused (*) */) ORTHANC_OVERRIDE { - Json::Value info; - if (context_.GetIndex().LookupResource(info, publicId, level_)) + ServerIndex::ExpandResourceOperation operation(publicId, level_); + context_.GetIndex().Apply(operation); + + if (operation.IsFound()) { if (success_) { @@ -277,7 +279,7 @@ } else { - target_ = info.toStyledString(); + target_ = operation.GetResource().toStyledString(); // Replace UNIX newlines with DOS newlines boost::replace_all(target_, "\n", "\r\n"); diff -r 9c0cff7a6ca2 -r 350a22c094f2 OrthancServer/Sources/ServerIndex.cpp --- a/OrthancServer/Sources/ServerIndex.cpp Tue Mar 02 16:51:19 2021 +0100 +++ b/OrthancServer/Sources/ServerIndex.cpp Tue Mar 02 19:36:59 2021 +0100 @@ -679,7 +679,8 @@ db_(db), maximumStorageSize_(0), maximumPatients_(0), - mainDicomTagsRegistry_(new MainDicomTagsRegistry) + mainDicomTagsRegistry_(new MainDicomTagsRegistry), + maxRetries_(0) { listener_.reset(new Listener(context)); db_.SetListener(*listener_); @@ -2622,4 +2623,163 @@ CopyListToVector(*instancesId, instancesList); } } + + + + + + /*** + ** PROTOTYPING FOR DB REFACTORING BELOW + ***/ + + ServerIndex::ExpandResourceOperation::ExpandResourceOperation(const std::string& resource, + ResourceType level) : + found_(false), + resource_(resource), + level_(level) + { + } + + + void ServerIndex::ExpandResourceOperation::Apply(ServerIndex::ReadOnlyTransaction& transaction) + { + found_ = transaction.LookupResource(item_, resource_, level_); + } + + + const Json::Value& ServerIndex::ExpandResourceOperation::GetResource() const + { + if (found_) + { + return item_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + + class ServerIndex::ReadOnlyWrapper : public IReadOnlyOperations + { + private: + ReadOnlyFunction func_; + + public: + ReadOnlyWrapper(ReadOnlyFunction func) : + func_(func) + { + assert(func_ != NULL); + } + + virtual void Apply(ReadOnlyTransaction& transaction) + { + func_(transaction); + } + }; + + + class ServerIndex::ReadWriteWrapper : public IReadWriteOperations + { + private: + ReadWriteFunction func_; + + public: + ReadWriteWrapper(ReadWriteFunction func) : + func_(func) + { + assert(func_ != NULL); + } + + virtual void Apply(ReadWriteTransaction& transaction) + { + func_(transaction); + } + }; + + + void ServerIndex::ApplyInternal(IReadOnlyOperations* readOperations, + IReadWriteOperations* writeOperations) + { + if ((readOperations == NULL && writeOperations == NULL) || + (readOperations != NULL && writeOperations != NULL)) + { + throw OrthancException(ErrorCode_InternalError); + } + + unsigned int count = 0; + + for (;;) + { + try + { + if (readOperations != NULL) + { + ReadOnlyTransaction transaction(*this); + readOperations->Apply(transaction); + } + else + { + assert(writeOperations != NULL); + ReadWriteTransaction transaction(*this); + writeOperations->Apply(transaction); + } + + return; // Success + } + catch (OrthancException& e) + { + if (e.GetErrorCode() == ErrorCode_DatabaseCannotSerialize) + { + if (count == maxRetries_) + { + throw; + } + else + { + count++; + boost::this_thread::sleep(boost::posix_time::milliseconds(100 * count)); + } + } + else if (e.GetErrorCode() == ErrorCode_DatabaseUnavailable) + { + if (count == maxRetries_) + { + throw; + } + else + { + count++; + boost::this_thread::sleep(boost::posix_time::milliseconds(1000)); + } + } + else + { + throw; + } + } + } + } + + void ServerIndex::Apply(IReadOnlyOperations& operations) + { + ApplyInternal(&operations, NULL); + } + + void ServerIndex::Apply(IReadWriteOperations& operations) + { + ApplyInternal(NULL, &operations); + } + + void ServerIndex::Apply(ReadOnlyFunction func) + { + ReadOnlyWrapper wrapper(func); + Apply(wrapper); + } + + void ServerIndex::Apply(ReadWriteFunction func) + { + ReadWriteWrapper wrapper(func); + Apply(wrapper); + } } diff -r 9c0cff7a6ca2 -r 350a22c094f2 OrthancServer/Sources/ServerIndex.h --- a/OrthancServer/Sources/ServerIndex.h Tue Mar 02 16:51:19 2021 +0100 +++ b/OrthancServer/Sources/ServerIndex.h Tue Mar 02 19:36:59 2021 +0100 @@ -156,10 +156,12 @@ /* out */ uint64_t& countSeries, /* out */ uint64_t& countInstances); + private: bool LookupResource(Json::Value& result, const std::string& publicId, ResourceType expectedType); + public: bool LookupAttachment(FileInfo& attachment, const std::string& instanceUuid, FileContentType contentType); @@ -209,10 +211,12 @@ void DeleteMetadata(const std::string& publicId, MetadataType type); + private: void GetAllMetadata(std::map& target, const std::string& publicId, ResourceType expectedType); + public: bool LookupMetadata(std::string& target, const std::string& publicId, ResourceType expectedType, @@ -289,5 +293,131 @@ const DatabaseLookup& lookup, ResourceType queryLevel, size_t limit); + + + + /*** + ** PROTOTYPING FOR DB REFACTORING BELOW + ***/ + + public: + class ReadOnlyTransaction : public boost::noncopyable + { + protected: + ServerIndex& index_; + + public: + ReadOnlyTransaction(ServerIndex& index) : + index_(index) + { + } + + bool LookupResource(Json::Value& result, + const std::string& publicId, + ResourceType expectedType) + { + return index_.LookupResource(result, publicId, expectedType); + } + + void GetAllMetadata(std::map& target, + const std::string& publicId, + ResourceType expectedType) + { + index_.GetAllMetadata(target, publicId, expectedType); + } + }; + + + class ReadWriteTransaction : public ReadOnlyTransaction + { + public: + ReadWriteTransaction(ServerIndex& index) : + ReadOnlyTransaction(index) + { + } + + StoreStatus Store(std::map& instanceMetadata, + const DicomMap& dicomSummary, + const Attachments& attachments, + const MetadataMap& metadata, + const DicomInstanceOrigin& origin, + bool overwrite, + bool hasTransferSyntax, + DicomTransferSyntax transferSyntax, + bool hasPixelDataOffset, + uint64_t pixelDataOffset) + { + return index_.Store(instanceMetadata, dicomSummary, attachments, metadata, origin, + overwrite, hasTransferSyntax, transferSyntax, hasPixelDataOffset, pixelDataOffset); + } + }; + + + class IReadOnlyOperations : public boost::noncopyable + { + public: + virtual ~IReadOnlyOperations() + { + } + + virtual void Apply(ReadOnlyTransaction& transaction) = 0; + }; + + + class IReadWriteOperations : public boost::noncopyable + { + public: + virtual ~IReadWriteOperations() + { + } + + virtual void Apply(ReadWriteTransaction& transaction) = 0; + }; + + + class ExpandResourceOperation : public ServerIndex::IReadOnlyOperations + { + private: + Json::Value item_; + bool found_; + std::string resource_; + ResourceType level_; + + public: + ExpandResourceOperation(const std::string& resource, + ResourceType level); + + virtual void Apply(ServerIndex::ReadOnlyTransaction& transaction) ORTHANC_OVERRIDE; + + bool IsFound() const + { + return found_; + } + + const Json::Value& GetResource() const; + }; + + + typedef void (*ReadOnlyFunction) (ReadOnlyTransaction& transaction); + typedef void (*ReadWriteFunction) (ReadWriteTransaction& transaction); + + + private: + class ReadOnlyWrapper; + class ReadWriteWrapper; + + void ApplyInternal(IReadOnlyOperations* readOperations, + IReadWriteOperations* writeOperations); + + unsigned int maxRetries_; + + public: + void Apply(IReadOnlyOperations& operations); + + void Apply(IReadWriteOperations& operations); + + void Apply(ReadOnlyFunction func); + + void Apply(ReadWriteFunction func); }; } diff -r 9c0cff7a6ca2 -r 350a22c094f2 OrthancServer/Sources/main.cpp --- a/OrthancServer/Sources/main.cpp Tue Mar 02 16:51:19 2021 +0100 +++ b/OrthancServer/Sources/main.cpp Tue Mar 02 19:36:59 2021 +0100 @@ -723,6 +723,7 @@ PrintErrorCode(ErrorCode_SslInitialization, "Cannot initialize SSL encryption, check out your certificates"); PrintErrorCode(ErrorCode_DiscontinuedAbi, "Calling a function that has been removed from the Orthanc Framework"); PrintErrorCode(ErrorCode_BadRange, "Incorrect range request"); + PrintErrorCode(ErrorCode_DatabaseCannotSerialize, "Database could not serialize access due to concurrent update, the transaction should be retried"); PrintErrorCode(ErrorCode_SQLiteNotOpened, "SQLite: The database is not opened"); PrintErrorCode(ErrorCode_SQLiteAlreadyOpened, "SQLite: Connection is already open"); PrintErrorCode(ErrorCode_SQLiteCannotOpen, "SQLite: Unable to open the database"); diff -r 9c0cff7a6ca2 -r 350a22c094f2 OrthancServer/UnitTestsSources/SizeOfTests.cpp --- a/OrthancServer/UnitTestsSources/SizeOfTests.cpp Tue Mar 02 16:51:19 2021 +0100 +++ b/OrthancServer/UnitTestsSources/SizeOfTests.cpp Tue Mar 02 19:36:59 2021 +0100 @@ -129,7 +129,6 @@ #include "../../OrthancFramework/Sources/Images/ImageBuffer.h" #include "../../OrthancFramework/Sources/Images/ImageProcessing.h" #include "../../OrthancFramework/Sources/Images/ImageTraits.h" -#include "../../OrthancFramework/Sources/Images/JpegErrorManager.h" #include "../../OrthancFramework/Sources/Images/JpegReader.h" #include "../../OrthancFramework/Sources/Images/JpegWriter.h" #include "../../OrthancFramework/Sources/Images/PamReader.h"