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;
35 public delegate void SessionStateItemExpireCallback(
36 string id, SessionStateStoreData item);
38 class SessionOnEndTargetWorkItem {
39 SessionOnEndTarget _target;
40 HttpSessionState _sessionState;
42 internal SessionOnEndTargetWorkItem(SessionOnEndTarget target, HttpSessionState sessionState) {
44 _sessionState = sessionState;
47 internal void RaiseOnEndCallback() {
48 _target.RaiseOnEnd(_sessionState);
53 * Calls the OnSessionEnd event. We use an object other than the SessionStateModule
54 * because the state of the module is unknown - it could have been disposed
55 * when a session ends.
57 class SessionOnEndTarget {
58 internal int _sessionEndEventHandlerCount;
60 internal SessionOnEndTarget() {
63 internal int SessionEndEventHandlerCount {
65 return _sessionEndEventHandlerCount;
68 _sessionEndEventHandlerCount = value;
72 internal void RaiseOnEnd(HttpSessionState sessionState) {
73 Debug.Trace("SessionOnEnd", "Firing OnSessionEnd for " + sessionState.SessionID);
75 if (_sessionEndEventHandlerCount > 0) {
76 HttpApplicationFactory.EndSession(sessionState, this, EventArgs.Empty);
80 internal void RaiseSessionOnEnd(String id, SessionStateStoreData item) {
81 HttpSessionStateContainer sessionStateContainer = new HttpSessionStateContainer(
87 SessionStateModule.s_configCookieless,
88 SessionStateModule.s_configMode,
91 HttpSessionState sessionState = new HttpSessionState(sessionStateContainer);
93 if (HttpRuntime.ShutdownInProgress) {
94 // call directly when shutting down
95 RaiseOnEnd(sessionState);
98 // post via thread pool
99 SessionOnEndTargetWorkItem workItem = new SessionOnEndTargetWorkItem(this, sessionState);
100 WorkItem.PostInternal(new WorkItemCallback(workItem.RaiseOnEndCallback));
108 * The sesssion state module provides session state services
109 * for an application.
113 /// <para>[To be supplied.]</para>
115 public sealed class SessionStateModule : ISessionStateModule {
117 internal const string SQL_CONNECTION_STRING_DEFAULT = "data source=localhost;Integrated Security=SSPI";
118 internal const string STATE_CONNECTION_STRING_DEFAULT = "tcpip=loopback:42424";
119 internal const int TIMEOUT_DEFAULT = 20;
120 internal const SessionStateMode MODE_DEFAULT = SessionStateMode.InProc;
122 private static long LOCKED_ITEM_POLLING_INTERVAL = 500; // in milliseconds
123 static readonly TimeSpan LOCKED_ITEM_POLLING_DELTA = new TimeSpan(250 * TimeSpan.TicksPerMillisecond);
125 static readonly TimeSpan DEFAULT_DBG_EXECUTION_TIMEOUT = new TimeSpan(0, 0, System.Web.Compilation.PageCodeDomTreeGenerator.DebugScriptTimeout);
127 // When we are using Cache to store session state (InProc and StateServer),
128 // can't specify a timeout value larger than 1 year because CacheEntry ctor
129 // will throw an exception.
130 internal const int MAX_CACHE_BASED_TIMEOUT_MINUTES = 365 * 24 * 60;
133 static int s_timeout;
135 #pragma warning disable 0649
136 static ReadWriteSpinLock s_lock;
137 #pragma warning restore 0649
139 static bool s_trustLevelInsufficient;
141 static TimeSpan s_configExecutionTimeout;
143 static bool s_configRegenerateExpiredSessionId;
144 static bool s_useHostingIdentity;
145 internal static HttpCookieMode s_configCookieless;
146 internal static SessionStateMode s_configMode;
148 // This is used as a perf optimization for IIS7 Integrated Mode. If session state is released
149 // in ReleaseState, we can disable the EndRequest notification if the mode is InProc or StateServer
150 // because neither InProcSessionStateStore.EndRequest nor OutOfProcSessionStateStore.EndRequest
152 static bool s_canSkipEndRequestCall;
154 private static bool s_PollIntervalRegLookedUp = false;
155 private static object s_PollIntervalRegLock = new object();
158 // Check if we can optmize for InProc case.
159 // Optimization details:
161 // If we are in InProc mode, and cookieless=false, in certain scenarios we
162 // can avoid reading the session ID from the cookies because that's an expensive operation.
163 // To allow that, we use s_sessionEverSet to keep track of whether we've ever created
164 // any session state.
166 // If no session has ever be created, we can optimize in the following two cases:
168 // Case 1: Page has disabled session state
169 // In BeginAcquireState, we usually read the session ID, and reset the timeout value
170 // of the session state. However, since no session has ever been created, we can
171 // skip both reading the session id and resetting the timeout.
173 // Case 2: Page has enabled session state
174 // In this case, we will delay reading (and creating it if not found) the session ID
175 // until it's really needed. (e.g. from HttpSessionStateContainer.SessionID)
177 // Please note that we optimize only if the app is using SessionIDManager
178 // as the session ID provider; otherwise, we do not have knowledge about
179 // the provider in order to optimize safely.
181 // And we will delay reading the id only if we are using cookie to store the session ID. If we
182 // use cookieless, in the delayed session ID creation scenario, cookieless requires a redirect,
183 // and it'll be bad to do that in the middle of a page execution.
185 static bool s_allowInProcOptimization;
186 static bool s_sessionEverSet;
189 // Another optimization is to delay the creation of a new session state store item
190 // until it's needed.
191 static bool s_allowDelayedStateStoreItemCreation;
192 static HttpSessionStateContainer s_delayedSessionState = new HttpSessionStateContainer();
194 /* per application vars */
195 EventHandler _sessionStartEventHandler;
197 TimerCallback _timerCallback;
198 volatile int _timerId;
199 ISessionIDManager _idManager;
200 bool _usingAspnetSessionIdManager;
201 SessionStateStoreProviderBase _store;
202 bool _supportSessionExpiry;
203 IPartitionResolver _partitionResolver;
204 bool _ignoreImpersonation;
205 readonly SessionOnEndTarget _onEndTarget = new SessionOnEndTarget();
207 /* per request data goes in _rq* variables */
210 HttpSessionStateContainer _rqSessionState;
213 ISessionStateItemCollection _rqSessionItems;
214 HttpStaticObjectsCollection _rqStaticObjects;
215 bool _rqIsNewSession;
216 bool _rqSessionStateNotFound;
218 HttpContext _rqContext;
219 HttpAsyncResult _rqAr;
220 SessionStateStoreData _rqItem;
221 object _rqLockId; // The id of its SessionStateItem ownership
222 // If the ownership change hands (e.g. this ownership
223 // times out), the lockId of the item at the store
226 DateTime _rqLastPollCompleted;
227 TimeSpan _rqExecutionTimeout;
229 SessionStateActions _rqActionFlags;
230 ImpersonationContext _rqIctx;
231 internal int _rqChangeImpersonationRefCount;
232 ImpersonationContext _rqTimerThreadImpersonationIctx;
233 bool _rqSupportSessionIdReissue;
237 /// Initializes a new instance of the <see cref='System.Web.State.SessionStateModule'/>
241 [SecurityPermission(SecurityAction.Demand, Unrestricted=true)]
242 public SessionStateModule() {
245 static bool CheckTrustLevel(SessionStateSection config) {
246 switch (config.Mode) {
247 case SessionStateMode.SQLServer:
248 case SessionStateMode.StateServer:
249 return HttpRuntime.HasAspNetHostingPermission(AspNetHostingPermissionLevel.Medium);
252 case SessionStateMode.Off:
253 case SessionStateMode.InProc: // In-proc session doesn't require any trust level (part of ASURT 124513)
258 [AspNetHostingPermission(SecurityAction.Assert, Level=AspNetHostingPermissionLevel.Low)]
259 private SessionStateStoreProviderBase SecureInstantiateProvider(ProviderSettings settings) {
260 return (SessionStateStoreProviderBase)ProvidersHelper.InstantiateProvider(settings, typeof(SessionStateStoreProviderBase));
263 // Create an instance of the custom store as specified in the config file
264 SessionStateStoreProviderBase InitCustomStore(SessionStateSection config) {
265 string providerName = config.CustomProvider;
268 if (String.IsNullOrEmpty(providerName)) {
269 throw new ConfigurationErrorsException(
270 SR.GetString(SR.Invalid_session_custom_provider, providerName),
271 config.ElementInformation.Properties["customProvider"].Source, config.ElementInformation.Properties["customProvider"].LineNumber);
274 ps = config.Providers[providerName];
276 throw new ConfigurationErrorsException(
277 SR.GetString(SR.Missing_session_custom_provider, providerName),
278 config.ElementInformation.Properties["customProvider"].Source, config.ElementInformation.Properties["customProvider"].LineNumber);
281 return SecureInstantiateProvider(ps);
284 IPartitionResolver InitPartitionResolver(SessionStateSection config) {
285 string partitionResolverType = config.PartitionResolverType;
287 IPartitionResolver iResolver;
289 if (String.IsNullOrEmpty(partitionResolverType)) {
293 if (config.Mode != SessionStateMode.StateServer &&
294 config.Mode != SessionStateMode.SQLServer) {
295 throw new ConfigurationErrorsException(SR.GetString(SR.Cant_use_partition_resolve),
296 config.ElementInformation.Properties["partitionResolverType"].Source, config.ElementInformation.Properties["partitionResolverType"].LineNumber);
300 resolverType = ConfigUtil.GetType(partitionResolverType, "partitionResolverType", config);
301 ConfigUtil.CheckAssignableType(typeof(IPartitionResolver), resolverType, config, "partitionResolverType");
303 iResolver = (IPartitionResolver)HttpRuntime.CreatePublicInstance(resolverType);
304 iResolver.Initialize();
309 ISessionIDManager InitSessionIDManager(SessionStateSection config) {
310 string sessionIDManagerType = config.SessionIDManagerType;
311 ISessionIDManager iManager;
313 if (String.IsNullOrEmpty(sessionIDManagerType)) {
314 iManager = new SessionIDManager();
315 _usingAspnetSessionIdManager = true;
320 managerType = ConfigUtil.GetType(sessionIDManagerType, "sessionIDManagerType", config);
321 ConfigUtil.CheckAssignableType(typeof(ISessionIDManager), managerType, config, "sessionIDManagerType");
323 iManager = (ISessionIDManager)HttpRuntime.CreatePublicInstance(managerType);
326 iManager.Initialize();
331 void InitModuleFromConfig(HttpApplication app, SessionStateSection config) {
332 if (config.Mode == SessionStateMode.Off) {
336 app.AddOnAcquireRequestStateAsync(
337 new BeginEventHandler(this.BeginAcquireState),
338 new EndEventHandler(this.EndAcquireState));
340 app.ReleaseRequestState += new EventHandler(this.OnReleaseState);
341 app.EndRequest += new EventHandler(this.OnEndRequest);
343 _partitionResolver = InitPartitionResolver(config);
345 switch (config.Mode) {
346 case SessionStateMode.InProc:
347 if (HttpRuntime.UseIntegratedPipeline) {
348 s_canSkipEndRequestCall = true;
350 _store = new InProcSessionStateStore();
351 _store.Initialize(null, null);
354 #if !FEATURE_PAL // FEATURE_PAL does not enable out of proc session state
355 case SessionStateMode.StateServer:
356 if (HttpRuntime.UseIntegratedPipeline) {
357 s_canSkipEndRequestCall = true;
359 _store = new OutOfProcSessionStateStore();
360 ((OutOfProcSessionStateStore)_store).Initialize(null, null, _partitionResolver);
363 case SessionStateMode.SQLServer:
364 _store = new SqlSessionStateStore();
365 ((SqlSessionStateStore)_store).Initialize(null, null, _partitionResolver);
367 ((SqlSessionStateStore)_store).SetModule(this);
370 #else // !FEATURE_PAL
371 case SessionStateMode.StateServer:
372 throw new NotImplementedException("ROTORTODO");
375 case SessionStateMode.SQLServer:
376 throw new NotImplementedException("ROTORTODO");
378 #endif // !FEATURE_PAL
380 case SessionStateMode.Custom:
381 _store = InitCustomStore(config);
388 // We depend on SessionIDManager to manage session id
389 _idManager = InitSessionIDManager(config);
391 if ((config.Mode == SessionStateMode.InProc || config.Mode == SessionStateMode.StateServer) &&
392 _usingAspnetSessionIdManager) {
393 // If we're using InProc mode or StateServer mode, and also using our own session id module,
394 // we know we don't care about impersonation in our all session state store read/write
395 // and session id read/write.
396 _ignoreImpersonation = true;
401 public void Init(HttpApplication app) {
402 bool initModuleCalled = false;
403 SessionStateSection config = RuntimeConfig.GetAppConfig().SessionState;
405 if (!s_oneTimeInit) {
406 s_lock.AcquireWriterLock();
408 if (!s_oneTimeInit) {
409 InitModuleFromConfig(app, config);
410 initModuleCalled = true;
412 if (!CheckTrustLevel(config))
413 s_trustLevelInsufficient = true;
415 s_timeout = (int)config.Timeout.TotalMinutes;
417 s_useHostingIdentity = config.UseHostingIdentity;
419 // See if we can try InProc optimization. See inline doc of s_allowInProcOptimization
421 if (config.Mode == SessionStateMode.InProc &&
422 _usingAspnetSessionIdManager) {
423 s_allowInProcOptimization = true;
426 if (config.Mode != SessionStateMode.Custom &&
427 config.Mode != SessionStateMode.Off &&
428 !config.RegenerateExpiredSessionId) {
429 s_allowDelayedStateStoreItemCreation = true;
432 s_configExecutionTimeout = RuntimeConfig.GetConfig().HttpRuntime.ExecutionTimeout;
434 s_configRegenerateExpiredSessionId = config.RegenerateExpiredSessionId;
435 s_configCookieless = config.Cookieless;
436 s_configMode = config.Mode;
438 // The last thing to set in this if-block.
439 s_oneTimeInit = true;
441 Debug.Trace("SessionStateModuleInit",
442 "Configuration: _mode=" + config.Mode +
443 ";Timeout=" + config.Timeout +
444 ";CookieMode=" + config.Cookieless +
445 ";SqlConnectionString=" + config.SqlConnectionString +
446 ";StateConnectionString=" + config.StateConnectionString +
447 ";s_allowInProcOptimization=" + s_allowInProcOptimization +
448 ";s_allowDelayedStateStoreItemCreation=" + s_allowDelayedStateStoreItemCreation);
453 s_lock.ReleaseWriterLock();
457 if (!initModuleCalled) {
458 InitModuleFromConfig(app, config);
461 if (s_trustLevelInsufficient) {
462 throw new HttpException(SR.GetString(SR.Session_state_need_higher_trust));
468 /// <para>[To be supplied.]</para>
470 public void Dispose() {
471 if (_timer != null) {
472 ((IDisposable)_timer).Dispose();
475 if (_store != null) {
480 void ResetPerRequestFields() {
481 Debug.Assert(_rqIctx == null, "_rqIctx == null");
482 Debug.Assert(_rqChangeImpersonationRefCount == 0, "_rqChangeImpersonationRefCount == 0");
484 _rqSessionState = null;
486 _rqSessionItems = null;
487 _rqStaticObjects = null;
488 _rqIsNewSession = false;
489 _rqSessionStateNotFound = true;
496 _rqLastPollCompleted = DateTime.MinValue;
497 _rqExecutionTimeout = TimeSpan.Zero;
498 _rqAddedCookie = false;
502 _rqChangeImpersonationRefCount = 0;
503 _rqTimerThreadImpersonationIctx = null;
504 _rqSupportSessionIdReissue = false;
508 * Add a OnStart event handler.
510 * @param sessionEventHandler
514 /// <para>[To be supplied.]</para>
516 public event EventHandler Start {
518 _sessionStartEventHandler += value;
521 _sessionStartEventHandler -= value;
525 void RaiseOnStart(EventArgs e) {
526 if (_sessionStartEventHandler == null)
529 Debug.Trace("SessionStateModuleRaiseOnStart",
530 "Session_Start called for session id:" + _rqId);
532 // Session_OnStart for ASPCOMPAT pages has to be raised from an STA thread
534 if (HttpRuntime.ApartmentThreading || _rqContext.InAspCompatMode) {
535 #if !FEATURE_PAL // FEATURE_PAL does not enable COM
536 AspCompatApplicationStep.RaiseAspCompatEvent(
538 _rqContext.ApplicationInstance,
540 _sessionStartEventHandler,
543 #else // !FEATURE_PAL
544 throw new NotImplementedException ("ROTORTODO");
545 #endif // !FEATURE_PAL
549 if (HttpContext.Current == null) {
550 // This can happen if it's called by a timer thread
551 DisposableHttpContextWrapper.SwitchContext(_rqContext);
554 _sessionStartEventHandler(this, e);
559 * Fire the OnStart event.
563 void OnStart(EventArgs e) {
568 * Add a OnEnd event handler.
570 * @param sessionEventHandler
574 /// <para>[To be supplied.]</para>
576 public event EventHandler End {
579 if (_store != null && _onEndTarget.SessionEndEventHandlerCount == 0) {
580 _supportSessionExpiry = _store.SetItemExpireCallback(
581 new SessionStateItemExpireCallback(_onEndTarget.RaiseSessionOnEnd));
583 ++_onEndTarget.SessionEndEventHandlerCount;
588 --_onEndTarget.SessionEndEventHandlerCount;
590 if (_store != null && _onEndTarget.SessionEndEventHandlerCount == 0) {
591 _store.SetItemExpireCallback(null);
592 _supportSessionExpiry = false;
599 * Acquire session state
601 IAsyncResult BeginAcquireState(Object source, EventArgs e, AsyncCallback cb, Object extraData) {
603 bool isCompleted = true;
604 bool skipReadingId = false;
606 Debug.Trace("SessionStateModuleOnAcquireState", "Beginning SessionStateModule::OnAcquireState");
608 _acquireCalled = true;
609 _releaseCalled = false;
610 ResetPerRequestFields();
612 _rqContext = ((HttpApplication)source).Context;
613 _rqAr = new HttpAsyncResult(cb, extraData);
615 ChangeImpersonation(_rqContext, false);
618 if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Information, EtwTraceFlags.AppSvc)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_BEGIN, _rqContext.WorkerRequest);
620 /* Notify the store we are beginning to get process request */
621 _store.InitializeRequest(_rqContext);
623 /* determine if the request requires state at all */
624 requiresState = _rqContext.RequiresSessionState;
626 // SessionIDManager may need to do a redirect if cookieless setting is AutoDetect
627 if (_idManager.InitializeRequest(_rqContext, false, out _rqSupportSessionIdReissue)) {
628 _rqAr.Complete(true, null, null);
629 if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Information, EtwTraceFlags.AppSvc)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_END, _rqContext.WorkerRequest);
633 // See if we can skip reading the session id. See inline doc of s_allowInProcOptimization
635 if (s_allowInProcOptimization &&
637 (!requiresState || // Case 1
638 !((SessionIDManager)_idManager).UseCookieless(_rqContext)) ) { // Case 2
640 skipReadingId = true;
643 if (!requiresState) {
645 Debug.Trace("SessionStateModuleOnAcquireState", "Skip reading id because page has disabled session state");
649 Debug.Trace("SessionStateModuleOnAcquireState", "Delay reading id because we're using InProc optimization, and we are not using cookieless");
655 _rqId = _idManager.GetSessionID(_rqContext);
656 Debug.Trace("SessionStateModuleOnAcquireState", "Current request id=" + _rqId);
659 if (!requiresState) {
661 Debug.Trace("SessionStateModuleOnAcquireState",
662 "Handler does not require state, " +
663 "session id skipped or no id found, " +
664 "skipReadingId=" + skipReadingId +
665 "\nReturning from SessionStateModule::OnAcquireState");
668 Debug.Trace("SessionStateModuleOnAcquireState",
669 "Handler does not require state, " +
670 "resetting timeout for SessionId=" + _rqId +
671 "\nReturning from SessionStateModule::OnAcquireState");
673 // Still need to update the sliding timeout to keep session alive.
674 // There is a plan to skip this for perf reason. But it was postponed to
676 _store.ResetItemTimeout(_rqContext, _rqId);
679 _rqAr.Complete(true, null, null);
681 if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Information, EtwTraceFlags.AppSvc)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_END, _rqContext.WorkerRequest);
685 _rqExecutionTimeout = _rqContext.Timeout;
687 // If the page is marked as DEBUG, HttpContext.Timeout will return a very large value (~1 year)
688 // In this case, we want to use the executionTimeout value specified in the config to avoid
689 // PollLockedSession to run forever.
690 if (_rqExecutionTimeout == DEFAULT_DBG_EXECUTION_TIMEOUT) {
691 _rqExecutionTimeout = s_configExecutionTimeout;
694 /* determine if we need just read-only access */
695 _rqReadonly = _rqContext.ReadOnlySessionState;
698 /* get the session state corresponding to this session id */
699 isCompleted = GetSessionStateItem();
701 else if (!skipReadingId) {
702 /* if there's no id yet, create it */
703 bool redirected = CreateSessionId();
708 if (s_configRegenerateExpiredSessionId) {
709 // See inline comments in CreateUninitializedSessionState()
710 CreateUninitializedSessionState();
713 _rqAr.Complete(true, null, null);
715 if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Information, EtwTraceFlags.AppSvc)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_END, _rqContext.WorkerRequest);
721 CompleteAcquireState();
722 _rqAr.Complete(true, null, null);
728 RestoreImpersonation();
732 internal bool CreateSessionId() {
733 // CreateSessionId should be called only if:
734 Debug.Assert(_rqId == null || // Session id isn't found in the request, OR
736 (_rqSessionStateNotFound && // The session state isn't found, AND
737 s_configRegenerateExpiredSessionId && // We are regenerating expired session id, AND
738 _rqSupportSessionIdReissue && // This request supports session id re-issue, AND
739 !_rqIdNew), // The above three condition should imply the session id
740 // isn't just created, but is sent by the request.
741 "CreateSessionId should be called only if we're generating new id, or re-generating expired one");
742 Debug.Assert(_rqChangeImpersonationRefCount > 0, "Must call ChangeImpersonation first");
745 _rqId = _idManager.CreateSessionID(_rqContext);
746 _idManager.SaveSessionID(_rqContext, _rqId, out redirected, out _rqAddedCookie);
751 internal void EnsureStateStoreItemLocked() {
753 // Ensure ownership of the session state item here as the session ID now can be put on the wire (by Response.Flush)
754 // and the client can initiate a request before this one reaches OnReleaseState and thus causing a race condition.
755 // Note: It changes when we call into the Session Store provider. Now it may happen at BeginAcquireState instead of OnReleaseState.
757 // Item is locked yet here only if this is a new session
758 if (!_rqSessionStateNotFound) {
762 Debug.Assert(_rqId != null, "Session State ID must exist");
763 Debug.Assert(_rqItem != null, "Session State item must exist");
765 ChangeImpersonation(_rqContext, false);
768 // Store the item if already have been created
769 _store.SetAndReleaseItemExclusive(_rqContext, _rqId, _rqItem, _rqLockId, true /*_rqSessionStateNotFound*/);
771 // Lock Session State Item in Session State Store
772 LockSessionStateItem();
778 RestoreImpersonation();
781 // Mark as old session here. The SessionState is fully initialized, the item is locked
782 _rqSessionStateNotFound = false;
783 s_sessionEverSet = true;
786 // Called when AcquireState is done. This function will add the returned
787 // SessionStateStore item to the request context.
788 void CompleteAcquireState() {
789 Debug.Trace("SessionStateModuleOnAcquireState", "Item retrieved=" + (_rqItem != null).ToString(CultureInfo.InvariantCulture));
790 bool delayInitStateStoreItem = false;
792 Debug.Assert(!(s_allowDelayedStateStoreItemCreation && s_configRegenerateExpiredSessionId),
793 "!(s_allowDelayedStateStoreItemCreation && s_configRegenerateExpiredSessionId)");
796 if (_rqItem != null) {
797 _rqSessionStateNotFound = false;
799 if ((_rqActionFlags & SessionStateActions.InitializeItem) != 0) {
800 Debug.Trace("SessionStateModuleOnAcquireState", "Initialize an uninit item");
801 _rqIsNewSession = true;
804 _rqIsNewSession = false;
808 _rqIsNewSession = true;
809 _rqSessionStateNotFound = true;
811 if (s_allowDelayedStateStoreItemCreation) {
812 Debug.Trace("SessionStateModuleOnAcquireState", "Delay creating new session state");
813 delayInitStateStoreItem = true;
816 // We couldn't find the session state.
817 if (!_rqIdNew && // If the request has a session id, that means the session state has expired
818 s_configRegenerateExpiredSessionId && // And we're asked to regenerate expired session
819 _rqSupportSessionIdReissue) { // And this request support session id reissue
821 // We will generate a new session id for this expired session state
822 bool redirected = CreateSessionId();
824 Debug.Trace("SessionStateModuleOnAcquireState", "Complete re-creating new id; redirected=" + redirected);
827 Debug.Trace("SessionStateModuleOnAcquireState", "Will redirect because we've reissued a new id and it's cookieless");
828 CreateUninitializedSessionState();
834 if (delayInitStateStoreItem) {
835 _rqSessionState = s_delayedSessionState;
838 InitStateStoreItem(true);
841 // Set session state module
842 SessionStateUtility.AddHttpSessionStateModuleToContext(_rqContext, this, delayInitStateStoreItem);
844 if (_rqIsNewSession) {
845 Debug.Trace("SessionStateModuleOnAcquireState", "Calling OnStart");
846 OnStart(EventArgs.Empty);
850 if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Information, EtwTraceFlags.AppSvc)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_END, _rqContext.WorkerRequest);
854 if (_rqIsNewSession) {
856 Debug.Assert(s_allowInProcOptimization, "s_allowInProcOptimization");
857 Debug.Trace("SessionStateModuleOnAcquireState", "New session: session id reading is delayed"+
858 "\nReturning from SessionStateModule::OnAcquireState");
861 Debug.Trace("SessionStateModuleOnAcquireState", "New session: SessionId= " + _rqId +
862 "\nReturning from SessionStateModule::OnAcquireState");
867 Debug.Trace("SessionStateModuleOnAcquireState", "Retrieved old session, SessionId= " + _rqId +
868 "\nReturning from SessionStateModule::OnAcquireState");
874 void CreateUninitializedSessionState() {
875 Debug.Assert(_rqChangeImpersonationRefCount > 0, "Must call ChangeImpersonation first");
877 // When we generate a new session id in cookieless case, and if "reissueExpiredSession" is
878 // true, we need to generate a new temporary empty session and save it
879 // under the new session id, otherwise when the next request (i.e. when the browser is
880 // redirected back to the web server) comes in, we will think it's accessing an expired session.
881 _store.CreateUninitializedItem(_rqContext, _rqId, s_timeout);
884 internal void InitStateStoreItem(bool addToContext) {
885 Debug.Assert(_rqId != null || s_allowInProcOptimization, "_rqId != null || s_allowInProcOptimization");
887 ChangeImpersonation(_rqContext, false);
890 if (_rqItem == null) {
891 Debug.Trace("InitStateStoreItem", "Creating new session state");
892 _rqItem = _store.CreateNewStoreData(_rqContext, s_timeout);
895 _rqSessionItems = _rqItem.Items;
896 if (_rqSessionItems == null) {
897 throw new HttpException(SR.GetString(SR.Null_value_for_SessionStateItemCollection));
900 // No check for null because we allow our custom provider to return a null StaticObjects.
901 _rqStaticObjects = _rqItem.StaticObjects;
903 _rqSessionItems.Dirty = false;
905 _rqSessionState = new HttpSessionStateContainer(
907 _rqId, // could be null if we're using InProc optimization
917 SessionStateUtility.AddHttpSessionStateToContext(_rqContext, _rqSessionState);
921 RestoreImpersonation();
925 // Used for InProc session id optimization
926 internal string DelayedGetSessionId() {
927 Debug.Assert(s_allowInProcOptimization, "Shouldn't be called if we don't allow InProc optimization");
928 Debug.Assert(_rqId == null, "Shouldn't be called if we already have the id");
929 Debug.Assert(!((SessionIDManager)_idManager).UseCookieless(_rqContext), "We can delay session id only if we are not using cookieless");
931 Debug.Trace("DelayedOperation", "Delayed getting session id");
935 ChangeImpersonation(_rqContext, false);
937 _rqId = _idManager.GetSessionID(_rqContext);
940 Debug.Trace("DelayedOperation", "Delayed creating session id");
942 redirected = CreateSessionId();
943 Debug.Assert(!redirected, "DelayedGetSessionId shouldn't redirect us here.");
947 RestoreImpersonation();
953 void LockSessionStateItem() {
957 Debug.Assert(_rqId != null, "_rqId != null");
958 Debug.Assert(_rqChangeImpersonationRefCount > 0, "Must call ChangeImpersonation first");
961 SessionStateStoreData storedItem = _store.GetItemExclusive(_rqContext, _rqId, out locked, out lockAge, out _rqLockId, out _rqActionFlags);
962 Debug.Assert(storedItem != null, "Must succeed in locking session state item.");
966 bool GetSessionStateItem() {
967 bool isCompleted = true;
971 Debug.Assert(_rqId != null, "_rqId != null");
972 Debug.Assert(_rqChangeImpersonationRefCount > 0, "Must call ChangeImpersonation first");
975 _rqItem = _store.GetItem(_rqContext, _rqId, out locked, out lockAge, out _rqLockId, out _rqActionFlags);
978 _rqItem = _store.GetItemExclusive(_rqContext, _rqId, out locked, out lockAge, out _rqLockId, out _rqActionFlags);
980 // DevDiv Bugs 146875: WebForm and WebService Session Access Concurrency Issue
981 // If we have an expired session, we need to insert the state in the store here to
982 // ensure serialized access in case more than one entity requests it simultaneously.
983 // If the state has already been created before, CreateUninitializedSessionState is a no-op.
984 if (_rqItem == null && locked == false && _rqId != null) {
985 if (!(s_configCookieless == HttpCookieMode.UseUri && s_configRegenerateExpiredSessionId == true)) {
986 CreateUninitializedSessionState();
987 _rqItem = _store.GetItemExclusive(_rqContext, _rqId, out locked, out lockAge, out _rqLockId, out _rqActionFlags);
992 // We didn't get it because it's locked....
993 if (_rqItem == null && locked) {
995 if (lockAge >= _rqExecutionTimeout) {
996 /* Release the lock on the item, which is held by another thread*/
997 Debug.Trace("SessionStateModuleOnAcquireState",
998 "Lock timed out, lockAge=" + lockAge +
1001 _store.ReleaseItemExclusive(_rqContext, _rqId, _rqLockId);
1004 Debug.Trace("SessionStateModuleOnAcquireState",
1005 "Item is locked, will poll, id=" + _rqId);
1007 isCompleted = false;
1008 PollLockedSession();
1014 void PollLockedSession() {
1015 if (_timerCallback == null) {
1016 _timerCallback = new TimerCallback(this.PollLockedSessionCallback);
1019 if (_timer == null) {
1023 if (!Debug.IsTagPresent("Timer") || Debug.IsTagEnabled("Timer"))
1026 if (!s_PollIntervalRegLookedUp)
1027 LookUpRegForPollInterval();
1028 _timer = new Timer(_timerCallback, _timerId, LOCKED_ITEM_POLLING_INTERVAL, LOCKED_ITEM_POLLING_INTERVAL);
1033 [RegistryPermission(SecurityAction.Assert, Unrestricted = true)]
1034 private static void LookUpRegForPollInterval() {
1035 lock (s_PollIntervalRegLock) {
1036 if (s_PollIntervalRegLookedUp)
1039 object o = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ASP.NET", "SessionStateLockedItemPollInterval", 0);
1040 if (o != null && (o is int || o is uint) && ((int)o) > 0)
1041 LOCKED_ITEM_POLLING_INTERVAL = (int) o;
1042 s_PollIntervalRegLookedUp = true;
1044 catch { // ignore exceptions
1050 void ResetPollTimer() {
1052 if (_timer != null) {
1053 ((IDisposable)_timer).Dispose();
1058 void ChangeImpersonation(HttpContext context, bool timerThread) {
1059 #if !FEATURE_PAL // FEATURE_PAL doesn't enable impersonation
1060 _rqChangeImpersonationRefCount++;
1062 if (_ignoreImpersonation) {
1066 // If SQL store isn't using integrated security, and we're using our own session id module,
1067 // we know we don't care about impersonation in our all session state store read/write
1068 // and session id read/write.
1069 if (s_configMode == SessionStateMode.SQLServer &&
1070 ((SqlSessionStateStore)_store).KnowForSureNotUsingIntegratedSecurity &&
1071 _usingAspnetSessionIdManager) {
1075 // Please note that there are two types of calls coming in. One is from a request thread,
1076 // where timerThread==false; the other is from PollLockedSessionCallback, where
1077 // timerThread==true.
1079 if (s_useHostingIdentity) {
1080 // If we're told to use Application Identity, in each case we should impersonate,
1081 // if not called yet.
1082 if (_rqIctx == null) {
1083 _rqIctx = new ApplicationImpersonationContext();
1088 // For the timer thread, we should explicity impersonate back to what the HttpContext was
1089 // orginally impersonating.
1090 _rqTimerThreadImpersonationIctx = new ClientImpersonationContext(context, false);
1093 // For a request thread, if we're told to not use hosting id, there's no need
1094 // to do anything special.
1095 Debug.Assert(_rqIctx == null, "_rqIctx == null");
1099 #endif // !FEATURE_PAL
1102 void RestoreImpersonation() {
1103 Debug.Assert(_rqChangeImpersonationRefCount != 0, "_rqChangeImpersonationRefCount != 0");
1105 _rqChangeImpersonationRefCount--;
1107 if (_rqChangeImpersonationRefCount == 0) {
1108 Debug.Assert(!(_rqIctx != null && _rqTimerThreadImpersonationIctx != null), "Should not have mixed mode of impersonation");
1110 if (_rqIctx != null) {
1115 if (_rqTimerThreadImpersonationIctx != null) {
1116 Debug.Assert(_rqContext != null, "_rqContext != null");
1117 _rqTimerThreadImpersonationIctx.Undo();
1118 _rqTimerThreadImpersonationIctx = null;
1123 void PollLockedSessionCallback(object state) {
1124 Debug.Assert(_rqId != null, "_rqId != null");
1125 Debug.Trace("SessionStateModuleOnAcquireState",
1126 "Polling callback called from timer, id=" + _rqId);
1128 bool isCompleted = false;
1129 Exception error = null;
1131 /* check whether we are currently in a callback */
1132 if (Interlocked.CompareExchange(ref _rqInCallback, 1, 0) != 0)
1137 * check whether this callback is for the current request,
1138 * and whether sufficient time has passed since the last poll
1141 int timerId = (int) state;
1142 if ( (timerId == _timerId) &&
1143 (DateTime.UtcNow - _rqLastPollCompleted >= LOCKED_ITEM_POLLING_DELTA)) {
1145 ChangeImpersonation(_rqContext, true);
1148 isCompleted = GetSessionStateItem();
1149 _rqLastPollCompleted = DateTime.UtcNow;
1151 Debug.Assert(_timer != null, "_timer != null");
1153 CompleteAcquireState();
1157 RestoreImpersonation();
1161 catch (Exception e) {
1166 Interlocked.Exchange(ref _rqInCallback, 0);
1169 if (isCompleted || error != null) {
1170 _rqAr.Complete(false, null, error);
1175 void EndAcquireState(IAsyncResult ar) {
1176 ((HttpAsyncResult)ar).End();
1179 // Called by OnReleaseState to get the session id.
1180 string ReleaseStateGetSessionID() {
1181 if (_rqId == null) {
1182 Debug.Assert(s_allowInProcOptimization, "s_allowInProcOptimization");
1183 DelayedGetSessionId();
1186 Debug.Assert(_rqId != null, "_rqId != null");
1191 * Release session state
1195 /// <para>[To be supplied.]</para>
1197 void OnReleaseState(Object source, EventArgs eventArgs) {
1198 HttpApplication app;
1199 HttpContext context;
1200 bool setItemCalled = false;
1202 Debug.Trace("SessionStateOnReleaseState", "Beginning SessionStateModule::OnReleaseState");
1204 Debug.Assert(!(_rqAddedCookie && !_rqIsNewSession),
1205 "If session id was added to the cookie, it must be a new session.");
1208 // Please note that due to InProc session id optimization, this function should not
1209 // use _rqId directly because it can still be null. Instead, use DelayedGetSessionId().
1211 _releaseCalled = true;
1213 app = (HttpApplication)source;
1214 context = app.Context;
1216 ChangeImpersonation(context, false);
1219 if (_rqSessionState != null) {
1220 bool delayedSessionState = (_rqSessionState == s_delayedSessionState);
1222 Debug.Trace("SessionStateOnReleaseState", "Remove session state from context");
1223 SessionStateUtility.RemoveHttpSessionStateFromContext(_rqContext, delayedSessionState);
1226 * Don't store untouched new sessions.
1230 // The store doesn't have the session state.
1231 // ( Please note we aren't checking _rqIsNewSession because _rqIsNewSession
1232 // is lalso true if the item is converted from temp to perm in a GetItemXXX() call.)
1233 _rqSessionStateNotFound
1235 // OnStart is not defined
1236 && _sessionStartEventHandler == null
1238 // Nothing has been stored in session state
1239 && (delayedSessionState || !_rqSessionItems.Dirty)
1240 && (delayedSessionState || _rqStaticObjects == null || _rqStaticObjects.NeverAccessed)
1243 Debug.Trace("SessionStateOnReleaseState", "Not storing unused new session.");
1245 else if (_rqSessionState.IsAbandoned) {
1246 Debug.Trace("SessionStateOnReleaseState", "Removing session due to abandonment, SessionId=" + _rqId);
1248 if (_rqSessionStateNotFound) {
1249 // The store provider doesn't have it, and so we don't need to remove it from the store.
1251 // However, if the store provider supports session expiry, and we have a Session_End in global.asax,
1252 // we need to explicitly call Session_End.
1253 if (_supportSessionExpiry) {
1254 if (delayedSessionState) {
1255 Debug.Assert(s_allowDelayedStateStoreItemCreation, "s_allowDelayedStateStoreItemCreation");
1256 Debug.Assert(_rqItem == null, "_rqItem == null");
1258 InitStateStoreItem(false /*addToContext*/);
1261 _onEndTarget.RaiseSessionOnEnd(ReleaseStateGetSessionID(), _rqItem);
1265 Debug.Assert(_rqItem != null, "_rqItem cannot null if it's not a new session");
1267 // Remove it from the store because the session is abandoned.
1268 _store.RemoveItem(_rqContext, ReleaseStateGetSessionID(), _rqLockId, _rqItem);
1271 else if (!_rqReadonly ||
1274 _sessionStartEventHandler != null &&
1275 !SessionIDManagerUseCookieless)) {
1276 // We need to save it since it isn't read-only
1277 // See Dev10 588711: Issuing a redirect from inside of Session_Start event
1278 // triggers an infinite loop when using pages with read-only session state
1280 // We save it only if there is no error, and if something has changed (unless it's a new session)
1281 if ( context.Error == null // no error
1282 && ( _rqSessionStateNotFound
1283 || _rqSessionItems.Dirty // SessionItems has changed.
1284 || (_rqStaticObjects != null && !_rqStaticObjects.NeverAccessed) // Static objects have been accessed
1285 || _rqItem.Timeout != _rqSessionState.Timeout // Timeout value has changed
1289 if (delayedSessionState) {
1290 Debug.Assert(_rqIsNewSession, "Saving a session and delayedSessionState is true: _rqIsNewSession must be true");
1291 Debug.Assert(s_allowDelayedStateStoreItemCreation, "Saving a session and delayedSessionState is true: s_allowDelayedStateStoreItemCreation");
1292 Debug.Assert(_rqItem == null, "Saving a session and delayedSessionState is true: _rqItem == null");
1294 InitStateStoreItem(false /*addToContext*/);
1298 if (_rqSessionItems.Dirty) {
1299 Debug.Trace("SessionStateOnReleaseState", "Setting new session due to dirty SessionItems, SessionId=" + _rqId);
1301 else if (_rqStaticObjects != null && !_rqStaticObjects.NeverAccessed) {
1302 Debug.Trace("SessionStateOnReleaseState", "Setting new session due to accessed Static Objects, SessionId=" + _rqId);
1304 else if (_rqSessionStateNotFound) {
1305 Debug.Trace("SessionStateOnReleaseState", "Setting new session because it's not found, SessionId=" + _rqId);
1308 Debug.Trace("SessionStateOnReleaseState", "Setting new session due to options change, SessionId=" + _rqId +
1309 "\n\t_rq.timeout=" + _rqItem.Timeout.ToString(CultureInfo.InvariantCulture) +
1310 ", _rqSessionState.timeout=" + _rqSessionState.Timeout.ToString(CultureInfo.InvariantCulture));
1313 if (_rqItem.Timeout != _rqSessionState.Timeout) {
1314 _rqItem.Timeout = _rqSessionState.Timeout;
1317 s_sessionEverSet = true;
1318 setItemCalled = true;
1319 _store.SetAndReleaseItemExclusive(_rqContext, ReleaseStateGetSessionID(), _rqItem, _rqLockId, _rqSessionStateNotFound);
1322 // Can't save it because of various reason. Just release our exclusive lock on it.
1323 Debug.Trace("SessionStateOnReleaseState", "Release exclusive lock on session, SessionId=" + _rqId);
1325 if (!_rqSessionStateNotFound) {
1326 Debug.Assert(_rqItem != null, "_rqItem cannot null if it's not a new session");
1327 _store.ReleaseItemExclusive(_rqContext, ReleaseStateGetSessionID(), _rqLockId);
1333 Debug.Trace("SessionStateOnReleaseState", "Session is read-only, ignoring SessionId=" + _rqId);
1337 Debug.Trace("SessionStateOnReleaseState", "Returning from SessionStateModule::OnReleaseState");
1340 if (_rqAddedCookie && !setItemCalled && context.Response.IsBuffered()) {
1341 _idManager.RemoveSessionID(_rqContext);
1346 RestoreImpersonation();
1349 // WOS 1679798: PERF: Session State Module should disable EndRequest on successful cleanup
1350 bool implementsIRequiresSessionState = context.RequiresSessionState;
1351 if (HttpRuntime.UseIntegratedPipeline
1352 && (context.NotificationContext.CurrentNotification == RequestNotification.ReleaseRequestState)
1353 && (s_canSkipEndRequestCall || !implementsIRequiresSessionState)) {
1354 context.DisableNotifications(RequestNotification.EndRequest, 0 /*postNotifications*/);
1355 _acquireCalled = false;
1356 _releaseCalled = false;
1357 ResetPerRequestFields();
1362 * End of request processing. Possibly does release if skipped due to errors
1366 /// <para>[To be supplied.]</para>
1368 void OnEndRequest(Object source, EventArgs eventArgs) {
1369 HttpApplication app;
1370 HttpContext context;
1373 Debug.Trace("SessionStateOnEndRequest", "Beginning SessionStateModule::OnEndRequest");
1375 app = (HttpApplication)source;
1376 context = app.Context;
1378 /* determine if the request requires state at all */
1379 if (!context.RequiresSessionState) {
1383 ChangeImpersonation(context, false);
1386 if (!_releaseCalled) {
1387 if (_acquireCalled) {
1389 * need to do release here if the request short-circuited due to an error
1391 OnReleaseState(source, eventArgs);
1395 * 'advise' -- update session timeout
1398 if (_rqContext == null) {
1399 _rqContext = context;
1402 // We haven't called BeginAcquireState. So we have to call these InitializeRequest
1405 _store.InitializeRequest(_rqContext);
1406 _idManager.InitializeRequest(_rqContext, true, out dummy);
1408 id = _idManager.GetSessionID(context);
1410 Debug.Trace("SessionStateOnEndRequest", "Resetting timeout for SessionId=" + id);
1411 _store.ResetItemTimeout(context, id);
1415 Debug.Trace("SessionStateOnEndRequest", "No session id found.");
1421 /* Notify the store we are finishing a request */
1422 _store.EndRequest(_rqContext);
1425 _acquireCalled = false;
1426 _releaseCalled = false;
1427 RestoreImpersonation();
1428 ResetPerRequestFields();
1431 Debug.Trace("SessionStateOnEndRequest", "Returning from SessionStateModule::OnEndRequest");
1434 internal static void ReadConnectionString(SessionStateSection config, ref string cntString, string propName) {
1435 ConfigsHelper.GetRegistryStringAttribute(ref cntString, config, propName);
1436 HandlerBase.CheckAndReadConnectionString(ref cntString, true);
1439 internal bool SessionIDManagerUseCookieless {
1441 // See VSWhidbey 399907
1442 if (!_usingAspnetSessionIdManager) {
1443 return s_configCookieless == HttpCookieMode.UseUri;
1446 return ((SessionIDManager)_idManager).UseCookieless(_rqContext);
1451 public void ReleaseSessionState(HttpContext context) {
1452 if (HttpRuntime.UseIntegratedPipeline && _acquireCalled && !_releaseCalled) {
1454 OnReleaseState(context.ApplicationInstance, null);
1460 public Task ReleaseSessionStateAsync(HttpContext context) {
1461 ReleaseSessionState(context);
1462 return TaskAsyncHelper.CompletedTask;