drogon
C++14/17-based HTTP application framework
Loading...
Searching...
No Matches
CacheMap.h
Go to the documentation of this file.
1
14
15#pragma once
16
17#include <trantor/net/EventLoop.h>
18#include <trantor/utils/Logger.h>
19#include <atomic>
20#include <deque>
21#include <map>
22#include <mutex>
23#include <set>
24#include <unordered_map>
25#include <unordered_set>
26#include <vector>
27#include <future>
28#include <assert.h>
29
30#define WHEELS_NUM 4
31#define BUCKET_NUM_PER_WHEEL 200
32#define TICK_INTERVAL 1.0
33
34namespace drogon
35{
39class CallbackEntry
40{
41 public:
42 CallbackEntry(std::function<void()> cb) : cb_(std::move(cb))
43 {
44 }
45
46 ~CallbackEntry()
47 {
48 cb_();
49 }
50
51 private:
52 std::function<void()> cb_;
53};
54
55using CallbackEntryPtr = std::shared_ptr<CallbackEntry>;
56using WeakCallbackEntryPtr = std::weak_ptr<CallbackEntry>;
57
58using CallbackBucket = std::unordered_set<CallbackEntryPtr>;
59using CallbackBucketQueue = std::deque<CallbackBucket>;
60
70template <typename T1, typename T2>
72{
73 public:
75
91 CacheMap(trantor::EventLoop *loop,
92 float tickInterval = TICK_INTERVAL,
93 size_t wheelsNum = WHEELS_NUM,
94 size_t bucketsNumPerWheel = BUCKET_NUM_PER_WHEEL,
95 std::function<void(const T1 &)> fnOnInsert = nullptr,
96 std::function<void(const T1 &)> fnOnErase = nullptr)
97 : loop_(loop),
98 tickInterval_(tickInterval),
99 wheelsNumber_(wheelsNum),
100 bucketsNumPerWheel_(bucketsNumPerWheel),
101 ctrlBlockPtr_(std::make_shared<ControlBlock>()),
102 fnOnInsert_(fnOnInsert),
103 fnOnErase_(fnOnErase)
104 {
105 wheels_.resize(wheelsNumber_);
106 for (size_t i = 0; i < wheelsNumber_; ++i)
107 {
108 wheels_[i].resize(bucketsNumPerWheel_);
109 }
110 if (tickInterval_ > 0 && wheelsNumber_ > 0 && bucketsNumPerWheel_ > 0)
111 {
112 timerId_ = loop_->runEvery(
113 tickInterval_, [this, ctrlBlockPtr = ctrlBlockPtr_]() {
114 std::lock_guard<std::mutex> lock(ctrlBlockPtr->mtx);
115 if (ctrlBlockPtr->destructed)
116 return;
117
118 size_t t = ++ticksCounter_;
119 size_t pow = 1;
120 for (size_t i = 0; i < wheelsNumber_; ++i)
121 {
122 if ((t % pow) == 0)
123 {
124 CallbackBucket tmp;
125 {
126 std::lock_guard<std::mutex> lock(bucketMutex_);
127 // use tmp val to make this critical area as
128 // short as possible.
129 wheels_[i].front().swap(tmp);
130 wheels_[i].pop_front();
131 wheels_[i].push_back(CallbackBucket());
132 }
133 }
134 pow = pow * bucketsNumPerWheel_;
135 }
136 });
137 loop_->runOnQuit([ctrlBlockPtr = ctrlBlockPtr_] {
138 std::lock_guard<std::mutex> lock(ctrlBlockPtr->mtx);
139 ctrlBlockPtr->loopEnded = true;
140 });
141 }
142 else
143 {
144 noWheels_ = true;
145 }
146 };
147
148 ~CacheMap()
149 {
150 std::lock_guard<std::mutex> lock(ctrlBlockPtr_->mtx);
151 ctrlBlockPtr_->destructed = true;
152 map_.clear();
153 if (!ctrlBlockPtr_->loopEnded)
154 {
155 loop_->invalidateTimer(timerId_);
156 }
157 for (auto iter = wheels_.rbegin(); iter != wheels_.rend(); ++iter)
158 {
159 iter->clear();
160 }
161 LOG_TRACE << "CacheMap destruct!";
162 }
163
164 struct MapValue
165 {
166 MapValue(const T2 &value,
167 size_t timeout,
168 std::function<void()> &&callback)
169 : value_(value),
170 timeout_(timeout),
171 timeoutCallback_(std::move(callback))
172 {
173 }
174
175 MapValue(T2 &&value, size_t timeout, std::function<void()> &&callback)
176 : value_(std::move(value)),
177 timeout_(timeout),
178 timeoutCallback_(std::move(callback))
179 {
180 }
181
182 MapValue(T2 &&value, size_t timeout)
183 : value_(std::move(value)), timeout_(timeout)
184 {
185 }
186
187 MapValue(const T2 &value, size_t timeout)
188 : value_(value), timeout_(timeout)
189 {
190 }
191
192 MapValue(T2 &&value) : value_(std::move(value))
193 {
194 }
195
196 MapValue(const T2 &value) : value_(value)
197 {
198 }
199
200 MapValue() = default;
201 T2 value_;
202 size_t timeout_{0};
203 std::function<void()> timeoutCallback_;
204 WeakCallbackEntryPtr weakEntryPtr_;
205 };
206
217 void insert(const T1 &key,
218 T2 &&value,
219 size_t timeout = 0,
220 std::function<void()> timeoutCallback = std::function<void()>())
221 {
222 if (timeout > 0)
223 {
224 MapValue v{std::move(value), timeout, std::move(timeoutCallback)};
225 std::lock_guard<std::mutex> lock(mtx_);
226 map_.insert(std::make_pair(key, std::move(v)));
227 eraseAfter(timeout, key);
228 }
229 else
230 {
231 MapValue v{std::move(value)};
232 std::lock_guard<std::mutex> lock(mtx_);
233 map_.insert(std::make_pair(key, std::move(v)));
234 }
235 if (fnOnInsert_)
236 fnOnInsert_(key);
237 }
238
249 void insert(const T1 &key,
250 const T2 &value,
251 size_t timeout = 0,
252 std::function<void()> timeoutCallback = std::function<void()>())
253 {
254 if (timeout > 0)
255 {
256 MapValue v{value, timeout, std::move(timeoutCallback)};
257 std::lock_guard<std::mutex> lock(mtx_);
258 map_.insert(std::make_pair(key, std::move(v)));
259 eraseAfter(timeout, key);
260 }
261 else
262 {
263 MapValue v{value};
264 std::lock_guard<std::mutex> lock(mtx_);
265 map_.insert(std::make_pair(key, std::move(v)));
266 }
267 if (fnOnInsert_)
268 fnOnInsert_(key);
269 }
270
280 T2 operator[](const T1 &key)
281 {
282 size_t timeout = 0;
283 std::lock_guard<std::mutex> lock(mtx_);
284 auto iter = map_.find(key);
285 if (iter != map_.end())
286 {
287 timeout = iter->second.timeout_;
288 if (timeout > 0)
289 eraseAfter(timeout, key);
290 return iter->second.value_;
291 }
292 return T2();
293 }
294
311 template <typename Callable>
312 void modify(const T1 &key, Callable &&handler, size_t timeout = 0)
313 {
314 {
315 std::lock_guard<std::mutex> lock(mtx_);
316 auto iter = map_.find(key);
317 if (iter != map_.end())
318 {
319 timeout = iter->second.timeout_;
320 handler(iter->second.value_);
321 if (timeout > 0)
322 eraseAfter(timeout, key);
323 return;
324 }
325
326 MapValue v{T2(), timeout};
327 handler(v.value_);
328 map_.insert(std::make_pair(key, std::move(v)));
329 if (timeout > 0)
330 {
331 eraseAfter(timeout, key);
332 }
333 }
334 if (fnOnInsert_)
335 fnOnInsert_(key);
336 }
337
339 bool find(const T1 &key)
340 {
341 size_t timeout = 0;
342 bool flag = false;
343
344 std::lock_guard<std::mutex> lock(mtx_);
345 auto iter = map_.find(key);
346 if (iter != map_.end())
347 {
348 timeout = iter->second.timeout_;
349 flag = true;
350 }
351
352 if (timeout > 0)
353 eraseAfter(timeout, key);
354
355 return flag;
356 }
357
359
363 bool findAndFetch(const T1 &key, T2 &value)
364 {
365 size_t timeout = 0;
366 bool flag = false;
367 std::lock_guard<std::mutex> lock(mtx_);
368 auto iter = map_.find(key);
369 if (iter != map_.end())
370 {
371 timeout = iter->second.timeout_;
372 flag = true;
373 value = iter->second.value_;
374 }
375
376 if (timeout > 0)
377 eraseAfter(timeout, key);
378
379 return flag;
380 }
381
383
387 void erase(const T1 &key)
388 {
389 // in this case,we don't evoke the timeout callback;
390 {
391 std::lock_guard<std::mutex> lock(mtx_);
392 map_.erase(key);
393 }
394 if (fnOnErase_)
395 fnOnErase_(key);
396 }
397
403 trantor::EventLoop *getLoop()
404 {
405 return loop_;
406 }
407
417 void runAfter(size_t delay, std::function<void()> &&task)
418 {
419 std::lock_guard<std::mutex> lock(bucketMutex_);
420 insertEntry(delay, std::make_shared<CallbackEntry>(std::move(task)));
421 }
422
423 void runAfter(size_t delay, const std::function<void()> &task)
424 {
425 std::lock_guard<std::mutex> lock(bucketMutex_);
426 insertEntry(delay, std::make_shared<CallbackEntry>(task));
427 }
428
429 private:
439 struct ControlBlock
440 {
441 ControlBlock() : destructed(false), loopEnded(false)
442 {
443 }
444
445 bool destructed;
446 bool loopEnded;
447 std::mutex mtx;
448 };
449
450 std::unordered_map<T1, MapValue> map_;
451
452 std::vector<CallbackBucketQueue> wheels_;
453
454 std::atomic<size_t> ticksCounter_{0};
455
456 std::mutex mtx_;
457 std::mutex bucketMutex_;
458 trantor::TimerId timerId_;
459 trantor::EventLoop *loop_;
460
461 float tickInterval_;
462 size_t wheelsNumber_;
463 size_t bucketsNumPerWheel_;
464 std::shared_ptr<ControlBlock> ctrlBlockPtr_;
465 std::function<void(const T1 &)> fnOnInsert_;
466 std::function<void(const T1 &)> fnOnErase_;
467
468 bool noWheels_{false};
469
470 void insertEntry(size_t delay, CallbackEntryPtr entryPtr)
471 {
472 // protected by bucketMutex;
473 if (delay <= 0)
474 return;
475 delay = static_cast<size_t>(delay / tickInterval_ + 1);
476 size_t t = ticksCounter_;
477 for (size_t i = 0; i < wheelsNumber_; ++i)
478 {
479 if (delay <= bucketsNumPerWheel_)
480 {
481 wheels_[i][delay - 1].insert(entryPtr);
482 break;
483 }
484 if (i < (wheelsNumber_ - 1))
485 {
486 entryPtr = std::make_shared<CallbackEntry>(
487 [this, delay, i, t, entryPtr]() {
488 if (delay > 0)
489 {
490 std::lock_guard<std::mutex> lock(bucketMutex_);
491 wheels_[i][(delay + (t % bucketsNumPerWheel_) - 1) %
492 bucketsNumPerWheel_]
493 .insert(entryPtr);
494 }
495 });
496 }
497 else
498 {
499 // delay is too long to put entry at valid position in wheels;
500 wheels_[i][bucketsNumPerWheel_ - 1].insert(entryPtr);
501 }
502 delay =
503 (delay + (t % bucketsNumPerWheel_) - 1) / bucketsNumPerWheel_;
504 t = t / bucketsNumPerWheel_;
505 }
506 }
507
508 void eraseAfter(size_t delay, const T1 &key)
509 {
510 if (noWheels_)
511 return;
512 assert(map_.find(key) != map_.end());
513
514 CallbackEntryPtr entryPtr;
515
516 if (map_.find(key) != map_.end())
517 {
518 entryPtr = map_[key].weakEntryPtr_.lock();
519 }
520
521 if (entryPtr)
522 {
523 std::lock_guard<std::mutex> lock(bucketMutex_);
524 insertEntry(delay, entryPtr);
525 }
526 else
527 {
528 std::function<void()> cb = [this, key]() {
529 bool erased{false};
530 std::function<void()> timeoutCallback;
531 {
532 std::lock_guard<std::mutex> lock(mtx_);
533 auto iter = map_.find(key);
534 if (iter != map_.end())
535 {
536 auto &value = iter->second;
537 auto entryPtr = value.weakEntryPtr_.lock();
538 // entryPtr is used to avoid race conditions
539 if (value.timeout_ > 0 && !entryPtr)
540 {
541 erased = true;
542 timeoutCallback = std::move(value.timeoutCallback_);
543 map_.erase(key);
544 }
545 }
546 }
547 if (erased && fnOnErase_)
548 fnOnErase_(key);
549 if (erased && timeoutCallback)
550 timeoutCallback();
551 };
552 entryPtr = std::make_shared<CallbackEntry>(std::move(cb));
553 map_[key].weakEntryPtr_ = entryPtr;
554 {
555 std::lock_guard<std::mutex> lock(bucketMutex_);
556 insertEntry(delay, entryPtr);
557 }
558 }
559 }
560};
561
562} // namespace drogon
Cache Map.
Definition CacheMap.h:72
bool find(const T1 &key)
Check if the value of the keyword exists.
Definition CacheMap.h:339
bool findAndFetch(const T1 &key, T2 &value)
Atomically find and get the value of a keyword.
Definition CacheMap.h:363
void runAfter(size_t delay, std::function< void()> &&task)
run the task function after a period of time.
Definition CacheMap.h:417
CacheMap(trantor::EventLoop *loop, float tickInterval=TICK_INTERVAL, size_t wheelsNum=WHEELS_NUM, size_t bucketsNumPerWheel=BUCKET_NUM_PER_WHEEL, std::function< void(const T1 &)> fnOnInsert=nullptr, std::function< void(const T1 &)> fnOnErase=nullptr)
constructor
Definition CacheMap.h:91
void erase(const T1 &key)
Erase the value of the keyword.
Definition CacheMap.h:387
void insert(const T1 &key, T2 &&value, size_t timeout=0, std::function< void()> timeoutCallback=std::function< void()>())
Insert a key-value pair into the cache.
Definition CacheMap.h:217
void insert(const T1 &key, const T2 &value, size_t timeout=0, std::function< void()> timeoutCallback=std::function< void()>())
Insert a key-value pair into the cache.
Definition CacheMap.h:249
T2 operator[](const T1 &key)
Return the value of the keyword.
Definition CacheMap.h:280
void modify(const T1 &key, Callable &&handler, size_t timeout=0)
Modify or visit the data identified by the key parameter.
Definition CacheMap.h:312
trantor::EventLoop * getLoop()
Get the event loop object.
Definition CacheMap.h:403
Drogon Test is a minimal effort test framework developed because the major C++ test frameworks doesn'...
Definition Attribute.h:23
STL namespace.
Definition CacheMap.h:165