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.meta.types.Qualifiers;
22 
23 import ocean.util.container.cache.ExpiringCache,
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 CachingStructLoader ( 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             return cast(typeof(&this))data.ptr;
89         }
90     }
91 
92     /**************************************************************************
93 
94         The cache class. We need to enable GC scanning of the values stored in
95         the cache because they contain references.
96 
97     ***************************************************************************/
98 
99     public static class Cache : ExpiringCache!(CacheValue.sizeof)
100     {
101         /***********************************************************************
102 
103             Constructor.
104 
105             Params:
106                 max_items = maximum number of items in the cache, set once,
107                             cannot be changed
108                 lifetime  = life time for all items in seconds; may be changed
109                             at any time. This value must be at least 1.
110 
111         ***********************************************************************/
112 
113         public this ( size_t max_items, time_t lifetime )
114         {
115             super(max_items, lifetime);
116             this.enableGcValueScanning();
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_.getRaw(key);
282         if (value_or_null !is null)
283         {
284             cached_value = CacheValue(value_or_null);
285 
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                 cached_value = CacheValue(this.cache_.createRaw(key));
299                 this.store(key, data, cached_value.value);
300             }
301         );
302 
303         return this.fiber_local_copy;
304     }
305 
306 
307     /**************************************************************************
308 
309         Looks up the record value corresponding to key and invokes got with
310         either that value, if found, or empty data if not found.
311         Should return without calling got if unable to look up the value.
312 
313         Params:
314             key = record key
315             got = delegate to call back with the value if found
316 
317      **************************************************************************/
318 
319     abstract protected void getData ( hash_t key, scope void delegate ( Contiguous!(S) data ) got );
320 
321     /**************************************************************************
322 
323         Gets the record value corresponding to key.
324 
325         Params:
326             key = key of the records to get
327 
328         Returns:
329             Pointers to the record value corresponding to key or null if the
330             record for key does not exist.
331 
332         Throws:
333             Exception on data error
334 
335      **************************************************************************/
336 
337     public S* opIn_r ( hash_t key )
338     {
339         return this.load(key).ptr;
340     }
341 
342 
343     /***************************************************************************
344 
345         Support for the 'in' operator
346 
347         Aliased to opIn_r, for backwards compatibility
348 
349     ***************************************************************************/
350 
351     public alias opBinaryRight ( istring op : "in" ) = opIn_r;
352 
353 
354     /**************************************************************************
355 
356         Remove all object from the cache.
357 
358      **************************************************************************/
359 
360     public void clear ( )
361     {
362         this.cache_.clear();
363     }
364 }
365 
366 unittest
367 {
368     struct Dummy {}
369     CachingStructLoader!(Dummy) loader;
370 }