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