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 moduleocean.util.container.cache.CachingStructLoader;
19 20 21 importocean.meta.types.Qualifiers;
22 23 importocean.util.container.cache.ExpiringCache,
24 ocean.util.container.cache.model.IExpiringCacheInfo;
25 importCacheValue = ocean.util.container.cache.model.Value;
26 27 importocean.util.serialize.contiguous;
28 29 importcore.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 classCachingStructLoader ( S )
47 {
48 importocean.core.Verify;
49 50 /***************************************************************************
51 52 The struct of data stored in the cache.
53 54 ***************************************************************************/55 56 privatestructCacheValue57 {
58 /***********************************************************************
59 60 The value to store.
61 "alias value this" would be nice.
62 63 ***********************************************************************/64 65 mixin .CacheValue.Value!(0);
66 Valuevalue;
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 statictypeof(&this) opCall ( void[] data )
86 {
87 verify (data.length == typeof(this).sizeof);
88 returncast(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 publicstaticclassCache : 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 publicthis ( size_tmax_items, time_tlifetime )
114 {
115 super(max_items, lifetime);
116 this.enableGcValueScanning();
117 }
118 }
119 120 /**************************************************************************
121 122 DhtDynamic cache_ instance
123 124 ***************************************************************************/125 126 privateCachecache_;
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 privateContiguous!(S) fiber_local_copy;
137 138 /**************************************************************************
139 140 Info interface for the cache instance is exposed to the public.
141 142 ***************************************************************************/143 144 publicIExpiringCacheInfocache;
145 146 /**************************************************************************
147 148 Constructor
149 150 Params:
151 cache_ = cache to use
152 153 **************************************************************************/154 155 publicthis ( Cachecache_ )
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 privateContiguous!(S) store ( hash_tkey, Contiguous!(S) data, refCacheValue.Valuecache_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 autodst = cast(Contiguous!(S)*) cast(void[]*) cache_slot;
197 198 if (data.length)
199 {
200 // storing shared cache value201 .copy(data, *dst);
202 // storing fiber-local copy of same value that will be returned203 // to application code204 .copy(data, this.fiber_local_copy);
205 }
206 else207 {
208 // storing empty value (cache miss)209 cache_slot[] = null;
210 this.fiber_local_copy.reset();
211 }
212 213 returnthis.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 privateContiguous!(S) copy ( void[] data )
232 {
233 if (!data.length)
234 {
235 returnthis.fiber_local_copy.reset();
236 }
237 238 .copy(Contiguous!(S)(data), this.fiber_local_copy);
239 returnthis.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 protectedvoidonStoringData ( 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 protectedContiguous!(S) load ( hash_tkey )
278 {
279 CacheValue* cached_value;
280 281 autovalue_or_null = this.cache_.getRaw(key);
282 if (value_or_null !isnull)
283 {
284 cached_value = CacheValue(value_or_null);
285 286 returnthis.copy(cached_value.value[]);
287 }
288 289 // value wasn't cached, need to perform external data request290 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 returnthis.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 abstractprotectedvoidgetData ( hash_tkey, scopevoiddelegate ( 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 publicS* opIn_r ( hash_tkey )
338 {
339 returnthis.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 publicaliasopBinaryRight ( istringop : "in" ) = opIn_r;
352 353 354 /**************************************************************************
355 356 Remove all object from the cache.
357 358 **************************************************************************/359 360 publicvoidclear ( )
361 {
362 this.cache_.clear();
363 }
364 }
365 366 unittest367 {
368 structDummy {}
369 CachingStructLoader!(Dummy) loader;
370 }