comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:95226b754d9e
1 /**
2 * Transfers accelerator plugin for Orthanc
3 * Copyright (C) 2018 Osimis, Belgium
4 *
5 * This program is free software: you can redistribute it and/or
6 * modify it under the terms of the GNU Affero General Public License
7 * as published by the Free Software Foundation, either version 3 of
8 * the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 **/
18
19
20 #include "OrthancInstancesCache.h"
21
22
23 namespace OrthancPlugins
24 {
25 void OrthancInstancesCache::CacheAccessor::CheckValid() const
26 {
27 if (instance_ == NULL)
28 {
29 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
30 }
31 }
32
33
34 OrthancInstancesCache::CacheAccessor::CacheAccessor(OrthancInstancesCache& cache,
35 const std::string& instanceId) :
36 lock_(cache.mutex_),
37 instance_(NULL)
38 {
39 cache.CheckInvariants();
40
41 if (cache.index_.Contains(instanceId))
42 {
43 // Move the instance at the end of the LRU recycling
44 cache.index_.MakeMostRecent(instanceId);
45
46 Content::const_iterator instance = cache.content_.find(instanceId);
47 assert(instance != cache.content_.end() &&
48 instance->first == instanceId &&
49 instance->second != NULL);
50
51 instance_ = instance->second;
52 }
53 }
54
55
56 const DicomInstanceInfo& OrthancInstancesCache::CacheAccessor::GetInfo() const
57 {
58 CheckValid();
59 return instance_->GetInfo();
60 }
61
62
63 void OrthancInstancesCache::CacheAccessor::GetChunk(std::string& chunk,
64 std::string& md5,
65 size_t offset,
66 size_t size)
67 {
68 CheckValid();
69 return instance_->GetChunk(chunk, md5, offset, size);
70 }
71
72
73 void OrthancInstancesCache::CheckInvariants()
74 {
75 #ifndef NDEBUG
76 size_t s = 0;
77
78 assert(content_.size() == index_.GetSize());
79
80 for (Content::const_iterator it = content_.begin();
81 it != content_.end(); ++it)
82 {
83 assert(it->second != NULL);
84 s += it->second->GetInfo().GetSize();
85
86 assert(index_.Contains(it->first));
87 }
88
89 assert(s == memorySize_);
90
91 if (memorySize_ > maxMemorySize_)
92 {
93 // It is only allowed to overtake the max memory size if the
94 // cache contains a single, large DICOM instance
95 assert(index_.GetSize() == 1 &&
96 content_.size() == 1 &&
97 memorySize_ == (content_.begin())->second->GetInfo().GetSize());
98 }
99 #endif
100 }
101
102
103 void OrthancInstancesCache::RemoveOldest()
104 {
105 CheckInvariants();
106
107 assert(!index_.IsEmpty());
108
109 std::string oldest = index_.RemoveOldest();
110
111 Content::iterator instance = content_.find(oldest);
112 assert(instance != content_.end() &&
113 instance->second != NULL);
114
115 memorySize_ -= instance->second->GetInfo().GetSize();
116 delete instance->second;
117 content_.erase(instance);
118 }
119
120
121 void OrthancInstancesCache::Store(const std::string& instanceId,
122 std::auto_ptr<SourceDicomInstance>& instance)
123 {
124 if (instance.get() == NULL)
125 {
126 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
127 }
128
129 if (index_.Contains(instanceId))
130 {
131 // This instance has been read by another thread since the cache
132 // lookup, give up
133 index_.MakeMostRecent(instanceId);
134 return;
135 }
136 else
137 {
138 // Make room in the cache for the new instance
139 while (!index_.IsEmpty() &&
140 memorySize_ + instance->GetInfo().GetSize() > maxMemorySize_)
141 {
142 RemoveOldest();
143 }
144
145 CheckInvariants();
146
147 index_.AddOrMakeMostRecent(instanceId);
148 memorySize_ += instance->GetInfo().GetSize();
149 content_[instanceId] = instance.release();
150
151 CheckInvariants();
152 }
153 }
154
155
156 OrthancInstancesCache::OrthancInstancesCache(OrthancPluginContext* context) :
157 context_(context),
158 memorySize_(0),
159 maxMemorySize_(512 * MB) // 512 MB by default
160 {
161 if (context == NULL)
162 {
163 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
164 }
165 }
166
167
168 OrthancInstancesCache::~OrthancInstancesCache()
169 {
170 CheckInvariants();
171
172 for (Content::iterator it = content_.begin();
173 it != content_.end(); ++it)
174 {
175 assert(it->second != NULL);
176 delete it->second;
177 }
178 }
179
180
181 size_t OrthancInstancesCache::GetMemorySize()
182 {
183 boost::mutex::scoped_lock lock(mutex_);
184 return memorySize_;
185 }
186
187
188 size_t OrthancInstancesCache::GetMaxMemorySize()
189 {
190 boost::mutex::scoped_lock lock(mutex_);
191 return maxMemorySize_;
192 }
193
194
195 void OrthancInstancesCache::SetMaxMemorySize(size_t size)
196 {
197 if (size <= 0)
198 {
199 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
200 }
201
202 boost::mutex::scoped_lock lock(mutex_);
203
204 while (memorySize_ > size)
205 {
206 RemoveOldest();
207 }
208
209 maxMemorySize_ = size;
210 CheckInvariants();
211 }
212
213
214 void OrthancInstancesCache::GetInstanceInfo(size_t& size,
215 std::string& md5,
216 const std::string& instanceId)
217 {
218 // Check whether the instance is part of the cache
219 {
220 CacheAccessor accessor(*this, instanceId);
221 if (accessor.IsValid())
222 {
223 size = accessor.GetInfo().GetSize();
224 md5 = accessor.GetInfo().GetMD5();
225 return;
226 }
227 }
228
229 // The instance was not in the cache, load it
230 std::auto_ptr<SourceDicomInstance> instance(new SourceDicomInstance(context_, instanceId));
231 size = instance->GetInfo().GetSize();
232 md5 = instance->GetInfo().GetMD5();
233
234 // Store the just-loaded DICOM instance into the cache
235 {
236 boost::mutex::scoped_lock lock(mutex_);
237 Store(instanceId, instance);
238 }
239 }
240
241
242 void OrthancInstancesCache::GetChunk(std::string& chunk,
243 std::string& md5,
244 const std::string& instanceId,
245 size_t offset,
246 size_t size)
247 {
248 // Check whether the instance is part of the cache
249 {
250 CacheAccessor accessor(*this, instanceId);
251 if (accessor.IsValid())
252 {
253 accessor.GetChunk(chunk, md5, offset, size);
254 return;
255 }
256 }
257
258 // The instance was not in the cache, load it
259 std::auto_ptr<SourceDicomInstance> instance(new SourceDicomInstance(context_, instanceId));
260 instance->GetChunk(chunk, md5, 0, instance->GetInfo().GetSize());
261
262 // Store the just-loaded DICOM instance into the cache
263 {
264 boost::mutex::scoped_lock lock(mutex_);
265 Store(instanceId, instance);
266 }
267 }
268
269
270 void OrthancInstancesCache::GetChunk(std::string& chunk,
271 std::string& md5,
272 const TransferBucket& bucket,
273 size_t chunkIndex)
274 {
275 GetChunk(chunk, md5, bucket.GetChunkInstanceId(chunkIndex),
276 bucket.GetChunkOffset(chunkIndex),
277 bucket.GetChunkSize(chunkIndex));
278 }
279 }