/// <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> /// 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(FireAsync); } }
/// <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 { var target = GetTargetInstance(); 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> /// 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); }
public void Reborn_should_not_call_reborn_on_result_if_task_completed_and_just_return_the_returned_phoenixState() { IPhoenixState reborn = Substitute.For <IPhoenixState>(); Func <Task <IPhoenixState> > action = () => Task.FromResult(reborn); var state = new RaisingPhoenix(); IPhoenixState rebornState; do { rebornState = state.Reborn(action); } while (object.ReferenceEquals(rebornState, state)); reborn.Received(0).Reborn(Arg.Any <Func <Task <IPhoenixState> > >()); }
/// <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) { _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; _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, 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); }
public void Reborn_should_execute_the_task_once() { var hitCount = 2000; var wait = new AutoResetEvent(false); var count = 0; Func <Task <IPhoenixState> > action = () => { count++; wait.Set(); IPhoenixState phoenixState = Substitute.For <IPhoenixState>(); return(Task.FromResult(phoenixState)); }; var state = new RaisingPhoenix(); for (var i = 0; i < hitCount; i++) { state.Reborn(action); } Assert.IsTrue(wait.WaitOne(1000)); Assert.AreEqual(1, count); }
/// <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); } }