Mercurial > hg > orthanc
comparison OrthancFramework/Sources/Cache/MemoryStringCache.cpp @ 5807:8279eaab0d1d attach-custom-data
merged default -> attach-custom-data
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Tue, 24 Sep 2024 11:39:52 +0200 |
parents | f7adfb22e20e |
children |
comparison
equal
deleted
inserted
replaced
5085:79f98ee4f04b | 5807:8279eaab0d1d |
---|---|
1 /** | 1 /** |
2 * Orthanc - A Lightweight, RESTful DICOM Store | 2 * Orthanc - A Lightweight, RESTful DICOM Store |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | 3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics |
4 * Department, University Hospital of Liege, Belgium | 4 * Department, University Hospital of Liege, Belgium |
5 * Copyright (C) 2017-2022 Osimis S.A., Belgium | 5 * Copyright (C) 2017-2023 Osimis S.A., Belgium |
6 * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium | 6 * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium |
7 * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium | |
7 * | 8 * |
8 * This program is free software: you can redistribute it and/or | 9 * This program is free software: you can redistribute it and/or |
9 * modify it under the terms of the GNU Lesser General Public License | 10 * modify it under the terms of the GNU Lesser General Public License |
10 * as published by the Free Software Foundation, either version 3 of | 11 * as published by the Free Software Foundation, either version 3 of |
11 * the License, or (at your option) any later version. | 12 * the License, or (at your option) any later version. |
51 { | 52 { |
52 return content_.size(); | 53 return content_.size(); |
53 } | 54 } |
54 }; | 55 }; |
55 | 56 |
57 | |
58 MemoryStringCache::Accessor::Accessor(MemoryStringCache& cache) | |
59 : cache_(cache), | |
60 shouldAdd_(false) | |
61 { | |
62 } | |
63 | |
64 | |
65 MemoryStringCache::Accessor::~Accessor() | |
66 { | |
67 // if this accessor was the one in charge of loading and adding the data into the cache | |
68 // and it failed to add, remove the key from the list to make sure others accessor | |
69 // stop waiting for it. | |
70 if (shouldAdd_) | |
71 { | |
72 cache_.RemoveFromItemsBeingLoaded(keyToAdd_); | |
73 } | |
74 } | |
75 | |
76 | |
77 bool MemoryStringCache::Accessor::Fetch(std::string& value, const std::string& key) | |
78 { | |
79 // if multiple accessors are fetching at the same time: | |
80 // the first one will return false and will be in charge of adding to the cache. | |
81 // others will wait. | |
82 // if the first one fails to add, or, if the content was too large to fit in the cache, | |
83 // the next one will be in charge of adding ... | |
84 if (!cache_.Fetch(value, key)) | |
85 { | |
86 shouldAdd_ = true; | |
87 keyToAdd_ = key; | |
88 return false; | |
89 } | |
90 | |
91 shouldAdd_ = false; | |
92 keyToAdd_.clear(); | |
93 | |
94 return true; | |
95 } | |
96 | |
97 | |
98 void MemoryStringCache::Accessor::Add(const std::string& key, const std::string& value) | |
99 { | |
100 cache_.Add(key, value); | |
101 shouldAdd_ = false; | |
102 } | |
103 | |
104 | |
105 void MemoryStringCache::Accessor::Add(const std::string& key, const char* buffer, size_t size) | |
106 { | |
107 cache_.Add(key, buffer, size); | |
108 shouldAdd_ = false; | |
109 } | |
110 | |
111 | |
112 MemoryStringCache::MemoryStringCache() : | |
113 currentSize_(0), | |
114 maxSize_(100 * 1024 * 1024) // 100 MB | |
115 { | |
116 } | |
117 | |
118 | |
119 MemoryStringCache::~MemoryStringCache() | |
120 { | |
121 Recycle(0); | |
122 assert(content_.IsEmpty()); | |
123 } | |
124 | |
125 | |
56 size_t MemoryStringCache::GetMaximumSize() | 126 size_t MemoryStringCache::GetMaximumSize() |
57 { | 127 { |
58 return cache_.GetMaximumSize(); | 128 return maxSize_; |
59 } | 129 } |
130 | |
60 | 131 |
61 void MemoryStringCache::SetMaximumSize(size_t size) | 132 void MemoryStringCache::SetMaximumSize(size_t size) |
62 { | 133 { |
63 cache_.SetMaximumSize(size); | 134 if (size == 0) |
64 } | 135 { |
136 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
137 } | |
138 | |
139 // // Make sure no accessor is currently open (as its data may be | |
140 // // removed if recycling is needed) | |
141 // WriterLock contentLock(contentMutex_); | |
142 | |
143 // Lock the global structure of the cache | |
144 boost::mutex::scoped_lock cacheLock(cacheMutex_); | |
145 | |
146 Recycle(size); | |
147 maxSize_ = size; | |
148 } | |
149 | |
65 | 150 |
66 void MemoryStringCache::Add(const std::string& key, | 151 void MemoryStringCache::Add(const std::string& key, |
67 const std::string& value) | 152 const std::string& value) |
68 { | 153 { |
69 cache_.Acquire(key, new StringValue(value)); | 154 std::unique_ptr<StringValue> item(new StringValue(value)); |
70 } | 155 size_t size = value.size(); |
156 | |
157 boost::mutex::scoped_lock cacheLock(cacheMutex_); | |
158 | |
159 if (size > maxSize_) | |
160 { | |
161 // This object is too large to be stored in the cache, discard it | |
162 } | |
163 else if (content_.Contains(key)) | |
164 { | |
165 // Value already stored, don't overwrite the old value but put it on top of the cache | |
166 content_.MakeMostRecent(key); | |
167 } | |
168 else | |
169 { | |
170 Recycle(maxSize_ - size); // Post-condition: currentSize_ <= maxSize_ - size | |
171 assert(currentSize_ + size <= maxSize_); | |
172 | |
173 content_.Add(key, item.release()); | |
174 currentSize_ += size; | |
175 } | |
176 | |
177 RemoveFromItemsBeingLoadedInternal(key); | |
178 } | |
179 | |
71 | 180 |
72 void MemoryStringCache::Add(const std::string& key, | 181 void MemoryStringCache::Add(const std::string& key, |
73 const void* buffer, | 182 const void* buffer, |
74 size_t size) | 183 size_t size) |
75 { | 184 { |
76 cache_.Acquire(key, new StringValue(reinterpret_cast<const char*>(buffer), size)); | 185 Add(key, std::string(reinterpret_cast<const char*>(buffer), size)); |
77 } | 186 } |
187 | |
78 | 188 |
79 void MemoryStringCache::Invalidate(const std::string &key) | 189 void MemoryStringCache::Invalidate(const std::string &key) |
80 { | 190 { |
81 cache_.Invalidate(key); | 191 boost::mutex::scoped_lock cacheLock(cacheMutex_); |
82 } | 192 |
83 | 193 StringValue* item = NULL; |
194 if (content_.Contains(key, item)) | |
195 { | |
196 assert(item != NULL); | |
197 const size_t size = item->GetMemoryUsage(); | |
198 delete item; | |
199 | |
200 content_.Invalidate(key); | |
201 | |
202 assert(currentSize_ >= size); | |
203 currentSize_ -= size; | |
204 } | |
205 | |
206 RemoveFromItemsBeingLoadedInternal(key); | |
207 } | |
208 | |
209 | |
84 bool MemoryStringCache::Fetch(std::string& value, | 210 bool MemoryStringCache::Fetch(std::string& value, |
85 const std::string& key) | 211 const std::string& key) |
86 { | 212 { |
87 MemoryObjectCache::Accessor reader(cache_, key, false /* multiple readers are allowed */); | 213 boost::mutex::scoped_lock cacheLock(cacheMutex_); |
88 | 214 |
89 if (reader.IsValid()) | 215 StringValue* item; |
90 { | 216 |
91 value = dynamic_cast<StringValue&>(reader.GetValue()).GetContent(); | 217 // if another client is currently loading the item, wait for it. |
218 while (itemsBeingLoaded_.find(key) != itemsBeingLoaded_.end() && !content_.Contains(key, item)) | |
219 { | |
220 cacheCond_.wait(cacheLock); | |
221 } | |
222 | |
223 if (content_.Contains(key, item)) | |
224 { | |
225 value = dynamic_cast<StringValue&>(*item).GetContent(); | |
226 content_.MakeMostRecent(key); | |
227 | |
92 return true; | 228 return true; |
93 } | 229 } |
94 else | 230 else |
95 { | 231 { |
232 // note that this accessor will be in charge of loading and adding. | |
233 itemsBeingLoaded_.insert(key); | |
96 return false; | 234 return false; |
97 } | 235 } |
98 } | 236 } |
237 | |
238 | |
239 void MemoryStringCache::RemoveFromItemsBeingLoaded(const std::string& key) | |
240 { | |
241 boost::mutex::scoped_lock cacheLock(cacheMutex_); | |
242 RemoveFromItemsBeingLoadedInternal(key); | |
243 } | |
244 | |
245 | |
246 void MemoryStringCache::RemoveFromItemsBeingLoadedInternal(const std::string& key) | |
247 { | |
248 // notify all waiting users, some of them potentially waiting for this item | |
249 itemsBeingLoaded_.erase(key); | |
250 cacheCond_.notify_all(); | |
251 } | |
252 | |
253 void MemoryStringCache::Recycle(size_t targetSize) | |
254 { | |
255 // WARNING: "cacheMutex_" must be locked | |
256 while (currentSize_ > targetSize) | |
257 { | |
258 assert(!content_.IsEmpty()); | |
259 | |
260 StringValue* item = NULL; | |
261 content_.RemoveOldest(item); | |
262 | |
263 assert(item != NULL); | |
264 const size_t size = item->GetMemoryUsage(); | |
265 delete item; | |
266 | |
267 assert(currentSize_ >= size); | |
268 currentSize_ -= size; | |
269 } | |
270 | |
271 // Post-condition: "currentSize_ <= targetSize" | |
272 } | |
273 | |
274 size_t MemoryStringCache::GetCurrentSize() const | |
275 { | |
276 boost::mutex::scoped_lock cacheLock(cacheMutex_); | |
277 | |
278 return currentSize_; | |
279 } | |
280 | |
281 size_t MemoryStringCache::GetNumberOfItems() const | |
282 { | |
283 boost::mutex::scoped_lock cacheLock(cacheMutex_); | |
284 return content_.GetSize(); | |
285 | |
286 } | |
287 | |
99 } | 288 } |