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 }