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);
                }
            }
        }
Пример #2
0
        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());
        }
Пример #4
0
 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)));
        }
Пример #11
0
        /// <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&lt;string&gt;)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&lt;string&gt;)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);
 }