1 //------------------------------------------------------------------------------
2 // <copyright file="InProcStateClientManager.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
7 namespace System.Web.SessionState {
8 using System.Threading;
9 using System.Runtime.Serialization.Formatters.Binary;
10 using System.Runtime.Serialization;
13 using System.Collections;
16 using System.Web.Caching;
17 using System.Web.Util;
19 using System.Collections.Specialized;
20 using System.Configuration.Provider;
22 internal sealed class InProcSessionStateStore : SessionStateStoreProviderBase {
23 internal static readonly int CACHEKEYPREFIXLENGTH = CacheInternal.PrefixInProcSessionState.Length;
24 internal static readonly int NewLockCookie = 1;
26 CacheItemRemovedCallback _callback;
28 SessionStateItemExpireCallback _expireCallback;
32 * Handle callbacks from the cache for session state expiry
34 public void OnCacheItemRemoved(String key, Object value, CacheItemRemovedReason reason) {
35 InProcSessionState state;
38 Debug.Trace("SessionOnEnd", "OnCacheItemRemoved called, reason = " + reason);
40 PerfCounters.DecrementCounter(AppPerfCounter.SESSIONS_ACTIVE);
42 state = (InProcSessionState) value;
44 if ((state._flags & (int)SessionStateItemFlags.IgnoreCacheItemRemoved) != 0 ||
45 (state._flags & (int)SessionStateItemFlags.Uninitialized) != 0) {
46 Debug.Trace("SessionOnEnd", "OnCacheItemRemoved ignored");
51 case CacheItemRemovedReason.Expired:
52 PerfCounters.IncrementCounter(AppPerfCounter.SESSIONS_TIMED_OUT);
55 case CacheItemRemovedReason.Removed:
56 PerfCounters.IncrementCounter(AppPerfCounter.SESSIONS_ABANDONED);
65 if (_expireCallback != null) {
66 id = key.Substring(CACHEKEYPREFIXLENGTH);
68 _expireCallback(id, SessionStateUtility.CreateLegitStoreData(null,
75 private string CreateSessionStateCacheKey(String id) {
76 return CacheInternal.PrefixInProcSessionState + id;
79 public override void Initialize(string name, NameValueCollection config)
81 if (String.IsNullOrEmpty(name))
82 name = "InProc Session State Provider";
83 base.Initialize(name, config);
85 _callback = new CacheItemRemovedCallback(this.OnCacheItemRemoved);
88 public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
90 _expireCallback = expireCallback;
94 public override void Dispose()
98 public override void InitializeRequest(HttpContext context)
102 SessionStateStoreData DoGet(HttpContext context,
106 out TimeSpan lockAge,
108 out SessionStateActions actionFlags) {
109 string key = CreateSessionStateCacheKey(id);
111 // Set default return values
114 lockAge = TimeSpan.Zero;
117 // Not technically necessary for InProc, but we do it to be consistent
119 SessionIDManager.CheckIdLength(id, true /* throwOnFail */);
121 InProcSessionState state = (InProcSessionState)HttpRuntime.Cache.InternalCache.Get(key);
123 bool lockedByOther; // True if the state is locked by another session
126 initialFlags = (int)state._flags;
127 if ((initialFlags & (int)SessionStateItemFlags.Uninitialized) != 0) {
128 // It is an uninitialized item. We have to remove that flag.
129 // We only allow one request to do that.
130 // For details, see inline doc for SessionStateItemFlags.Uninitialized flag.
132 // If initialFlags != return value of CompareExchange, it means another request has
135 Debug.Trace("SessionStateClientSet", "Removing the Uninit flag for item; key = " + key);
136 if (initialFlags == Interlocked.CompareExchange(
138 initialFlags & (~((int)SessionStateItemFlags.Uninitialized)),
140 actionFlags = SessionStateActions.InitializeItem;
145 lockedByOther = true;
147 // If unlocked, use a spinlock to test and lock the state.
148 if (!state._locked) {
149 state._spinLock.AcquireWriterLock();
151 if (!state._locked) {
152 lockedByOther = false;
153 state._locked = true;
154 state._utcLockDate = DateTime.UtcNow;
157 lockId = state._lockCookie;
160 state._spinLock.ReleaseWriterLock();
164 // It's already locked by another request. Return the lockCookie to caller.
165 lockId = state._lockCookie;
170 state._spinLock.AcquireReaderLock();
172 lockedByOther = state._locked;
173 lockId = state._lockCookie;
176 state._spinLock.ReleaseReaderLock();
181 // Item found, but locked
183 lockAge = DateTime.UtcNow - state._utcLockDate;
187 return SessionStateUtility.CreateLegitStoreData(context, state._sessionItems,
188 state._staticObjects, state._timeout);
196 public override SessionStateStoreData GetItem(HttpContext context,
199 out TimeSpan lockAge,
201 out SessionStateActions actionFlags) {
202 return DoGet(context, id, false, out locked, out lockAge, out lockId, out actionFlags);
206 public override SessionStateStoreData GetItemExclusive(HttpContext context,
209 out TimeSpan lockAge,
211 out SessionStateActions actionFlags) {
212 return DoGet(context, id, true, out locked, out lockAge, out lockId, out actionFlags);
215 // Unlock an item locked by GetExclusive
216 // 'lockId' is the lock context returned by previous call to GetExclusive
217 public override void ReleaseItemExclusive(HttpContext context,
220 Debug.Assert(lockId != null, "lockId != null");
222 string key = CreateSessionStateCacheKey(id);
223 int lockCookie = (int)lockId;
225 SessionIDManager.CheckIdLength(id, true /* throwOnFail */);
227 InProcSessionState state = (InProcSessionState)HttpRuntime.Cache.InternalCache.Get(key);
229 /* If the state isn't there, we probably took too long to run. */
234 state._spinLock.AcquireWriterLock();
236 if (state._locked && lockCookie == state._lockCookie) {
237 state._locked = false;
241 state._spinLock.ReleaseWriterLock();
246 public override void SetAndReleaseItemExclusive(HttpContext context,
248 SessionStateStoreData item,
251 string key = CreateSessionStateCacheKey(id);
252 bool doInsert = true;
253 CacheStoreProvider cacheInternal = HttpRuntime.Cache.InternalCache;
254 int lockCookieForInsert = NewLockCookie;
255 ISessionStateItemCollection items = null;
256 HttpStaticObjectsCollection staticObjects = null;
258 Debug.Assert(item.Items != null, "item.Items != null");
259 Debug.Assert(item.StaticObjects != null, "item.StaticObjects != null");
260 Debug.Assert(item.Timeout <= SessionStateModule.MAX_CACHE_BASED_TIMEOUT_MINUTES, "item.Timeout <= SessionStateModule.MAX_CACHE_BASED_TIMEOUT_MINUTES");
262 SessionIDManager.CheckIdLength(id, true /* throwOnFail */);
264 if (item.Items.Count > 0) {
268 if (!item.StaticObjects.NeverAccessed) {
269 staticObjects = item.StaticObjects;
273 Debug.Assert(lockId != null, "lockId != null");
274 InProcSessionState stateCurrent = (InProcSessionState) cacheInternal.Get(key);
275 int lockCookie = (int)lockId;
277 /* If the state isn't there, we probably took too long to run. */
278 if (stateCurrent == null)
281 Debug.Trace("SessionStateClientSet", "state is inStorage; key = " + key);
282 Debug.Assert((stateCurrent._flags & (int)SessionStateItemFlags.Uninitialized) == 0, "Should never set an unitialized item; key = " + key);
284 stateCurrent._spinLock.AcquireWriterLock();
287 /* Only set the state if we are the owner */
288 if (!stateCurrent._locked || stateCurrent._lockCookie != lockCookie) {
289 Debug.Trace("SessionStateClientSet", "Leave because we're not the owner; key = " + key);
293 /* We can change the state in place if the timeout hasn't changed */
294 if (stateCurrent._timeout == item.Timeout) {
302 stateCurrent._flags);
304 // Don't need to insert into the Cache because an in-place copy is good enough.
306 Debug.Trace("SessionStateClientSet", "Changing state inplace; key = " + key);
309 /* We are going to insert a new item to replace the current one in Cache
310 because the expiry time has changed.
312 Pleas note that an insert will cause the Session_End to be incorrectly raised.
314 Please note that the item itself should not expire between now and
315 where we do UtcInsert below because cacheInternal.Get above have just
316 updated its expiry time.
318 stateCurrent._flags |= (int)SessionStateItemFlags.IgnoreCacheItemRemoved;
320 /* By setting _lockCookie to 0, we prevent an overwriting by ReleaseExclusive
321 when we drop the lock.
322 The scenario can happen if another request is polling and trying to prempt
323 the lock we have on the item.
325 lockCookieForInsert = lockCookie;
326 stateCurrent._lockCookie = 0;
330 stateCurrent._spinLock.ReleaseWriterLock();
335 Debug.Trace("SessionStateClientSet", "Inserting state into Cache; key = " + key);
336 InProcSessionState state = new InProcSessionState(
348 // protected from ThreadAbortEx
349 cacheInternal.Insert(key, state, new CacheInsertOptions() {
350 SlidingExpiration = new TimeSpan(0, state._timeout, 0),
351 Priority = CacheItemPriority.NotRemovable,
352 OnRemovedCallback = _callback
354 PerfCounters.IncrementCounter(AppPerfCounter.SESSIONS_TOTAL);
355 PerfCounters.IncrementCounter(AppPerfCounter.SESSIONS_ACTIVE);
363 public override void CreateUninitializedItem(HttpContext context, String id, int timeout) {
364 string key = CreateSessionStateCacheKey(id);
366 Debug.Assert(timeout <= SessionStateModule.MAX_CACHE_BASED_TIMEOUT_MINUTES, "item.Timeout <= SessionStateModule.MAX_CACHE_BASED_TIMEOUT_MINUTES");
368 SessionIDManager.CheckIdLength(id, true /* throwOnFail */);
370 Debug.Trace("SessionStateClientSet", "Inserting an uninitialized item into Cache; key = " + key);
372 InProcSessionState state = new InProcSessionState(
379 (int)SessionStateItemFlags.Uninitialized);
382 // We do not want to overwrite an item with an uninitialized item if it is
383 // already in the cache
387 // protected from ThreadAbortEx
388 object existingEntry = HttpRuntime.Cache.InternalCache.Add(key, state, new CacheInsertOptions() {
389 SlidingExpiration = new TimeSpan(0, timeout, 0),
390 Priority = CacheItemPriority.NotRemovable,
391 OnRemovedCallback = _callback
393 if (existingEntry == null) {
394 PerfCounters.IncrementCounter(AppPerfCounter.SESSIONS_TOTAL);
395 PerfCounters.IncrementCounter(AppPerfCounter.SESSIONS_ACTIVE);
400 // Remove an item. Note that the item is originally obtained by GetExclusive
401 // Same note as Set on lockId
402 public override void RemoveItem(HttpContext context,
405 SessionStateStoreData item) {
406 Debug.Assert(lockId != null, "lockId != null");
408 string key = CreateSessionStateCacheKey(id);
409 CacheStoreProvider cacheInternal = HttpRuntime.Cache.InternalCache;
410 int lockCookie = (int)lockId;
412 SessionIDManager.CheckIdLength(id, true /* throwOnFail */);
414 InProcSessionState state = (InProcSessionState) cacheInternal.Get(key);
416 /* If the item isn't there, we probably took too long to run. */
420 state._spinLock.AcquireWriterLock();
423 /* Only remove the item if we are the owner */
424 if (!state._locked || state._lockCookie != lockCookie)
427 /* prevent overwriting when we drop the lock */
428 state._lockCookie = 0;
431 state._spinLock.ReleaseWriterLock();
434 cacheInternal.Remove(key);
439 // Reset the expire time of an item based on its timeout value
440 public override void ResetItemTimeout(HttpContext context, String id)
442 string key = CreateSessionStateCacheKey(id);
444 SessionIDManager.CheckIdLength(id, true /* throwOnFail */);
445 HttpRuntime.Cache.InternalCache.Get(key);
448 // Create a new SessionStateStoreData.
449 public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
451 return SessionStateUtility.CreateLegitStoreData(context, null, null, timeout);
454 // Called during EndRequest event
455 public override void EndRequest(HttpContext context)
459 [System.Diagnostics.Conditional("DBG")]
460 internal static void TraceSessionStats() {
462 Debug.Trace("SessionState",
463 "sessionsTotal=" + PerfCounters.GetCounter(AppPerfCounter.SESSIONS_TOTAL) +
464 ", sessionsActive=" + PerfCounters.GetCounter(AppPerfCounter.SESSIONS_ACTIVE) +
465 ", sessionsAbandoned=" + PerfCounters.GetCounter(AppPerfCounter.SESSIONS_ABANDONED) +
466 ", sessionsTimedout=" + PerfCounters.GetCounter(AppPerfCounter.SESSIONS_TIMED_OUT)
472 internal sealed class InProcSessionState {
473 internal ISessionStateItemCollection _sessionItems;
474 internal HttpStaticObjectsCollection _staticObjects;
475 internal int _timeout; // USed to set slidingExpiration in CacheEntry
476 internal bool _locked; // If it's locked by another thread
477 internal DateTime _utcLockDate;
478 internal int _lockCookie;
479 #pragma warning disable 0649
480 internal ReadWriteSpinLock _spinLock;
481 #pragma warning restore 0649
484 internal InProcSessionState(
485 ISessionStateItemCollection sessionItems,
486 HttpStaticObjectsCollection staticObjects,
489 DateTime utcLockDate,
493 Copy(sessionItems, staticObjects, timeout, locked, utcLockDate, lockCookie, flags);
497 ISessionStateItemCollection sessionItems,
498 HttpStaticObjectsCollection staticObjects,
501 DateTime utcLockDate,
505 this._sessionItems = sessionItems;
506 this._staticObjects = staticObjects;
507 this._timeout = timeout;
508 this._locked = locked;
509 this._utcLockDate = utcLockDate;
510 this._lockCookie = lockCookie;