static void EnsureLocalCacheFreshness(object sender, ElapsedEventArgs e) { try { List <string> expiredKeys = new List <string>(); DateTime now = DateTime.Now; foreach (KeyValuePair <string, SessionAndRefCount> val in LocalSharedSessionDictionary.localCache) { if (val.Value.LastAccess.AddMilliseconds(cacheItemExpirationAgeMillis) < now || val.Value.RequestReferences <= 0) { expiredKeys.Add(val.Key); } } foreach (string expKey in expiredKeys) { SessionAndRefCount removed; LocalSharedSessionDictionary.localCache.TryRemove(expKey, out removed); } } catch (Exception sharedDictExc) { if (RedisSessionConfig.SessionExceptionLoggingDel != null) { RedisSessionConfig.SessionExceptionLoggingDel(sharedDictExc); } } }
public IEnumerator GetEnumerator() { // we are going to return the default enumerator implementation of ConcurrentDictionary, but // there is one snafu. The keys that have not been read yet are of type // NotYetDeserializedPlaceholderValue, we need to deserialize them now. lock (this.enumLock) { foreach (KeyValuePair <string, object> itm in this.Items) { if (itm.Value is NotYetDeserializedPlaceholderValue) { // deserialize the value this.MemoizedDeserializeGet(itm.Key); } } try { return(this.Items.GetEnumerator()); } catch (Exception exc) { if (RedisSessionConfig.SessionExceptionLoggingDel != null) { RedisSessionConfig.SessionExceptionLoggingDel(exc); } } } return(new ConcurrentDictionary <string, object>().GetEnumerator()); }
/// <summary> /// Gets a hash from Redis and passes it to the constructor of RedisSessionStateItemCollection /// </summary> /// <param name="redisKey">The key of the Redis hash</param> /// <param name="context">The HttpContext of the current web request</param> /// <returns>An instance of RedisSessionStateItemCollection, may be empty if Redis call fails</returns> public static RedisSessionStateItemCollection GetItemFromRedis( string redisKey, HttpContextBase context, TimeSpan expirationTimeout) { RedisConnectionWrapper rConnWrap = RedisSessionStateStoreProvider.RedisConnWrapperFromContext( context); IDatabase redisConn = rConnWrap.GetConnection(); try { HashEntry[] redisData = redisConn.HashGetAll(redisKey); redisConn.KeyExpire(redisKey, expirationTimeout, CommandFlags.FireAndForget); return(new RedisSessionStateItemCollection( redisData, rConnWrap.ConnectionID, 0)); } catch (Exception e) { if (RedisSessionConfig.SessionExceptionLoggingDel != null) { RedisSessionConfig.SessionExceptionLoggingDel(e); } } return(new RedisSessionStateItemCollection()); }
private void LogException(HttpContextBase context, Exception ex) { if (RedisSessionConfig.RedisSessionAccessorExceptionLoggingDel != null) { RedisSessionConfig.RedisSessionAccessorExceptionLoggingDel(context, "RedisSessionAccessor unable to get Redis session for id: " + this.SessionRedisHashKey, ex); } }
/// <summary> /// Checks if any items have changed in the Session, and stores the results to Redis /// </summary> /// <param name="context">The HttpContext of the current web request</param> /// <param name="id">The Session Id and Redis key name</param> /// <param name="item">The Session properties</param> /// <param name="lockId">The object used to exclusively lock the Session (there shouldn't be one)</param> /// <param name="newItem">Whether or not the Session was created in this HttpContext</param> public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem) { try { string currentRedisHashId = RedisSessionStateStoreProvider.RedisHashIdFromSessionId( new HttpContextWrapper(context), id); LocalSharedSessionDictionary sharedSessDict = new LocalSharedSessionDictionary(); RedisSessionStateItemCollection redisItems = sharedSessDict.GetSessionForEndRequest(currentRedisHashId); if (redisItems != null) { RedisSessionStateStoreProvider.SerializeToRedis( new HttpContextWrapper(context), redisItems, currentRedisHashId, this.SessionTimeout); } } catch (Exception e) { if (RedisSessionConfig.SessionExceptionLoggingDel != null) { RedisSessionConfig.SessionExceptionLoggingDel(e); } } }
/// <summary> /// Helper method for getting a Redis key name from a Session Id /// </summary> /// <param name="context">The current HttpContext</param> /// <param name="sessId">The value of the Session ID cookie from the web request</param> /// <returns>A string which is the address of the Session in Redis</returns> public static string RedisHashIdFromSessionId(HttpContextBase context, string sessId) { if (RedisSessionConfig.RedisKeyFromSessionIdDel == null) { return(sessId); } else { return(RedisSessionConfig.RedisKeyFromSessionIdDel(context, sessId)); } }
/// <summary> /// Helper method for serializing objects to Redis /// </summary> /// <param name="confirmedChangedObjects">keys and values that have definitely changed</param> /// <param name="allObjects">keys and values that have been accessed during the current HttpContext</param> /// <param name="allObjectsOriginalState">keys and serialized values before we tampered with them</param> /// <param name="deletedObjects">keys that were deleted during the current HttpContext</param> /// <param name="currentRedisHashId">The current Redis key name</param> /// <param name="redisConn">A connection to Redis</param> public static void SerializeToRedis(HttpContextBase context, RedisSessionStateItemCollection redisItems, string currentRedisHashId, TimeSpan expirationTimeout) { RedisConnectionWrapper rConnWrap = RedisSessionStateStoreProvider.RedisConnWrapperFromContext(context); var setItems = new List <HashEntry>(); var delItems = new List <RedisValue>(); // Determine if we are adding or removing keys, separate them into their own lists // note that redisItems.GetChangedObjectsEnumerator contains complex logic foreach (KeyValuePair <string, string> changedObj in redisItems.GetChangedObjectsEnumerator()) { if (changedObj.Value != null) { setItems.Add(new HashEntry(changedObj.Key, changedObj.Value)); } else { delItems.Add(changedObj.Key); } } IDatabase redisConn = rConnWrap.GetConnection(currentRedisHashId); if (setItems.Count > 0) { HashEntry[] writeItems = setItems.ToArray(); redisConn.HashSet(currentRedisHashId, writeItems, CommandFlags.FireAndForget); // call appropriate delegate if set for changing keys if (RedisSessionConfig.RedisWriteFieldDel != null) { RedisSessionConfig.RedisWriteFieldDel(context, writeItems, currentRedisHashId); } } if (delItems != null && delItems.Count > 0) { RedisValue[] removeItems = delItems.ToArray(); redisConn.HashDelete(currentRedisHashId, removeItems, CommandFlags.FireAndForget); // call appropriate delegate if set for removing keys if (RedisSessionConfig.RedisRemoveFieldDel != null) { RedisSessionConfig.RedisRemoveFieldDel(context, removeItems, currentRedisHashId); } } // always refresh the timeout of the session hash redisConn.KeyExpire(currentRedisHashId, expirationTimeout, CommandFlags.FireAndForget); }
/// <summary> /// Initializes a new instance of the RedisSessionAccessor class, which provides access to a /// local Redis items collection outside of the standard ASP.NET pipeline Session hooks /// </summary> /// <param name="context">The context of the current request</param> public RedisSessionAccessor(HttpContextBase context) { try { this.RequestContext = context; this.SharedSessions = new LocalSharedSessionDictionary(); // if we have the session ID if (this.RequestContext.Request.Cookies[RedisSessionConfig.SessionHttpCookieName] != null) { this.SessionRedisHashKey = RedisSessionStateStoreProvider.RedisHashIdFromSessionId( this.RequestContext, this.RequestContext.Request.Cookies[RedisSessionConfig.SessionHttpCookieName].Value); } if (!string.IsNullOrEmpty(this.SessionRedisHashKey)) { RedisSessionStateItemCollection items = this.SharedSessions.GetSessionForBeginRequest( this.SessionRedisHashKey, (string redisKey) => { return(RedisSessionStateStoreProvider.GetItemFromRedis( redisKey, this.RequestContext, RedisSessionConfig.SessionTimeout)); }); this.Session = new FakeHttpSessionState( items, this.RequestContext.Request.Cookies[RedisSessionConfig.SessionHttpCookieName].Value); } } catch (Exception exc) { if (RedisSessionConfig.RedisSessionAccessorExceptionLoggingDel != null) { string errMsg = string.Format( "RedisSessionAccessor unable to get Redis session for id: {0}", this.SessionRedisHashKey); RedisSessionConfig.RedisSessionAccessorExceptionLoggingDel( context, errMsg, exc); } } }
/// <summary> /// Gets a Session from Redis, indicating a non-exclusive lock on the Session. Note that GetItemExclusive /// calls this method internally, meaning we do not support locks at all retrieving the Session. /// </summary> /// <param name="context">The HttpContext of the current request</param> /// <param name="id">The Session Id, which is the key name in Redis</param> /// <param name="locked">Whether or not the Session is locked to exclusive access for a single request /// thread</param> /// <param name="lockAge">The age of the lock</param> /// <param name="lockId">The object used to lock the Session</param> /// <param name="actions">Whether or not to initialize the Session (never)</param> /// <returns>The Session objects wrapped in a SessionStateStoreData object</returns> public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions) { locked = false; lockAge = new TimeSpan(0); lockId = null; actions = SessionStateActions.None; try { return(new SessionStateStoreData(GetSessionItems(context, id), SessionStateUtility.GetSessionStaticObjects(context), Convert.ToInt32(RedisSessionConfig.SessionTimeout.TotalMinutes))); } catch (Exception e) { RedisSessionConfig.LogSessionException(e); } return(CreateNewStoreData(context, Convert.ToInt32(RedisSessionConfig.SessionTimeout.TotalMinutes))); }
/// <summary> /// Gets a Session from Redis, indicating a non-exclusive lock on the Session. Note that GetItemExclusive /// calls this method internally, meaning we do not support locks at all retrieving the Session. /// </summary> /// <param name="context">The HttpContext of the current request</param> /// <param name="id">The Session Id, which is the key name in Redis</param> /// <param name="locked">Whether or not the Session is locked to exclusive access for a single request /// thread</param> /// <param name="lockAge">The age of the lock</param> /// <param name="lockId">The object used to lock the Session</param> /// <param name="actions">Whether or not to initialize the Session (never)</param> /// <returns>The Session objects wrapped in a SessionStateStoreData object</returns> public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions) { locked = false; lockAge = new TimeSpan(0); lockId = null; actions = SessionStateActions.None; if (id == null) { return(this.CreateNewStoreData(context, Convert.ToInt32(this.SessionTimeout.TotalMinutes))); } try { string parsedRedisHashId = RedisSessionStateStoreProvider.RedisHashIdFromSessionId( new HttpContextWrapper(context), id); LocalSharedSessionDictionary sharedSessDict = new LocalSharedSessionDictionary(); RedisSessionStateItemCollection items = sharedSessDict.GetSessionForBeginRequest( parsedRedisHashId, (redisKey) => { return(RedisSessionStateStoreProvider.GetItemFromRedis( redisKey, new HttpContextWrapper(context), this.SessionTimeout)); }); return(new SessionStateStoreData( items, SessionStateUtility.GetSessionStaticObjects(context), Convert.ToInt32(this.SessionTimeout.TotalMinutes))); } catch (Exception e) { if (RedisSessionConfig.SessionExceptionLoggingDel != null) { RedisSessionConfig.SessionExceptionLoggingDel(e); } } return(this.CreateNewStoreData(context, Convert.ToInt32(this.SessionTimeout.TotalMinutes))); }
/// <summary> /// Returns a class that allows iteration over the objects in the Session collection that have changed /// since being pulled from Redis. Each key is checked for being all of the following: key was /// accessed, key was not removed or set to null, if the key holds the same object reference as it /// did when pulled from redis, then the current object at the reference address is serialized and /// the serialized string is compared with the serialized string that was originally pulled from /// Redis. The last condition is to ensure Dirty checking is correct, i.e. /// ((List<string>)Session["a"]).Add("a"); /// does not change what Session["a"] refers to, but it does change the object and we need to write /// that back to Redis at the end. /// </summary> /// <returns>an IEnumerator that allows iteration over the changed elements of the Session.</returns> public IEnumerable <KeyValuePair <string, string> > GetChangedObjectsEnumerator() { var changedObjs = new List <KeyValuePair <string, string> >(); lock (enumLock) { try { // for items that have definitely changed (ints, strings, value types and // reference types whose refs have changed), add to the resulting list // and reset their serialized raw data foreach (KeyValuePair <string, ActionAndValue> changeData in ChangedKeysDict) { if (changeData.Value is SetValue) { UpdateSerializedDataForChanges(changedObjs, changeData); } else if (changeData.Value is DeleteValue) { UpdateSerializedDataForDeletes(changedObjs, changeData); } } // check each key that was accessed for changes foreach (KeyValuePair <string, object> itm in this.Items) { try { UpdateChangedObjectList(changedObjs, itm); } catch (Exception) { } } } catch (Exception enumExc) { RedisSessionConfig.LogSessionException(enumExc); } } return(changedObjs); }
static void EnsureLocalCacheFreshness(object sender, ElapsedEventArgs e) { try { rwlock.EnterWriteLock(); cacheFreshnessTimer.Stop(); LocalSessionInfo removed; foreach (string expKey in GetPurgableKeys()) { localCache.TryRemove(expKey, out removed); } } catch (Exception sharedDictExc) { RedisSessionConfig.LogSessionException(sharedDictExc); } finally { rwlock.ExitWriteLock(); cacheFreshnessTimer.Start(); } }
/// <summary> /// Checks if any items have changed in the Session, and stores the results to Redis /// </summary> /// <param name="context">The HttpContext of the current web request</param> /// <param name="id">The Session Id and Redis key name</param> /// <param name="item">The Session properties</param> /// <param name="lockId">The object used to exclusively lock the Session (there shouldn't be one)</param> /// <param name="newItem">Whether or not the Session was created in this HttpContext</param> public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem) { try { string currentRedisHashId = RedisSessionStateStoreProvider.RedisHashIdFromSessionId( new HttpContextWrapper(context), id); LocalSharedSessionDictionary sharedSessDict = new LocalSharedSessionDictionary(); RedisSessionStateItemCollection redisItems = sharedSessDict.GetSessionForEndRequest(currentRedisHashId); // we were unable to pull it from shared cache, meaning either this is the first request or // something went wrong with the local cache. We still have all the parts needed to write // to redis, however, by looking at SessionStateStoreData passed in from the Session module // and the current hash key provided by the id parameter. if (redisItems == null) { redisItems = (RedisSessionStateItemCollection)item.Items; } if (redisItems != null) { RedisSessionStateStoreProvider.SerializeToRedis( new HttpContextWrapper(context), redisItems, currentRedisHashId, this.SessionTimeout); } } catch (Exception e) { if (RedisSessionConfig.SessionExceptionLoggingDel != null) { RedisSessionConfig.SessionExceptionLoggingDel(e); } } }
public IEnumerator GetEnumerator() { // we are going to return the enumerator with keys, because // default asp.net implementation of ISessionStateImemCollection returns an enumerator that can be used to read all the key names in the collection. // The legacy asp.net code (e.g. TraceContext) requires that this enumerator was an enumerator by keys. lock (this.enumLock) { try { return(this.Items.Keys.GetEnumerator()); } catch (Exception exc) { if (RedisSessionConfig.SessionExceptionLoggingDel != null) { RedisSessionConfig.SessionExceptionLoggingDel(exc); } } } // To match ASP.NET behavior, dictionaries should match keys case insensitively return(new ConcurrentDictionary <string, object>(StringComparer.InvariantCultureIgnoreCase).GetEnumerator()); }
/// <summary> /// Returns a class that allows iteration over the objects in the Session collection that have changed /// since being pulled from Redis. Each key is checked for being all of the following: key was /// accessed, key was not removed or set to null, if the key holds the same object reference as it /// did when pulled from redis, then the current object at the reference address is serialized and /// the serialized string is compared with the serialized string that was originally pulled from /// Redis. The last condition is to ensure Dirty checking is correct, i.e. /// ((List<string>)Session["a"]).Add("a"); /// does not change what Session["a"] refers to, but it does change the object and we need to write /// that back to Redis at the end. /// </summary> /// <returns>an IEnumerator that allows iteration over the changed elements of the Session.</returns> public IEnumerable <KeyValuePair <string, string> > GetChangedObjectsEnumerator() { List <KeyValuePair <string, string> > changedObjs = new List <KeyValuePair <string, string> >(); lock (enumLock) { try { // for items that have definitely changed (ints, strings, value types and // reference types whose refs have changed), add to the resulting list // and reset their serialized raw data foreach (KeyValuePair <string, ActionAndValue> changeData in ChangedKeysDict) { if (changeData.Value is SetValue) { string newSerVal = this.cereal.SerializeOne(changeData.Key, changeData.Value.Value); // now set the SerializedRawData of the key to what we are returning to the // Session provider, which will write it to Redis so the next thread won't // think the obj has changed when it tries to write this.SerializedRawData.AddOrUpdate( changeData.Key, (key) => { // ok we added to the initial state, return the new value changedObjs.Add( new KeyValuePair <string, string>( changeData.Key, newSerVal)); return(newSerVal); }, (key, oldVal) => { if (oldVal != newSerVal) { // ok we reset the initial state, return the new value changedObjs.Add( new KeyValuePair <string, string>( changeData.Key, newSerVal)); return(newSerVal); } return(oldVal); }); } if (changeData.Value is DeleteValue) { // remove what SerializedRawData thinks is the original Redis state for the key, // because it will be removed by the Session provider once it reads from the // enumerator we are returning string remSerVal; if (this.SerializedRawData.TryRemove(changeData.Key, out remSerVal)) { // null means delete to the serializer, perhaps change this in the future changedObjs.Add( new KeyValuePair <string, string>( changeData.Key, null)); } } } // check each key that was accessed for changes foreach (KeyValuePair <string, object> itm in this.Items) { try { // only check keys that are not already in the changedObjs list bool alreadyAdded = false; foreach (KeyValuePair <string, string> markedItem in changedObjs) { if (markedItem.Key == itm.Key) { alreadyAdded = true; break; } } if (!alreadyAdded && !(itm.Value is NotYetDeserializedPlaceholderValue)) { string serVal = this.cereal.SerializeOne(itm.Key, itm.Value); string origSerVal; if (this.SerializedRawData.TryGetValue(itm.Key, out origSerVal)) { if (serVal != origSerVal) { // object's value has changed, add to output list changedObjs.Add( new KeyValuePair <string, string>( itm.Key, serVal)); // and reset the original state of it to what it is now this.SerializedRawData.TryUpdate( itm.Key, serVal, origSerVal); } } } } catch (Exception) { } } } catch (Exception enumExc) { if (RedisSessionConfig.SessionExceptionLoggingDel != null) { RedisSessionConfig.SessionExceptionLoggingDel(enumExc); } } } return(changedObjs); }
/// <summary> /// Helper method for getting a Redis key name from a Session Id /// </summary> /// <param name="context">The current HttpContext</param> /// <param name="sessId">The value of the Session ID cookie from the web request</param> /// <returns>A string which is the address of the Session in Redis</returns> public static string RedisHashIdFromSessionId(HttpContextBase context, string sessId) { return((RedisSessionConfig.RedisKeyFromSessionIdDel != null) ? RedisSessionConfig.RedisKeyFromSessionIdDel(context, sessId) : sessId); }