1 //------------------------------------------------------------------------------
2 // <copyright file="SessionStateModule.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
10 * Copyright (c) 1998-2002, Microsoft Corporation
14 namespace System.Web.SessionState {
17 using System.Threading;
18 using System.Collections;
19 using System.Configuration;
21 using System.Web.Caching;
22 using System.Web.Util;
23 using System.Web.Configuration;
25 using System.Security.Cryptography;
26 using System.Data.SqlClient;
27 using System.Globalization;
28 using System.Security.Permissions;
30 using System.Threading.Tasks;
31 using System.Web.Hosting;
32 using System.Web.Management;
33 using Microsoft.Win32;
34 using System.Collections.Concurrent;
35 using System.Collections.Generic;
37 public delegate void SessionStateItemExpireCallback(
38 string id, SessionStateStoreData item);
40 class SessionOnEndTargetWorkItem {
41 SessionOnEndTarget _target;
42 HttpSessionState _sessionState;
44 internal SessionOnEndTargetWorkItem(SessionOnEndTarget target, HttpSessionState sessionState) {
46 _sessionState = sessionState;
49 internal void RaiseOnEndCallback() {
50 _target.RaiseOnEnd(_sessionState);
55 * Calls the OnSessionEnd event. We use an object other than the SessionStateModule
56 * because the state of the module is unknown - it could have been disposed
57 * when a session ends.
59 class SessionOnEndTarget {
60 internal int _sessionEndEventHandlerCount;
62 internal SessionOnEndTarget() {
65 internal int SessionEndEventHandlerCount {
67 return _sessionEndEventHandlerCount;
70 _sessionEndEventHandlerCount = value;
74 internal void RaiseOnEnd(HttpSessionState sessionState) {
75 Debug.Trace("SessionOnEnd", "Firing OnSessionEnd for " + sessionState.SessionID);
77 if (_sessionEndEventHandlerCount > 0) {
78 HttpApplicationFactory.EndSession(sessionState, this, EventArgs.Empty);
82 internal void RaiseSessionOnEnd(String id, SessionStateStoreData item) {
83 HttpSessionStateContainer sessionStateContainer = new HttpSessionStateContainer(
89 SessionStateModule.s_configCookieless,
90 SessionStateModule.s_configMode,
93 HttpSessionState sessionState = new HttpSessionState(sessionStateContainer);
95 if (HttpRuntime.ShutdownInProgress) {
96 // call directly when shutting down
97 RaiseOnEnd(sessionState);
100 // post via thread pool
101 SessionOnEndTargetWorkItem workItem = new SessionOnEndTargetWorkItem(this, sessionState);
102 WorkItem.PostInternal(new WorkItemCallback(workItem.RaiseOnEndCallback));
110 * The sesssion state module provides session state services
111 * for an application.
115 /// <para>[To be supplied.]</para>
117 public sealed class SessionStateModule : ISessionStateModule {
119 internal const string SQL_CONNECTION_STRING_DEFAULT = "data source=localhost;Integrated Security=SSPI";
120 internal const string STATE_CONNECTION_STRING_DEFAULT = "tcpip=loopback:42424";
121 internal const int TIMEOUT_DEFAULT = 20;
122 internal const SessionStateMode MODE_DEFAULT = SessionStateMode.InProc;
124 private static long LOCKED_ITEM_POLLING_INTERVAL = 500; // in milliseconds
125 static readonly TimeSpan LOCKED_ITEM_POLLING_DELTA = new TimeSpan(250 * TimeSpan.TicksPerMillisecond);
127 static readonly TimeSpan DEFAULT_DBG_EXECUTION_TIMEOUT = new TimeSpan(0, 0, System.Web.Compilation.PageCodeDomTreeGenerator.DebugScriptTimeout);
129 // When we are using Cache to store session state (InProc and StateServer),
130 // can't specify a timeout value larger than 1 year because CacheEntry ctor
131 // will throw an exception.
132 internal const int MAX_CACHE_BASED_TIMEOUT_MINUTES = 365 * 24 * 60;
135 static int s_timeout;
137 #pragma warning disable 0649
138 static ReadWriteSpinLock s_lock;
139 #pragma warning restore 0649
141 static bool s_trustLevelInsufficient;
143 static TimeSpan s_configExecutionTimeout;
145 static bool s_configRegenerateExpiredSessionId;
146 static bool s_useHostingIdentity;
147 internal static HttpCookieMode s_configCookieless;
148 internal static SessionStateMode s_configMode;
150 // This is used as a perf optimization for IIS7 Integrated Mode. If session state is released
151 // in ReleaseState, we can disable the EndRequest notification if the mode is InProc or StateServer
152 // because neither InProcSessionStateStore.EndRequest nor OutOfProcSessionStateStore.EndRequest
154 static bool s_canSkipEndRequestCall;
156 private static bool s_PollIntervalRegLookedUp = false;
157 private static object s_PollIntervalRegLock = new object();
159 private static ConcurrentDictionary<string, int> s_queuedRequestsNumPerSession = new ConcurrentDictionary<string, int>();
161 // Check if we can optmize for InProc case.
162 // Optimization details:
164 // If we are in InProc mode, and cookieless=false, in certain scenarios we
165 // can avoid reading the session ID from the cookies because that's an expensive operation.
166 // To allow that, we use s_sessionEverSet to keep track of whether we've ever created
167 // any session state.
169 // If no session has ever be created, we can optimize in the following two cases:
171 // Case 1: Page has disabled session state
172 // In BeginAcquireState, we usually read the session ID, and reset the timeout value
173 // of the session state. However, since no session has ever been created, we can
174 // skip both reading the session id and resetting the timeout.
176 // Case 2: Page has enabled session state
177 // In this case, we will delay reading (and creating it if not found) the session ID
178 // until it's really needed. (e.g. from HttpSessionStateContainer.SessionID)
180 // Please note that we optimize only if the app is using SessionIDManager
181 // as the session ID provider; otherwise, we do not have knowledge about
182 // the provider in order to optimize safely.
184 // And we will delay reading the id only if we are using cookie to store the session ID. If we
185 // use cookieless, in the delayed session ID creation scenario, cookieless requires a redirect,
186 // and it'll be bad to do that in the middle of a page execution.
188 static bool s_allowInProcOptimization;
189 static bool s_sessionEverSet;
192 // Another optimization is to delay the creation of a new session state store item
193 // until it's needed.
194 static bool s_allowDelayedStateStoreItemCreation;
195 static HttpSessionStateContainer s_delayedSessionState = new HttpSessionStateContainer();
197 /* per application vars */
198 EventHandler _sessionStartEventHandler;
200 TimerCallback _timerCallback;
201 volatile int _timerId;
202 ISessionIDManager _idManager;
203 bool _usingAspnetSessionIdManager;
204 SessionStateStoreProviderBase _store;
205 bool _supportSessionExpiry;
206 IPartitionResolver _partitionResolver;
207 bool _ignoreImpersonation;
208 readonly SessionOnEndTarget _onEndTarget = new SessionOnEndTarget();
210 /* per request data goes in _rq* variables */
213 HttpSessionStateContainer _rqSessionState;
216 ISessionStateItemCollection _rqSessionItems;
217 HttpStaticObjectsCollection _rqStaticObjects;
218 bool _rqIsNewSession;
219 bool _rqSessionStateNotFound;
221 HttpContext _rqContext;
222 HttpAsyncResult _rqAr;
223 SessionStateStoreData _rqItem;
224 object _rqLockId; // The id of its SessionStateItem ownership
225 // If the ownership change hands (e.g. this ownership
226 // times out), the lockId of the item at the store
229 DateTime _rqLastPollCompleted;
230 TimeSpan _rqExecutionTimeout;
232 SessionStateActions _rqActionFlags;
233 ImpersonationContext _rqIctx;
234 internal int _rqChangeImpersonationRefCount;
235 ImpersonationContext _rqTimerThreadImpersonationIctx;
236 bool _rqSupportSessionIdReissue;
240 /// Initializes a new instance of the <see cref='System.Web.State.SessionStateModule'/>
244 [SecurityPermission(SecurityAction.Demand, Unrestricted=true)]
245 public SessionStateModule() {
248 static bool CheckTrustLevel(SessionStateSection config) {
249 switch (config.Mode) {
250 case SessionStateMode.SQLServer:
251 case SessionStateMode.StateServer:
252 return HttpRuntime.HasAspNetHostingPermission(AspNetHostingPermissionLevel.Medium);
255 case SessionStateMode.Off:
256 case SessionStateMode.InProc: // In-proc session doesn't require any trust level (part of ASURT 124513)
261 [AspNetHostingPermission(SecurityAction.Assert, Level=AspNetHostingPermissionLevel.Low)]
262 private SessionStateStoreProviderBase SecureInstantiateProvider(ProviderSettings settings) {
263 return (SessionStateStoreProviderBase)ProvidersHelper.InstantiateProvider(settings, typeof(SessionStateStoreProviderBase));
266 // Create an instance of the custom store as specified in the config file
267 SessionStateStoreProviderBase InitCustomStore(SessionStateSection config) {
268 string providerName = config.CustomProvider;
271 if (String.IsNullOrEmpty(providerName)) {
272 throw new ConfigurationErrorsException(
273 SR.GetString(SR.Invalid_session_custom_provider, providerName),
274 config.ElementInformation.Properties["customProvider"].Source, config.ElementInformation.Properties["customProvider"].LineNumber);
277 ps = config.Providers[providerName];
279 throw new ConfigurationErrorsException(
280 SR.GetString(SR.Missing_session_custom_provider, providerName),
281 config.ElementInformation.Properties["customProvider"].Source, config.ElementInformation.Properties["customProvider"].LineNumber);
284 return SecureInstantiateProvider(ps);
287 IPartitionResolver InitPartitionResolver(SessionStateSection config) {
288 string partitionResolverType = config.PartitionResolverType;
290 IPartitionResolver iResolver;
292 if (String.IsNullOrEmpty(partitionResolverType)) {
296 if (config.Mode != SessionStateMode.StateServer &&
297 config.Mode != SessionStateMode.SQLServer) {
298 throw new ConfigurationErrorsException(SR.GetString(SR.Cant_use_partition_resolve),
299 config.ElementInformation.Properties["partitionResolverType"].Source, config.ElementInformation.Properties["partitionResolverType"].LineNumber);
303 resolverType = ConfigUtil.GetType(partitionResolverType, "partitionResolverType", config);
304 ConfigUtil.CheckAssignableType(typeof(IPartitionResolver), resolverType, config, "partitionResolverType");
306 iResolver = (IPartitionResolver)HttpRuntime.CreatePublicInstance(resolverType);
307 iResolver.Initialize();
312 ISessionIDManager InitSessionIDManager(SessionStateSection config) {
313 string sessionIDManagerType = config.SessionIDManagerType;
314 ISessionIDManager iManager;
316 if (String.IsNullOrEmpty(sessionIDManagerType)) {
317 iManager = new SessionIDManager();
318 _usingAspnetSessionIdManager = true;
323 managerType = ConfigUtil.GetType(sessionIDManagerType, "sessionIDManagerType", config);
324 ConfigUtil.CheckAssignableType(typeof(ISessionIDManager), managerType, config, "sessionIDManagerType");
326 iManager = (ISessionIDManager)HttpRuntime.CreatePublicInstance(managerType);
329 iManager.Initialize();
334 void InitModuleFromConfig(HttpApplication app, SessionStateSection config) {
335 if (config.Mode == SessionStateMode.Off) {
339 app.AddOnAcquireRequestStateAsync(
340 new BeginEventHandler(this.BeginAcquireState),
341 new EndEventHandler(this.EndAcquireState));
343 app.ReleaseRequestState += new EventHandler(this.OnReleaseState);
344 app.EndRequest += new EventHandler(this.OnEndRequest);
346 _partitionResolver = InitPartitionResolver(config);
348 switch (config.Mode) {
349 case SessionStateMode.InProc:
350 if (HttpRuntime.UseIntegratedPipeline) {
351 s_canSkipEndRequestCall = true;
353 _store = new InProcSessionStateStore();
354 _store.Initialize(null, null);
357 #if !FEATURE_PAL // FEATURE_PAL does not enable out of proc session state
358 case SessionStateMode.StateServer:
359 if (HttpRuntime.UseIntegratedPipeline) {
360 s_canSkipEndRequestCall = true;
362 _store = new OutOfProcSessionStateStore();
363 ((OutOfProcSessionStateStore)_store).Initialize(null, null, _partitionResolver);
366 case SessionStateMode.SQLServer:
367 _store = new SqlSessionStateStore();
368 ((SqlSessionStateStore)_store).Initialize(null, null, _partitionResolver);
370 ((SqlSessionStateStore)_store).SetModule(this);
373 #else // !FEATURE_PAL
374 case SessionStateMode.StateServer:
375 throw new NotImplementedException("ROTORTODO");
378 case SessionStateMode.SQLServer:
379 throw new NotImplementedException("ROTORTODO");
381 #endif // !FEATURE_PAL
383 case SessionStateMode.Custom:
384 _store = InitCustomStore(config);
391 // We depend on SessionIDManager to manage session id
392 _idManager = InitSessionIDManager(config);
394 if ((config.Mode == SessionStateMode.InProc || config.Mode == SessionStateMode.StateServer) &&
395 _usingAspnetSessionIdManager) {
396 // If we're using InProc mode or StateServer mode, and also using our own session id module,
397 // we know we don't care about impersonation in our all session state store read/write
398 // and session id read/write.
399 _ignoreImpersonation = true;
404 public void Init(HttpApplication app) {
405 bool initModuleCalled = false;
406 SessionStateSection config = RuntimeConfig.GetAppConfig().SessionState;
408 if (!s_oneTimeInit) {
409 s_lock.AcquireWriterLock();
411 if (!s_oneTimeInit) {
412 InitModuleFromConfig(app, config);
413 initModuleCalled = true;
415 if (!CheckTrustLevel(config))
416 s_trustLevelInsufficient = true;
418 s_timeout = (int)config.Timeout.TotalMinutes;
420 s_useHostingIdentity = config.UseHostingIdentity;
422 // See if we can try InProc optimization. See inline doc of s_allowInProcOptimization
424 if (config.Mode == SessionStateMode.InProc &&
425 _usingAspnetSessionIdManager) {
426 s_allowInProcOptimization = true;
429 if (config.Mode != SessionStateMode.Custom &&
430 config.Mode != SessionStateMode.Off &&
431 !config.RegenerateExpiredSessionId) {
432 s_allowDelayedStateStoreItemCreation = true;
435 s_configExecutionTimeout = RuntimeConfig.GetConfig().HttpRuntime.ExecutionTimeout;
437 s_configRegenerateExpiredSessionId = config.RegenerateExpiredSessionId;
438 s_configCookieless = config.Cookieless;
439 s_configMode = config.Mode;
441 // The last thing to set in this if-block.
442 s_oneTimeInit = true;
444 Debug.Trace("SessionStateModuleInit",
445 "Configuration: _mode=" + config.Mode +
446 ";Timeout=" + config.Timeout +
447 ";CookieMode=" + config.Cookieless +
448 ";SqlConnectionString=" + config.SqlConnectionString +
449 ";StateConnectionString=" + config.StateConnectionString +
450 ";s_allowInProcOptimization=" + s_allowInProcOptimization +
451 ";s_allowDelayedStateStoreItemCreation=" + s_allowDelayedStateStoreItemCreation);
456 s_lock.ReleaseWriterLock();
460 if (!initModuleCalled) {
461 InitModuleFromConfig(app, config);
464 if (s_trustLevelInsufficient) {
465 throw new HttpException(SR.GetString(SR.Session_state_need_higher_trust));
471 /// <para>[To be supplied.]</para>
473 public void Dispose() {
474 if (_timer != null) {
475 ((IDisposable)_timer).Dispose();
478 if (_store != null) {
483 void ResetPerRequestFields() {
484 Debug.Assert(_rqIctx == null, "_rqIctx == null");
485 Debug.Assert(_rqChangeImpersonationRefCount == 0, "_rqChangeImpersonationRefCount == 0");
487 _rqSessionState = null;
489 _rqSessionItems = null;
490 _rqStaticObjects = null;
491 _rqIsNewSession = false;
492 _rqSessionStateNotFound = true;
499 _rqLastPollCompleted = DateTime.MinValue;
500 _rqExecutionTimeout = TimeSpan.Zero;
501 _rqAddedCookie = false;
505 _rqChangeImpersonationRefCount = 0;
506 _rqTimerThreadImpersonationIctx = null;
507 _rqSupportSessionIdReissue = false;
511 * Add a OnStart event handler.
513 * @param sessionEventHandler
517 /// <para>[To be supplied.]</para>
519 public event EventHandler Start {
521 _sessionStartEventHandler += value;
524 _sessionStartEventHandler -= value;
528 void RaiseOnStart(EventArgs e) {
529 if (_sessionStartEventHandler == null)
532 Debug.Trace("SessionStateModuleRaiseOnStart",
533 "Session_Start called for session id:" + _rqId);
535 // Session_OnStart for ASPCOMPAT pages has to be raised from an STA thread
537 if (HttpRuntime.ApartmentThreading || _rqContext.InAspCompatMode) {
538 #if !FEATURE_PAL // FEATURE_PAL does not enable COM
539 AspCompatApplicationStep.RaiseAspCompatEvent(
541 _rqContext.ApplicationInstance,
543 _sessionStartEventHandler,
546 #else // !FEATURE_PAL
547 throw new NotImplementedException ("ROTORTODO");
548 #endif // !FEATURE_PAL
552 if (HttpContext.Current == null) {
553 // This can happen if it's called by a timer thread
554 DisposableHttpContextWrapper.SwitchContext(_rqContext);
557 _sessionStartEventHandler(this, e);
562 * Fire the OnStart event.
566 void OnStart(EventArgs e) {
571 * Add a OnEnd event handler.
573 * @param sessionEventHandler
577 /// <para>[To be supplied.]</para>
579 public event EventHandler End {
582 if (_store != null && _onEndTarget.SessionEndEventHandlerCount == 0) {
583 _supportSessionExpiry = _store.SetItemExpireCallback(
584 new SessionStateItemExpireCallback(_onEndTarget.RaiseSessionOnEnd));
586 ++_onEndTarget.SessionEndEventHandlerCount;
591 --_onEndTarget.SessionEndEventHandlerCount;
593 if (_store != null && _onEndTarget.SessionEndEventHandlerCount == 0) {
594 _store.SetItemExpireCallback(null);
595 _supportSessionExpiry = false;
602 * Acquire session state
604 IAsyncResult BeginAcquireState(Object source, EventArgs e, AsyncCallback cb, Object extraData) {
606 bool isCompleted = true;
607 bool skipReadingId = false;
609 Debug.Trace("SessionStateModuleOnAcquireState", "Beginning SessionStateModule::OnAcquireState");
611 _acquireCalled = true;
612 _releaseCalled = false;
613 ResetPerRequestFields();
615 _rqContext = ((HttpApplication)source).Context;
616 _rqAr = new HttpAsyncResult(cb, extraData);
618 ChangeImpersonation(_rqContext, false);
621 if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Information, EtwTraceFlags.AppSvc)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_BEGIN, _rqContext.WorkerRequest);
623 /* Notify the store we are beginning to get process request */
624 _store.InitializeRequest(_rqContext);
626 /* determine if the request requires state at all */
627 requiresState = _rqContext.RequiresSessionState;
629 // SessionIDManager may need to do a redirect if cookieless setting is AutoDetect
630 if (_idManager.InitializeRequest(_rqContext, false, out _rqSupportSessionIdReissue)) {
631 _rqAr.Complete(true, null, null);
632 if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Information, EtwTraceFlags.AppSvc)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_END, _rqContext.WorkerRequest);
636 // See if we can skip reading the session id. See inline doc of s_allowInProcOptimization
638 if (s_allowInProcOptimization &&
640 (!requiresState || // Case 1
641 !((SessionIDManager)_idManager).UseCookieless(_rqContext)) ) { // Case 2
643 skipReadingId = true;
646 if (!requiresState) {
648 Debug.Trace("SessionStateModuleOnAcquireState", "Skip reading id because page has disabled session state");
652 Debug.Trace("SessionStateModuleOnAcquireState", "Delay reading id because we're using InProc optimization, and we are not using cookieless");
658 _rqId = _idManager.GetSessionID(_rqContext);
659 Debug.Trace("SessionStateModuleOnAcquireState", "Current request id=" + _rqId);
662 if (!requiresState) {
664 Debug.Trace("SessionStateModuleOnAcquireState",
665 "Handler does not require state, " +
666 "session id skipped or no id found, " +
667 "skipReadingId=" + skipReadingId +
668 "\nReturning from SessionStateModule::OnAcquireState");
671 Debug.Trace("SessionStateModuleOnAcquireState",
672 "Handler does not require state, " +
673 "resetting timeout for SessionId=" + _rqId +
674 "\nReturning from SessionStateModule::OnAcquireState");
676 // Still need to update the sliding timeout to keep session alive.
677 // There is a plan to skip this for perf reason. But it was postponed to
679 _store.ResetItemTimeout(_rqContext, _rqId);
682 _rqAr.Complete(true, null, null);
684 if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Information, EtwTraceFlags.AppSvc)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_END, _rqContext.WorkerRequest);
688 _rqExecutionTimeout = _rqContext.Timeout;
690 // If the page is marked as DEBUG, HttpContext.Timeout will return a very large value (~1 year)
691 // In this case, we want to use the executionTimeout value specified in the config to avoid
692 // PollLockedSession to run forever.
693 if (_rqExecutionTimeout == DEFAULT_DBG_EXECUTION_TIMEOUT) {
694 _rqExecutionTimeout = s_configExecutionTimeout;
697 /* determine if we need just read-only access */
698 _rqReadonly = _rqContext.ReadOnlySessionState;
701 /* get the session state corresponding to this session id */
702 isCompleted = GetSessionStateItem();
704 else if (!skipReadingId) {
705 /* if there's no id yet, create it */
706 bool redirected = CreateSessionId();
711 if (s_configRegenerateExpiredSessionId) {
712 // See inline comments in CreateUninitializedSessionState()
713 CreateUninitializedSessionState();
716 _rqAr.Complete(true, null, null);
718 if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Information, EtwTraceFlags.AppSvc)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_END, _rqContext.WorkerRequest);
724 CompleteAcquireState();
725 _rqAr.Complete(true, null, null);
731 RestoreImpersonation();
735 internal bool CreateSessionId() {
736 // CreateSessionId should be called only if:
737 Debug.Assert(_rqId == null || // Session id isn't found in the request, OR
739 (_rqSessionStateNotFound && // The session state isn't found, AND
740 s_configRegenerateExpiredSessionId && // We are regenerating expired session id, AND
741 _rqSupportSessionIdReissue && // This request supports session id re-issue, AND
742 !_rqIdNew), // The above three condition should imply the session id
743 // isn't just created, but is sent by the request.
744 "CreateSessionId should be called only if we're generating new id, or re-generating expired one");
745 Debug.Assert(_rqChangeImpersonationRefCount > 0, "Must call ChangeImpersonation first");
748 _rqId = _idManager.CreateSessionID(_rqContext);
749 _idManager.SaveSessionID(_rqContext, _rqId, out redirected, out _rqAddedCookie);
754 internal void EnsureStateStoreItemLocked() {
756 // Ensure ownership of the session state item here as the session ID now can be put on the wire (by Response.Flush)
757 // and the client can initiate a request before this one reaches OnReleaseState and thus causing a race condition.
758 // Note: It changes when we call into the Session Store provider. Now it may happen at BeginAcquireState instead of OnReleaseState.
760 // Item is locked yet here only if this is a new session
761 if (!_rqSessionStateNotFound) {
765 Debug.Assert(_rqId != null, "Session State ID must exist");
766 Debug.Assert(_rqItem != null, "Session State item must exist");
768 ChangeImpersonation(_rqContext, false);
771 // Store the item if already have been created
772 _store.SetAndReleaseItemExclusive(_rqContext, _rqId, _rqItem, _rqLockId, true /*_rqSessionStateNotFound*/);
774 // Lock Session State Item in Session State Store
775 LockSessionStateItem();
781 RestoreImpersonation();
784 // Mark as old session here. The SessionState is fully initialized, the item is locked
785 _rqSessionStateNotFound = false;
786 s_sessionEverSet = true;
789 // Called when AcquireState is done. This function will add the returned
790 // SessionStateStore item to the request context.
791 void CompleteAcquireState() {
792 Debug.Trace("SessionStateModuleOnAcquireState", "Item retrieved=" + (_rqItem != null).ToString(CultureInfo.InvariantCulture));
793 bool delayInitStateStoreItem = false;
795 Debug.Assert(!(s_allowDelayedStateStoreItemCreation && s_configRegenerateExpiredSessionId),
796 "!(s_allowDelayedStateStoreItemCreation && s_configRegenerateExpiredSessionId)");
799 if (_rqItem != null) {
800 _rqSessionStateNotFound = false;
802 if ((_rqActionFlags & SessionStateActions.InitializeItem) != 0) {
803 Debug.Trace("SessionStateModuleOnAcquireState", "Initialize an uninit item");
804 _rqIsNewSession = true;
807 _rqIsNewSession = false;
811 _rqIsNewSession = true;
812 _rqSessionStateNotFound = true;
814 if (s_allowDelayedStateStoreItemCreation) {
815 Debug.Trace("SessionStateModuleOnAcquireState", "Delay creating new session state");
816 delayInitStateStoreItem = true;
819 // We couldn't find the session state.
820 if (!_rqIdNew && // If the request has a session id, that means the session state has expired
821 s_configRegenerateExpiredSessionId && // And we're asked to regenerate expired session
822 _rqSupportSessionIdReissue) { // And this request support session id reissue
824 // We will generate a new session id for this expired session state
825 bool redirected = CreateSessionId();
827 Debug.Trace("SessionStateModuleOnAcquireState", "Complete re-creating new id; redirected=" + redirected);
830 Debug.Trace("SessionStateModuleOnAcquireState", "Will redirect because we've reissued a new id and it's cookieless");
831 CreateUninitializedSessionState();
837 if (delayInitStateStoreItem) {
838 _rqSessionState = s_delayedSessionState;
841 InitStateStoreItem(true);
844 // Set session state module
845 SessionStateUtility.AddHttpSessionStateModuleToContext(_rqContext, this, delayInitStateStoreItem);
847 if (_rqIsNewSession) {
848 Debug.Trace("SessionStateModuleOnAcquireState", "Calling OnStart");
849 OnStart(EventArgs.Empty);
853 if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Information, EtwTraceFlags.AppSvc)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_END, _rqContext.WorkerRequest);
857 if (_rqIsNewSession) {
859 Debug.Assert(s_allowInProcOptimization, "s_allowInProcOptimization");
860 Debug.Trace("SessionStateModuleOnAcquireState", "New session: session id reading is delayed"+
861 "\nReturning from SessionStateModule::OnAcquireState");
864 Debug.Trace("SessionStateModuleOnAcquireState", "New session: SessionId= " + _rqId +
865 "\nReturning from SessionStateModule::OnAcquireState");
870 Debug.Trace("SessionStateModuleOnAcquireState", "Retrieved old session, SessionId= " + _rqId +
871 "\nReturning from SessionStateModule::OnAcquireState");
877 void CreateUninitializedSessionState() {
878 Debug.Assert(_rqChangeImpersonationRefCount > 0, "Must call ChangeImpersonation first");
880 // When we generate a new session id in cookieless case, and if "reissueExpiredSession" is
881 // true, we need to generate a new temporary empty session and save it
882 // under the new session id, otherwise when the next request (i.e. when the browser is
883 // redirected back to the web server) comes in, we will think it's accessing an expired session.
884 _store.CreateUninitializedItem(_rqContext, _rqId, s_timeout);
887 internal void InitStateStoreItem(bool addToContext) {
888 Debug.Assert(_rqId != null || s_allowInProcOptimization, "_rqId != null || s_allowInProcOptimization");
890 ChangeImpersonation(_rqContext, false);
893 if (_rqItem == null) {
894 Debug.Trace("InitStateStoreItem", "Creating new session state");
895 _rqItem = _store.CreateNewStoreData(_rqContext, s_timeout);
898 _rqSessionItems = _rqItem.Items;
899 if (_rqSessionItems == null) {
900 throw new HttpException(SR.GetString(SR.Null_value_for_SessionStateItemCollection));
903 // No check for null because we allow our custom provider to return a null StaticObjects.
904 _rqStaticObjects = _rqItem.StaticObjects;
906 _rqSessionItems.Dirty = false;
908 _rqSessionState = new HttpSessionStateContainer(
910 _rqId, // could be null if we're using InProc optimization
920 SessionStateUtility.AddHttpSessionStateToContext(_rqContext, _rqSessionState);
924 RestoreImpersonation();
928 // Used for InProc session id optimization
929 internal string DelayedGetSessionId() {
930 Debug.Assert(s_allowInProcOptimization, "Shouldn't be called if we don't allow InProc optimization");
931 Debug.Assert(_rqId == null, "Shouldn't be called if we already have the id");
932 Debug.Assert(!((SessionIDManager)_idManager).UseCookieless(_rqContext), "We can delay session id only if we are not using cookieless");
934 Debug.Trace("DelayedOperation", "Delayed getting session id");
938 ChangeImpersonation(_rqContext, false);
940 _rqId = _idManager.GetSessionID(_rqContext);
943 Debug.Trace("DelayedOperation", "Delayed creating session id");
945 redirected = CreateSessionId();
946 Debug.Assert(!redirected, "DelayedGetSessionId shouldn't redirect us here.");
950 RestoreImpersonation();
956 void LockSessionStateItem() {
960 Debug.Assert(_rqId != null, "_rqId != null");
961 Debug.Assert(_rqChangeImpersonationRefCount > 0, "Must call ChangeImpersonation first");
964 SessionStateStoreData storedItem = _store.GetItemExclusive(_rqContext, _rqId, out locked, out lockAge, out _rqLockId, out _rqActionFlags);
965 Debug.Assert(storedItem != null, "Must succeed in locking session state item.");
969 bool GetSessionStateItem() {
970 bool isCompleted = true;
974 Debug.Assert(_rqId != null, "_rqId != null");
975 Debug.Assert(_rqChangeImpersonationRefCount > 0, "Must call ChangeImpersonation first");
978 _rqItem = _store.GetItem(_rqContext, _rqId, out locked, out lockAge, out _rqLockId, out _rqActionFlags);
981 _rqItem = _store.GetItemExclusive(_rqContext, _rqId, out locked, out lockAge, out _rqLockId, out _rqActionFlags);
983 // DevDiv Bugs 146875: WebForm and WebService Session Access Concurrency Issue
984 // If we have an expired session, we need to insert the state in the store here to
985 // ensure serialized access in case more than one entity requests it simultaneously.
986 // If the state has already been created before, CreateUninitializedSessionState is a no-op.
987 if (_rqItem == null && locked == false && _rqId != null) {
988 if (!(s_configCookieless == HttpCookieMode.UseUri && s_configRegenerateExpiredSessionId == true)) {
989 CreateUninitializedSessionState();
990 _rqItem = _store.GetItemExclusive(_rqContext, _rqId, out locked, out lockAge, out _rqLockId, out _rqActionFlags);
995 // We didn't get it because it's locked....
996 if (_rqItem == null && locked) {
998 if (lockAge >= _rqExecutionTimeout) {
999 /* Release the lock on the item, which is held by another thread*/
1000 Debug.Trace("SessionStateModuleOnAcquireState",
1001 "Lock timed out, lockAge=" + lockAge +
1004 _store.ReleaseItemExclusive(_rqContext, _rqId, _rqLockId);
1007 Debug.Trace("SessionStateModuleOnAcquireState",
1008 "Item is locked, will poll, id=" + _rqId);
1010 isCompleted = false;
1011 PollLockedSession();
1017 void PollLockedSession() {
1018 EnsureRequestTimeout();
1020 if (_timerCallback == null) {
1021 _timerCallback = new TimerCallback(this.PollLockedSessionCallback);
1024 if (_timer == null) {
1027 // Only call this method once when setting up timer to poll the session item.
1028 // It should not be called in timer's callback
1031 if (!Debug.IsTagPresent("Timer") || Debug.IsTagEnabled("Timer"))
1034 if (!s_PollIntervalRegLookedUp)
1035 LookUpRegForPollInterval();
1036 _timer = new Timer(_timerCallback, _timerId, LOCKED_ITEM_POLLING_INTERVAL, LOCKED_ITEM_POLLING_INTERVAL);
1041 private void EnsureRequestTimeout() {
1042 // Request may be blocked in acquiring state longer than execution timeout.
1043 // In that case, it will be timeout anyway after it gets the session item.
1044 // So it makes sense to timeout it when waiting longer than executionTimeout.
1045 if (_rqContext.HasTimeoutExpired) {
1046 throw new HttpException(SR.GetString(SR.Request_timed_out));
1050 private static bool IsRequestQueueEnabled {
1052 return (AppSettings.RequestQueueLimitPerSession != AppSettings.UnlimitedRequestsPerSession);
1056 private void QueueRef() {
1057 if (!IsRequestQueueEnabled || _rqId == null) {
1064 s_queuedRequestsNumPerSession.TryGetValue(_rqId, out count);
1066 if (count >= AppSettings.RequestQueueLimitPerSession) {
1067 throw new HttpException(SR.GetString(SR.Request_Queue_Limit_Per_Session_Exceeded));
1072 s_queuedRequestsNumPerSession.AddOrUpdate(_rqId, 1, (key, value) => value + 1);
1075 private void DequeRef() {
1076 if (!IsRequestQueueEnabled || _rqId == null) {
1080 // Decrement the counter
1081 if (s_queuedRequestsNumPerSession.AddOrUpdate(_rqId, 0, (key, value) => value - 1) == 0) {
1083 // Remove the element when no more references
1084 ((ICollection<KeyValuePair<string, int>>)s_queuedRequestsNumPerSession).Remove(new KeyValuePair<string,int>(_rqId, 0));
1088 [RegistryPermission(SecurityAction.Assert, Unrestricted = true)]
1089 private static void LookUpRegForPollInterval() {
1090 lock (s_PollIntervalRegLock) {
1091 if (s_PollIntervalRegLookedUp)
1094 object o = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ASP.NET", "SessionStateLockedItemPollInterval", 0);
1095 if (o != null && (o is int || o is uint) && ((int)o) > 0)
1096 LOCKED_ITEM_POLLING_INTERVAL = (int) o;
1097 s_PollIntervalRegLookedUp = true;
1099 catch { // ignore exceptions
1105 void ResetPollTimer() {
1107 if (_timer != null) {
1108 ((IDisposable)_timer).Dispose();
1113 void ChangeImpersonation(HttpContext context, bool timerThread) {
1114 #if !FEATURE_PAL // FEATURE_PAL doesn't enable impersonation
1115 _rqChangeImpersonationRefCount++;
1117 if (_ignoreImpersonation) {
1121 // If SQL store isn't using integrated security, and we're using our own session id module,
1122 // we know we don't care about impersonation in our all session state store read/write
1123 // and session id read/write.
1124 if (s_configMode == SessionStateMode.SQLServer &&
1125 ((SqlSessionStateStore)_store).KnowForSureNotUsingIntegratedSecurity &&
1126 _usingAspnetSessionIdManager) {
1130 // Please note that there are two types of calls coming in. One is from a request thread,
1131 // where timerThread==false; the other is from PollLockedSessionCallback, where
1132 // timerThread==true.
1134 if (s_useHostingIdentity) {
1135 // If we're told to use Application Identity, in each case we should impersonate,
1136 // if not called yet.
1137 if (_rqIctx == null) {
1138 _rqIctx = new ApplicationImpersonationContext();
1143 // For the timer thread, we should explicity impersonate back to what the HttpContext was
1144 // orginally impersonating.
1145 _rqTimerThreadImpersonationIctx = new ClientImpersonationContext(context, false);
1148 // For a request thread, if we're told to not use hosting id, there's no need
1149 // to do anything special.
1150 Debug.Assert(_rqIctx == null, "_rqIctx == null");
1154 #endif // !FEATURE_PAL
1157 void RestoreImpersonation() {
1158 Debug.Assert(_rqChangeImpersonationRefCount != 0, "_rqChangeImpersonationRefCount != 0");
1160 _rqChangeImpersonationRefCount--;
1162 if (_rqChangeImpersonationRefCount == 0) {
1163 Debug.Assert(!(_rqIctx != null && _rqTimerThreadImpersonationIctx != null), "Should not have mixed mode of impersonation");
1165 if (_rqIctx != null) {
1170 if (_rqTimerThreadImpersonationIctx != null) {
1171 Debug.Assert(_rqContext != null, "_rqContext != null");
1172 _rqTimerThreadImpersonationIctx.Undo();
1173 _rqTimerThreadImpersonationIctx = null;
1178 void PollLockedSessionCallback(object state) {
1179 Debug.Assert(_rqId != null, "_rqId != null");
1180 Debug.Trace("SessionStateModuleOnAcquireState",
1181 "Polling callback called from timer, id=" + _rqId);
1183 bool isCompleted = false;
1184 Exception error = null;
1186 /* check whether we are currently in a callback */
1187 if (Interlocked.CompareExchange(ref _rqInCallback, 1, 0) != 0)
1192 * check whether this callback is for the current request,
1193 * and whether sufficient time has passed since the last poll
1196 int timerId = (int) state;
1197 if ( (timerId == _timerId) &&
1198 (DateTime.UtcNow - _rqLastPollCompleted >= LOCKED_ITEM_POLLING_DELTA)) {
1200 ChangeImpersonation(_rqContext, true);
1203 isCompleted = GetSessionStateItem();
1204 _rqLastPollCompleted = DateTime.UtcNow;
1206 Debug.Assert(_timer != null, "_timer != null");
1208 CompleteAcquireState();
1212 RestoreImpersonation();
1216 catch (Exception e) {
1221 Interlocked.Exchange(ref _rqInCallback, 0);
1224 if (isCompleted || error != null) {
1227 _rqAr.Complete(false, null, error);
1232 void EndAcquireState(IAsyncResult ar) {
1233 ((HttpAsyncResult)ar).End();
1236 // Called by OnReleaseState to get the session id.
1237 string ReleaseStateGetSessionID() {
1238 if (_rqId == null) {
1239 Debug.Assert(s_allowInProcOptimization, "s_allowInProcOptimization");
1240 DelayedGetSessionId();
1243 Debug.Assert(_rqId != null, "_rqId != null");
1248 * Release session state
1252 /// <para>[To be supplied.]</para>
1254 void OnReleaseState(Object source, EventArgs eventArgs) {
1255 HttpApplication app;
1256 HttpContext context;
1257 bool setItemCalled = false;
1259 Debug.Trace("SessionStateOnReleaseState", "Beginning SessionStateModule::OnReleaseState");
1261 Debug.Assert(!(_rqAddedCookie && !_rqIsNewSession),
1262 "If session id was added to the cookie, it must be a new session.");
1265 // Please note that due to InProc session id optimization, this function should not
1266 // use _rqId directly because it can still be null. Instead, use DelayedGetSessionId().
1268 _releaseCalled = true;
1270 app = (HttpApplication)source;
1271 context = app.Context;
1273 ChangeImpersonation(context, false);
1276 if (_rqSessionState != null) {
1277 bool delayedSessionState = (_rqSessionState == s_delayedSessionState);
1279 Debug.Trace("SessionStateOnReleaseState", "Remove session state from context");
1280 SessionStateUtility.RemoveHttpSessionStateFromContext(_rqContext, delayedSessionState);
1283 * Don't store untouched new sessions.
1287 // The store doesn't have the session state.
1288 // ( Please note we aren't checking _rqIsNewSession because _rqIsNewSession
1289 // is lalso true if the item is converted from temp to perm in a GetItemXXX() call.)
1290 _rqSessionStateNotFound
1292 // OnStart is not defined
1293 && _sessionStartEventHandler == null
1295 // Nothing has been stored in session state
1296 && (delayedSessionState || !_rqSessionItems.Dirty)
1297 && (delayedSessionState || _rqStaticObjects == null || _rqStaticObjects.NeverAccessed)
1300 Debug.Trace("SessionStateOnReleaseState", "Not storing unused new session.");
1302 else if (_rqSessionState.IsAbandoned) {
1303 Debug.Trace("SessionStateOnReleaseState", "Removing session due to abandonment, SessionId=" + _rqId);
1305 if (_rqSessionStateNotFound) {
1306 // The store provider doesn't have it, and so we don't need to remove it from the store.
1308 // However, if the store provider supports session expiry, and we have a Session_End in global.asax,
1309 // we need to explicitly call Session_End.
1310 if (_supportSessionExpiry) {
1311 if (delayedSessionState) {
1312 Debug.Assert(s_allowDelayedStateStoreItemCreation, "s_allowDelayedStateStoreItemCreation");
1313 Debug.Assert(_rqItem == null, "_rqItem == null");
1315 InitStateStoreItem(false /*addToContext*/);
1318 _onEndTarget.RaiseSessionOnEnd(ReleaseStateGetSessionID(), _rqItem);
1322 Debug.Assert(_rqItem != null, "_rqItem cannot null if it's not a new session");
1324 // Remove it from the store because the session is abandoned.
1325 _store.RemoveItem(_rqContext, ReleaseStateGetSessionID(), _rqLockId, _rqItem);
1328 else if (!_rqReadonly ||
1331 _sessionStartEventHandler != null &&
1332 !SessionIDManagerUseCookieless)) {
1333 // We need to save it since it isn't read-only
1334 // See Dev10 588711: Issuing a redirect from inside of Session_Start event
1335 // triggers an infinite loop when using pages with read-only session state
1337 // We save it only if there is no error, and if something has changed (unless it's a new session)
1338 if ( context.Error == null // no error
1339 && ( _rqSessionStateNotFound
1340 || _rqSessionItems.Dirty // SessionItems has changed.
1341 || (_rqStaticObjects != null && !_rqStaticObjects.NeverAccessed) // Static objects have been accessed
1342 || _rqItem.Timeout != _rqSessionState.Timeout // Timeout value has changed
1346 if (delayedSessionState) {
1347 Debug.Assert(_rqIsNewSession, "Saving a session and delayedSessionState is true: _rqIsNewSession must be true");
1348 Debug.Assert(s_allowDelayedStateStoreItemCreation, "Saving a session and delayedSessionState is true: s_allowDelayedStateStoreItemCreation");
1349 Debug.Assert(_rqItem == null, "Saving a session and delayedSessionState is true: _rqItem == null");
1351 InitStateStoreItem(false /*addToContext*/);
1355 if (_rqSessionItems.Dirty) {
1356 Debug.Trace("SessionStateOnReleaseState", "Setting new session due to dirty SessionItems, SessionId=" + _rqId);
1358 else if (_rqStaticObjects != null && !_rqStaticObjects.NeverAccessed) {
1359 Debug.Trace("SessionStateOnReleaseState", "Setting new session due to accessed Static Objects, SessionId=" + _rqId);
1361 else if (_rqSessionStateNotFound) {
1362 Debug.Trace("SessionStateOnReleaseState", "Setting new session because it's not found, SessionId=" + _rqId);
1365 Debug.Trace("SessionStateOnReleaseState", "Setting new session due to options change, SessionId=" + _rqId +
1366 "\n\t_rq.timeout=" + _rqItem.Timeout.ToString(CultureInfo.InvariantCulture) +
1367 ", _rqSessionState.timeout=" + _rqSessionState.Timeout.ToString(CultureInfo.InvariantCulture));
1370 if (_rqItem.Timeout != _rqSessionState.Timeout) {
1371 _rqItem.Timeout = _rqSessionState.Timeout;
1374 s_sessionEverSet = true;
1375 setItemCalled = true;
1376 _store.SetAndReleaseItemExclusive(_rqContext, ReleaseStateGetSessionID(), _rqItem, _rqLockId, _rqSessionStateNotFound);
1379 // Can't save it because of various reason. Just release our exclusive lock on it.
1380 Debug.Trace("SessionStateOnReleaseState", "Release exclusive lock on session, SessionId=" + _rqId);
1382 if (!_rqSessionStateNotFound) {
1383 Debug.Assert(_rqItem != null, "_rqItem cannot null if it's not a new session");
1384 _store.ReleaseItemExclusive(_rqContext, ReleaseStateGetSessionID(), _rqLockId);
1390 Debug.Trace("SessionStateOnReleaseState", "Session is read-only, ignoring SessionId=" + _rqId);
1394 Debug.Trace("SessionStateOnReleaseState", "Returning from SessionStateModule::OnReleaseState");
1397 if (_rqAddedCookie && !setItemCalled && context.Response.IsBuffered()) {
1398 _idManager.RemoveSessionID(_rqContext);
1403 RestoreImpersonation();
1406 // WOS 1679798: PERF: Session State Module should disable EndRequest on successful cleanup
1407 bool implementsIRequiresSessionState = context.RequiresSessionState;
1408 if (HttpRuntime.UseIntegratedPipeline
1409 && (context.NotificationContext.CurrentNotification == RequestNotification.ReleaseRequestState)
1410 && (s_canSkipEndRequestCall || !implementsIRequiresSessionState)) {
1411 context.DisableNotifications(RequestNotification.EndRequest, 0 /*postNotifications*/);
1412 _acquireCalled = false;
1413 _releaseCalled = false;
1414 ResetPerRequestFields();
1419 * End of request processing. Possibly does release if skipped due to errors
1423 /// <para>[To be supplied.]</para>
1425 void OnEndRequest(Object source, EventArgs eventArgs) {
1426 HttpApplication app;
1427 HttpContext context;
1430 Debug.Trace("SessionStateOnEndRequest", "Beginning SessionStateModule::OnEndRequest");
1432 app = (HttpApplication)source;
1433 context = app.Context;
1435 /* determine if the request requires state at all */
1436 if (!context.RequiresSessionState) {
1440 ChangeImpersonation(context, false);
1443 if (!_releaseCalled) {
1444 if (_acquireCalled) {
1446 * need to do release here if the request short-circuited due to an error
1448 OnReleaseState(source, eventArgs);
1452 * 'advise' -- update session timeout
1455 if (_rqContext == null) {
1456 _rqContext = context;
1459 // We haven't called BeginAcquireState. So we have to call these InitializeRequest
1462 _store.InitializeRequest(_rqContext);
1463 _idManager.InitializeRequest(_rqContext, true, out dummy);
1465 id = _idManager.GetSessionID(context);
1467 Debug.Trace("SessionStateOnEndRequest", "Resetting timeout for SessionId=" + id);
1468 _store.ResetItemTimeout(context, id);
1472 Debug.Trace("SessionStateOnEndRequest", "No session id found.");
1478 /* Notify the store we are finishing a request */
1479 _store.EndRequest(_rqContext);
1482 _acquireCalled = false;
1483 _releaseCalled = false;
1484 RestoreImpersonation();
1485 ResetPerRequestFields();
1488 Debug.Trace("SessionStateOnEndRequest", "Returning from SessionStateModule::OnEndRequest");
1491 internal static void ReadConnectionString(SessionStateSection config, ref string cntString, string propName) {
1492 ConfigsHelper.GetRegistryStringAttribute(ref cntString, config, propName);
1493 HandlerBase.CheckAndReadConnectionString(ref cntString, true);
1496 internal bool SessionIDManagerUseCookieless {
1498 // See VSWhidbey 399907
1499 if (!_usingAspnetSessionIdManager) {
1500 return s_configCookieless == HttpCookieMode.UseUri;
1503 return ((SessionIDManager)_idManager).UseCookieless(_rqContext);
1508 public void ReleaseSessionState(HttpContext context) {
1509 if (HttpRuntime.UseIntegratedPipeline && _acquireCalled && !_releaseCalled) {
1511 OnReleaseState(context.ApplicationInstance, null);
1517 public Task ReleaseSessionStateAsync(HttpContext context) {
1518 ReleaseSessionState(context);
1519 return TaskAsyncHelper.CompletedTask;