1 /******************************************************************************
2 
3     Wraps a cache for struct values. When a record cannot be found in the
4     cache, an abstract method is called to look up the record in an external
5     source.
6 
7     Copyright:
8         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
9         All rights reserved.
10 
11     License:
12         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
13         Alternatively, this file may be distributed under the terms of the Tango
14         3-Clause BSD License (see LICENSE_BSD.txt for details).
15 
16 *******************************************************************************/
17 
18 module ocean.util.container.cache.ExpiredCacheReloader;
19 
20 
21 import ocean.meta.types.Qualifiers;
22 
23 import ocean.util.container.cache.ExpiringLRUCache,
24        ocean.util.container.cache.model.IExpiringCacheInfo;
25 import CacheValue = ocean.util.container.cache.model.Value;
26 
27 import ocean.util.serialize.contiguous;
28 
29 import core.stdc.time: time_t, time;
30 
31 /*******************************************************************************
32 
33     Base class for a cache. To use it you need to inherit it and override
34     `getData` method. It operates only on contiguous struct buffers and does
35     a copy of such buffer before returning cache element.
36 
37     Recommended code pattern is to have a shared Cache but a fiber-local
38     CachingStructLoader so that last accessed element won't become invalidated
39     between context switches
40 
41     Params:
42         S = type of the deserialized struct expected in contiguous buffer
43 
44 *******************************************************************************/
45 
46 class ExpiredCacheReloader ( S )
47 {
48     import ocean.core.Verify;
49 
50     /***************************************************************************
51 
52         The struct of data stored in the cache.
53 
54     ***************************************************************************/
55 
56     private struct CacheValue
57     {
58         /***********************************************************************
59 
60             The value to store.
61             "alias value this" would be nice.
62 
63         ***********************************************************************/
64 
65         mixin .CacheValue.Value!(0);
66         Value value;
67 
68         /***********************************************************************
69 
70             Casts a reference to a cache value as obtained from get/createRaw()
71             to a pointer to an instance of this struct.
72 
73             Params:
74                 data = data of an instance of this struct
75 
76             Returns:
77                 a pointer to an instance of this struct referencing data;
78                 i.e. cast(typeof(this))data.ptr.
79 
80             In:
81                 data.length must match the size of this struct.
82 
83          **********************************************************************/
84 
85         static typeof(&this) opCall ( void[] data )
86         {
87             verify (data.length == typeof(this).sizeof);
88 
89             return cast(typeof(&this))data.ptr;
90         }
91     }
92 
93     /**************************************************************************
94 
95         The cache class. We need to enable GC scanning of the values stored in
96         the cache because they contain references.
97 
98     ***************************************************************************/
99 
100     public static class Cache : ExpiringLRUCache!(CacheValue)
101     {
102         /***********************************************************************
103 
104             Constructor.
105 
106             Params:
107                 max_items = maximum number of items in the cache, set once,
108                             cannot be changed
109                 lifetime  = life time for all items in seconds; may be changed
110                             at any time. This value must be at least 1.
111 
112         ***********************************************************************/
113 
114         public this ( size_t max_items, time_t lifetime )
115         {
116             super(max_items, lifetime);
117         }
118     }
119 
120     /**************************************************************************
121 
122         DhtDynamic cache_ instance
123 
124     ***************************************************************************/
125 
126     private Cache cache_;
127 
128     /**************************************************************************
129 
130         Cached element is copied in this buffer before being returned. If
131         fiber-local instances of CachingStructLoader are used this will
132         guarantee that all pointers remain good between context switches.
133 
134     ***************************************************************************/
135 
136     private Contiguous!(S) fiber_local_copy;
137 
138     /**************************************************************************
139 
140         Info interface for the cache instance is exposed to the public.
141 
142     ***************************************************************************/
143 
144     public IExpiringCacheInfo cache;
145 
146     /**************************************************************************
147 
148         Constructor
149 
150         Params:
151             cache_ = cache to use
152 
153      **************************************************************************/
154 
155     public this ( Cache cache_ )
156     {
157         this.cache = this.cache_ = cache_;
158     }
159 
160     /**************************************************************************
161 
162         Copies contiguous struct data into cache slot. Deletes the cache
163         entry on deserialization error. Data can contain 0-length buffer.
164 
165         After that copies that data once again to this.fiber_local_copy and
166         returns it. `this.fiber_local_copy.length == 0` will indicate that
167         empty value was stored
168 
169         Params:
170             key  = cache element key
171             data = data to store in the cache
172             cache_slot = cache slot to store copy data to
173 
174         Returns:
175             this.fiber_local_copy
176 
177         Throws:
178             StructLoaderException on error deserializing data.
179 
180     ***************************************************************************/
181 
182     private Contiguous!(S) store ( hash_t key, Contiguous!(S) data, ref CacheValue.Value cache_slot )
183     {
184         scope (failure) this.cache_.remove(key);
185 
186         /* Unfourtunately there is no way to define CacheValue that stores
187          * Contiguous!(S) inside and `copy` function needs access to that cache
188          * slot buffer by reference to be able to resize it.
189          *
190          * This is a cheap workaround that uses the fact that array and struct
191          * that contains single array field have identical binary layout : get
192          * pointer to internal void[] buffer and reinterpret cast it as if it
193          * was Contiguous!(S) buffer. All resizes done from `copy` will then be
194          * done directly on cache slot.
195          */
196         auto dst = cast(Contiguous!(S)*) cast(void[]*) cache_slot;
197 
198         if (data.length)
199         {
200             // storing shared cache value
201             .copy(data, *dst);
202             // storing fiber-local copy of same value that will be returned
203             // to application code
204             .copy(data, this.fiber_local_copy);
205         }
206         else
207         {
208             // storing empty value (cache miss)
209             cache_slot[] = null;
210             this.fiber_local_copy.reset();
211         }
212 
213         return this.fiber_local_copy;
214     }
215 
216     /**************************************************************************
217 
218         Loads/deserializes data if it is not null or empty.
219 
220         Params:
221             data = data to load/deserialize. It must conform Contiguous!(S)
222                 requirements - only reason this method accepts void is because
223                 CacheValue can't store Contiguous!(S) preserving the type
224                 information.
225 
226         Returns:
227             deseralized data or null of data was null or empty.
228 
229     ***************************************************************************/
230 
231     private Contiguous!(S) copy ( void[] data )
232     {
233         if (!data.length)
234         {
235             return this.fiber_local_copy.reset();
236         }
237 
238         .copy(Contiguous!(S)(data), this.fiber_local_copy);
239         return this.fiber_local_copy;
240     }
241 
242     /**************************************************************************
243 
244         This method is called before storing new entry into the cache. It can
245         be used to do any adjustments necessary for specific cached type. Does
246         nothing by default which is most common case for exsting caches.
247 
248         If overridden this method must always modify data in-place
249 
250         Params:
251             data = deserialized element data, use `data.ptr` to access it as S*
252 
253     ***************************************************************************/
254 
255     protected void onStoringData ( Contiguous!(S) data )
256     {
257     }
258 
259     /**************************************************************************
260 
261         Gets the record value corresponding to key.
262 
263         If the caller and `getData` use some sort of multitasking (fibers) it is
264         possible that while `getData` is busy it does a reentrant call of this
265         method with the same key. In this case it will return null, even though
266         the record may exist.
267 
268         Params:
269             key = record key
270 
271         Returns:
272             the record value or null if either not found or currently waiting
273             for `getData` to fetch the value for this key.
274 
275      **************************************************************************/
276 
277     protected Contiguous!(S) load ( hash_t key )
278     {
279         CacheValue* cached_value;
280 
281         auto value_or_null = this.cache_.getAndRefresh(key);
282         if (value_or_null !is null)
283         {
284             auto void_arr = (cast(void*)value_or_null)[0 .. (*value_or_null).sizeof];
285             cached_value = CacheValue(void_arr);
286             return this.copy(cached_value.value[]);
287         }
288 
289         // value wasn't cached, need to perform external data request
290 
291         this.fiber_local_copy.reset();
292 
293         this.getData(
294             key,
295             (Contiguous!(S) data)
296             {
297                 this.onStoringData(data);
298                 auto value_ptr = this.cache_.getRefreshOrCreate(key);
299                 auto void_arr = (cast(void*)value_ptr)[0 .. (*value_ptr).sizeof];
300                 cached_value = CacheValue(void_arr);
301                 this.store(key, data, cached_value.value);
302             }
303         );
304 
305         return this.fiber_local_copy;
306     }
307 
308 
309     /**************************************************************************
310 
311         Looks up the record value corresponding to key and invokes got with
312         either that value, if found, or empty data if not found.
313         Should return without calling got if unable to look up the value.
314 
315         Params:
316             key = record key
317             got = delegate to call back with the value if found
318 
319      **************************************************************************/
320 
321     abstract protected void getData ( hash_t key, scope void delegate ( Contiguous!(S) data ) got );
322 
323     /**************************************************************************
324 
325         Gets the record value corresponding to key.
326 
327         Params:
328             key = key of the records to get
329 
330         Returns:
331             Pointers to the record value corresponding to key or null if the
332             record for key does not exist.
333 
334         Throws:
335             Exception on data error
336 
337      **************************************************************************/
338 
339     public S* opIn_r ( hash_t key )
340     {
341         return this.load(key).ptr;
342     }
343 
344     /***************************************************************************
345 
346         Support for the 'in' operator
347 
348         Aliased to opIn_r, for backwards compatibility
349 
350     ***************************************************************************/
351 
352     public alias opBinaryRight ( istring op : "in" ) = opIn_r;
353 }
354 
355 unittest
356 {
357     struct Dummy {}
358     ExpiredCacheReloader!(Dummy) loader;
359 }