diff Framework/OrthancInstancesCache.cpp @ 0:95226b754d9e

initial release
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 17 Sep 2018 11:34:55 +0200
parents
children 4c3437217518
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/OrthancInstancesCache.cpp	Mon Sep 17 11:34:55 2018 +0200
@@ -0,0 +1,279 @@
+/**
+ * Transfers accelerator plugin for Orthanc
+ * Copyright (C) 2018 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "OrthancInstancesCache.h"
+
+
+namespace OrthancPlugins
+{
+  void OrthancInstancesCache::CacheAccessor::CheckValid() const
+  {
+    if (instance_ == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+      
+
+  OrthancInstancesCache::CacheAccessor::CacheAccessor(OrthancInstancesCache& cache,
+                                                      const std::string& instanceId) :
+    lock_(cache.mutex_),
+    instance_(NULL)
+  {
+    cache.CheckInvariants();
+      
+    if (cache.index_.Contains(instanceId))
+    {
+      // Move the instance at the end of the LRU recycling
+      cache.index_.MakeMostRecent(instanceId);
+        
+      Content::const_iterator instance = cache.content_.find(instanceId);
+      assert(instance != cache.content_.end() &&
+             instance->first == instanceId &&
+             instance->second != NULL);
+
+      instance_ = instance->second;
+    }
+  }
+
+
+  const DicomInstanceInfo& OrthancInstancesCache::CacheAccessor::GetInfo() const
+  {
+    CheckValid();
+    return instance_->GetInfo();
+  }
+
+
+  void OrthancInstancesCache::CacheAccessor::GetChunk(std::string& chunk,
+                                                      std::string& md5,
+                                                      size_t offset,
+                                                      size_t size)
+  {
+    CheckValid();
+    return instance_->GetChunk(chunk, md5, offset, size);
+  }
+
+
+  void OrthancInstancesCache::CheckInvariants()
+  {
+#ifndef NDEBUG  
+    size_t s = 0;
+
+    assert(content_.size() == index_.GetSize());
+      
+    for (Content::const_iterator it = content_.begin();
+         it != content_.end(); ++it)
+    {
+      assert(it->second != NULL);
+      s += it->second->GetInfo().GetSize();
+
+      assert(index_.Contains(it->first));
+    }
+
+    assert(s == memorySize_);
+      
+    if (memorySize_ > maxMemorySize_)
+    {
+      // It is only allowed to overtake the max memory size if the
+      // cache contains a single, large DICOM instance
+      assert(index_.GetSize() == 1 &&
+             content_.size() == 1 &&
+             memorySize_ == (content_.begin())->second->GetInfo().GetSize());
+    }
+#endif
+  }
+
+
+  void OrthancInstancesCache::RemoveOldest()
+  {
+    CheckInvariants();
+
+    assert(!index_.IsEmpty());
+
+    std::string oldest = index_.RemoveOldest();
+
+    Content::iterator instance = content_.find(oldest);
+    assert(instance != content_.end() &&
+           instance->second != NULL);
+
+    memorySize_ -= instance->second->GetInfo().GetSize();
+    delete instance->second;
+    content_.erase(instance);
+  }
+
+
+  void OrthancInstancesCache::Store(const std::string& instanceId,
+                                    std::auto_ptr<SourceDicomInstance>& instance)
+  {
+    if (instance.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+      
+    if (index_.Contains(instanceId))
+    {
+      // This instance has been read by another thread since the cache
+      // lookup, give up
+      index_.MakeMostRecent(instanceId);
+      return;
+    }
+    else
+    {
+      // Make room in the cache for the new instance
+      while (!index_.IsEmpty() &&
+             memorySize_ + instance->GetInfo().GetSize() > maxMemorySize_)
+      {
+        RemoveOldest();
+      }
+
+      CheckInvariants();
+
+      index_.AddOrMakeMostRecent(instanceId);
+      memorySize_ += instance->GetInfo().GetSize();
+      content_[instanceId] = instance.release();
+
+      CheckInvariants();
+    }
+  }
+    
+
+  OrthancInstancesCache::OrthancInstancesCache(OrthancPluginContext* context) :
+    context_(context),
+    memorySize_(0),
+    maxMemorySize_(512 * MB)  // 512 MB by default
+  {
+    if (context == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+  }
+    
+
+  OrthancInstancesCache::~OrthancInstancesCache()
+  {
+    CheckInvariants();
+      
+    for (Content::iterator it = content_.begin();
+         it != content_.end(); ++it)
+    {
+      assert(it->second != NULL);
+      delete it->second;
+    }
+  }
+  
+
+  size_t OrthancInstancesCache::GetMemorySize() 
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    return memorySize_;
+  }
+    
+
+  size_t OrthancInstancesCache::GetMaxMemorySize()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    return maxMemorySize_;
+  }
+    
+
+  void OrthancInstancesCache::SetMaxMemorySize(size_t size)
+  {
+    if (size <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    
+    boost::mutex::scoped_lock lock(mutex_);
+
+    while (memorySize_ > size)
+    {
+      RemoveOldest();
+    }
+
+    maxMemorySize_ = size;
+    CheckInvariants();      
+  }
+
+
+  void OrthancInstancesCache::GetInstanceInfo(size_t& size,
+                                              std::string& md5,
+                                              const std::string& instanceId)
+  {
+    // Check whether the instance is part of the cache
+    {
+      CacheAccessor accessor(*this, instanceId);
+      if (accessor.IsValid())
+      {
+        size = accessor.GetInfo().GetSize();
+        md5 = accessor.GetInfo().GetMD5();
+        return;
+      }
+    }
+      
+    // The instance was not in the cache, load it
+    std::auto_ptr<SourceDicomInstance> instance(new SourceDicomInstance(context_, instanceId));
+    size = instance->GetInfo().GetSize();
+    md5 = instance->GetInfo().GetMD5();
+
+    // Store the just-loaded DICOM instance into the cache
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      Store(instanceId, instance);
+    }
+  }
+      
+    
+  void OrthancInstancesCache::GetChunk(std::string& chunk,
+                                       std::string& md5,
+                                       const std::string& instanceId,
+                                       size_t offset,
+                                       size_t size)
+  {
+    // Check whether the instance is part of the cache
+    {
+      CacheAccessor accessor(*this, instanceId);
+      if (accessor.IsValid())
+      {
+        accessor.GetChunk(chunk, md5, offset, size);
+        return;
+      }
+    }
+      
+    // The instance was not in the cache, load it
+    std::auto_ptr<SourceDicomInstance> instance(new SourceDicomInstance(context_, instanceId));
+    instance->GetChunk(chunk, md5, 0, instance->GetInfo().GetSize());
+
+    // Store the just-loaded DICOM instance into the cache
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      Store(instanceId, instance);
+    }
+  }    
+
+
+  void OrthancInstancesCache::GetChunk(std::string& chunk,
+                                       std::string& md5,
+                                       const TransferBucket& bucket,
+                                       size_t chunkIndex)
+  {
+    GetChunk(chunk, md5, bucket.GetChunkInstanceId(chunkIndex),
+             bucket.GetChunkOffset(chunkIndex),
+             bucket.GetChunkSize(chunkIndex));
+  }
+}