changeset 4551:350a22c094f2 db-changes

testing replay of transactions
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 02 Mar 2021 19:36:59 +0100
parents 9c0cff7a6ca2
children beb8ba8a0b12
files OrthancFramework/Resources/CodeGeneration/ErrorCodes.json OrthancFramework/Sources/Enumerations.cpp OrthancFramework/Sources/Enumerations.h OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h OrthancServer/Sources/LuaScripting.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp OrthancServer/Sources/OrthancWebDav.cpp OrthancServer/Sources/ServerIndex.cpp OrthancServer/Sources/ServerIndex.h OrthancServer/Sources/main.cpp OrthancServer/UnitTestsSources/SizeOfTests.cpp
diffstat 11 files changed, 424 insertions(+), 62 deletions(-) [+]
line wrap: on
line diff
--- 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"
   }, 
 
 
--- 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;
 
--- 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 */,
--- 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 */,
--- 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<MetadataType, std::string> 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<MetadataType, std::string>::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<MetadataType, std::string> metadata;
-        that.context_.GetIndex().GetAllMetadata(metadata, change_.GetPublicId(), change_.GetResourceType());
-        
-        Json::Value formattedMetadata = Json::objectValue;
-
-        for (std::map<MetadataType, std::string>::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);
     }
   };
 
--- 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<MetadataType, std::string> 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<MetadataType, std::string>  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<MetadataType, std::string>::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<MetadataType, std::string>::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<std::string>::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());
     }
   }
 
--- 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");
--- 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);
+  }
 }
--- 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<MetadataType, std::string>& 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<MetadataType, std::string>& 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<MetadataType, std::string>& 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);
   };
 }
--- 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");
--- 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"