changeset 6911:89677acd8f8d streaming

optimization: avoid reading from storage area if extracting range from a cached attachment
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 02 Jun 2026 16:13:19 +0200
parents 3417bcf097a0
children a66b5e2dfb6e
files OrthancFramework/Sources/DataSource/DataSourceReader.cpp OrthancFramework/Sources/DataSource/DicomDataSource.cpp OrthancFramework/Sources/DataSource/DicomDataSource.h OrthancFramework/Sources/DataSource/IDataSource.h OrthancFramework/Sources/DataSource/StorageAreaDataSource.cpp OrthancFramework/Sources/DataSource/StorageAreaDataSource.h OrthancFramework/Sources/DataSource/TranscoderDataSource.cpp OrthancFramework/Sources/DataSource/TranscoderDataSource.h
diffstat 8 files changed, 134 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancFramework/Sources/DataSource/DataSourceReader.cpp	Tue Jun 02 15:03:11 2026 +0200
+++ b/OrthancFramework/Sources/DataSource/DataSourceReader.cpp	Tue Jun 02 16:13:19 2026 +0200
@@ -180,7 +180,7 @@
             preReservation.reset(new Internals::DataSourceMemoryBudget::Lock(*budget_, estimatedSize));
           }
 
-          value.reset(source_.Load(*id_));
+          value.reset(source_.Load(*id_, cache_));
 
           if (!value)
           {
--- a/OrthancFramework/Sources/DataSource/DicomDataSource.cpp	Tue Jun 02 15:03:11 2026 +0200
+++ b/OrthancFramework/Sources/DataSource/DicomDataSource.cpp	Tue Jun 02 16:13:19 2026 +0200
@@ -183,7 +183,8 @@
   }
 
 
-  IDynamicObject* DicomDataSource::Load(const IDataIdentifier& obj)
+  IDynamicObject* DicomDataSource::Load(const IDataIdentifier& obj,
+                                        const boost::shared_ptr<SharedObjectCache>& readerCache)
   {
     const Identifier& id = dynamic_cast<const Identifier&>(obj);
 
--- a/OrthancFramework/Sources/DataSource/DicomDataSource.h	Tue Jun 02 15:03:11 2026 +0200
+++ b/OrthancFramework/Sources/DataSource/DicomDataSource.h	Tue Jun 02 16:13:19 2026 +0200
@@ -60,7 +60,8 @@
   public:
     explicit DicomDataSource(const boost::shared_ptr<DataSourceReader>& storageAreaReader);
 
-    virtual IDynamicObject* Load(const IDataIdentifier& obj) ORTHANC_OVERRIDE;
+    virtual IDynamicObject* Load(const IDataIdentifier& obj,
+                                 const boost::shared_ptr<SharedObjectCache>& readerCache /* could be NULL */) ORTHANC_OVERRIDE;
 
     virtual size_t GetValueSize(const IDynamicObject& obj) const ORTHANC_OVERRIDE;
 
--- a/OrthancFramework/Sources/DataSource/IDataSource.h	Tue Jun 02 15:03:11 2026 +0200
+++ b/OrthancFramework/Sources/DataSource/IDataSource.h	Tue Jun 02 16:13:19 2026 +0200
@@ -27,8 +27,13 @@
 #include "../IDynamicObject.h"
 #include "IDataIdentifier.h"
 
+#include <boost/shared_ptr.hpp>
+
+
 namespace Orthanc
 {
+  class SharedObjectCache;
+
   class ORTHANC_PUBLIC IDataSource : public boost::noncopyable
   {
   public:
@@ -36,7 +41,8 @@
     {
     }
 
-    virtual IDynamicObject* Load(const IDataIdentifier& identifier) = 0;
+    virtual IDynamicObject* Load(const IDataIdentifier& identifier,
+                                 const boost::shared_ptr<SharedObjectCache>& readerCache /* could be NULL */) = 0;
 
     virtual size_t GetValueSize(const IDynamicObject& value) const = 0;
   };
--- a/OrthancFramework/Sources/DataSource/StorageAreaDataSource.cpp	Tue Jun 02 15:03:11 2026 +0200
+++ b/OrthancFramework/Sources/DataSource/StorageAreaDataSource.cpp	Tue Jun 02 16:13:19 2026 +0200
@@ -25,6 +25,7 @@
 #include "../PrecompiledHeaders.h"
 #include "StorageAreaDataSource.h"
 
+#include "../Cache/SharedObjectCache.h"
 #include "../MetricsRegistry.h"
 #include "../OrthancException.h"
 #include "../StringMemoryBuffer.h"
@@ -42,6 +43,19 @@
 
 namespace Orthanc
 {
+  static std::string ComputeCacheKey(std::string uuid,
+                                     FileContentType type,
+                                     uint64_t start,
+                                     uint64_t end)
+  {
+    // custom data is not part of the cache key, as it is for internal use by the plugin
+    // (it is opaque to the Orthanc core)
+    return (uuid + "|" +
+            boost::lexical_cast<std::string>(type) + "|" +
+            boost::lexical_cast<std::string>(start) + "|" +
+            boost::lexical_cast<std::string>(end));
+  }
+
   class StorageAreaDataSource::Value : public IDynamicObject
   {
   private:
@@ -70,6 +84,21 @@
     {
       return checkMD5_;
     }
+
+    void ExtractRange(std::string& target,
+                      uint64_t start,
+                      uint64_t end) const
+    {
+      if (start >= buffer_->GetSize() ||
+          end > buffer_->GetSize())
+      {
+        throw OrthancException(ErrorCode_BadRange);
+      }
+      else
+      {
+        target.assign(reinterpret_cast<const char*>(buffer_->GetData()) + start, end - start);
+      }
+    }
   };
 
 
@@ -94,6 +123,8 @@
     uint64_t                          end_;
     std::string                       customData_;
     std::unique_ptr<IPostProcessing>  postProcessing_;
+    bool                              hasWholeKey_;
+    std::string                       wholeKey_;
 
   public:
     Identifier(const std::string& uuid,
@@ -105,7 +136,8 @@
       type_(type),
       start_(start),
       end_(end),
-      customData_(customData)
+      customData_(customData),
+      hasWholeKey_(false)
     {
       if (start > end)
       {
@@ -119,6 +151,54 @@
       }
     }
 
+    uint64_t GetStart() const
+    {
+      return start_;
+    }
+
+    uint64_t GetEnd() const
+    {
+      return end_;
+    }
+
+    /**
+     * WARNING: SetWholeKey() must only be used to retrieve compressed
+     * date from the storage area (or if there is no compression).
+     **/
+    void SetWholeKey(const std::string& key)
+    {
+      if (hasWholeKey_)
+      {
+        throw OrthancException(ErrorCode_BadSequenceOfCalls);
+      }
+      else if (key.empty())
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+      else
+      {
+        hasWholeKey_ = true;
+        wholeKey_ = key;
+      }
+    }
+
+    bool HasWholeKey() const
+    {
+      return hasWholeKey_;
+    }
+
+    const std::string GetWholeKey() const
+    {
+      if (hasWholeKey_)
+      {
+        return wholeKey_;
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_BadSequenceOfCalls);
+      }
+    }
+
     IMemoryBuffer* Read(IPluginStorageArea& area) const
     {
       return area.ReadRange(uuid_, type_, start_, end_, customData_);
@@ -126,12 +206,7 @@
 
     virtual bool GetCacheKey(std::string& key) const ORTHANC_OVERRIDE
     {
-      // custom data is not part of the cache key, as it is for internal use by the plugin
-      // (it is opaque to the Orthanc core)
-      key = (uuid_ + "|" +
-             boost::lexical_cast<std::string>(type_) + "|" +
-             boost::lexical_cast<std::string>(start_) + "|" +
-             boost::lexical_cast<std::string>(end_));
+      key = ComputeCacheKey(uuid_, type_, start_, end_);
       return true;
     }
 
@@ -305,10 +380,27 @@
   }
 
 
-  IDynamicObject* StorageAreaDataSource::Load(const IDataIdentifier& identifier)
+  IDynamicObject* StorageAreaDataSource::Load(const IDataIdentifier& identifier,
+                                              const boost::shared_ptr<SharedObjectCache>& readerCache)
   {
     const Identifier& id = dynamic_cast<const Identifier&>(identifier);
 
+    if (id.HasWholeKey() &&
+        readerCache)
+    {
+      // Extract the request range from the whole compressed attachment if the latter is already present in the cache
+      boost::shared_ptr<IDynamicObject> wholeCached = readerCache->GetCachedValue(id.GetWholeKey());
+      if (wholeCached)
+      {
+        const Value& wholeRange = dynamic_cast<const Value&>(*wholeCached);
+
+        std::string content;
+        wholeRange.ExtractRange(content, id.GetStart(), id.GetEnd());
+
+        return new Value(StringMemoryBuffer::CreateFromSwap(content), false);
+      }
+    }
+
     std::unique_ptr<IMemoryBuffer> buffer;
 
     {
@@ -430,8 +522,14 @@
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-    return CreateRangeRequest(attachment.GetUuid(), attachment.GetContentType(),
-                              0, untilPosition, attachment.GetCustomData());
+    std::unique_ptr<Identifier> id(new Identifier(attachment.GetUuid(), attachment.GetContentType(),
+                                                  0, untilPosition, attachment.GetCustomData()));
+
+    // Using "SetWholeKey()" allows to extract a range if the whole attachment is already in the reader cache
+    assert(attachment.GetCompressionType() == CompressionType_None);
+    id->SetWholeKey(ComputeCacheKey(attachment.GetUuid(), attachment.GetContentType(), 0, attachment.GetCompressedSize()));
+
+    return id.release();
   }
 
 
@@ -468,8 +566,14 @@
       }
       else
       {
-        return Execute(reader, CreateRangeRequest(attachment.GetUuid(), attachment.GetContentType(),
-                                                  start, endExclusive, attachment.GetCustomData()));
+        std::unique_ptr<Identifier> id(new Identifier(attachment.GetUuid(), attachment.GetContentType(),
+                                                      start, endExclusive, attachment.GetCustomData()));
+
+        // Using "SetWholeKey()" allows to extract a range if the whole compressed attachment is already in the reader cache
+        assert(!uncompress || attachment.GetCompressionType() == CompressionType_None);
+        id->SetWholeKey(ComputeCacheKey(attachment.GetUuid(), attachment.GetContentType(), 0, attachment.GetCompressedSize()));
+
+        return Execute(reader, id.release());
       }
     }
   }
--- a/OrthancFramework/Sources/DataSource/StorageAreaDataSource.h	Tue Jun 02 15:03:11 2026 +0200
+++ b/OrthancFramework/Sources/DataSource/StorageAreaDataSource.h	Tue Jun 02 16:13:19 2026 +0200
@@ -69,7 +69,8 @@
 
     virtual size_t GetValueSize(const IDynamicObject& value) const ORTHANC_OVERRIDE;
 
-    virtual IDynamicObject* Load(const IDataIdentifier& identifier) ORTHANC_OVERRIDE;
+    virtual IDynamicObject* Load(const IDataIdentifier& identifier,
+                                 const boost::shared_ptr<SharedObjectCache>& readerCache /* could be NULL */) ORTHANC_OVERRIDE;
 
     class ORTHANC_PUBLIC Range : public boost::noncopyable
     {
--- a/OrthancFramework/Sources/DataSource/TranscoderDataSource.cpp	Tue Jun 02 15:03:11 2026 +0200
+++ b/OrthancFramework/Sources/DataSource/TranscoderDataSource.cpp	Tue Jun 02 16:13:19 2026 +0200
@@ -245,7 +245,8 @@
   }
 
 
-  IDynamicObject* TranscoderDataSource::Load(const IDataIdentifier& id)
+  IDynamicObject* TranscoderDataSource::Load(const IDataIdentifier& id,
+                                             const boost::shared_ptr<SharedObjectCache>& readerCache)
   {
     return dynamic_cast<const Identifier&>(id).Load(*transcoder_, *storageAreaReader_);
   }
--- a/OrthancFramework/Sources/DataSource/TranscoderDataSource.h	Tue Jun 02 15:03:11 2026 +0200
+++ b/OrthancFramework/Sources/DataSource/TranscoderDataSource.h	Tue Jun 02 16:13:19 2026 +0200
@@ -49,7 +49,8 @@
     TranscoderDataSource(const boost::shared_ptr<IDicomTranscoder>& transcoder,
                          const boost::shared_ptr<DataSourceReader>& storageAreaReader);
 
-    virtual IDynamicObject* Load(const IDataIdentifier& id) ORTHANC_OVERRIDE;
+    virtual IDynamicObject* Load(const IDataIdentifier& id,
+                                 const boost::shared_ptr<SharedObjectCache>& readerCache /* could be NULL */) ORTHANC_OVERRIDE;
 
     virtual size_t GetValueSize(const IDynamicObject& value) const ORTHANC_OVERRIDE;