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 }