Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
Cache.cs
1using System;
4using System.Threading;
5using System.Threading.Tasks;
6using Waher.Events;
8
10{
16 public class Cache<KeyType, ValueType> : ICache, IDictionary<KeyType, ValueType>
17 {
18 private readonly Guid id = Guid.NewGuid();
19 private readonly Dictionary<KeyType, CacheItem<KeyType, ValueType>> valuesByKey = new Dictionary<KeyType, CacheItem<KeyType, ValueType>>();
20 private readonly SortedDictionary<DateTime, KeyType> keysByLastUsage = new SortedDictionary<DateTime, KeyType>();
21 private readonly SortedDictionary<DateTime, KeyType> keysByCreation = new SortedDictionary<DateTime, KeyType>();
22 private readonly SortedDictionary<DateTime, KeyType> keysByExpiry = new SortedDictionary<DateTime, KeyType>();
23 private readonly Random rnd = new Random();
24 private readonly object synchObject = new object();
25 private readonly int maxItems;
26 private readonly bool standalone;
27 private TimeSpan maxTimeUsed;
28 private TimeSpan maxTimeUnused;
29 private Timer timer;
30 private int maxTimerIntervalMs = 5000;
31 private int minTimerIntervalMs = 100;
32 private bool hasExplicitExpiry = false;
33
40 public Cache(int MaxItems, TimeSpan MaxTimeUsed, TimeSpan MaxTimeUnused)
41 : this(MaxItems, MaxTimeUsed, MaxTimeUnused, false)
42 {
43 }
44
53 public Cache(int MaxItems, TimeSpan MaxTimeUsed, TimeSpan MaxTimeUnused, bool Standalone)
54 {
55 this.maxItems = MaxItems;
56 this.maxTimeUsed = MaxTimeUsed;
57 this.maxTimeUnused = MaxTimeUnused;
58 this.standalone = Standalone;
59
60 Caches.Register(this.id, this);
61 }
62
63 private void CreateTimerLocked()
64 {
65 if (this.maxTimeUsed < TimeSpan.MaxValue ||
66 this.maxTimeUnused < TimeSpan.MaxValue)
67 {
68 int Interval = Math.Min((int)((this.maxTimeUnused.TotalMilliseconds / 2) + 0.5), this.maxTimerIntervalMs);
69 if (Interval < this.minTimerIntervalMs)
70 Interval = this.minTimerIntervalMs;
71
72 this.timer = new Timer(this.TimerCallback, null, Interval, Interval);
73 }
74 else
75 this.timer = null;
76 }
77
82 {
83 get => this.minTimerIntervalMs;
84 set => this.minTimerIntervalMs = value;
85 }
86
91 {
92 get => this.maxTimerIntervalMs;
93 set => this.maxTimerIntervalMs = value;
94 }
95
99 public void Dispose()
100 {
101 Caches.Unregister(this.id);
102 this.Clear();
103 }
104
109 public bool Standalone => this.standalone;
110
111 private void TimerCallback(object state)
112 {
113 ChunkedList<CacheItem<KeyType, ValueType>> ToRemoveNotUsed = null;
115 ChunkedList<CacheItem<KeyType, ValueType>> ToRemoveExpired = null;
116 DateTime UtcNow = DateTime.UtcNow;
117 DateTime Limit;
118 bool Removed = false;
119
120 try
121 {
122 lock (this.synchObject)
123 {
124 if (this.maxTimeUnused < TimeSpan.MaxValue)
125 {
126 Limit = UtcNow - this.maxTimeUnused;
127
128 foreach (KeyValuePair<DateTime, KeyType> P in this.keysByLastUsage)
129 {
130 if (P.Key > Limit)
131 break;
132
133 if (ToRemoveNotUsed is null)
134 ToRemoveNotUsed = new ChunkedList<CacheItem<KeyType, ValueType>>();
135
136 ToRemoveNotUsed.Add(this.valuesByKey[P.Value]);
137 }
138
139 if (!(ToRemoveNotUsed is null))
140 {
142 CacheItem<KeyType, ValueType> Item;
143
144 while (!(Loop is null))
145 {
146 for (int i = Loop.Start, c = Loop.Pos; i < c; i++)
147 {
148 Item = Loop[i];
149
150 this.valuesByKey.Remove(Item.Key);
151 this.keysByCreation.Remove(Item.Created);
152 this.keysByLastUsage.Remove(Item.LastUsed);
153
154 if (Item.Expires.HasValue &&
155 this.keysByExpiry.Remove(Item.Expires.Value))
156 {
157 this.hasExplicitExpiry = this.keysByExpiry.Count > 0;
158 }
159 }
160
161 Loop = Loop.Next;
162 }
163
164 Removed = true;
165 }
166 }
167
168 if (this.maxTimeUsed < TimeSpan.MaxValue)
169 {
170 Limit = UtcNow - this.maxTimeUsed;
171
172 foreach (KeyValuePair<DateTime, KeyType> P in this.keysByCreation)
173 {
174 if (P.Key > Limit)
175 break;
176
177 if (ToRemoveOld is null)
179
180 ToRemoveOld.Add(this.valuesByKey[P.Value]);
181 }
182
183 if (!(ToRemoveOld is null))
184 {
186 CacheItem<KeyType, ValueType> Item;
187
188 while (!(Loop is null))
189 {
190 for (int i = Loop.Start, c = Loop.Pos; i < c; i++)
191 {
192 Item = Loop[i];
193
194 this.valuesByKey.Remove(Item.Key);
195 this.keysByCreation.Remove(Item.Created);
196 this.keysByLastUsage.Remove(Item.LastUsed);
197
198 if (Item.Expires.HasValue &&
199 this.keysByExpiry.Remove(Item.Expires.Value))
200 {
201 this.hasExplicitExpiry = this.keysByExpiry.Count > 0;
202 }
203 }
204
205 Loop = Loop.Next;
206 }
207
208 Removed = true;
209 }
210 }
211
212 if (this.hasExplicitExpiry)
213 {
214 foreach (KeyValuePair<DateTime, KeyType> P in this.keysByExpiry)
215 {
216 if (P.Key > UtcNow)
217 break;
218
219 if (ToRemoveExpired is null)
220 ToRemoveExpired = new ChunkedList<CacheItem<KeyType, ValueType>>();
221
222 ToRemoveExpired.Add(this.valuesByKey[P.Value]);
223 }
224
225 if (!(ToRemoveExpired is null))
226 {
228 CacheItem<KeyType, ValueType> Item;
229
230 while (!(Loop is null))
231 {
232 for (int i = Loop.Start, c = Loop.Pos; i < c; i++)
233 {
234 Item = Loop[i];
235
236 this.valuesByKey.Remove(Item.Key);
237 this.keysByCreation.Remove(Item.Created);
238 this.keysByLastUsage.Remove(Item.LastUsed);
239 this.keysByExpiry.Remove(Item.Expires.Value);
240 }
241
242 Loop = Loop.Next;
243 }
244
245 this.hasExplicitExpiry = this.keysByExpiry.Count > 0;
246 Removed = true;
247 }
248 }
249
250 if (Removed && this.valuesByKey.Count == 0)
251 {
252 this.timer?.Dispose();
253 this.timer = null;
254 }
255 }
256
257 if (!(ToRemoveNotUsed is null))
258 this.OnRemoved(ToRemoveNotUsed, RemovedReason.NotUsed);
259
260 if (!(ToRemoveOld is null))
261 this.OnRemoved(ToRemoveOld, RemovedReason.Old);
262
263 if (!(ToRemoveExpired is null))
264 this.OnRemoved(ToRemoveExpired, RemovedReason.Old);
265 }
266 catch (Exception ex)
267 {
268 Log.Exception(ex);
269 }
270 }
271
275 public int MaxItems => this.maxItems;
276
280 public TimeSpan MaxTimeUsed
281 {
282 get => this.maxTimeUsed;
283 set => this.maxTimeUsed = value;
284 }
285
289 public TimeSpan MaxTimeUnused
290 {
291 get => this.maxTimeUnused;
292 set => this.maxTimeUnused = value;
293 }
294
300 public bool Ping(KeyType Key)
301 {
302 return this.TryGetValue(Key, out _);
303 }
304
311 public bool TryGetValue(KeyType Key, out ValueType Value)
312 {
313 lock (this.synchObject)
314 {
315 if (this.valuesByKey.TryGetValue(Key, out CacheItem<KeyType, ValueType> Item))
316 {
317 Value = Item.Value;
318
319 this.keysByLastUsage.Remove(Item.LastUsed);
320 Item.LastUsed = this.GetLastUsageTimeLocked();
321 this.keysByLastUsage[Item.LastUsed] = Key;
322
323 return true;
324 }
325 else
326 {
327 Value = default;
328 return false;
329 }
330 }
331 }
332
336 public int Count
337 {
338 get
339 {
340 lock (this.synchObject)
341 {
342 return this.valuesByKey.Count;
343 }
344 }
345 }
346
350 public ICollection<KeyType> Keys => this.GetKeys();
351
355 public ICollection<ValueType> Values => this.GetValues();
356
360 public bool IsReadOnly => false;
361
366 public KeyType[] GetKeys()
367 {
368 KeyType[] Result;
369
370 lock (this.synchObject)
371 {
372 Result = new KeyType[this.valuesByKey.Count];
373 this.valuesByKey.Keys.CopyTo(Result, 0);
374 }
375
376 return Result;
377 }
378
383 public ValueType[] GetValues()
384 {
385 ValueType[] Result;
386 int i = 0;
387
388 lock (this.synchObject)
389 {
390 Result = new ValueType[this.valuesByKey.Count];
391
392 foreach (CacheItem<KeyType, ValueType> Rec in this.valuesByKey.Values)
393 Result[i++] = Rec.Value;
394 }
395
396 return Result;
397 }
398
404 public bool ContainsKey(KeyType Key)
405 {
406 return this.TryGetValue(Key, out ValueType _);
407 }
408
409 private DateTime GetLastUsageTimeLocked()
410 {
411 DateTime TP = DateTime.UtcNow;
412
413 while (this.keysByLastUsage.ContainsKey(TP))
414 TP = TP.AddTicks(this.rnd.Next(1, 10));
415
416 return TP;
417 }
418
425 public ValueType this[KeyType Key]
426 {
427 get
428 {
429 if (this.TryGetValue(Key, out ValueType Result))
430 return Result;
431 else
432 throw new ArgumentException("Value not found.", nameof(Key));
433 }
434
435 set
436 {
437 this.Add(Key, value);
438 }
439 }
440
446 public void Add(KeyType Key, ValueType Value)
447 {
448 this.Add(Key, Value, null, null);
449 }
450
457 public void Add(KeyType Key, ValueType Value, DateTime? Expires)
458 {
459 this.Add(Key, Value, Expires, null);
460 }
461
468 public void Add(KeyType Key, ValueType Value, TimeSpan? Expires)
469 {
470 this.Add(Key, Value, null, Expires);
471 }
472
480 private void Add(KeyType Key, ValueType Value, DateTime? Expires, TimeSpan? Expires2)
481 {
482 CacheItem<KeyType, ValueType> Prev;
483 CacheItem<KeyType, ValueType> Item;
484 RemovedReason Reason;
485
486 lock (this.synchObject)
487 {
488 if (this.valuesByKey.TryGetValue(Key, out Prev))
489 {
490 this.valuesByKey.Remove(Key);
491 this.keysByCreation.Remove(Prev.Created);
492 this.keysByLastUsage.Remove(Prev.LastUsed);
493
494 if (Prev.Expires.HasValue &&
495 this.keysByExpiry.Remove(Prev.Expires.Value))
496 {
497 this.hasExplicitExpiry = this.keysByExpiry.Count > 0;
498 }
499
500 Reason = RemovedReason.Replaced;
501 }
502 else
503 {
504 Reason = RemovedReason.Space;
505
506 if (this.valuesByKey.Count >= this.maxItems)
507 {
508 KeyType OldKey = default;
509 bool Found = false;
510
511 foreach (KeyType Key2 in this.keysByLastUsage.Values)
512 {
513 OldKey = Key2;
514 Found = true;
515 break;
516 }
517
518 if (Found)
519 {
520 Prev = this.valuesByKey[OldKey];
521
522 this.valuesByKey.Remove(OldKey);
523 this.keysByCreation.Remove(Prev.Created);
524 this.keysByLastUsage.Remove(Prev.LastUsed);
525
526 if (Prev.Expires.HasValue &&
527 this.keysByExpiry.Remove(Prev.Expires.Value))
528 {
529 this.hasExplicitExpiry = this.keysByExpiry.Count > 0;
530 }
531 }
532 else
533 Prev = null;
534 }
535 else
536 Prev = null;
537 }
538
539 DateTime TP = DateTime.UtcNow;
540
541 while (this.keysByCreation.ContainsKey(TP) ||
542 this.keysByLastUsage.ContainsKey(TP))
543 {
544 TP = TP.AddTicks(this.rnd.Next(1, 10));
545 }
546
547 if (Expires2.HasValue)
548 Expires = TP.Add(Expires2.Value);
549
550 if (Expires.HasValue)
551 {
552 if (Expires.Value.Kind != DateTimeKind.Utc)
553 Expires = Expires.Value.ToUniversalTime();
554
555 while (this.keysByExpiry.ContainsKey(Expires.Value))
556 Expires = Expires.Value.AddTicks(this.rnd.Next(1, 10));
557 }
558
559 Item = new CacheItem<KeyType, ValueType>(Key, Value, TP, Expires);
560
561 this.valuesByKey[Key] = Item;
562 this.keysByCreation[TP] = Key;
563 this.keysByLastUsage[TP] = Key;
564
565 if (Expires.HasValue)
566 {
567 this.keysByExpiry[Expires.Value] = Key;
568 this.hasExplicitExpiry = true;
569 }
570
571 if (this.timer is null)
572 this.CreateTimerLocked();
573 }
574
575 if (!(Prev is null))
576 _ = this.OnRemoved(Key, Prev.Value, Reason);
577 }
578
579 private async Task OnRemoved(KeyType Key, ValueType Value, RemovedReason Reason)
580 {
581 try
582 {
583 EventHandlerAsync<CacheItemEventArgs<KeyType, ValueType>> h = this.Removed;
584
585 if (!(h is null))
586 await h.Raise(this, new CacheItemEventArgs<KeyType, ValueType>(Key, Value, Reason));
587 }
588 catch (Exception ex)
589 {
590 Log.Exception(ex);
591 }
592 }
593
594 private async void OnRemoved(IEnumerable<CacheItem<KeyType, ValueType>> Items, RemovedReason Reason)
595 {
596 EventHandlerAsync<CacheItemEventArgs<KeyType, ValueType>> h = this.Removed;
597 try
598 {
599 if (!(h is null))
600 {
601 foreach (CacheItem<KeyType, ValueType> Item in Items)
602 await h.Raise(this, new CacheItemEventArgs<KeyType, ValueType>(Item.Key, Item.Value, Reason));
603 }
604 }
605 catch (Exception ex)
606 {
607 Log.Exception(ex);
608 }
609 }
610
616 public bool Remove(KeyType Key)
617 {
618 if (this.RemoveNoEvent(Key, out CacheItem<KeyType, ValueType> Item))
619 {
620 _ = this.OnRemoved(Key, Item.Value, RemovedReason.Manual);
621 return true;
622 }
623 else
624 return false;
625 }
626
633 public async Task<bool> RemoveAsync(KeyType Key)
634 {
635 if (this.RemoveNoEvent(Key, out CacheItem<KeyType, ValueType> Item))
636 {
637 await this.OnRemoved(Key, Item.Value, RemovedReason.Manual);
638 return true;
639 }
640 else
641 return false;
642 }
643
644 private bool RemoveNoEvent(KeyType Key, out CacheItem<KeyType, ValueType> Item)
645 {
646 lock (this.synchObject)
647 {
648 if (!this.valuesByKey.TryGetValue(Key, out Item))
649 return false;
650
651 this.valuesByKey.Remove(Item.Key);
652 this.keysByCreation.Remove(Item.Created);
653 this.keysByLastUsage.Remove(Item.LastUsed);
654
655 if (Item.Expires.HasValue &&
656 this.keysByExpiry.Remove(Item.Expires.Value))
657 {
658 this.hasExplicitExpiry = this.keysByExpiry.Count > 0;
659 }
660
661 if (this.valuesByKey.Count == 0)
662 {
663 this.timer?.Dispose();
664 this.timer = null;
665 }
666 }
667
668 return true;
669 }
670
674 public event EventHandlerAsync<CacheItemEventArgs<KeyType, ValueType>> Removed = null;
675
679 public void Clear()
680 {
681 CacheItem<KeyType, ValueType>[] Values;
682
683 lock (this.synchObject)
684 {
685 Values = new CacheItem<KeyType, ValueType>[this.valuesByKey.Count];
686 this.valuesByKey.Values.CopyTo(Values, 0);
687 this.valuesByKey.Clear();
688 this.keysByLastUsage.Clear();
689 this.keysByCreation.Clear();
690 this.keysByExpiry.Clear();
691 this.hasExplicitExpiry = false;
692
693 this.timer?.Dispose();
694 this.timer = null;
695 }
696
697 this.OnRemoved(Values, RemovedReason.Manual);
698 }
699
704 public void Add(KeyValuePair<KeyType, ValueType> item)
705 {
706 this.Add(item.Key, item.Value);
707 }
708
714 public bool Contains(KeyValuePair<KeyType, ValueType> item)
715 {
716 return this.TryGetValue(item.Key, out ValueType Value) && Value.Equals(item.Value);
717 }
718
724 public void CopyTo(KeyValuePair<KeyType, ValueType>[] array, int arrayIndex)
725 {
726 lock (this.synchObject)
727 {
728 foreach (CacheItem<KeyType, ValueType> Item in this.valuesByKey.Values)
729 array[arrayIndex++] = new KeyValuePair<KeyType, ValueType>(Item.Key, Item.Value);
730 }
731 }
732
737 public KeyValuePair<KeyType, ValueType>[] ToArray()
738 {
739 KeyValuePair<KeyType, ValueType>[] Result;
740 int i = 0;
741
742 lock (this.synchObject)
743 {
744 Result = new KeyValuePair<KeyType, ValueType>[this.valuesByKey.Count];
745
746 foreach (CacheItem<KeyType, ValueType> Item in this.valuesByKey.Values)
747 Result[i++] = new KeyValuePair<KeyType, ValueType>(Item.Key, Item.Value);
748 }
749
750 return Result;
751 }
752
758 public bool Remove(KeyValuePair<KeyType, ValueType> Item)
759 {
760 if (this.TryGetValue(Item.Key, out ValueType Value) && Value.Equals(Item.Value))
761 return this.Remove(Item.Key);
762 else
763 return false;
764 }
765
770 public IEnumerator<KeyValuePair<KeyType, ValueType>> GetEnumerator()
771 {
772 IEnumerable<KeyValuePair<KeyType, ValueType>> Array = this.ToArray();
773 return Array.GetEnumerator();
774 }
775
780 IEnumerator IEnumerable.GetEnumerator()
781 {
782 return this.ToArray().GetEnumerator();
783 }
784 }
785}
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1657
Implements an in-memory cache.
Definition: Cache.cs:17
ValueType[] GetValues()
Gets all available values in the cache.
Definition: Cache.cs:383
bool ContainsKey(KeyType Key)
Checks if a key is available in the cache.
Definition: Cache.cs:404
EventHandlerAsync< CacheItemEventArgs< KeyType, ValueType > > Removed
Event raised when an item has been removed from the cache.
Definition: Cache.cs:674
KeyValuePair< KeyType, ValueType >[] ToArray()
Returns the contents of the cache as an array.
Definition: Cache.cs:737
Cache(int MaxItems, TimeSpan MaxTimeUsed, TimeSpan MaxTimeUnused)
Implements an in-memory cache.
Definition: Cache.cs:40
bool Remove(KeyValuePair< KeyType, ValueType > Item)
Removes an item from the cache.
Definition: Cache.cs:758
void Dispose()
IDisposable.Dispose
Definition: Cache.cs:99
bool Standalone
If cache is a standalone cache, or if it can be managed collectively with other caches.
Definition: Cache.cs:109
int Count
Number of items in cache
Definition: Cache.cs:337
int MaxTimerIntervalMs
Maximum expiry timer interval, in milliseconds.
Definition: Cache.cs:91
int MinTimerIntervalMs
Minimum expiry timer interval, in milliseconds.
Definition: Cache.cs:82
void Add(KeyType Key, ValueType Value, DateTime? Expires)
Adds an item to the cache.
Definition: Cache.cs:457
ICollection< ValueType > Values
Values in cache.
Definition: Cache.cs:355
bool IsReadOnly
If the dictionary is read-only.
Definition: Cache.cs:360
void Add(KeyType Key, ValueType Value, TimeSpan? Expires)
Adds an item to the cache.
Definition: Cache.cs:468
TimeSpan MaxTimeUsed
Maximum time to keep items that are being used.
Definition: Cache.cs:281
bool Ping(KeyType Key)
Pings an entry in the cache, to keep it from being removed.
Definition: Cache.cs:300
bool Remove(KeyType Key)
Removes an item from the cache.
Definition: Cache.cs:616
async Task< bool > RemoveAsync(KeyType Key)
Removes an item from the cache. Waits for the removal event to complete before returning.
Definition: Cache.cs:633
bool TryGetValue(KeyType Key, out ValueType Value)
Tries to get a value from the cache.
Definition: Cache.cs:311
int MaxItems
Maximum number of items in cache.
Definition: Cache.cs:275
void CopyTo(KeyValuePair< KeyType, ValueType >[] array, int arrayIndex)
Copies all items in the cache to an array.
Definition: Cache.cs:724
void Add(KeyValuePair< KeyType, ValueType > item)
Adds an item to the cache.
Definition: Cache.cs:704
IEnumerator< KeyValuePair< KeyType, ValueType > > GetEnumerator()
Gets an enumerator of contents in the cache.
Definition: Cache.cs:770
KeyType[] GetKeys()
Gets all available keys in the cache.
Definition: Cache.cs:366
ICollection< KeyType > Keys
Keys in cache.
Definition: Cache.cs:350
void Add(KeyType Key, ValueType Value)
Adds an item to the cache.
Definition: Cache.cs:446
void Clear()
Clears the cache.
Definition: Cache.cs:679
Cache(int MaxItems, TimeSpan MaxTimeUsed, TimeSpan MaxTimeUnused, bool Standalone)
Implements an in-memory cache.
Definition: Cache.cs:53
bool Contains(KeyValuePair< KeyType, ValueType > item)
Checks if an item (key and value) exists in the cache.
Definition: Cache.cs:714
TimeSpan MaxTimeUnused
Maximum time to keep items that are not being used.
Definition: Cache.cs:290
Repository of all active caches.
Definition: Caches.cs:11
Node referencing a chunk in a ChunkedList<T>
Definition: ChunkNode.cs:11
ChunkNode< T > Next
Next chunk
Definition: ChunkNode.cs:26
int Pos
Index after the last element in chunk.
Definition: ChunkNode.cs:51
int Start
Index of first element in chunk.
Definition: ChunkNode.cs:46
A chunked list is a linked list of chunks of objects of type T .
Definition: ChunkedList.cs:54
ChunkNode< T > FirstChunk
First chunk
Definition: ChunkedList.cs:259
void Add(T Item)
Adds an item to the collection.
Definition: ChunkedList.cs:272
Interface for caches.
Definition: ICache.cs:9
Definition: ImplTypes.g.cs:58
RemovedReason
Reason for removing the item.