1 ///
2 module cachetools.cache2q;
3 
4 /// Implements Q2 cache
5 /// http://www.vldb.org/conf/1994/P439.PDF
6 
7 private import std.experimental.allocator;
8 private import std.experimental.allocator.mallocator : Mallocator;
9 private import core.stdc.time;
10 private import std.typecons;
11 
12 private import cachetools.internal;
13 private import cachetools.interfaces;
14 private import cachetools.containers.hashmap;
15 private import cachetools.containers.lists;
16 
17 /* Pseudocode from the paper
18 // If there is space, we give it to X.
19 // If there is no space, we free a page slot to
20 // make room for page X.
21 reclaimfor(page X)
22     begin
23         if there are free page slots then
24             put X into a free page slot
25         else if( |Alin| > Kin)
26             page out the tail of Alin, call it Y
27             add identifier of Y to the head of Alout
28             if(]Alout] >Kout)
29                 remove identifier of Z from
30                 the tail of Alout
31             end if
32             put X into the reclaimed page slot
33         else
34             page out the tail of Am, call it Y
35             // do not put it on Alout; it hasn’t been
36             // accessed for a while
37             put X into the reclaimed page slot
38         end if
39 end
40 
41 On accessing a page X :
42 begin
43     if X is in Am then
44         move X to the head of Am
45     else if (X is in Alout) then
46         reclaimfor(Х)
47         add X to the head of Am
48     else if (X is in Alin) // do nothing
49     else // X is in no queue
50         reclaimfor(X)
51         add X to the head of Alin
52     end if
53 end 
54 */
55 /**
56     2Q cache is variant of multi-level LRU cache. Original paper http://www.vldb.org/conf/1994/P439.PDF
57     It is adaptive, scan-resistant and can give more hits than plain LRU.
58     $(P This cache consists from three parts (In, Out and Main) where 'In' receive all new elements, 'Out' receives all
59     overflows from 'In', and 'Main' is LRU cache which hold all long-lived data.)
60 **/
61 class Cache2Q(K, V, Allocator=Mallocator)
62 {
63     private
64     {
65         struct ListElement {
66             StoredType!K        key;    // we keep key here so we can remove element from map when we evict with LRU or TTL)
67         }
68         alias ListType = CompressedList!(ListElement, Allocator);
69         alias ListElementPtrType = ListType.NodePointer;
70         alias DListType = DList!(ListElement, Allocator);
71         alias DListElementPtrType = DListType.Node!ListElement*;
72 
73         struct MapElement
74         {
75             StoredType!V        value;
76             ListElementPtrType  list_element_ptr;
77             time_t              expired_at;
78         }
79         struct MainMapElement
80         {
81             StoredType!V         value;
82             DListElementPtrType  list_element_ptr;
83             time_t               expired_at;
84         }
85 
86         int _kin, _kout, _km;
87 
88         CompressedList!(ListElement, Allocator)     _InList;
89         CompressedList!(ListElement, Allocator)     _OutList;
90         DList!(ListElement, Allocator)              _MainList;
91 
92         HashMap!(K, MapElement, Allocator)          _InMap;
93         HashMap!(K, MapElement, Allocator)          _OutMap;
94         HashMap!(K, MainMapElement, Allocator)      _MainMap;
95 
96         time_t                                      _ttl; // global ttl (if > 0)
97 
98         bool                                        __reportCacheEvents;
99         SList!(CacheEvent!(K, V), Allocator)        __events; // unbounded list of cache events
100 
101     }
102     final this() @safe {
103         _InMap.grow_factor(4);
104         _OutMap.grow_factor(4);
105         _MainMap.grow_factor(4);
106     }
107     final this(int s) @safe {
108         _kin = s/6;
109         _kout = s/6;
110         _km = 2*s/3;
111         _InMap.grow_factor(4);
112         _OutMap.grow_factor(4);
113         _MainMap.grow_factor(4);
114     }
115     ///
116     /// Set total cache size. 'In' and 'Out' gets 1/6 of total size, Main gets 2/3 of size.
117     ///
118     final auto size(uint s) @safe
119     {
120         _kin =  1*s/6;
121         _kout = 1*s/6;
122         _km =   4*s/6;
123         return this;
124     }
125     ///
126     /// Set In queue size
127     ///
128     final auto sizeIn(uint s) @safe
129     {
130         _kin =  s;
131         return this;
132     }
133 
134     ///
135     /// Set Out queue size
136     ///
137     final auto sizeOut(uint s) @safe
138     {
139         _kout =  s;
140         return this;
141     }
142 
143     ///
144     /// Set Main queue size
145     ///
146     final auto sizeMain(uint s) @safe
147     {
148         _km =  s;
149         return this;
150     }
151 
152     ///
153     /// Number of elements in cache.
154     ///
155     final int length() @safe
156     {
157         return _InMap.length + _OutMap.length + _MainMap.length;
158     }
159     ///
160     /// Drop all elements from cache.
161     ///
162     final void clear() @safe
163     {
164         _InList.clear();
165         _OutList.clear();
166         _MainList.clear();
167         if ( __reportCacheEvents ) {
168             foreach(p; _InMap.byPair) {
169                 __events.insertBack(CacheEvent!(K, V)(EventType.Removed, p.key, p.value.value));
170             }
171             foreach(p; _OutMap.byPair){
172                 __events.insertBack(CacheEvent!(K, V)(EventType.Removed, p.key, p.value.value));
173             }
174             foreach(p; _MainMap.byPair){
175                 __events.insertBack(CacheEvent!(K, V)(EventType.Removed, p.key, p.value.value));
176             }
177         }
178         _InMap.clear();
179         _OutMap.clear();
180         _MainMap.clear();
181     }
182     ///
183     /// Set default ttl (seconds)
184     ///
185     final void ttl(time_t v) @safe 
186     {
187         _ttl = v;
188     }
189     ///
190     auto enableCacheEvents() pure nothrow @safe @nogc
191     {
192         __reportCacheEvents = true;
193         return this;
194     }
195     ///
196     final auto cacheEvents() @safe nothrow
197     {
198         auto r = CacheEventRange!(K, V)(__events);
199         __events.clear;
200         return r;
201     }
202 
203     struct CacheEventRange(K, V)
204     {
205 
206         private SList!(CacheEvent!(K, V), Allocator) __events;
207 
208         void opAssign(CacheEventRange!(K, V) other) @safe
209         {
210             __events.clear();
211             __events = other.__events;
212         }
213 
214         this(ref SList!(CacheEvent!(K, V), Allocator) events) @safe
215         {
216             __events = events;
217         }
218 
219         bool empty() @safe const nothrow pure
220         {
221             return __events.empty();
222         }
223 
224         void popFront() @safe nothrow
225         {
226             __events.popFront();
227         }
228 
229         auto front() @safe
230         {
231             return __events.front();
232         }
233 
234         auto length() pure const nothrow @safe
235         {
236             return __events.length;
237         }
238         auto save() @safe {
239             return CacheEventRange!(K, V)(__events);
240         }
241     }
242     ///
243     /// Get element from cache.
244     ///
245     final Nullable!V get(K k) @safe
246     {
247         debug(cachetools) safe_tracef("get %s", k);
248 
249         MainMapElement* keyInMain = k in _MainMap;
250         if ( keyInMain )
251         {
252             debug(cachetools) safe_tracef("%s in main cache: %s", k, *keyInMain);
253             if ( keyInMain.expired_at > 0 && keyInMain.expired_at <= time(null) ) 
254             {
255                 // expired
256                 if (__reportCacheEvents)
257                 {
258                     __events.insertBack(CacheEvent!(K, V)(EventType.Expired, k, keyInMain.value));
259                 }
260                 _MainList.remove(keyInMain.list_element_ptr);
261                 _MainMap.remove(k);
262                 return Nullable!V();
263             }
264             _MainList.move_to_head(keyInMain.list_element_ptr);
265             return Nullable!V(keyInMain.value);
266         }
267         debug(cachetools) safe_tracef("%s not in main cache", k);
268 
269         auto keyInOut = k in _OutMap;
270         if ( keyInOut )
271         {
272             debug(cachetools) safe_tracef("%s in A1Out cache: %s", k, *keyInOut);
273             if (keyInOut.expired_at > 0 && keyInOut.expired_at <= time(null))
274             {
275                 // expired
276                 if (__reportCacheEvents)
277                 {
278                     __events.insertBack(CacheEvent!(K, V)(EventType.Expired, k, keyInOut.value));
279                 }
280                 () @trusted {
281                     _OutList.remove(keyInOut.list_element_ptr);
282                 }();
283                 _OutMap.remove(k);
284                 return Nullable!V();
285             }
286             // move from Out to Main
287             auto value = keyInOut.value;
288             auto expired_at = keyInOut.expired_at;
289 
290             () @trusted
291             {
292                 assert((*keyInOut.list_element_ptr).key == k);
293                 _OutList.remove(keyInOut.list_element_ptr);
294             }();
295 
296             bool removed = _OutMap.remove(k);
297             assert(removed);
298             debug(cachetools) safe_tracef("%s removed from A1Out cache", k);
299 
300             auto mlp = _MainList.insertFront(ListElement(k));
301             _MainMap.put(k, MainMapElement(value, mlp, expired_at));
302             debug(cachetools) safe_tracef("%s placed to Main cache", k);
303             if ( _MainList.length > _km )
304             {
305                 debug(cachetools) safe_tracef("Main cache overflowed, pop %s", _MainList.tail().key);
306                 if (__reportCacheEvents)
307                 {
308                     auto key_to_evict = _MainList.tail().key;
309                     auto mptr = key_to_evict in _MainMap;
310                     __events.insertBack(CacheEvent!(K, V)(EventType.Evicted, key_to_evict, mptr.value));
311                 }
312                 _MainMap.remove(_MainList.tail().key);
313                 _MainList.popBack();
314             }
315             return Nullable!V(value);
316         }
317         debug(cachetools) safe_tracef("%s not in Out cache", k);
318 
319         auto keyInIn = k in _InMap;
320         if ( keyInIn )
321         {
322             debug(cachetools) safe_tracef("%s in In cache", k);
323             if (keyInIn.expired_at > 0 && keyInIn.expired_at <= time(null))
324             {
325                 // expired
326                 if (__reportCacheEvents) {
327                     __events.insertBack(CacheEvent!(K, V)(EventType.Expired, k, keyInIn.value));
328                 }
329                 () @trusted {
330                     _InList.remove(keyInIn.list_element_ptr);
331                 }();
332                 _InMap.remove(k);
333                 return Nullable!V();
334             }
335             // just return value
336             return Nullable!V(keyInIn.value);
337         }
338         debug(cachetools) safe_tracef("%s not in In cache", k);
339 
340         return Nullable!V();
341     }
342 
343     ///
344     /// Put element to cache.
345     ///
346     /// Evict something if we have to.
347     ///
348     final PutResult put(K k, V v, TTL ttl = TTL()) @safe
349     out
350     {
351         assert(__result != PutResult(PutResultFlag.None));
352     }
353     do
354     {
355         time_t exp_time;
356 
357         if ( _ttl > 0 && ttl.useDefault  ) {
358             exp_time = time(null) + _ttl;
359         }
360 
361         if ( ttl.value > 0 ) {
362             exp_time = time(null) + ttl.value;
363         }
364 
365         auto keyInMain = k in _MainMap;
366         if ( keyInMain )
367         {
368             if ( __reportCacheEvents ) {
369                 __events.insertBack(CacheEvent!(K, V)(EventType.Updated, k, keyInMain.value));
370             }
371             keyInMain.value = v;
372             keyInMain.expired_at = exp_time;
373             debug(cachetools) safe_tracef("%s in Main cache", k);
374             return PutResult(PutResultFlag.Replaced);
375         }
376         debug(cachetools) safe_tracef("%s not in Main cache", k);
377 
378         auto keyInOut = k in _OutMap;
379         if ( keyInOut )
380         {
381             if ( __reportCacheEvents ) {
382                 __events.insertBack(CacheEvent!(K, V)(EventType.Updated, k, keyInOut.value));
383             }
384             keyInOut.value = v;
385             keyInOut.expired_at = exp_time;
386             debug(cachetools) safe_tracef("%s in Out cache", k);
387             return PutResult(PutResultFlag.Replaced);
388         }
389         debug(cachetools) safe_tracef("%s not in Out cache", k);
390 
391         auto keyInIn = k in _InMap;
392         if ( keyInIn )
393         {
394             if ( __reportCacheEvents ) {
395                 __events.insertBack(CacheEvent!(K, V)(EventType.Updated, k, keyInIn.value));
396             }
397             keyInIn.value = v;
398             keyInIn.expired_at = exp_time;
399             debug(cachetools) safe_tracef("%s in In cache", k);
400             return PutResult(PutResultFlag.Replaced);
401         }
402         else
403         {
404             debug(cachetools) safe_tracef("insert %s in A1InFifo", k);
405             auto lp = _InList.insertBack(ListElement(k));
406             _InMap.put(k, MapElement(v, lp, exp_time));
407             if ( _InList.length <= _kin )
408             {
409                 return PutResult(PutResultFlag.Inserted);
410             }
411 
412             debug(cachetools) safe_tracef("pop %s from InLlist", _InList.front.key);
413 
414             auto toOutK = _InList.front.key;
415             _InList.popFront();
416 
417             auto in_ptr = toOutK in _InMap;
418 
419             auto toOutV = in_ptr.value;
420             auto toOutE = in_ptr.expired_at;
421             bool removed = _InMap.remove(toOutK);
422 
423             assert(removed);
424             assert(_InList.length == _InMap.length);
425 
426             if ( toOutE > 0 && toOutE <= time(null) )
427             {
428                 // expired, we done
429                 if (__reportCacheEvents) {
430                     __events.insertBack(CacheEvent!(K, V)(EventType.Expired, toOutK, toOutV));
431                 }
432                 return PutResult(PutResultFlag.Inserted|PutResultFlag.Evicted);
433             }
434 
435             // and push to Out
436             lp = _OutList.insertBack(ListElement(toOutK));
437             _OutMap.put(toOutK, MapElement(toOutV, lp, toOutE));
438             if ( _OutList.length <= _kout )
439             {
440                 return PutResult(PutResultFlag.Inserted|PutResultFlag.Evicted);
441             }
442             //
443             // Out overflowed - throw away head
444             //
445             debug(cachetools) safe_tracef("pop %s from Out", _OutList.front.key);
446 
447             if (__reportCacheEvents)
448             {
449                 // store in event list
450                 auto evicted_key = _OutList.front.key;
451                 auto mptr = evicted_key in _OutMap;
452                 __events.insertBack(CacheEvent!(K,V)(EventType.Evicted, evicted_key, mptr.value));
453             }
454 
455             removed = _OutMap.remove(_OutList.front.key);
456             _OutList.popFront();
457 
458             assert(removed);
459             assert(_OutList.length == _OutMap.length);
460 
461             return PutResult(PutResultFlag.Inserted|PutResultFlag.Evicted);
462         }
463     }
464     ///
465     /// Remove element from cache.
466     ///
467     final bool remove(K k) @safe
468     {
469         debug(cachetools) safe_tracef("remove from 2qcache key %s", k);
470         auto inIn = k in _InMap;
471         if ( inIn )
472         {
473             auto lp = inIn.list_element_ptr;
474 
475             if (__reportCacheEvents)
476             {
477                 __events.insertBack(CacheEvent!(K, V)(EventType.Removed, k, inIn.value));
478             }
479 
480             () @trusted
481             {
482                 _InList.remove(lp);
483             }();
484             _InMap.remove(k);
485             return true;
486         }
487         auto inOut = k in _OutMap;
488         if ( inOut )
489         {
490 
491             if (__reportCacheEvents)
492             {
493                 __events.insertBack(CacheEvent!(K, V)(EventType.Removed, k, inOut.value));
494             }
495 
496             auto lp = inOut.list_element_ptr;
497             () @trusted
498             {
499                 _OutList.remove(lp);
500             }();
501             _OutMap.remove(k);
502             return true;
503         }
504         auto inMain = k in _MainMap;
505         if ( inMain )
506         {
507 
508             if (__reportCacheEvents)
509             {
510                 __events.insertBack(CacheEvent!(K, V)(EventType.Removed, k, inMain.value));
511             }
512 
513             auto lp = inMain.list_element_ptr;
514             _MainList.remove(lp);
515             _MainMap.remove(k);
516             return true;
517         }
518         return false;
519     }
520 }
521 
522 @safe unittest
523 {
524     import std.stdio, std.format;
525     import std.datetime;
526     import core.thread;
527     import std.algorithm;
528     import std.experimental.logger;
529     globalLogLevel = LogLevel.info;
530     info("Testing 2Q");
531     auto cache = new Cache2Q!(int, int);
532     cache.size = 12;
533     foreach(i;0..11)
534     {
535         cache.put(i,i);
536         cache.get(i-3);
537     }
538     cache.put(11,11);
539     // In:   [11, 10]
540     // Out:  [8, 9]
541     // Main: [0, 6, 7, 2, 3, 1, 5, 4]
542     assert(cache._InMap.length == 2);
543     assert(cache._OutMap.length == 2);
544     assert(cache._MainMap.length == 8);
545     assert(cache.length==12, "expected 12, got %d".format(cache.length));
546     foreach(i;0..12)
547     {
548         assert(cache.get(i) == i, "missed %s".format(i));
549     }
550     cache.clear();
551     assert(cache.length==0);
552     foreach(i;0..11)
553     {
554         cache.put(i,i);
555         cache.get(i-3);
556     }
557     cache.put(11,11);
558     foreach(i;0..12)
559     {
560         assert(cache.remove(i), "failed to remove %s".format(i));
561     }
562     assert(cache.length==0);
563     foreach(i;0..11)
564     {
565         cache.put(i,i);
566         cache.get(i-3);
567     }
568     cache.put(11,11);
569     // In:   [11, 10]
570     // Out:  [8, 9]
571     // Main: [0, 6, 7, 2, 3, 1, 5, 4]
572     cache.put(11,22);
573     cache.put(8, 88);
574     cache.put(5,55);
575     assert(cache.get(5) == 55);
576     assert(cache.get(11) == 22);
577     assert(cache.length==12, "expected 12, got %d".format(cache.length));
578     assert(cache.get(8) == 88); // 8 moved from Out to Main
579     assert(cache.length==11, "expected 11, got %d".format(cache.length));
580     cache.put(12,12);   // in iverflowed, out filled
581     cache.put(13, 13);  // in overflowed, out overflowed to main
582     assert(cache.length==12, "expected 12, got %d".format(cache.length));
583     globalLogLevel = LogLevel.info;
584 }
585 
586 unittest
587 {
588     // testing ttl
589     import std.stdio, std.format;
590     import std.datetime;
591     import std.range;
592     import std.algorithm;
593     import core.thread;
594     import std.experimental.logger;
595 
596     globalLogLevel = LogLevel.info;
597     auto cache = new Cache2Q!(int, int);
598     cache.sizeIn = 2;
599     cache.sizeOut = 2;
600     cache.sizeMain = 4;
601     cache.enableCacheEvents;
602 
603     cache.put(1, 1, TTL(1));
604     cache.put(2, 2, TTL(1));
605     // in: 1, 2
606     cache.put(3,3);
607     cache.put(4,4);
608     // in: 3, 4
609     // out 1, 2
610     cache.get(1);
611     // in: 3, 4
612     // out 2
613     // main: 1
614     cache.put(5,5, TTL(1));
615     // In: 4(-), 5(1)   //
616     // Out: 2(1), 3(-)  // TTL in parens
617     // Main: 1(1)       //
618     assert(4 in cache._InMap && 5 in cache._InMap);
619     assert(2 in cache._OutMap && 3 in cache._OutMap);
620     assert(1 in cache._MainMap);
621     Thread.sleep(1500.msecs);
622     assert(cache.get(1).isNull);
623     assert(cache.get(2).isNull);
624     assert(cache.get(5).isNull);
625     assert(cache.get(3) == 3);
626     assert(cache.get(4) == 4);
627     cache.clear;
628     auto e = cache.cacheEvents;
629     assert(e.filter!(a => a.event == EventType.Removed).count == 2);
630     assert(e.filter!(a => a.event == EventType.Expired).count == 3);
631     cache.ttl = 1;
632     cache.put(1, 1);            // default TTL - this must not survive 1s sleep
633     cache.put(2, 2, ~TTL());    // no TTL, ignore default - this must survive any time 
634     cache.put(3, 3, TTL(2));    // set TTL for this item - this must not survive 2s
635     Thread.sleep(1000.msecs);
636     assert(cache.get(1).isNull); // expired
637     assert(cache.get(2) == 2);
638     assert(cache.get(3) == 3);
639     Thread.sleep(1000.msecs);
640     assert(cache.get(2) == 2);
641     assert(cache.get(3).isNull); // expired
642     e = cache.cacheEvents;
643     assert(e.map!(a => a.event).all!(a => a == EventType.Expired));
644     cache.remove(2);
645     e = cache.cacheEvents;
646     assert(e.length == 1 && e.front.key == 2);
647     // test cache events after clear
648     cache.sizeIn = 10;
649     iota(5).each!(i => cache.put(i,i));
650     cache.clear;
651     e = cache.cacheEvents;
652     assert(e.length == 5);
653     assert(e.map!(a => a.event).all!(a => a == EventType.Removed));
654 
655     // test for clear from all queues
656     cache.sizeIn = 2;
657     cache.sizeOut = 2;
658     cache.sizeMain = 1;
659     cache.ttl = 0;
660     cache.put(1, 1);
661     cache.put(2, 2);
662     // in: 1, 2
663     cache.put(3, 3);
664     cache.put(4, 4);
665     // in: 3, 4
666     // out 1, 2
667     cache.get(1);
668     // in: 3, 4
669     // out 2
670     // main: 1
671     cache.put(5, 5);
672     // In: 4, 5
673     // Out: 2, 3
674     // Main: 1
675     cache.clear;
676     e = cache.cacheEvents;
677     assert(e.length == 5);
678     // test for eviction events from all queues
679     cache.put(1, 1);
680     cache.put(2, 2);
681     // in: 1, 2
682     cache.put(3, 3);
683     cache.put(4, 4);
684     // in: 3, 4
685     // out 1, 2
686     cache.get(1);
687     // in: 3, 4
688     // out 2
689     // main: 1
690     cache.put(5, 5);
691     // In: 4, 5
692     // Out: 2, 3
693     // Main: 1
694     cache.get(2); // 1 evicted and replaced by 2
695     // In: 4, 5
696     // Out: 3
697     // Main: 2
698     e = cache.cacheEvents;
699     assert(e.length == 1);
700     assert(e.front.key == 1);
701     assert(e.front.event == EventType.Evicted);
702     cache.put(4, 44, TTL(1)); // create 'updated' event in In queue
703     e = cache.cacheEvents;
704     assert(e.length == 1);
705     assert(e.front.key == 4);
706     assert(e.front.event == EventType.Updated);
707     cache.put(3, 33); // create 'updated' event in Out queue
708     e = cache.cacheEvents;
709     assert(e.length == 1);
710     assert(e.front.key == 3);
711     assert(e.front.event == EventType.Updated);
712     cache.put(2, 22); // create 'updated' event in In queue
713     e = cache.cacheEvents;
714     assert(e.length == 1);
715     assert(e.front.key == 2);
716     assert(e.front.event == EventType.Updated);
717     Thread.sleep(1.seconds);
718     // In: 4, 5
719     // Out: 3
720     // Main: 2
721     cache.put(6,6);     // now key '4' expired and must be dropped from In queue
722     e = cache.cacheEvents;
723     assert(e.length == 1);
724     assert(e.front.key == 4);
725     assert(e.front.event == EventType.Expired);
726     // In: 5, 6
727     // Out: 3
728     // Main: 2
729     cache.put(7, 7);
730     // In: 6, 7
731     // Out: 3, 5
732     // Main: 2
733     cache.put(8, 8);
734     // In: 7, 8
735     // Out: 5, 6 -> 3 evicted
736     // Main: 2
737     e = cache.cacheEvents;
738     assert(e.length == 1);
739     assert(e.front.key == 3);
740     assert(e.front.event == EventType.Evicted);
741     cache.remove(7); // remove from In
742     cache.remove(5); // remove from Out
743     cache.remove(2); // remove from main
744     cache.remove(0); // remove something that were not in cache
745     e = cache.cacheEvents;
746     assert(e.length == 3);
747     assert(e.all!(a => a.event == EventType.Removed));
748     assert(e.map!"a.key".array == [7,5,2]);
749 }
750 
751 ///
752 ///
753 ///
754 @safe @nogc unittest
755 {
756 
757     // create cache with total size 1024
758     auto cache = () @trusted {
759         auto allocator = Mallocator.instance;
760         return allocator.make!(Cache2Q!(int, string))(1024);
761     }();
762 
763     cache.sizeIn = 10;              // if you need, later you can set any size for In queue
764     cache.sizeOut = 55;             // and for out quque
765     cache.sizeMain = 600;           // and for main cache
766     cache.put(1, "one");
767     assert(cache.get(1) == "one");  // key 1 is in cache
768     assert(cache.get(2).isNull);    // key 2 not in cache
769     assert(cache.length == 1);      // # of elements in cache
770     cache.clear;                    // clear cache
771 }