/// <summary> /// true if the phoenix is executing rebornAction. This is to avoid many call on method Reborn many time /// </summary> public IPhoenixState Reborn(Func<Task<IPhoenixState>> rebornAction) { if (_newPhoenixState != null) { return _newPhoenixState; } if (!_startable) { return this; } _startable = false; Func<Task<IPhoenixState>> wrapper = async () => { try { var t = rebornAction(); _newPhoenixState = await t; } catch { _startable = true; } return _newPhoenixState; }; Global.BackgroundTaskManager.Run(wrapper); return this; }
/// <summary> /// Initialize a phoenix with provided cacheDuration and staleWhileRevalidate values /// </summary> /// <param name="invocation"></param> /// <param name="info"></param> public Phoenix(_IInvocation invocation, CacheInfo info) { _info = info; _phoenixState = _info.StaleWhileRevalidate > 0 ? (IPhoenixState)new RaisingPhoenix() : new DisposingPhoenix(Die); if (invocation.Proxy != null) { // It is really a dynamic proxy _instanceTargetField = invocation.Proxy.GetType().GetField("__target", BindingFlags.Public | BindingFlags.Instance); } Arguments = invocation.Arguments; MethodInfo = invocation.Method; _timer = new Timer(_ => Reborn(), null, _info.GetRefreshTime(), TimeSpan.Zero); }
/// <summary> /// Initialize a phoenix with provided cacheDuration and staleWhileRevalidate values /// </summary> /// <param name="invocation"></param> /// <param name="info"></param> public Phoenix(_IInvocation invocation, CacheItem info) { _id = Guid.NewGuid().ToString("N"); _info = info.CloneWithoutData(); _phoenixState = _info.StaleWhileRevalidate > 0 ? (IPhoenixState) new InActivePhoenix() : new DisposingPhoenix(DieAsync()); if (invocation.Proxy != null) { // It is really a dynamic proxy _instanceTargetField = invocation.Proxy.GetType().GetField("__target", BindingFlags.Public | BindingFlags.Instance); } Arguments = invocation.Arguments; MethodInfo = invocation.Method; _allPhoenix[_id] = this; //NOTE: Memory leak: http://www.codeproject.com/Questions/185734/Threading-Timer-prevents-GC-collection _timer = new Timer(RebornCallback, _id, _info.GetRefreshTime(), TimeSpan.Zero); }
/// <summary> /// Rebuild the cache and return the new <see cref="IPhoenixState"/> /// </summary> /// <returns></returns> protected virtual async Task<IPhoenixState> FireAsync() { MethodInfo.CheckMethodForCacheSupported(out _isAsync); try { using (var dependencyScope = Activator.BeginScope()) { var target = GetTargetInstance(dependencyScope); var invokedResult = await InvokeAndGetBareResult(target).ConfigureAwait(false); var cacheItem = GetCacheItem(invokedResult); if (cacheItem == null) { var disposing = new DisposingPhoenix(DieAsync()); return disposing.Reborn(null); } var cacheStore = Global.CacheStoreProvider.GetAsyncCacheStore(_info.StoreId); //NOTE: Because the cacheItem was created before, the cacheStore cannot be null await cacheStore.SetAsync(_info.Key, cacheItem, DateTime.UtcNow.AddSeconds(_info.MaxAge + _info.StaleWhileRevalidate)).ConfigureAwait(false); Global.Logger.Info($"Updated key \"{_info.Key}\", store \"{_info.StoreId}\""); Retry(_info.GetRefreshTime()); _phoenixState = new InActivePhoenix(); return _phoenixState; } } catch (Exception ex) { Global.Logger.Error($"Error while refreshing key {_info.Key}, store \"{_info.StoreId}\". Will retry after 1 second.", ex); Retry(TimeSpan.FromSeconds(1)); throw; } }
/// <summary> /// Refresh the cache and change the internal <see cref="IPhoenixState"/> to avoid refreshing too many unnecessary times /// <para>The call will happen in background so the caller will not have to wait</para> /// </summary> public virtual void Reborn() { lock (PhoenixCage) { _phoenixState = _phoenixState.Reborn(Fire); } }