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 }