public IFuture <T> LoadFuture(string key, CacheLoader <T> loader) { stopwatch_.Start(); T previous_value = old_value_.Value; try { // If the T is a value type and its value is default(T) it will never // be reloaded and always loaded from the cache loader. We could // make the parameter T nullable, but this makes the code much more // complicated and we do not want that. if (typeof(T).IsValueType || (object)previous_value == null) { T new_value = loader.Load(key); return(Set(new_value) ? this : Futures.ImmediateFuture(new_value)); } else { IFuture <T> new_value_future = loader.Reload(key, previous_value); return(new_value_future ?? Futures.ImmediateFuture(default(T))); } } catch (Exception exception) { return(SetException(exception) ? this : Futures.ImmediateFailedFuture <T>(exception)); } }
public void ShouldThrowExceptionWhenCacheLoaderReturnsNull() { CacheBuilder <string> ref_cache_builder = new CacheBuilder <string>(); LoadingCacheMock <string> ref_cache = new LoadingCacheMock <string>(ref_cache_builder); CacheBuilder <long> val_cache_builder = new CacheBuilder <long>(); LoadingCacheMock <long> val_cache = new LoadingCacheMock <long>(val_cache_builder); CacheLoader <string> ref_loader = new StringCacheLoader(); try { ref_cache.Get("missing-ref-key", ref_loader); } catch (ExecutionException exception) { Assert.IsAssignableFrom <InvalidCacheLoadException>( exception.InnerException); } CacheLoader <long> val_loader = CacheLoader <long> .From(delegate(string key) { return(default(long)); }); Assert.DoesNotThrow( delegate() { val_cache.Get("missing-ref-key", val_loader); }); }
// At most one of LoadSync/LoadAsync may be called for any given // LoadingValueReference. T LoadSync(string key, LoadingValueReference <T> loading_value_reference, CacheLoader <T> loader) { IFuture <T> loading_future = loading_value_reference.LoadFuture(key, loader); return(GetUninterruptibly(key, loading_value_reference, loading_future)); }
/// <summary> /// Initializes a new instance of the <see cref="LoadingCache{T}"/> using the /// specified cache provider, builder and automatic loader. /// </summary> /// <param name="provider"> /// A <see cref="ICacheProvider"/> that is used to cache object. /// </param> /// <param name="builder"> /// A <see cref="CacheBuilder{T}"/> object that contains information about /// the cache configuration. /// </param> /// <param name="loader"> /// An <see cref="CacheLoader{T}"/> that is used to automatically load new /// items in cache when an item for a given key does not exists or is /// expired. /// </param> /// <exception cref="ArgumentNullException"> /// <paramref name="provider"/>, <paramref name="builder"/> or /// <paramref name="loader"/> are <c>null</c>. /// </exception> public LoadingCache(ICacheProvider provider, CacheBuilder <T> builder, CacheLoader <T> loader) : this(provider, builder) { if (loader == null) { Thrower.ThrowArgumentNullException(ExceptionArgument.loader); } default_cache_loader_ = loader; }
/// <summary> /// Schedule a refresh for an entry. /// </summary> /// <param name="entry"> /// The entry to be refreshed. /// </param> /// <param name="key"> /// The key associated with the entry to be refreshed. /// </param> /// <param name="old_value"> /// The old value of the entry. /// </param> /// <param name="now"> /// The current time, it is used to check if the entry needs a refresh. /// </param> /// <param name="loader"> /// A <see cref="CacheLoader{T}"/> object that is used to load the new /// value for the entry. /// </param> /// <returns> /// The refreshed value if a refresh was performed; otherwise, the old /// value. /// </returns> T ScheduleRefresh(CacheEntry <T> entry, string key, T old_value, long now, CacheLoader <T> loader) { if (Refreshes && (now - entry.WriteTime > refresh_nanos_)) { T new_value; if (Refresh(key, loader, out new_value)) { return(new_value); } } return(old_value); }
IFuture <T> LoadAsync(string key, LoadingValueReference <T> loading_value_reference, CacheLoader <T> loader) { IFuture <T> loading_future = loading_value_reference.LoadFuture(key, loader); loading_future.AddListener(delegate() { try { T new_value = GetUninterruptibly(key, loading_value_reference, loading_future); // update loading future for the sake of other pending requests. loading_value_reference.Set(new_value); } catch (Exception exception) { MustLogger.ForCurrentProcess.Warn("Exception thrown during refresh", exception); } }, Executors.SameThreadExecutor()); return(loading_future); }
/// <summary> /// Initializes a new instance of the <see cref="LoadingCache{T}"/> class /// by using the specified cache provider and builder. /// </summary> /// <param name="provider"> /// A <see cref="ICacheProvider"/> that is used to cache object. /// </param> /// <param name="builder"> /// A <see cref="CacheBuilder{T}"/> object that contains information about /// the cache configuration. /// </param> /// <exception cref="ArgumentNullException"> /// <paramref name="provider"/> or <paramref name="builder"/> are /// <c>null</c>. /// </exception> public LoadingCache(ICacheProvider provider, CacheBuilder <T> builder) { if (provider == null || builder == null) { Thrower.ThrowArgumentNullException(provider == null ? ExceptionArgument.provider : ExceptionArgument.builder); } cache_provider_ = provider; cache_guid_ = Guid.NewGuid().ToString("N"); expire_after_access_nanos_ = builder.ExpiryAfterAccessNanos; expire_after_write_nanos_ = builder.ExpiryAfterWriteNanos; refresh_nanos_ = builder.RefreshNanos; strength_type_ = builder.ValueStrength; mutex_ = new object(); t_is_value_type_ = typeof(T).IsValueType; default_cache_loader_ = CacheLoader <T> .From(delegate { throw new NotSupportedException(); }); }
/// <summary> /// Gets the value associated with the given key, creating or retrieving /// that value if necessary. /// </summary> /// <param name="key">The identifier for the cache item to retrieve. /// </param> /// <param name="loader">A <see cref="CacheLoader{T}"/> object that could /// be used to create the value if it is not present in the cache.</param> /// <remarks> /// No state associated with this cache is modified until loading is /// complete. /// </remarks> /// <exception cref="ExecutionException">A failure occur while loading /// the item using the specified loader delegate.</exception> /// <exception cref="ArgumentNullException"><paramref name="key"/> or /// <paramref name="loader"/> are <c>null</c>.</exception> public T Get(string key, CacheLoader <T> loader) { if (key == null || loader == null) { Thrower.ThrowArgumentNullException( key == null ? ExceptionArgument.key : ExceptionArgument.loader); } try { T value; CacheEntry <T> entry; // the cache provider should provide the thread safeness behavior for // the reading operation. bool ok = cache_provider_.Get(CacheKey(key), out entry); if (ok) { long now = Clock.NanoTime; if (GetLiveValue(entry, now, out value)) { RecordRead(entry, now); return(ScheduleRefresh(entry, key, value, now, loader)); } IValueReference <T> value_reference = entry.ValueReference; if (value_reference.IsLoading) { return(WaitForLoadingValue(entry, key, value_reference)); } } // at this point entry does not exists or is expired, so lets load // the value. return(LockedGetOrLoad(key, loader)); } catch (Exception e) { MustLogger.ForCurrentProcess.Error(kTypeForLogger + "Get]", e); throw; } }
/// <summary> /// Refreshes the value associated with <paramref name="key"/>, unless /// another thread is already doing so. /// </summary> /// <param name="key"> /// The key associated with the value to refresh. /// </param> /// <param name="loader"> /// A <see cref="CacheLoader{T}"/> that is used to refresh the value. /// </param> /// <param name="value"> /// The newly refreshed value associated with <paramref name="key"/> if /// the value was refreshed inline, or the default value of /// <typeparamref name="T"/> if another thread is performing the refresh or /// if a error occurs during refresh. /// </param> /// <returns> /// <c>true</c> if the value was refreshed and <c>false</c> if another /// thread is performing the refresh or if a error has been occured during /// the refresh. /// </returns> bool Refresh(string key, CacheLoader <T> loader, out T value) { LoadingValueReference <T> loading_value_reference = InsertLoadingValueReference(key); value = default(T); if (loading_value_reference == null) { return(false); } IFuture <T> result = LoadAsync(key, loading_value_reference, loader); if (result.IsCompleted) { try { value = result.Get(); } catch { // don't let refresh exceptions propagate; error was already logged. } } return(false); }
/// <summary> /// Initializes a new instance of the <see cref="LocalManualCache{T}"/> by /// using the specified cache provider, builder and automatic loader. /// </summary> /// <param name="provider"> /// A <see cref="ICacheProvider"/> object that is used to store the cached /// items. /// </param> /// <param name="builder"> /// A <see cref="CacheBuilder{T}"/> containing the configured options for /// this cache. /// </param> /// <param name="loader"> /// A <see cref="loader"/> that is used to automatically load values. /// </param> protected LocalManualCache(ICacheProvider provider, CacheBuilder <T> builder, CacheLoader <T> loader) : base(provider, builder, loader) { }
/// <summary> /// Builds a cache, which either returns an already-loaded value for a /// given key or atomically computes or retrieves it using the supplied /// <see cref="CacheLoader{T}"/>. /// </summary> /// <param name="loader"> /// The cache loader used to obtain new values. /// </param> /// <param name="provider"> /// An object that implements the plugabble <see cref="ICacheProvider"/> /// interface and is used to cache the objects. /// </param> /// <returns> /// A <see cref="ILoadingCache{T}"/> object having the requested features. /// </returns> /// <remarks> /// If another thread is currently loadind the value for the given key, /// the returned cache, simply waits for that thread to finish and returns /// its loaded value. Note that multiple threads can concurrently load /// values for distinct keys. /// <para> /// This method does not alter the state of the /// <see cref="CacheBuilder{T}"/> instance, so it can be invoked again to /// create multiple independent caches. /// </para> /// </remarks> public ILoadingCache <T> Build(ICacheProvider provider, CacheLoader <T> loader) { return(new LoadingCache <T>(provider, this, loader)); }
/// <summary> /// Atomically get or loads and get the value for the specified key. /// </summary> /// <param name="key"> /// The key associated with the value to get. /// </param> /// <param name="loader"> /// A <see cref="CacheLoader{T}"/> that could be used to load the value /// for the key <paramref name="key"/>. /// </param> /// <returns> /// A object of type <typeparamref name="T"/> that is associated with the /// key <paramref name="key"/>. /// </returns> T LockedGetOrLoad(string key, CacheLoader <T> loader) { CacheEntry <T> entry; IValueReference <T> value_reference = null; LoadingValueReference <T> loading_value_reference = null; bool create_new_entry = true; lock (mutex_) { // re-read the time once inside the lock long now = Clock.NanoTime; if (cache_provider_.Get(CacheKey(key), out entry)) { value_reference = entry.ValueReference; if (value_reference.IsLoading) { create_new_entry = false; } else { T value = value_reference.Value; if (IsExpired(entry, now)) { // TODO(neylor.silva) Notificate the caller about the // expiration(Reason: EXPIRED). } else { RecordRead(entry, now); // TODO(neylor.sila): Record hits stats. return(value); } // TODO(neylor.silva): Update the cache count(size). } } // at this point an entry was not found or it is expired. if (create_new_entry) { loading_value_reference = new LoadingValueReference <T>(); if (entry == null) { entry = new CacheEntry <T>(key); entry.ValueReference = loading_value_reference; cache_provider_.Set(CacheKey(key), entry); } else { // entry exists but is expired, lets update it with a new // loading value. entry.ValueReference = loading_value_reference; } } } // at this point an entry associated with the specified key exists // in cache, but it is a loading the value. if (create_new_entry) { try { // TODO (neylor.silva): Add a mechanism to detect recursive loads. return(LoadSync(key, loading_value_reference, loader)); } finally { // TODO (neylor.silva): Record the misses stats. } } else { // the entry already exists and the loading process is already // started. Wait for loading. return(WaitForLoadingValue(entry, key, value_reference)); } }