294 lines
6.6 KiB
C++
294 lines
6.6 KiB
C++
/**
|
|
* Orthanc - A Lightweight, RESTful DICOM Store
|
|
* Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
|
|
* Department, University Hospital of Liege, Belgium
|
|
* Copyright (C) 2017-2023 Osimis S.A., Belgium
|
|
* Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
|
|
* Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
|
|
*
|
|
* This program is free software: you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this program. If not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
**/
|
|
|
|
|
|
#include "../PrecompiledHeaders.h"
|
|
#include "MemoryObjectCache.h"
|
|
|
|
#include "../Compatibility.h"
|
|
|
|
namespace Orthanc
|
|
{
|
|
class MemoryObjectCache::Item : public boost::noncopyable
|
|
{
|
|
private:
|
|
ICacheable* value_;
|
|
boost::posix_time::ptime time_;
|
|
|
|
public:
|
|
explicit Item(ICacheable* value) : // Takes ownership
|
|
value_(value),
|
|
time_(boost::posix_time::second_clock::local_time())
|
|
{
|
|
if (value == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
}
|
|
|
|
~Item()
|
|
{
|
|
assert(value_ != NULL);
|
|
delete value_;
|
|
}
|
|
|
|
ICacheable& GetValue() const
|
|
{
|
|
assert(value_ != NULL);
|
|
return *value_;
|
|
}
|
|
|
|
const boost::posix_time::ptime& GetTime() const
|
|
{
|
|
return time_;
|
|
}
|
|
};
|
|
|
|
|
|
void MemoryObjectCache::Recycle(size_t targetSize)
|
|
{
|
|
// WARNING: "cacheMutex_" must be locked
|
|
while (currentSize_ > targetSize)
|
|
{
|
|
assert(!content_.IsEmpty());
|
|
|
|
Item* item = NULL;
|
|
content_.RemoveOldest(item);
|
|
|
|
assert(item != NULL);
|
|
const size_t size = item->GetValue().GetMemoryUsage();
|
|
delete item;
|
|
|
|
assert(currentSize_ >= size);
|
|
currentSize_ -= size;
|
|
}
|
|
|
|
// Post-condition: "currentSize_ <= targetSize"
|
|
}
|
|
|
|
|
|
MemoryObjectCache::MemoryObjectCache() :
|
|
currentSize_(0),
|
|
maxSize_(100 * 1024 * 1024) // 100 MB
|
|
{
|
|
}
|
|
|
|
|
|
MemoryObjectCache::~MemoryObjectCache()
|
|
{
|
|
Recycle(0);
|
|
assert(content_.IsEmpty());
|
|
}
|
|
|
|
|
|
size_t MemoryObjectCache::GetNumberOfItems()
|
|
{
|
|
#if !defined(__EMSCRIPTEN__)
|
|
boost::mutex::scoped_lock lock(cacheMutex_);
|
|
#endif
|
|
|
|
return content_.GetSize();
|
|
}
|
|
|
|
|
|
size_t MemoryObjectCache::GetCurrentSize()
|
|
{
|
|
#if !defined(__EMSCRIPTEN__)
|
|
boost::mutex::scoped_lock lock(cacheMutex_);
|
|
#endif
|
|
|
|
return currentSize_;
|
|
}
|
|
|
|
|
|
size_t MemoryObjectCache::GetMaximumSize()
|
|
{
|
|
#if !defined(__EMSCRIPTEN__)
|
|
boost::mutex::scoped_lock lock(cacheMutex_);
|
|
#endif
|
|
|
|
return maxSize_;
|
|
}
|
|
|
|
|
|
void MemoryObjectCache::SetMaximumSize(size_t size)
|
|
{
|
|
if (size == 0)
|
|
{
|
|
throw OrthancException(ErrorCode_ParameterOutOfRange);
|
|
}
|
|
|
|
#if !defined(__EMSCRIPTEN__)
|
|
// Make sure no accessor is currently open (as its data may be
|
|
// removed if recycling is needed)
|
|
WriterLock contentLock(contentMutex_);
|
|
|
|
// Lock the global structure of the cache
|
|
boost::mutex::scoped_lock cacheLock(cacheMutex_);
|
|
#endif
|
|
|
|
Recycle(size);
|
|
maxSize_ = size;
|
|
}
|
|
|
|
|
|
void MemoryObjectCache::Acquire(const std::string& key,
|
|
ICacheable* value)
|
|
{
|
|
std::unique_ptr<Item> item(new Item(value));
|
|
|
|
if (value == NULL)
|
|
{
|
|
throw OrthancException(ErrorCode_NullPointer);
|
|
}
|
|
else
|
|
{
|
|
#if !defined(__EMSCRIPTEN__)
|
|
// Make sure no accessor is currently open (as its data may be
|
|
// removed if recycling is needed)
|
|
WriterLock contentLock(contentMutex_);
|
|
|
|
// Lock the global structure of the cache
|
|
boost::mutex::scoped_lock cacheLock(cacheMutex_);
|
|
#endif
|
|
|
|
const size_t size = item->GetValue().GetMemoryUsage();
|
|
|
|
if (size > maxSize_)
|
|
{
|
|
// This object is too large to be stored in the cache, discard it
|
|
}
|
|
else if (content_.Contains(key))
|
|
{
|
|
// Value already stored, don't overwrite the old value
|
|
content_.MakeMostRecent(key);
|
|
}
|
|
else
|
|
{
|
|
Recycle(maxSize_ - size); // Post-condition: currentSize_ <= maxSize_ - size
|
|
assert(currentSize_ + size <= maxSize_);
|
|
|
|
content_.Add(key, item.release());
|
|
currentSize_ += size;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void MemoryObjectCache::Invalidate(const std::string& key)
|
|
{
|
|
#if !defined(__EMSCRIPTEN__)
|
|
// Make sure no accessor is currently open (as it may correspond
|
|
// to the key to remove)
|
|
WriterLock contentLock(contentMutex_);
|
|
|
|
// Lock the global structure of the cache
|
|
boost::mutex::scoped_lock cacheLock(cacheMutex_);
|
|
#endif
|
|
|
|
Item* item = NULL;
|
|
if (content_.Contains(key, item))
|
|
{
|
|
assert(item != NULL);
|
|
const size_t size = item->GetValue().GetMemoryUsage();
|
|
delete item;
|
|
|
|
content_.Invalidate(key);
|
|
|
|
assert(currentSize_ >= size);
|
|
currentSize_ -= size;
|
|
}
|
|
}
|
|
|
|
|
|
MemoryObjectCache::Accessor::Accessor(MemoryObjectCache& cache,
|
|
const std::string& key,
|
|
bool unique) :
|
|
item_(NULL)
|
|
{
|
|
#if !defined(__EMSCRIPTEN__)
|
|
if (unique)
|
|
{
|
|
writerLock_ = WriterLock(cache.contentMutex_);
|
|
}
|
|
else
|
|
{
|
|
readerLock_ = ReaderLock(cache.contentMutex_);
|
|
}
|
|
|
|
// Lock the global structure of the cache, must be *after* the
|
|
// reader/writer lock
|
|
cacheLock_ = boost::mutex::scoped_lock(cache.cacheMutex_);
|
|
#endif
|
|
|
|
if (cache.content_.Contains(key, item_))
|
|
{
|
|
cache.content_.MakeMostRecent(key);
|
|
}
|
|
|
|
#if !defined(__EMSCRIPTEN__)
|
|
cacheLock_.unlock();
|
|
|
|
if (item_ == NULL)
|
|
{
|
|
// This item does not exist in the cache, we can release the
|
|
// reader/writer lock
|
|
if (unique)
|
|
{
|
|
writerLock_.unlock();
|
|
}
|
|
else
|
|
{
|
|
readerLock_.unlock();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
ICacheable& MemoryObjectCache::Accessor::GetValue() const
|
|
{
|
|
if (IsValid())
|
|
{
|
|
return item_->GetValue();
|
|
}
|
|
else
|
|
{
|
|
throw OrthancException(ErrorCode_BadSequenceOfCalls);
|
|
}
|
|
}
|
|
|
|
|
|
const boost::posix_time::ptime& MemoryObjectCache::Accessor::GetTime() const
|
|
{
|
|
if (IsValid())
|
|
{
|
|
return item_->GetTime();
|
|
}
|
|
else
|
|
{
|
|
throw OrthancException(ErrorCode_BadSequenceOfCalls);
|
|
}
|
|
}
|
|
}
|