/// <summary> /// Remove the loading value reference from the cache. /// </summary> /// <param name="key">The key associated with the loading value.</param> /// <param name="value_reference">The value reference to be removed from /// the cache.</param> /// <returns> /// <c>true</c> if the value is succesfully removed from the cache; /// otherwise, false. /// </returns> /// <remarks> /// If a loading value reference has an active value means that the loading /// process has failed and the old valud is still valid, in that case this /// method will replace the loading value reference with the old value. /// </remarks> bool RemoveLoadingValue(string key, LoadingValueReference <T> value_reference) { lock (mutex_) { CacheEntry <T> entry; if (cache_provider_.Get(CacheKey(key), out entry)) { IValueReference <T> value = entry.ValueReference; if (value == value_reference) { // If a loading value is active means that the new value has failed // to load and the old value is still valid. if (value_reference.IsActive) { entry.ValueReference = value_reference.OldValue; } else { cache_provider_.Remove(CacheKey(key)); } return(true); } } return(false); } }
/// <summary> /// Waits uninterruptibly for <paramref name="new_value"/> to be loaded. /// </summary> /// <param name="key">The key associated with the laoding value.</param> /// <param name="loading_value_reference"></param> /// <param name="new_value"></param> /// <returns></returns> T GetUninterruptibly(string key, LoadingValueReference <T> loading_value_reference, IFuture <T> new_value) { T value = default(T); try { value = Uninterruptibles.GetUninterruptibly(new_value); // Cache loader should never returns null for reference types. if (IsNull(value)) { throw new InvalidCacheLoadException( "CacheLoader returned a null for key " + key + "."); } // TODO(neylor.silva): Record load success stats. StoreLoadedValue(key, loading_value_reference, value); return(value); } finally { if (IsNull(value)) { // TODO(neylor.silva): Record load exception stats. RemoveLoadingValue(key, loading_value_reference); } } }
/// <summary> /// Store the loaded value in cache. /// </summary> /// <param name="key"> /// The key associated with the value. /// </param> /// <param name="old_value_reference"> /// A <see cref="LoadingValueReference{T}"/> that was used to load the /// value. /// </param> /// <param name="new_value"> /// The value to be stored in cache. /// </param> /// <returns> /// <c>true</c> if the value was successfully stored in cache; otherwise, /// false. /// </returns> /// <remarks> /// This method is called to store the result of the loading computation. /// If the value was sucessfully loaded it will be stored in cache using /// the given key. /// <para> /// This method fails to store the value if a value for the given key was /// already replaced by another item, while it is loading. /// </para> /// </remarks> bool StoreLoadedValue(string key, LoadingValueReference <T> old_value_reference, T new_value) { lock (mutex_) { long now = Clock.NanoTime; CacheEntry <T> entry; if (cache_provider_.Get(CacheKey(key), out entry)) { IValueReference <T> value_reference = entry.ValueReference; if (old_value_reference == value_reference) { // If the old value is still active notify the caller that we // are replacing it. if (old_value_reference.IsActive) { // TODO(neylor.silva): Notify the caller about the removal cause // of the item from the cache(REPLACED). } SetValue(entry, key, new_value, now); return(true); } // the loaded value was already clobbered. // TODO(neylor.silva): Notify the caller about the removal cause // of the item from the cache(REPLACED). return(false); } // an entry for the given key does not exist yet, create a new one. entry = new CacheEntry <T>(key); SetValue(entry, key, new_value, now); return(true); } }
// 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> /// Creates a new <see cref="LoadingValueReference{T}"/> and inserts it on /// the cache by using the <paramref name="key"/>. /// </summary> /// <param name="key">The key that will be associated with the newly /// created <see cref="LoadingValueReference{T}"/>.</param> /// <returns>The newly inserted <see cref="LoadingValueReference{T}"/>, or /// null if the live value reference is already loading.</returns> LoadingValueReference <T> InsertLoadingValueReference(string key) { lock (mutex_) { long now = Clock.NanoTime; LoadingValueReference <T> loading_value_reference; // look for an existing entry CacheEntry <T> entry; if (cache_provider_.Get(CacheKey(key), out entry)) { IValueReference <T> value_reference = entry.ValueReference; if (value_reference.IsLoading) { // refresh is a no-op if loading is pending. return(null); } // continue returning old value while loading loading_value_reference = new LoadingValueReference <T>(value_reference); entry.ValueReference = loading_value_reference; return(loading_value_reference); } loading_value_reference = new LoadingValueReference <T>(); entry = new CacheEntry <T>(key); entry.ValueReference = loading_value_reference; // send the entry to the cache provider. cache_provider_.Set(CacheKey(key), entry); return(loading_value_reference); } }
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> /// 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> /// 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)); } }