1 /*******************************************************************************
2 
3     Cache class, caches raw data of either fixed or dynamic length
4 
5     Cache of raw data (ubyte[] / void[]) items of either fixed or variable
6     length. The cache is initialised with a fixed capacity (the number of items
7     that can be stored). When the cache reaches its full capacity, any newly
8     added items will replace older items in the cache. The cache keeps track of
9     the last time each item was written or read, and will replace the oldest
10     items first.
11 
12     The basic Cache template is used to store raw data. A second template exists
13     which takes a type as its parameter. This implements a thin wrapper around
14     the basic Cache, allowing the transparent storage of (no-op) serialized
15     values of the specified type.
16 
17 
18     Note that with variable value length anything stored in the cache is
19     invisible to the garbage collector because the internal value data type of
20     the cache is ubyte[]. So if you store references (objects, pointers, slices
21     to dynamic arrays) in the cache with variable value length, make sure your
22     application keeps a reference to it. Otherwise the object referenced to may
23     be garbage collected and attempting to use it after getting it from the
24     cache will make your program go to HELL.
25 
26 
27     When a cache element is removed explicitly (by calling remove()), the value
28     of the removed element is kept in the cache in a spare location. If required
29     it is possible to erase the value by overriding Cache.replaceRemovedItem(),
30     see the description of this method for an example.
31 
32     When a cache element is removed automatically if the cache is full and a new
33     element is added, Cache.whenCacheItemDropped(size_t index) will be called. If
34     required it is possible to be notified of this occurrence by overriding
35     Cache.whenCacheItemDropped method.
36 
37     Cache.createRaw() and Cache.getOrCreateRaw() return a reference to a record
38     value in the cache. Cache.getRaw() behaves like Cache.getOrCreateRaw() if
39     the record was found in the cache or returns null otherwise.
40     For fixed length values the record value reference is a slice to the record
41     value.
42     Usage example:
43 
44     ---
45 
46         import ocean.util.container.Cache;
47 
48         // Create a fixed-size cache which can store 2 items, each of length 3.
49         auto cache = new Cache!(3)(2);
50 
51         // Add an item using createRaw(). createRaw() returns a void[] array
52         // with length 3 which references the value in the cache.
53 
54         hash_t key = 0x12345678;
55 
56         char[3] val = "abc";
57 
58         cache.createRaw(key)[] = val[];
59 
60         // Obtain an item using getRaw(). If found, getRaw() returns a value
61         // slice just like createRaw() or null if not found.
62 
63         char[] val_got = cache.getRaw(key);
64 
65         if (val_got !is null)
66         {
67             // val_got contains the value that corresponds to key.
68             // The value in the cache can be modified in-place by setting array
69             // elements or copying to the whole array:
70 
71             (cast(char[])val_got)[2] = '!'; // Set the third value byte to '!'.
72 
73             (cast(char[])val_got)[] = "def"; // Set the value to "def".
74         }
75         else
76         {
77             // not found
78         }
79 
80     ---
81 
82     For variable length arrays it is a pointer to the Cache.Value struct which
83     encapsulates the value, which is void[], providing access to the value via
84     operator overloading:
85 
86         - opAssign sets the value array instance to an input array slice
87           (overwriting the previous array instance),
88         - opSliceAssign treats the value array as an allocated buffer and copies
89           the content of the an input array slice into the value array,
90         - opSlice returns the value array.
91 
92     opSliceAssign reuses an existing buffer and is therefore memory-friendly as
93     long as opAssign is not used with the same value instance.
94 
95     Rule of thumb: For each cache instance with variable value size use either
96     opAssign or opSliceAssign with the values, never both.
97 
98     Usage Example 1: Store string slices in a cache using Value.opAssign.
99 
100     ---
101 
102         auto cache = new Cache!()(100);
103 
104         char[] str1 = "Hello World",
105                str2 = "Eggs and Spam";
106 
107         {
108             auto val = cache.createRaw(4711);
109 
110             // Store a slice to str1 in the array using (*val).opAssign.
111 
112             *val = str1;
113         }
114 
115         {
116             auto val = cache.getRaw(4711);
117 
118             // (*val)[] ((*val).opSlice) now returns a slice to str1.
119 
120             // Replace this value with a slice to str2 using (*val).opAssign.
121 
122             *val = str2;
123         }
124 
125     ---
126 
127     Usage Example 2: Store copies of strings in a cache using
128                      Value.opSliceAssign.
129 
130     ---
131 
132         auto cache = new Cache!()(100);
133 
134         char[] str1 = "Hello World",
135                str2 = "Eggs and Spam";
136 
137         {
138             auto val = cache.createRaw(4711);
139 
140             // Allocate a value array buffer with str1.length and copy the
141             // content of str1 into that value buffer.
142 
143             (*val)[] = str1;
144         }
145 
146         {
147             auto val = cache.getRaw(4711);
148 
149             // (*val)[] ((*val).opSlice) now returns the value array buffer
150             // which contains a copy of the content of str1.
151 
152             // Use (*val)[] = str2 ((*val).opSliceAssign(x)) to resize the value
153             // array buffer to str2.length and copy the content of str2 into it.
154 
155             (*val)[] = str2;
156         }
157 
158     ---
159 
160     For special situations it is possible to obtain a pointer to the value
161     array. One such situation is when the value array needs to be modified by
162     an external function which doesn't know about the cache.
163 
164     ---
165 
166         void setValue ( ref void[] value )
167         {
168             value = "Hello World!";
169         }
170 
171         auto cache = new Cache!()(100);
172 
173         auto val = cache.createRaw(4711);
174 
175         void[]* val_array = cast (void[]*) (*s);
176 
177         setValue(*val_array);
178 
179     ---
180 
181     Link with:
182         -Llibebtree.a
183 
184     TODO:
185         Extend the cache by making values visible to the GC by default and
186         provide GC hiding as an option.
187 
188     Copyright:
189         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
190         All rights reserved.
191 
192     License:
193         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
194         Alternatively, this file may be distributed under the terms of the Tango
195         3-Clause BSD License (see LICENSE_BSD.txt for details).
196 
197 *******************************************************************************/
198 
199 module ocean.util.container.cache.Cache;
200 
201 
202 
203 
204 import ocean.util.container.cache.model.ICache;
205 import ocean.util.container.cache.model.ITrackCreateTimesCache;
206 import ocean.util.container.cache.model.Value;
207 import core.stdc.time: time_t;
208 import ocean.core.Test;
209 
210 import core.memory;
211 
212 debug import ocean.io.Stdout;
213 
214 debug (CacheTimes)
215 {
216     import ocean.core.Array: concat;
217     import core.stdc.stdio: FILE, fopen, fclose, fprintf, perror;
218     import core.sys.posix.time: ctime_r;
219 }
220 
221 version (unittest)
222 {
223     import ocean.core.Test;
224 }
225 
226 
227 /*******************************************************************************
228 
229     Evaluates to either ICache or ITrackCreateTimesCache, depending on
230     TrackCreateTimes.
231 
232 *******************************************************************************/
233 
234 template CacheBase ( bool TrackCreateTimes = false )
235 {
236     static if (TrackCreateTimes)
237     {
238         alias ITrackCreateTimesCache CacheBase;
239     }
240     else
241     {
242         alias ICache CacheBase;
243     }
244 }
245 
246 /*******************************************************************************
247 
248     Data cache class template. Stores items of raw data, either of fixed or
249     dynamic size.
250 
251     Params:
252         ValueSize = size of a data item. If 0 is specified (the default), the
253             items stored in the cache are of variable (dynamic) size
254         TrackCreateTimes = if true, each cache item is stored with its create
255             time, in addition to its last access time
256 
257 *******************************************************************************/
258 
259 class Cache ( size_t ValueSize = 0, bool TrackCreateTimes = false ) : CacheBase!(TrackCreateTimes)
260 {
261     import ocean.core.Verify;
262 
263     /***************************************************************************
264 
265         Mixin the type definition for the values.
266 
267     ***************************************************************************/
268 
269     mixin Value!(ValueSize);
270 
271     /***************************************************************************
272 
273         Cached item struct, storing a key and value.
274 
275     ***************************************************************************/
276 
277     struct CacheItem
278     {
279         hash_t key;
280         Value value;
281 
282         static if ( TrackCreateTimes )
283         {
284             time_t create_time;
285         }
286 
287         /***********************************************************************
288 
289             Copies value to this.value.
290 
291             Params:
292                 value = value to copy
293 
294             Returns:
295                 this.value
296 
297         ***********************************************************************/
298 
299         ValueRef setValue ( Value value )
300         {
301             static if ( is_dynamic )
302             {
303                 this.value = value;
304                 return &this.value;
305             }
306             else
307             {
308                 return this.value[] = value[];
309             }
310         }
311 
312         static if ( is_dynamic )
313         {
314             ValueRef value_ref ( )
315             {
316                 return &this.value;
317             }
318         }
319         else
320         {
321             ValueRef value_ref ( )
322             {
323                 return this.value[];
324             }
325         }
326     }
327 
328 
329     /***************************************************************************
330 
331         Array of cached items.
332 
333     ***************************************************************************/
334 
335     private CacheItem[] items;
336 
337 
338     /***************************************************************************
339 
340         Constructor.
341 
342         Params:
343             max_items = maximum number of items in the cache, set once, cannot
344                 be changed
345 
346     ***************************************************************************/
347 
348     public this ( size_t max_items )
349     {
350         super(max_items);
351 
352         this.items = new CacheItem[max_items];
353     }
354 
355     /***************************************************************************
356 
357         Creates an item in the cache and sets its create time. If the cache is
358         full, the oldest item is replaced with the new item. (In the case where
359         several items are equally old, the choice of which one to be replaced is
360         made arbitrarily.)
361 
362         Params:
363             key = item key
364 
365         Returns:
366             a reference to the value of the created item. If an existing item
367             was replaced, this reference refers to its current value, otherwise
368             it may refer to the value of a previously removed element.
369 
370         Out:
371             The returned reference is never null; for values of fixed size the
372             slice length is ValueSize.
373 
374     ***************************************************************************/
375 
376     public ValueRef createRaw ( hash_t key )
377     out (val)
378     {
379         static if (is_dynamic)
380         {
381             assert (val !is null);
382         }
383         else
384         {
385             assert (val.length == ValueSize);
386         }
387     }
388     do
389     {
390         bool existed;
391 
392         time_t access_time;
393 
394         with (*this.getOrAdd(key, existed, access_time))
395         {
396             static if ( TrackCreateTimes )
397             {
398                 create_time = access_time;
399             }
400 
401             return value_ref;
402         }
403     }
404 
405     /***************************************************************************
406 
407         Gets an item from the cache. If the item was found in the cache, its
408         access time is updated.
409 
410         Note that, if you change the value referenced by the returned reference,
411         the create time will not be updated.
412 
413         Params:
414             key = key to lookup
415 
416         Returns:
417             a reference to item value or null if no such item was found.
418 
419         Out:
420             For values of fixed size the slice length is ValueSize unless the
421             returned reference is null.
422 
423     ***************************************************************************/
424 
425     public ValueRef getRaw ( hash_t key )
426     out (val)
427     {
428         static if (!is_dynamic)
429         {
430             if (val !is null)
431             {
432                 assert (val.length == ValueSize);
433             }
434         }
435     }
436     do
437     {
438         time_t access_time;
439 
440         CacheItem* item = this.get__(key, access_time);
441 
442         return (item !is null)? item.value_ref : null;
443     }
444 
445     /***************************************************************************
446 
447         Gets an item from the cache or creates it if not already existing. If
448         the item was found in the cache, its access time is updated, otherwise
449         its create time is set.
450 
451         Note that the create time is set only if an item is created, not if it
452         already existed and you change the value referenced by the returned
453         reference.
454 
455         Params:
456             key         = key to lookup
457             existed     = true:  the item already existed,
458                           false: the item was created
459 
460         Returns:
461             a reference to the value of the obtained or created item. If an item
462             was created, the returned reference may refer to the value of a
463             previously removed element.
464 
465         Out:
466             The returned reference is never null; for values of fixed size the
467             slice length is ValueSize.
468 
469     ***************************************************************************/
470 
471     public ValueRef getOrCreateRaw ( hash_t key, out bool existed )
472     out (val)
473     {
474         static if (is_dynamic)
475         {
476             assert (val !is null);
477         }
478         else
479         {
480             assert (val.length == ValueSize);
481         }
482     }
483     do
484     {
485         time_t access_time;
486 
487         with (*this.getOrAdd(key, existed, access_time))
488         {
489             static if ( TrackCreateTimes ) if (!existed)
490             {
491                 create_time = access_time;
492             }
493 
494             return value_ref;
495         }
496     }
497 
498     /***************************************************************************
499 
500         Checks whether an item exists in the cache and returns its create time.
501 
502         Params:
503             key = key to lookup
504 
505         Returns:
506             item's create time, or 0 if the item doesn't exist
507 
508     ***************************************************************************/
509 
510     static if ( TrackCreateTimes )
511     {
512         public override time_t createTime ( hash_t key )
513         {
514             TimeToIndex.Node** node = key in this;
515 
516             return node? this.items[(*node).key.lo].create_time : 0;
517         }
518     }
519 
520     /***************************************************************************
521 
522         Obtains the key of the cache item corresponding to index.
523 
524         Params:
525             index = cache item index, guaranteed to be below length
526 
527         Returns:
528             cache item key
529 
530     ***************************************************************************/
531 
532     protected override hash_t keyByIndex ( size_t index )
533     {
534         verify (index <= this.length);
535 
536         return this.items[index].key;
537     }
538 
539     /***************************************************************************
540 
541         Called when a cache element is removed, replaces the cache items at
542         index "replaced" with the one at index "replace" by swapping the items.
543 
544         Unlike all other situations where indices and are used, "replaced" and
545         "replace" must be always valid, i.e. less than length.
546 
547         Note: A subclass may erase removed elements by overriding this method as
548               follows:
549 
550         ---
551         protected override hash_t replaceRemovedItem ( size_t replaced,
552                                                        size_t replace )
553         {
554             scope (success) this.items[replace] = this.items[replace].init;
555 
556             return (this.items[replaced] = this.items[replace]).key;
557         }
558         ---
559 
560         Params:
561             replaced = index of the cache item that is to be replaced
562             replace  = index of the cache item that will replace the replaced
563                        item
564 
565         Returns:
566             the key of the cache item that was at index "replace" before and is
567             at index "replaced" now.
568 
569         In:
570             "replaced" and "replace" must be different and be valid cache item
571             indices, i.e. less than this.length.
572 
573         Out:
574             The returned key must be the key of this.items[replaced].
575 
576     ***************************************************************************/
577 
578     protected override hash_t replaceRemovedItem ( size_t replaced, size_t replace )
579     out (key)
580     {
581         assert(key == this.items[replaced].key);
582     }
583     do
584     {
585         verify(replaced != replace);
586 
587         size_t length = this.length;
588 
589         verify(replaced < length);
590         verify(replace < length);
591 
592         CacheItem tmp       = this.items[replace];
593         this.items[replace] = this.items[replaced];
594 
595         return (this.items[replaced] = tmp).key;
596     }
597 
598     /***************************************************************************
599 
600         Obtains the cache item that corresponds to node and updates the access
601         time.
602         If realtime is enabled, time is expected to be equal to or
603         greater than the time stored in node. If disabled and the access time is
604         less, the node will not be updated and null returned.
605 
606 
607         Params:
608             node = time-to-index tree node
609             access_time = access time
610 
611         Returns:
612             the cache item or a null if realtime is disabled and the access time
613             is less than the access time in the node.
614 
615         Out:
616             If realtime is enabled, the returned pointer is never null.
617 
618     ***************************************************************************/
619 
620     protected CacheItem* access ( ref TimeToIndex.Node node, out time_t access_time )
621     out (item)
622     {
623         assert (item !is null);
624     }
625     do
626     {
627         return this.getItem(this.accessIndex(node, access_time));
628     }
629 
630     /***************************************************************************
631 
632         Obtains the cache item that corresponds to node and updates the access
633         time.
634         If realtime is enabled and key could be found, time is expected to be at
635         least the time value stored in node. If disabled and access_time is
636         less, the result is the same as if key could not be found.
637 
638 
639         Params:
640             key = time-to-index tree node key
641             access_time = access time
642 
643         Returns:
644             the corresponding cache item or null if key could not be found or
645             realtime is disabled and the access time is less than the access
646             time in the cache element.
647 
648     ***************************************************************************/
649 
650     protected CacheItem* get__ ( hash_t key, out time_t access_time )
651     {
652         return this.getItem(this.get_(key, access_time));
653     }
654 
655     /***************************************************************************
656 
657         Obtains the cache item that corresponds to index. Returns null if index
658         is length or greater.
659 
660         Params:
661             index = cache item index
662 
663         Returns:
664             the corresponding cache item or null if index is length or greater.
665 
666     ***************************************************************************/
667 
668     protected CacheItem* getItem ( size_t index )
669     {
670         return (index < this.length)? &this.items[index] : null;
671     }
672 
673     /***************************************************************************
674 
675         Gets an item from the cache or creates it if not already existing. If
676         the item was found in the cache, its access time is updated.
677 
678         Params:
679             key         = key to lookup
680             existed     = true:  the item already existed,
681                           false: the item was created
682 
683         Returns:
684             a pointer to the obtained or created item.
685 
686         Out:
687             The returned pointer is never null.
688 
689     ***************************************************************************/
690 
691     private CacheItem* getOrAdd ( hash_t key, out bool existed, out time_t access_time )
692     out (item)
693     {
694         assert (item !is null);
695     }
696     do
697     {
698         CacheItem* item = this.get__(key, access_time);
699 
700         existed = item !is null;
701 
702         return existed? item : this.add(key, access_time);
703     }
704 
705     /***************************************************************************
706 
707         Adds an item to the cache. If the cache is full, the oldest item will be
708         removed and replaced with the new item.
709 
710         Params:
711             key         = key of item
712             access_time = access time (also the create time)
713 
714         Returns:
715             the added cache item.
716 
717         Out:
718             The returned pointer is never null.
719 
720     ***************************************************************************/
721 
722     protected CacheItem* add ( hash_t key, out time_t access_time )
723     out (cache_item)
724     {
725         assert (cache_item !is null);
726     }
727     do
728     {
729         // Add key->item mapping
730 
731         CacheItem* cache_item = &this.items[this.register(key, access_time)];
732 
733         // Set the key in chosen element of items array.
734 
735         cache_item.key = key;
736 
737         return cache_item;
738     }
739 
740     /***************************************************************************
741 
742         Makes the GC scan the cache items. Should be called by the subclass
743         constructor if it stores values that contain GC references.
744         This method should be called after the constructor of this class has
745         returned.
746 
747     ***************************************************************************/
748 
749     static if (!is_dynamic)
750     {
751         protected void enableGcValueScanning ( )
752         {
753             verify(this.items !is null,
754                    "please call enableGcValueScanning() *after* the super
755                     constructor");
756 
757             GC.clrAttr(this.items.ptr, GC.BlkAttr.NO_SCAN);
758         }
759     }
760 
761     debug (CacheTimes)
762     {
763         /**********************************************************************
764 
765             String nul-termination buffer
766 
767         ***********************************************************************/
768 
769         private char[] nt_buffer;
770 
771         /**********************************************************************
772 
773             Writes the access times and the number of records with that time to
774             a file, appending to that file.
775 
776         ***********************************************************************/
777 
778         void dumpTimes ( char[] filename, time_t now )
779         {
780             FILE* f = fopen(this.nt_buffer.concat(filename, "\0").ptr, "a");
781 
782             if (f)
783             {
784                 scope (exit) fclose(f);
785 
786                 char[26] buf;
787 
788                 fprintf(f, "> %10u %s", now, ctime_r(&now, buf.ptr));
789 
790                 TimeToIndex.Mapping mapping = this.time_to_index.firstMapping;
791 
792                 if (mapping)
793                 {
794                     time_t t = mapping.key;
795 
796                     uint n = 0;
797 
798                     foreach (time_t u; this.time_to_index)
799                     {
800                         if (t == u)
801                         {
802                             n++;
803                         }
804                         else
805                         {
806                             fprintf(f, "%10u %10u\n", t, n);
807                             t = u;
808                             n = 0;
809                         }
810                     }
811                 }
812             }
813             else
814             {
815                 perror(this.nt_buffer.concat("unable to open \"", filename, "\"\0").ptr);
816             }
817         }
818     }
819 }
820 
821 /*******************************************************************************
822 
823     Typed cache class template. Stores items of a particular type.
824 
825     Params:
826         T = type of item to store in cache
827         TrackCreateTimes = if true, each cache item is stored with its create
828             time, in addition to its last access time
829 
830 *******************************************************************************/
831 
832 class Cache ( T, bool TrackCreateTimes = false ) : Cache!(T.sizeof, TrackCreateTimes)
833 {
834     /***************************************************************************
835 
836         Constructor.
837 
838         Params:
839             max_items = maximum number of items in the cache, set once, cannot
840                 be changed
841 
842     ***************************************************************************/
843 
844     public this ( size_t max_items )
845     {
846         super(max_items);
847     }
848 
849     /***************************************************************************
850 
851         Puts an item into the cache. If the cache is full, the oldest item is
852         replaced with the new item. (In the case where several items are equally
853         old, the choice of which one to be replaced is made arbitrarily.)
854 
855         Params:
856             key   = item key
857             value = item to store in cache
858 
859         Returns:
860             true if a record was updated / overwritten, false if a new record
861             was added
862 
863     ***************************************************************************/
864 
865     public bool put ( hash_t key, T value )
866     {
867         bool existed;
868 
869         T* dst = this.getOrCreate(key, existed);
870 
871         if (dst)
872         {
873             *dst = value;
874         }
875 
876         return existed;
877     }
878 
879     /***************************************************************************
880 
881         Creates a cache item and sets the create time. If the cache is full, the
882         oldest item is replaced with the new item. (In the case where several
883         items are equally old, the choice of which one to be replaced is made
884         arbitrarily.)
885 
886         Params:
887             key   = item key
888 
889         Returns:
890             true if a record was updated / overwritten, false if a new record
891             was added
892 
893     ***************************************************************************/
894 
895     public T* create ( hash_t key )
896     out (val)
897     {
898         assert (val !is null);
899     }
900     do
901     {
902         return cast (T*) this.createRaw(key)[0 .. T.sizeof].ptr;
903     }
904 
905     /***************************************************************************
906 
907         Gets an item from the cache. A pointer to the item is returned, if
908         found. If the item exists in the cache, its update time is updated.
909 
910         Note that, if the item already existed and you change the value pointed
911         to by the returned pointer, the create time will not be updated.
912 
913         Params:
914             key = key to lookup
915 
916         Returns:
917             pointer to item value, may be null if key not found
918 
919      ***************************************************************************/
920 
921     public T* get ( hash_t key )
922     {
923         return cast (T*) this.getRaw(key);
924     }
925 
926 
927     /***************************************************************************
928 
929         Gets an item from the cache or creates it if not already existing. If
930         the item exists in the cache, its update time is updated, otherwise its
931         create time is set.
932 
933         Note that the create time is set only if an item is created, not if it
934         already existed and you change the value referenced by the returned
935         pointer.
936 
937         Params:
938             key         = key to lookup
939             existed     = true:  the item already existed,
940                           false: the item was created
941 
942         Returns:
943             pointer to item value
944 
945     ***************************************************************************/
946 
947     public T* getOrCreate ( hash_t key, out bool existed )
948     out (val)
949     {
950         assert (val !is null);
951     }
952     do
953     {
954         return cast (T*) this.getOrCreateRaw(key, existed)[0 .. T.sizeof].ptr;
955     }
956 }
957 
958 /*******************************************************************************
959 
960     Unit test
961 
962 *******************************************************************************/
963 
964 version (unittest)
965 {
966     import core.sys.posix.stdlib: srand48, mrand48, drand48;
967     import core.sys.posix.unistd: getpid;
968     import core.stdc.time: time;
969     import ocean.io.Stdout;
970     import ocean.core.Array: shuffle;
971 }
972 
973 unittest
974 {
975     srand48(time(null)+getpid());
976 
977     static ulong ulrand ( )
978     {
979         return (cast (ulong) mrand48() << 0x20) | cast (uint) mrand48();
980     }
981 
982     time_t time = 234567;
983 
984     // ---------------------------------------------------------------------
985     // Test of static sized cache
986 
987     version (all)
988     {{
989         static immutable n_records  = 33,
990               capacity   = 22,
991               n_overflow = 7;
992 
993         static assert (n_records >= capacity,
994                        "Number of records smaller than capacity!");
995 
996         struct Record
997         {
998             hash_t  key; // random number
999             size_t  val; // counter
1000         }
1001 
1002         // Initialise the list of records.
1003 
1004         Record[n_records] records;
1005 
1006         foreach (i, ref record; records)
1007         {
1008             record = Record(ulrand(), i);
1009         }
1010 
1011         // Populate the cache to the limit.
1012 
1013         time_t t = 0;
1014 
1015         scope cache = new class Cache!(size_t)
1016         {
1017             this ( ) {super(capacity);}
1018 
1019             override time_t now ( ) {return ++t;}
1020         };
1021 
1022         test!("==")(capacity, cache.max_length,
1023                 "Max length of cache does not equal configured capacity!");
1024 
1025         foreach (record; records[0 .. cache.max_length])
1026         {
1027             cache.put(record.key, record.val);
1028         }
1029 
1030         // Shuffle records and count how many of the first n_overflow of the
1031         // shuffled records are in the cache. If either all or none of these are
1032         // in the cache, shuffle and try again.
1033 
1034         uint n_existing;
1035 
1036         do
1037         {
1038             n_existing = 0;
1039             foreach (i, record; records.shuffle(drand48)[0 .. n_overflow])
1040             {
1041                 n_existing += cache.exists(record.key);
1042             }
1043         }
1044         while (!n_existing || n_existing == n_overflow);
1045 
1046         test (n_existing > 0 && n_existing < n_overflow, "n_existing has unexpected value");
1047 
1048         // Get the shuffled records from the cache and verify them. Record the
1049         // keys of the first n_overflow existing records which will get the
1050         // least (oldest) access time by cache.getItem() and therefore be the
1051         // first records to be removed on a cache overflow.
1052 
1053         hash_t[n_overflow] oldest_keys;
1054 
1055         {
1056             uint i = 0;
1057 
1058             foreach (record; records)
1059             {
1060                 auto v = cache.get(record.key);
1061 
1062                 if (record.val < cache.max_length)
1063                 {
1064                     test!("!is") (v, null);
1065                     test!("==") (*v, record.val);
1066 
1067                     if (i < n_overflow)
1068                     {
1069                         oldest_keys[i++] = record.key;
1070                     }
1071                 }
1072                 else
1073                 {
1074                     test!("is") (v, null);
1075                 }
1076             }
1077 
1078             test!("==") (i, n_overflow);
1079         }
1080 
1081         test!("==") (t, cache.max_length * 2);
1082 
1083         // Put the first n_overflow shuffled records so that the cache will
1084         // overflow.
1085         // Existing records should be updated to a new value. To enable
1086         // verification of the update, change the values to 4711 + i.
1087 
1088         foreach (i, ref record; records[0 .. n_overflow])
1089         {
1090             record.val = 4711 + i;
1091 
1092             cache.put(record.key, record.val);
1093         }
1094 
1095         test!("==") (t, cache.max_length * 2 + n_overflow);
1096 
1097         // Verify the records.
1098 
1099         foreach (i, record; records[0 .. n_overflow])
1100         {
1101             auto v = cache.get(record.key);
1102 
1103             test!("!is") (v, null);
1104             test!("==") (*v, 4711 + i);
1105         }
1106 
1107         test!("==") (t, cache.max_length * 2 + n_overflow * 2);
1108 
1109         // oldest_keys[n_existing .. $] should have been removed from the
1110         // cache due to cache overflow.
1111 
1112         foreach (key; oldest_keys[n_existing .. $])
1113         {
1114             auto v = cache.get(key);
1115 
1116             test!("is") (v, null);
1117         }
1118 
1119         // cache.get should not have evaluated the lazy ++t.
1120 
1121         test!("==") (t, cache.max_length * 2 + n_overflow * 2);
1122 
1123         // Verify that all other records still exist in the cache.
1124 
1125         {
1126             uint n = 0;
1127 
1128             foreach (record; records[n_overflow .. $])
1129             {
1130                 auto v = cache.get(record.key);
1131 
1132                 if (v !is null)
1133                 {
1134                     test!("==") (*v, record.val);
1135 
1136                     n++;
1137                 }
1138             }
1139 
1140             test!("==") (n, cache.max_length - n_overflow);
1141         }
1142 
1143         test!("==") (t, cache.max_length * 3 + n_overflow);
1144     }}
1145     else
1146     {
1147         struct Data
1148         {
1149             int x;
1150         }
1151 
1152         scope static_cache = new Cache!(Data)(2);
1153 
1154         with (static_cache)
1155         {
1156             test!("==")(length, 0);
1157 
1158             {
1159                 bool replaced = put(1, time, Data(23));
1160 
1161                 test(!replaced);
1162 
1163                 test!("==")(length, 1);
1164                 test(exists(1));
1165 
1166                 Data* item = get(1, time);
1167                 test!("!is")(item, null);
1168                 test!("==")(item.x, 23);
1169             }
1170 
1171             {
1172                 bool replaced = put(2, time + 1, Data(24));
1173 
1174                 test(!replaced);
1175 
1176                 test!("==")(length, 2);
1177                 test(exists(2));
1178 
1179                 Data* item = get(2, time + 1);
1180                 test!("!is")(item, null);
1181                 test!("==")(item.x, 24);
1182             }
1183 
1184             {
1185                 bool replaced = put(2, time + 1, Data(25));
1186 
1187                 test(replaced);
1188 
1189                 test!("==")(length, 2);
1190                 test(exists(2));
1191 
1192                 Data* item = get(2, time + 1);
1193                 test!("!is")(item, null);
1194                 test!("==")(item.x, 25);
1195             }
1196 
1197             {
1198                 bool replaced = put(3, time + 2, Data(26));
1199 
1200                 test(!replaced);
1201 
1202                 test!("==")(length, 2);
1203                 test(!exists(1));
1204                 test(exists(2));
1205                 test(exists(3));
1206 
1207                 Data* item = get(3, time + 2);
1208                 test!("!is")(item);
1209                 test!("==")(item.x, 26);
1210             }
1211 
1212             {
1213                 bool replaced = put(4, time + 3, Data(27));
1214 
1215                 test(!replaced);
1216 
1217                 test!("==")(length, 2);
1218                 test(!exists(1));
1219                 test(!exists(2));
1220                 test(exists(3));
1221                 test(exists(4));
1222 
1223                 Data* item = get(4, time + 3);
1224                 test!("!is")(item);
1225                 test!("==")(item.x, 27);
1226             }
1227 
1228             clear();
1229             test!("==")(length, 0);
1230             test(!exists(1));
1231             test!("is")(get(1, time), null);
1232             test(!exists(2));
1233             test!("is")(get(2, time), null);
1234             test(!exists(3));
1235             test!("is")(get(3, time), null);
1236             test(!exists(4));
1237             test!("is")(get(4, time), null);
1238 
1239             {
1240                 bool replaced = put(1, time, Data(23));
1241 
1242                 test(!replaced);
1243 
1244                 test!("==")(length, 1);
1245                 test(exists(1));
1246 
1247                 Data* item = get(1, time);
1248                 test!("!is")(item);
1249                 test!("==")(item.x, 23);
1250             }
1251 
1252             {
1253                 bool replaced = put(2, time + 1, Data(24));
1254 
1255                 test(!replaced);
1256 
1257                 test!("==")(length, 2);
1258                 test(exists(2));
1259 
1260                 Data* item = get(2, time + 1);
1261                 test!("!is")(item);
1262                 test!("==")(item.x, 24);
1263             }
1264 
1265             remove(1);
1266             test!("==")(length, 1);
1267             test(!exists(1));
1268             test(exists(2));
1269         }
1270     }
1271 
1272     // ---------------------------------------------------------------------
1273     // Test of dynamic sized cache
1274 
1275     {
1276         ubyte[] data1 = cast(ubyte[])"hello world";
1277         ubyte[] data2 = cast(ubyte[])"goodbye world";
1278         ubyte[] data3 = cast(ubyte[])"hallo welt";
1279 
1280         scope dynamic_cache = new class Cache!()
1281         {
1282             this ( ) {super(2);}
1283 
1284             time_t now_sec ( ) {return ++time;}
1285         };
1286 
1287         test!("==")(dynamic_cache.length, 0);
1288 
1289         version (all)
1290         {
1291             *dynamic_cache.createRaw(1) = data1;
1292             {
1293                 auto val = dynamic_cache.getRaw(1);
1294                 test!("!is")(val, null);
1295                 test!("==")((*val)[], data1);
1296             }
1297 
1298             *dynamic_cache.createRaw(2) = data2;
1299             {
1300                 auto val = dynamic_cache.getRaw(1);
1301                 test!("!is")(val, null);
1302                 test!("==")((*val)[], data1);
1303             }
1304             {
1305                 auto val = dynamic_cache.getRaw(2);
1306                 test!("!is")(val, null);
1307                 test!("==")((*val)[], data2);
1308             }
1309 
1310             *dynamic_cache.createRaw(3) = data3;
1311             test!("is")(dynamic_cache.getRaw(1), null);
1312             {
1313                 auto val = dynamic_cache.getRaw(2);
1314                 test!("!is")(val, null);
1315                 test!("==")((*val)[], data2);
1316             }
1317             {
1318                 auto val = dynamic_cache.getRaw(3);
1319                 test!("!is")(val, null);
1320                 test!("==")((*val)[], data3);
1321             }
1322 
1323             dynamic_cache.clear;
1324             test!("==")(dynamic_cache.length, 0);
1325             test!("is")(dynamic_cache.getRaw(1), null);
1326             test!("is")(dynamic_cache.getRaw(2), null);
1327             test!("is")(dynamic_cache.getRaw(3), null);
1328 
1329             *dynamic_cache.createRaw(1) = data1;
1330             test!("==")(dynamic_cache.length, 1);
1331             {
1332                 auto val = dynamic_cache.getRaw(1);
1333                 test!("!is")(val, null);
1334                 test!("==")((*val)[], data1);
1335             }
1336 
1337             *dynamic_cache.createRaw(2) = data2;
1338             test!("==")(dynamic_cache.length, 2);
1339             {
1340                 auto val = dynamic_cache.getRaw(2);
1341                 test!("!is")(val, null);
1342                 test!("==")((*val)[], data2);
1343             }
1344 
1345             dynamic_cache.remove(1);
1346             test!("==")(dynamic_cache.length, 1);
1347             test!("is")(dynamic_cache.getRaw(1), null);
1348             {
1349                 auto val = dynamic_cache.getRaw(2);
1350                 test!("!is")(val, null);
1351                 test!("==")((*val)[], data2);
1352             }
1353         }
1354         else
1355         {
1356             dynamic_cache.putRaw(1, data1);
1357             test(dynamic_cache.exists(1));
1358             test!("==")((*dynamic_cache.getRaw(1))[], data1);
1359 
1360             dynamic_cache.putRaw(2, data2);
1361             test(dynamic_cache.exists(1));
1362             test(dynamic_cache.exists(2));
1363             test!("==")((*dynamic_cache.getRaw(1))[], data1);
1364             test!("==")((*dynamic_cache.getRaw(2))[], data2);
1365 
1366             dynamic_cache.putRaw(3, data3);
1367             test(!dynamic_cache.exists(1));
1368             test(dynamic_cache.exists(2));
1369             test(dynamic_cache.exists(3));
1370             test!("==")((*dynamic_cache.getRaw(2))[], data2);
1371             test!("==")((*dynamic_cache.getRaw(3))[], data3);
1372 
1373             dynamic_cache.clear;
1374             test!("==")(dynamic_cache.length, 0);
1375             test(!dynamic_cache.exists(1));
1376             test(!dynamic_cache.exists(2));
1377             test(!dynamic_cache.exists(3));
1378 
1379             dynamic_cache.putRaw(1, data1);
1380             test!("==")(dynamic_cache.length, 1);
1381             test(dynamic_cache.exists(1));
1382             test!("==")((*dynamic_cache.getRaw(1))[], data1);
1383 
1384             dynamic_cache.putRaw(2, data2);
1385             test!("==")(dynamic_cache.length, 2);
1386             test(dynamic_cache.exists(2));
1387             test!("==")((*dynamic_cache.getRaw(2))[], data2);
1388 
1389             dynamic_cache.remove(1);
1390             test!("==")(dynamic_cache.length, 1);
1391             test(!dynamic_cache.exists(1));
1392             test(dynamic_cache.exists(2));
1393         }
1394     }
1395 }
1396 
1397 
1398 
1399 
1400 /*******************************************************************************
1401 
1402     Performance test
1403 
1404 *******************************************************************************/
1405 
1406 debug ( OceanPerformanceTest )
1407 {
1408     import ocean.core.Memory;
1409 
1410     import ocean.math.random.Random;
1411 
1412     import ocean.time.StopWatch;
1413 
1414     import ocean.io.Stdout : Stderr;
1415 
1416     unittest
1417     {
1418         GC.disable;
1419 
1420         Stderr.formatln("Starting Cache performance test");
1421 
1422         auto random = new Random;
1423 
1424         static immutable cache_size = 100_000;
1425 
1426         static immutable max_item_size = 1024 * 4;
1427 
1428         StopWatch sw;
1429 
1430         auto cache = new Cache!()(cache_size);
1431 
1432         ubyte[] value;
1433         value.length = max_item_size;
1434 
1435         time_t time = 1;
1436 
1437         // Fill cache
1438         Stderr.formatln("Filling cache:");
1439         sw.start;
1440         for ( uint i; i < cache_size; i++ )
1441         {
1442             cache.put(i, time, value);
1443             ubyte d_time;
1444             random(d_time);
1445             time += d_time % 16;
1446         }
1447         Stderr.formatln("{} puts, {} puts/s", cache_size, cast(float)cache_size / (cast(float)sw.microsec / 1_000_000));
1448 
1449         // Put values into full cache
1450         static immutable puts = 1_000_000;
1451         Stderr.formatln("Writing to cache:   ");
1452         sw.start;
1453         for ( uint i; i < puts; i++ )
1454         {
1455             cache.put(i % cache_size, time, value);
1456             ubyte d_time;
1457             random(d_time);
1458             time += d_time % 16;
1459         }
1460         Stderr.formatln("{} puts, {} puts/s", puts, cast(float)puts / (cast(float)sw.microsec / 1_000_000));
1461 
1462         // Get values from cache
1463         static immutable gets = 1_000_000;
1464         Stderr.formatln("Reading from cache: {} gets, {} gets/s", gets, cast(float)gets / (cast(float)sw.microsec / 1_000_000));
1465         sw.start;
1466         for ( uint i; i < gets; i++ )
1467         {
1468             cache.get(i % cache_size, time);
1469             ubyte d_time;
1470             random(d_time);
1471             time += d_time % 16;
1472         }
1473         Stderr.formatln("Writing to cache: {} gets, {} gets/s", gets, cast(float)gets / (cast(float)sw.microsec / 1_000_000));
1474 
1475         Stderr.formatln("Cache performance test finished");
1476     }
1477 }
1478 
1479 version (CacheTest) void main ( ) { }
1480 
1481 
1482 unittest
1483 {
1484     class CacheImpl: Cache!()
1485     {
1486         private bool* item_dropped;
1487         private size_t* index;
1488 
1489         public this (size_t max_items, bool* item_dropped)
1490         {
1491             super(max_items);
1492             this.item_dropped = item_dropped;
1493         }
1494 
1495         protected override void whenCacheItemDropped ( size_t index )
1496         {
1497             *item_dropped = true;
1498         }
1499     }
1500 
1501 
1502    // Test if the whenCacheItemDropped is being called
1503     static immutable max_items = 10;
1504     auto item_dropped = false;
1505     size_t index = 0;
1506 
1507     auto cache = new CacheImpl( max_items, &item_dropped );
1508 
1509     for(int i = 0; i < max_items * 2; i++)
1510     {
1511         auto data = cache.createRaw(i);
1512 
1513         if(i > max_items - 1)
1514         {
1515             // Test if it is being called
1516             test(item_dropped);
1517 
1518             // Test the next case
1519             item_dropped = false;
1520         }
1521         else
1522         {
1523             test(!item_dropped);
1524         }
1525     }
1526 }