Merge pull request #3389 from lambdageek/bug-43099
[mono.git] / mcs / class / referencesource / System.Web / State / SessionStateModule.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="SessionStateModule.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6
7 /*
8  * SessionStateModule
9  *
10  * Copyright (c) 1998-2002, Microsoft Corporation
11  *
12  */
13
14 namespace System.Web.SessionState {
15
16     using System;
17     using System.Threading;
18     using System.Collections;
19     using System.Configuration;
20     using System.IO;
21     using System.Web.Caching;
22     using System.Web.Util;
23     using System.Web.Configuration;
24     using System.Xml;
25     using System.Security.Cryptography;
26     using System.Data.SqlClient;
27     using System.Globalization;
28     using System.Security.Permissions;
29     using System.Text;
30     using System.Threading.Tasks;
31     using System.Web.Hosting;
32     using System.Web.Management;
33     using Microsoft.Win32;
34
35     public delegate void SessionStateItemExpireCallback(
36             string id, SessionStateStoreData item);
37
38     class SessionOnEndTargetWorkItem {
39         SessionOnEndTarget  _target;
40         HttpSessionState    _sessionState;
41
42         internal SessionOnEndTargetWorkItem(SessionOnEndTarget target, HttpSessionState sessionState) {
43             _target = target;
44             _sessionState = sessionState;
45         }
46
47         internal void RaiseOnEndCallback() {
48             _target.RaiseOnEnd(_sessionState);
49         }
50     }
51
52     /*
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.
56      */
57     class SessionOnEndTarget {
58         internal int _sessionEndEventHandlerCount;
59
60         internal SessionOnEndTarget() {
61         }
62
63         internal int SessionEndEventHandlerCount {
64             get {
65                 return _sessionEndEventHandlerCount;
66             }
67             set {
68                 _sessionEndEventHandlerCount = value;
69             }
70         }
71
72         internal void RaiseOnEnd(HttpSessionState sessionState) {
73             Debug.Trace("SessionOnEnd", "Firing OnSessionEnd for " + sessionState.SessionID);
74
75             if (_sessionEndEventHandlerCount > 0) {
76                 HttpApplicationFactory.EndSession(sessionState, this, EventArgs.Empty);
77             }
78         }
79
80         internal void RaiseSessionOnEnd(String id, SessionStateStoreData item) {
81             HttpSessionStateContainer sessionStateContainer = new HttpSessionStateContainer(
82                     id,
83                     item.Items,
84                     item.StaticObjects,
85                     item.Timeout,
86                     false,
87                     SessionStateModule.s_configCookieless,
88                     SessionStateModule.s_configMode,
89                     true);
90
91             HttpSessionState    sessionState = new HttpSessionState(sessionStateContainer);
92
93             if (HttpRuntime.ShutdownInProgress) {
94                 // call directly when shutting down
95                 RaiseOnEnd(sessionState);
96             }
97             else {
98                 // post via thread pool
99                 SessionOnEndTargetWorkItem workItem = new SessionOnEndTargetWorkItem(this, sessionState);
100                 WorkItem.PostInternal(new WorkItemCallback(workItem.RaiseOnEndCallback));
101             }
102         }
103
104     }
105
106
107     /*
108      * The sesssion state module provides session state services
109      * for an application.
110      */
111
112     /// <devdoc>
113     ///    <para>[To be supplied.]</para>
114     /// </devdoc>
115     public sealed class SessionStateModule : ISessionStateModule {
116
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;
121
122         private static long                 LOCKED_ITEM_POLLING_INTERVAL = 500; // in milliseconds
123         static readonly TimeSpan            LOCKED_ITEM_POLLING_DELTA = new TimeSpan(250 * TimeSpan.TicksPerMillisecond);
124
125         static readonly TimeSpan            DEFAULT_DBG_EXECUTION_TIMEOUT = new TimeSpan(0, 0, System.Web.Compilation.PageCodeDomTreeGenerator.DebugScriptTimeout);
126
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;
131
132         bool                                s_oneTimeInit;
133         static int                          s_timeout;
134
135         #pragma warning disable 0649
136         static ReadWriteSpinLock            s_lock;
137         #pragma warning restore 0649
138
139         static bool                         s_trustLevelInsufficient;
140
141         static TimeSpan                     s_configExecutionTimeout;
142
143         static bool                         s_configRegenerateExpiredSessionId;
144         static bool                         s_useHostingIdentity;
145         internal static HttpCookieMode      s_configCookieless;
146         internal static SessionStateMode    s_configMode;
147         
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
151         // are implemented.
152         static bool                         s_canSkipEndRequestCall;
153
154         private static bool s_PollIntervalRegLookedUp = false;
155         private static object s_PollIntervalRegLock = new object();
156
157         //
158         // Check if we can optmize for InProc case.
159         // Optimization details:
160         //
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.
165         //
166         // If no session has ever be created, we can optimize in the following two cases:
167         //
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.
172         //
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)
176         //
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.
180         //
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.
184         //
185         static bool                         s_allowInProcOptimization;
186         static bool                         s_sessionEverSet;
187
188         //
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();
193
194         /* per application vars */
195         EventHandler                   _sessionStartEventHandler;
196         Timer                          _timer;
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();
206
207         /* per request data goes in _rq* variables */
208         bool                            _acquireCalled;
209         bool                            _releaseCalled;
210         HttpSessionStateContainer       _rqSessionState;
211         String                          _rqId;
212         bool                            _rqIdNew;
213         ISessionStateItemCollection     _rqSessionItems;
214         HttpStaticObjectsCollection     _rqStaticObjects;
215         bool                            _rqIsNewSession;
216         bool                            _rqSessionStateNotFound;
217         bool                            _rqReadonly;
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
224                                                     // will change.
225         int                             _rqInCallback;
226         DateTime                        _rqLastPollCompleted;
227         TimeSpan                        _rqExecutionTimeout;
228         bool                            _rqAddedCookie;
229         SessionStateActions             _rqActionFlags;
230         ImpersonationContext            _rqIctx;
231         internal int                    _rqChangeImpersonationRefCount;
232         ImpersonationContext            _rqTimerThreadImpersonationIctx;
233         bool                            _rqSupportSessionIdReissue;
234
235         /// <devdoc>
236         ///    <para>
237         ///       Initializes a new instance of the <see cref='System.Web.State.SessionStateModule'/>
238         ///       class.
239         ///     </para>
240         /// </devdoc>
241         [SecurityPermission(SecurityAction.Demand, Unrestricted=true)]
242         public SessionStateModule() {
243         }
244
245         static bool CheckTrustLevel(SessionStateSection config) {
246             switch (config.Mode) {
247                 case SessionStateMode.SQLServer:
248                 case SessionStateMode.StateServer:
249                     return HttpRuntime.HasAspNetHostingPermission(AspNetHostingPermissionLevel.Medium);
250
251                 default:
252                 case SessionStateMode.Off:
253                 case SessionStateMode.InProc: // In-proc session doesn't require any trust level (part of ASURT 124513)
254                     return true;
255             }
256         }
257
258         [AspNetHostingPermission(SecurityAction.Assert, Level=AspNetHostingPermissionLevel.Low)]
259         private SessionStateStoreProviderBase SecureInstantiateProvider(ProviderSettings settings) {
260             return (SessionStateStoreProviderBase)ProvidersHelper.InstantiateProvider(settings, typeof(SessionStateStoreProviderBase));
261         }
262
263         // Create an instance of the custom store as specified in the config file
264         SessionStateStoreProviderBase InitCustomStore(SessionStateSection config) {
265             string          providerName = config.CustomProvider;
266             ProviderSettings  ps;
267
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);
272             }
273
274             ps = config.Providers[providerName];
275             if (ps == null) {
276                 throw new ConfigurationErrorsException(
277                         SR.GetString(SR.Missing_session_custom_provider, providerName),
278                         config.ElementInformation.Properties["customProvider"].Source, config.ElementInformation.Properties["customProvider"].LineNumber);
279             }
280
281             return SecureInstantiateProvider(ps);
282         }
283
284         IPartitionResolver InitPartitionResolver(SessionStateSection config) {
285             string  partitionResolverType = config.PartitionResolverType;
286             Type    resolverType;
287             IPartitionResolver  iResolver;
288
289             if (String.IsNullOrEmpty(partitionResolverType)) {
290                 return null;
291             }
292
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);
297             }
298
299
300             resolverType = ConfigUtil.GetType(partitionResolverType, "partitionResolverType", config);
301             ConfigUtil.CheckAssignableType(typeof(IPartitionResolver), resolverType, config, "partitionResolverType");
302
303             iResolver = (IPartitionResolver)HttpRuntime.CreatePublicInstance(resolverType);
304             iResolver.Initialize();
305
306             return iResolver;
307         }
308
309         ISessionIDManager InitSessionIDManager(SessionStateSection config) {
310             string  sessionIDManagerType = config.SessionIDManagerType;
311             ISessionIDManager  iManager;
312
313             if (String.IsNullOrEmpty(sessionIDManagerType)) {
314                 iManager = new SessionIDManager();
315                 _usingAspnetSessionIdManager = true;
316             }
317             else {
318                 Type    managerType;
319
320                 managerType = ConfigUtil.GetType(sessionIDManagerType, "sessionIDManagerType", config);
321                 ConfigUtil.CheckAssignableType(typeof(ISessionIDManager), managerType, config, "sessionIDManagerType");
322
323                 iManager = (ISessionIDManager)HttpRuntime.CreatePublicInstance(managerType);
324             }
325
326             iManager.Initialize();
327
328             return iManager;
329         }
330
331         void InitModuleFromConfig(HttpApplication app, SessionStateSection config) {
332             if (config.Mode == SessionStateMode.Off) {
333                 return;
334             }
335
336             app.AddOnAcquireRequestStateAsync(
337                     new BeginEventHandler(this.BeginAcquireState),
338                     new EndEventHandler(this.EndAcquireState));
339
340             app.ReleaseRequestState += new EventHandler(this.OnReleaseState);
341             app.EndRequest += new EventHandler(this.OnEndRequest);
342
343             _partitionResolver = InitPartitionResolver(config);
344
345             switch (config.Mode) {
346                 case SessionStateMode.InProc:
347                     if (HttpRuntime.UseIntegratedPipeline) {
348                         s_canSkipEndRequestCall = true;
349                     }
350                     _store = new InProcSessionStateStore();
351                     _store.Initialize(null, null);
352                     break;
353
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;
358                     }
359                     _store = new OutOfProcSessionStateStore();
360                     ((OutOfProcSessionStateStore)_store).Initialize(null, null, _partitionResolver);
361                     break;
362
363                 case SessionStateMode.SQLServer:
364                     _store = new SqlSessionStateStore();
365                     ((SqlSessionStateStore)_store).Initialize(null, null, _partitionResolver);
366 #if DBG
367                     ((SqlSessionStateStore)_store).SetModule(this);
368 #endif
369                     break;
370 #else // !FEATURE_PAL
371                 case SessionStateMode.StateServer:
372                     throw new NotImplementedException("ROTORTODO");
373                     break;
374
375                 case SessionStateMode.SQLServer:
376                     throw new NotImplementedException("ROTORTODO");
377                     break;
378 #endif // !FEATURE_PAL
379
380                 case SessionStateMode.Custom:
381                     _store = InitCustomStore(config);
382                     break;
383
384                 default:
385                     break;
386             }
387
388             // We depend on SessionIDManager to manage session id
389             _idManager = InitSessionIDManager(config);
390
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;
397             }
398
399         }
400
401         public void Init(HttpApplication app) {
402             bool initModuleCalled = false;
403             SessionStateSection config = RuntimeConfig.GetAppConfig().SessionState;
404
405             if (!s_oneTimeInit) {
406                 s_lock.AcquireWriterLock();
407                 try {
408                     if (!s_oneTimeInit) {
409                         InitModuleFromConfig(app, config);
410                         initModuleCalled = true;
411
412                         if (!CheckTrustLevel(config))
413                             s_trustLevelInsufficient = true;
414
415                         s_timeout = (int)config.Timeout.TotalMinutes;
416
417                         s_useHostingIdentity = config.UseHostingIdentity;
418
419                         // See if we can try InProc optimization. See inline doc of s_allowInProcOptimization
420                         // for details.
421                         if (config.Mode == SessionStateMode.InProc &&
422                             _usingAspnetSessionIdManager) {
423                             s_allowInProcOptimization = true;
424                         }
425
426                         if (config.Mode != SessionStateMode.Custom &&
427                             config.Mode != SessionStateMode.Off &&
428                             !config.RegenerateExpiredSessionId) {
429                             s_allowDelayedStateStoreItemCreation = true;
430                         }
431
432                         s_configExecutionTimeout = RuntimeConfig.GetConfig().HttpRuntime.ExecutionTimeout;
433
434                         s_configRegenerateExpiredSessionId = config.RegenerateExpiredSessionId;
435                         s_configCookieless = config.Cookieless;
436                         s_configMode = config.Mode;
437
438                         // The last thing to set in this if-block.
439                         s_oneTimeInit = true;
440
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);
449
450                     }
451                 }
452                 finally {
453                     s_lock.ReleaseWriterLock();
454                 }
455             }
456
457             if (!initModuleCalled) {
458                 InitModuleFromConfig(app, config);
459             }
460
461             if (s_trustLevelInsufficient) {
462                 throw new HttpException(SR.GetString(SR.Session_state_need_higher_trust));
463             }
464         }
465
466
467         /// <devdoc>
468         ///    <para>[To be supplied.]</para>
469         /// </devdoc>
470         public void Dispose() {
471             if (_timer != null) {
472                 ((IDisposable)_timer).Dispose();
473             }
474
475             if (_store != null) {
476                 _store.Dispose();
477             }
478         }
479
480         void ResetPerRequestFields() {
481             Debug.Assert(_rqIctx == null, "_rqIctx == null");
482             Debug.Assert(_rqChangeImpersonationRefCount == 0, "_rqChangeImpersonationRefCount == 0");
483
484             _rqSessionState = null;
485             _rqId = null;
486             _rqSessionItems = null;
487             _rqStaticObjects = null;
488             _rqIsNewSession = false;
489             _rqSessionStateNotFound = true;
490             _rqReadonly = false;
491             _rqItem = null;
492             _rqContext = null;
493             _rqAr = null;
494             _rqLockId = null;
495             _rqInCallback = 0;
496             _rqLastPollCompleted = DateTime.MinValue;
497             _rqExecutionTimeout = TimeSpan.Zero;
498             _rqAddedCookie = false;
499             _rqIdNew = false;
500             _rqActionFlags = 0;
501             _rqIctx = null;
502             _rqChangeImpersonationRefCount = 0;
503             _rqTimerThreadImpersonationIctx = null;
504             _rqSupportSessionIdReissue = false;
505         }
506
507         /*
508          * Add a OnStart event handler.
509          *
510          * @param sessionEventHandler
511          */
512
513         /// <devdoc>
514         ///    <para>[To be supplied.]</para>
515         /// </devdoc>
516         public event EventHandler Start {
517             add {
518                 _sessionStartEventHandler += value;
519             }
520             remove {
521                 _sessionStartEventHandler -= value;
522             }
523         }
524
525         void RaiseOnStart(EventArgs e) {
526             if (_sessionStartEventHandler == null)
527                 return;
528
529             Debug.Trace("SessionStateModuleRaiseOnStart",
530                 "Session_Start called for session id:" + _rqId);
531
532             // Session_OnStart for ASPCOMPAT pages has to be raised from an STA thread
533             // 
534             if (HttpRuntime.ApartmentThreading || _rqContext.InAspCompatMode) {
535 #if !FEATURE_PAL // FEATURE_PAL does not enable COM
536                 AspCompatApplicationStep.RaiseAspCompatEvent(
537                     _rqContext,
538                     _rqContext.ApplicationInstance,
539                     null,
540                     _sessionStartEventHandler,
541                     this,
542                     e);
543 #else // !FEATURE_PAL
544                 throw new NotImplementedException ("ROTORTODO");
545 #endif // !FEATURE_PAL
546
547             }
548             else {
549                 if (HttpContext.Current == null) {
550                     // This can happen if it's called by a timer thread
551                     DisposableHttpContextWrapper.SwitchContext(_rqContext);
552                 }
553
554                 _sessionStartEventHandler(this, e);
555             }
556         }
557
558         /*
559          * Fire the OnStart event.
560          *
561          * @param e
562          */
563         void OnStart(EventArgs e) {
564             RaiseOnStart(e);
565         }
566
567         /*
568          * Add a OnEnd event handler.
569          *
570          * @param sessionEventHandler
571          */
572
573         /// <devdoc>
574         ///    <para>[To be supplied.]</para>
575         /// </devdoc>
576         public event EventHandler End {
577             add {
578                 lock(_onEndTarget) {
579                     if (_store != null && _onEndTarget.SessionEndEventHandlerCount == 0) {
580                         _supportSessionExpiry = _store.SetItemExpireCallback(
581                                 new SessionStateItemExpireCallback(_onEndTarget.RaiseSessionOnEnd));
582                     }
583                     ++_onEndTarget.SessionEndEventHandlerCount;
584                 }
585             }
586             remove {
587                 lock(_onEndTarget) {
588                     --_onEndTarget.SessionEndEventHandlerCount;
589                     // 
590                     if (_store != null && _onEndTarget.SessionEndEventHandlerCount == 0) {
591                         _store.SetItemExpireCallback(null);
592                         _supportSessionExpiry = false;
593                     }
594                 }
595             }
596         }
597
598         /*
599          * Acquire session state
600          */
601         IAsyncResult BeginAcquireState(Object source, EventArgs e, AsyncCallback cb, Object extraData) {
602             bool                requiresState;
603             bool                isCompleted = true;
604             bool                skipReadingId = false;
605
606             Debug.Trace("SessionStateModuleOnAcquireState", "Beginning SessionStateModule::OnAcquireState");
607
608             _acquireCalled = true;
609             _releaseCalled = false;
610             ResetPerRequestFields();
611
612             _rqContext = ((HttpApplication)source).Context;
613             _rqAr = new HttpAsyncResult(cb, extraData);
614
615             ChangeImpersonation(_rqContext, false);
616
617             try {
618                 if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Information, EtwTraceFlags.AppSvc)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_BEGIN, _rqContext.WorkerRequest);
619
620                 /* Notify the store we are beginning to get process request */
621                 _store.InitializeRequest(_rqContext);
622
623                 /* determine if the request requires state at all */
624                 requiresState = _rqContext.RequiresSessionState;
625
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);
630                     return _rqAr;
631                 }
632
633                 // See if we can skip reading the session id.  See inline doc of s_allowInProcOptimization
634                 // for details.
635                 if (s_allowInProcOptimization &&
636                     !s_sessionEverSet &&
637                      (!requiresState ||             // Case 1
638                       !((SessionIDManager)_idManager).UseCookieless(_rqContext)) ) {  // Case 2
639
640                     skipReadingId = true;
641
642 #if DBG
643                     if (!requiresState) {
644                         // Case 1
645                         Debug.Trace("SessionStateModuleOnAcquireState", "Skip reading id because page has disabled session state");
646                     }
647                     else {
648                         // Case 2
649                         Debug.Trace("SessionStateModuleOnAcquireState", "Delay reading id because we're using InProc optimization, and we are not using cookieless");
650                     }
651 #endif
652                 }
653                 else {
654                     /* Get sessionid */
655                     _rqId = _idManager.GetSessionID(_rqContext);
656                     Debug.Trace("SessionStateModuleOnAcquireState", "Current request id=" + _rqId);
657                 }
658
659                 if (!requiresState) {
660                     if (_rqId == null) {
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");
666                     }
667                     else {
668                         Debug.Trace("SessionStateModuleOnAcquireState",
669                                     "Handler does not require state, " +
670                                     "resetting timeout for SessionId=" + _rqId +
671                                     "\nReturning from SessionStateModule::OnAcquireState");
672
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
675                         // after Whidbey.
676                         _store.ResetItemTimeout(_rqContext, _rqId);
677                     }
678
679                     _rqAr.Complete(true, null, null);
680
681                     if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Information, EtwTraceFlags.AppSvc)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_END, _rqContext.WorkerRequest);
682                     return _rqAr;
683                 }
684
685                 _rqExecutionTimeout = _rqContext.Timeout;
686
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;
692                 }
693
694                 /* determine if we need just read-only access */
695                 _rqReadonly = _rqContext.ReadOnlySessionState;
696
697                 if (_rqId != null) {
698                     /* get the session state corresponding to this session id */
699                     isCompleted = GetSessionStateItem();
700                 }
701                 else if (!skipReadingId) {
702                     /* if there's no id yet, create it */
703                     bool    redirected = CreateSessionId();
704
705                     _rqIdNew = true;
706
707                     if (redirected) {
708                         if (s_configRegenerateExpiredSessionId) {
709                             // See inline comments in CreateUninitializedSessionState()
710                             CreateUninitializedSessionState();
711                         }
712
713                         _rqAr.Complete(true, null, null);
714
715                         if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Information, EtwTraceFlags.AppSvc)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_END, _rqContext.WorkerRequest);
716                         return _rqAr;
717                     }
718                 }
719
720                 if (isCompleted) {
721                     CompleteAcquireState();
722                     _rqAr.Complete(true, null, null);
723                 }
724
725                 return _rqAr;
726             }
727             finally {
728                 RestoreImpersonation();
729             }
730         }
731
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
735
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");
743
744             bool redirected;
745             _rqId = _idManager.CreateSessionID(_rqContext);
746             _idManager.SaveSessionID(_rqContext, _rqId, out redirected, out _rqAddedCookie);
747
748             return redirected;
749         }
750
751         internal void EnsureStateStoreItemLocked() {
752             // DevDiv 665141: 
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.
756
757             // Item is locked yet here only if this is a new session
758             if (!_rqSessionStateNotFound) {
759                 return;
760             }
761
762             Debug.Assert(_rqId != null, "Session State ID must exist");
763             Debug.Assert(_rqItem != null, "Session State item must exist");
764
765             ChangeImpersonation(_rqContext, false);
766
767             try {
768                 // Store the item if already have been created
769                 _store.SetAndReleaseItemExclusive(_rqContext, _rqId, _rqItem, _rqLockId, true /*_rqSessionStateNotFound*/);
770
771                 // Lock Session State Item in Session State Store
772                 LockSessionStateItem();
773             }
774             catch {
775                 throw;
776             }
777             finally {
778                 RestoreImpersonation();
779             }
780
781             // Mark as old session here. The SessionState is fully initialized, the item is locked
782             _rqSessionStateNotFound = false;
783             s_sessionEverSet = true;
784         }
785  
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;
791
792             Debug.Assert(!(s_allowDelayedStateStoreItemCreation && s_configRegenerateExpiredSessionId),
793                 "!(s_allowDelayedStateStoreItemCreation && s_configRegenerateExpiredSessionId)");
794
795             try {
796                 if (_rqItem != null) {
797                     _rqSessionStateNotFound = false;
798
799                     if ((_rqActionFlags & SessionStateActions.InitializeItem) != 0) {
800                         Debug.Trace("SessionStateModuleOnAcquireState", "Initialize an uninit item");
801                         _rqIsNewSession = true;
802                     }
803                     else {
804                         _rqIsNewSession = false;
805                     }
806                 }
807                 else {
808                     _rqIsNewSession = true;
809                     _rqSessionStateNotFound = true;
810
811                     if (s_allowDelayedStateStoreItemCreation) {
812                         Debug.Trace("SessionStateModuleOnAcquireState", "Delay creating new session state");
813                         delayInitStateStoreItem = true;
814                     }
815
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
820
821                         // We will generate a new session id for this expired session state
822                         bool redirected = CreateSessionId();
823
824                         Debug.Trace("SessionStateModuleOnAcquireState", "Complete re-creating new id; redirected=" + redirected);
825
826                         if (redirected) {
827                             Debug.Trace("SessionStateModuleOnAcquireState", "Will redirect because we've reissued a new id and it's cookieless");
828                             CreateUninitializedSessionState();
829                             return;
830                         }
831                     }
832                 }
833
834                 if (delayInitStateStoreItem) {
835                     _rqSessionState = s_delayedSessionState;
836                 }
837                 else {
838                     InitStateStoreItem(true);
839                 }
840
841                 // Set session state module
842                 SessionStateUtility.AddHttpSessionStateModuleToContext(_rqContext, this, delayInitStateStoreItem);
843
844                 if (_rqIsNewSession) {
845                     Debug.Trace("SessionStateModuleOnAcquireState", "Calling OnStart");
846                     OnStart(EventArgs.Empty);
847                 }
848             }
849             finally {
850                 if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Information, EtwTraceFlags.AppSvc)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_END, _rqContext.WorkerRequest);
851             }
852
853 #if DBG
854             if (_rqIsNewSession) {
855                 if (_rqId == null) {
856                     Debug.Assert(s_allowInProcOptimization, "s_allowInProcOptimization");
857                     Debug.Trace("SessionStateModuleOnAcquireState", "New session: session id reading is delayed"+
858                                 "\nReturning from SessionStateModule::OnAcquireState");
859                 }
860                 else {
861                     Debug.Trace("SessionStateModuleOnAcquireState", "New session: SessionId= " + _rqId +
862                                 "\nReturning from SessionStateModule::OnAcquireState");
863                 }
864
865             }
866             else {
867                 Debug.Trace("SessionStateModuleOnAcquireState", "Retrieved old session, SessionId= " + _rqId +
868                             "\nReturning from SessionStateModule::OnAcquireState");
869
870             }
871 #endif
872         }
873
874         void CreateUninitializedSessionState() {
875             Debug.Assert(_rqChangeImpersonationRefCount > 0, "Must call ChangeImpersonation first");
876
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);
882         }
883
884         internal void InitStateStoreItem(bool addToContext) {
885             Debug.Assert(_rqId != null || s_allowInProcOptimization, "_rqId != null || s_allowInProcOptimization");
886
887             ChangeImpersonation(_rqContext, false);
888             try {
889
890                 if (_rqItem == null) {
891                     Debug.Trace("InitStateStoreItem", "Creating new session state");
892                     _rqItem = _store.CreateNewStoreData(_rqContext, s_timeout);
893                 }
894
895                 _rqSessionItems = _rqItem.Items;
896                 if (_rqSessionItems == null) {
897                     throw new HttpException(SR.GetString(SR.Null_value_for_SessionStateItemCollection));
898                 }
899
900                 // No check for null because we allow our custom provider to return a null StaticObjects.
901                 _rqStaticObjects = _rqItem.StaticObjects;
902
903                 _rqSessionItems.Dirty = false;
904
905                 _rqSessionState = new HttpSessionStateContainer(
906                         this,
907                         _rqId,            // could be null if we're using InProc optimization
908                         _rqSessionItems,
909                         _rqStaticObjects,
910                         _rqItem.Timeout,
911                         _rqIsNewSession,
912                         s_configCookieless,
913                         s_configMode,
914                         _rqReadonly);
915
916                 if (addToContext) {
917                     SessionStateUtility.AddHttpSessionStateToContext(_rqContext, _rqSessionState);
918                 }
919             }
920             finally {
921                 RestoreImpersonation();
922             }
923         }
924
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");
930
931             Debug.Trace("DelayedOperation", "Delayed getting session id");
932
933             bool    redirected;
934
935             ChangeImpersonation(_rqContext, false);
936             try {
937                 _rqId = _idManager.GetSessionID(_rqContext);
938
939                 if (_rqId == null) {
940                     Debug.Trace("DelayedOperation", "Delayed creating session id");
941
942                     redirected = CreateSessionId();
943                     Debug.Assert(!redirected, "DelayedGetSessionId shouldn't redirect us here.");
944                 }
945             }
946             finally {
947                 RestoreImpersonation();
948             }
949
950             return _rqId;
951         }
952
953         void LockSessionStateItem() {
954             bool locked;
955             TimeSpan lockAge;
956
957             Debug.Assert(_rqId != null, "_rqId != null");
958             Debug.Assert(_rqChangeImpersonationRefCount > 0, "Must call ChangeImpersonation first");
959
960             if (!_rqReadonly) {
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.");
963             }
964         }
965
966         bool GetSessionStateItem() {
967             bool            isCompleted = true;
968             bool            locked;
969             TimeSpan        lockAge;
970
971             Debug.Assert(_rqId != null, "_rqId != null");
972             Debug.Assert(_rqChangeImpersonationRefCount > 0, "Must call ChangeImpersonation first");
973
974             if (_rqReadonly) {
975                 _rqItem = _store.GetItem(_rqContext, _rqId, out locked, out lockAge, out _rqLockId, out _rqActionFlags);
976             }
977             else {
978                 _rqItem = _store.GetItemExclusive(_rqContext, _rqId, out locked, out lockAge, out _rqLockId, out _rqActionFlags);
979                 
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);
988                     }
989                 }
990             }
991
992             // We didn't get it because it's locked....
993             if (_rqItem == null && locked) {
994                 // 
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 +
999                                 ", id=" + _rqId);
1000
1001                     _store.ReleaseItemExclusive(_rqContext, _rqId, _rqLockId);
1002                 }
1003
1004                 Debug.Trace("SessionStateModuleOnAcquireState",
1005                             "Item is locked, will poll, id=" + _rqId);
1006
1007                 isCompleted = false;
1008                 PollLockedSession();
1009             }
1010
1011             return isCompleted;
1012         }
1013
1014         void PollLockedSession() {
1015             if (_timerCallback == null) {
1016                 _timerCallback = new TimerCallback(this.PollLockedSessionCallback);
1017             }
1018
1019             if (_timer == null) {
1020                 _timerId++;
1021
1022 #if DBG
1023                 if (!Debug.IsTagPresent("Timer") || Debug.IsTagEnabled("Timer"))
1024 #endif
1025                 {
1026                     if (!s_PollIntervalRegLookedUp)
1027                         LookUpRegForPollInterval();
1028                     _timer = new Timer(_timerCallback, _timerId, LOCKED_ITEM_POLLING_INTERVAL, LOCKED_ITEM_POLLING_INTERVAL);
1029                 }
1030             }
1031         }
1032
1033         [RegistryPermission(SecurityAction.Assert, Unrestricted = true)]
1034         private static void LookUpRegForPollInterval() {
1035             lock (s_PollIntervalRegLock) {
1036                 if (s_PollIntervalRegLookedUp)
1037                     return;
1038                 try {
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;
1043                 }
1044                 catch { // ignore exceptions
1045                 }
1046             }
1047         }
1048
1049
1050         void ResetPollTimer() {
1051             _timerId++;
1052             if (_timer != null) {
1053                 ((IDisposable)_timer).Dispose();
1054                 _timer = null;
1055             }
1056         }
1057
1058         void ChangeImpersonation(HttpContext context, bool timerThread) {
1059 #if !FEATURE_PAL // FEATURE_PAL doesn't enable impersonation
1060             _rqChangeImpersonationRefCount++;
1061
1062             if (_ignoreImpersonation) {
1063                 return;
1064             }
1065
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) {
1072                 return;
1073             }
1074
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.
1078
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();
1084                 }
1085             }
1086             else {
1087                 if (timerThread) {
1088                     // For the timer thread, we should explicity impersonate back to what the HttpContext was
1089                     // orginally impersonating.
1090                     _rqTimerThreadImpersonationIctx = new ClientImpersonationContext(context, false);
1091                 }
1092                 else {
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");
1096                     return;
1097                 }
1098             }
1099 #endif // !FEATURE_PAL
1100         }
1101
1102         void RestoreImpersonation() {
1103             Debug.Assert(_rqChangeImpersonationRefCount != 0, "_rqChangeImpersonationRefCount != 0");
1104
1105             _rqChangeImpersonationRefCount--;
1106
1107             if (_rqChangeImpersonationRefCount == 0) {
1108                 Debug.Assert(!(_rqIctx != null && _rqTimerThreadImpersonationIctx != null), "Should not have mixed mode of impersonation");
1109
1110                 if (_rqIctx != null) {
1111                     _rqIctx.Undo();
1112                     _rqIctx = null;
1113                 }
1114
1115                 if (_rqTimerThreadImpersonationIctx != null) {
1116                     Debug.Assert(_rqContext != null, "_rqContext != null");
1117                     _rqTimerThreadImpersonationIctx.Undo();
1118                     _rqTimerThreadImpersonationIctx = null;
1119                 }
1120             }
1121         }
1122
1123         void PollLockedSessionCallback(object state) {
1124             Debug.Assert(_rqId != null, "_rqId != null");
1125             Debug.Trace("SessionStateModuleOnAcquireState",
1126                         "Polling callback called from timer, id=" + _rqId);
1127
1128             bool isCompleted = false;
1129             Exception error = null;
1130
1131             /* check whether we are currently in a callback */
1132             if (Interlocked.CompareExchange(ref _rqInCallback, 1, 0) != 0)
1133                 return;
1134
1135             try {
1136                 /*
1137                  * check whether this callback is for the current request,
1138                  * and whether sufficient time has passed since the last poll
1139                  * to try again.
1140                  */
1141                 int timerId = (int) state;
1142                 if (    (timerId == _timerId) &&
1143                         (DateTime.UtcNow - _rqLastPollCompleted >= LOCKED_ITEM_POLLING_DELTA)) {
1144
1145                     ChangeImpersonation(_rqContext, true);
1146
1147                     try {
1148                         isCompleted = GetSessionStateItem();
1149                         _rqLastPollCompleted = DateTime.UtcNow;
1150                         if (isCompleted) {
1151                             Debug.Assert(_timer != null, "_timer != null");
1152                             ResetPollTimer();
1153                             CompleteAcquireState();
1154                         }
1155                     }
1156                     finally {
1157                         RestoreImpersonation();
1158                     }
1159                 }
1160             }
1161             catch (Exception e) {
1162                 ResetPollTimer();
1163                 error = e;
1164             }
1165             finally {
1166                 Interlocked.Exchange(ref _rqInCallback, 0);
1167             }
1168
1169             if (isCompleted || error != null) {
1170                 _rqAr.Complete(false, null, error);
1171             }
1172         }
1173
1174
1175         void EndAcquireState(IAsyncResult ar) {
1176             ((HttpAsyncResult)ar).End();
1177         }
1178
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();
1184             }
1185
1186             Debug.Assert(_rqId != null, "_rqId != null");
1187             return _rqId;
1188         }
1189
1190         /*
1191          * Release session state
1192          */
1193
1194         /// <devdoc>
1195         ///    <para>[To be supplied.]</para>
1196         /// </devdoc>
1197         void OnReleaseState(Object source, EventArgs eventArgs) {
1198             HttpApplication             app;
1199             HttpContext                 context;
1200             bool                        setItemCalled = false;
1201
1202             Debug.Trace("SessionStateOnReleaseState", "Beginning SessionStateModule::OnReleaseState");
1203
1204             Debug.Assert(!(_rqAddedCookie && !_rqIsNewSession),
1205                 "If session id was added to the cookie, it must be a new session.");
1206
1207             // !!!
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().
1210
1211             _releaseCalled = true;
1212
1213             app = (HttpApplication)source;
1214             context = app.Context;
1215
1216             ChangeImpersonation(context, false);
1217
1218             try {
1219                 if (_rqSessionState != null) {
1220                     bool delayedSessionState = (_rqSessionState == s_delayedSessionState);
1221
1222                     Debug.Trace("SessionStateOnReleaseState", "Remove session state from context");
1223                     SessionStateUtility.RemoveHttpSessionStateFromContext(_rqContext, delayedSessionState);
1224
1225                     /*
1226                      * Don't store untouched new sessions.
1227                      */
1228
1229                     if (
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
1234
1235                                 // OnStart is not defined
1236                                && _sessionStartEventHandler == null
1237
1238                                // Nothing has been stored in session state
1239                                && (delayedSessionState || !_rqSessionItems.Dirty)
1240                                && (delayedSessionState || _rqStaticObjects == null || _rqStaticObjects.NeverAccessed)
1241                         ) {
1242
1243                         Debug.Trace("SessionStateOnReleaseState", "Not storing unused new session.");
1244                     }
1245                     else if (_rqSessionState.IsAbandoned) {
1246                         Debug.Trace("SessionStateOnReleaseState", "Removing session due to abandonment, SessionId=" + _rqId);
1247
1248                         if (_rqSessionStateNotFound) {
1249                             // The store provider doesn't have it, and so we don't need to remove it from the store.
1250
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");
1257
1258                                     InitStateStoreItem(false /*addToContext*/);
1259                                 }
1260
1261                                 _onEndTarget.RaiseSessionOnEnd(ReleaseStateGetSessionID(), _rqItem);
1262                             }
1263                         }
1264                         else {
1265                             Debug.Assert(_rqItem != null, "_rqItem cannot null if it's not a new session");
1266
1267                             // Remove it from the store because the session is abandoned.
1268                             _store.RemoveItem(_rqContext, ReleaseStateGetSessionID(), _rqLockId, _rqItem);
1269                         }
1270                     }
1271                     else if (!_rqReadonly ||
1272                              (_rqReadonly &&
1273                               _rqIsNewSession &&
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
1279
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
1286                                     )
1287                             ) {
1288
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");
1293
1294                                 InitStateStoreItem(false /*addToContext*/);
1295                             }
1296
1297 #if DBG
1298                             if (_rqSessionItems.Dirty) {
1299                                 Debug.Trace("SessionStateOnReleaseState", "Setting new session due to dirty SessionItems, SessionId=" + _rqId);
1300                             }
1301                             else if (_rqStaticObjects != null && !_rqStaticObjects.NeverAccessed) {
1302                                 Debug.Trace("SessionStateOnReleaseState", "Setting new session due to accessed Static Objects, SessionId=" + _rqId);
1303                             }
1304                             else if (_rqSessionStateNotFound) {
1305                                 Debug.Trace("SessionStateOnReleaseState", "Setting new session because it's not found, SessionId=" + _rqId);
1306                             }
1307                             else {
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));
1311                             }
1312 #endif
1313                             if (_rqItem.Timeout != _rqSessionState.Timeout) {
1314                                 _rqItem.Timeout = _rqSessionState.Timeout;
1315                             }
1316
1317                             s_sessionEverSet = true;
1318                             setItemCalled = true;
1319                             _store.SetAndReleaseItemExclusive(_rqContext, ReleaseStateGetSessionID(), _rqItem, _rqLockId, _rqSessionStateNotFound);
1320                         }
1321                         else {
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);
1324
1325                             if (!_rqSessionStateNotFound) {
1326                                 Debug.Assert(_rqItem != null, "_rqItem cannot null if it's not a new session");
1327                                 _store.ReleaseItemExclusive(_rqContext, ReleaseStateGetSessionID(), _rqLockId);
1328                             }
1329                         }
1330                     }
1331 #if DBG
1332                     else {
1333                         Debug.Trace("SessionStateOnReleaseState", "Session is read-only, ignoring SessionId=" + _rqId);
1334                     }
1335 #endif
1336
1337                     Debug.Trace("SessionStateOnReleaseState", "Returning from SessionStateModule::OnReleaseState");
1338                 }
1339
1340                 if (_rqAddedCookie && !setItemCalled && context.Response.IsBuffered()) {
1341                     _idManager.RemoveSessionID(_rqContext);
1342                 }
1343
1344             }
1345             finally {
1346                 RestoreImpersonation();
1347             }
1348
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();
1358             }
1359         }
1360
1361         /*
1362          * End of request processing. Possibly does release if skipped due to errors
1363          */
1364
1365         /// <devdoc>
1366         ///    <para>[To be supplied.]</para>
1367         /// </devdoc>
1368         void OnEndRequest(Object source, EventArgs eventArgs) {
1369             HttpApplication app;
1370             HttpContext context;
1371             String id;
1372
1373             Debug.Trace("SessionStateOnEndRequest", "Beginning SessionStateModule::OnEndRequest");
1374
1375             app = (HttpApplication)source;
1376             context = app.Context;
1377
1378             /* determine if the request requires state at all */
1379             if (!context.RequiresSessionState) {
1380                 return;
1381             }
1382
1383             ChangeImpersonation(context, false);
1384
1385             try {
1386                 if (!_releaseCalled) {
1387                     if (_acquireCalled) {
1388                         /*
1389                          * need to do release here if the request short-circuited due to an error
1390                          */
1391                         OnReleaseState(source, eventArgs);
1392                     }
1393                     else {
1394                         /*
1395                          * 'advise' -- update session timeout
1396                          */
1397
1398                         if (_rqContext == null) {
1399                             _rqContext = context;
1400                         }
1401
1402                         // We haven't called BeginAcquireState.  So we have to call these InitializeRequest
1403                         // methods here.
1404                         bool    dummy;
1405                         _store.InitializeRequest(_rqContext);
1406                         _idManager.InitializeRequest(_rqContext, true, out dummy);
1407
1408                         id = _idManager.GetSessionID(context);
1409                         if (id != null) {
1410                             Debug.Trace("SessionStateOnEndRequest", "Resetting timeout for SessionId=" + id);
1411                             _store.ResetItemTimeout(context, id);
1412                         }
1413 #if DBG
1414                         else {
1415                             Debug.Trace("SessionStateOnEndRequest", "No session id found.");
1416                         }
1417 #endif
1418                     }
1419                 }
1420
1421                 /* Notify the store we are finishing a request */
1422                 _store.EndRequest(_rqContext);
1423             }
1424             finally {
1425                 _acquireCalled = false;
1426                 _releaseCalled = false;
1427                 RestoreImpersonation();
1428                 ResetPerRequestFields();
1429             }
1430
1431             Debug.Trace("SessionStateOnEndRequest", "Returning from SessionStateModule::OnEndRequest");
1432         }
1433
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);
1437         }
1438
1439         internal bool SessionIDManagerUseCookieless {
1440             get {
1441                 // See VSWhidbey 399907
1442                 if (!_usingAspnetSessionIdManager) {
1443                     return s_configCookieless == HttpCookieMode.UseUri;
1444                 }
1445                 else {
1446                     return ((SessionIDManager)_idManager).UseCookieless(_rqContext);
1447                 }
1448             }
1449         }
1450         
1451         public void ReleaseSessionState(HttpContext context) {
1452             if (HttpRuntime.UseIntegratedPipeline && _acquireCalled && !_releaseCalled) {
1453                 try {
1454                     OnReleaseState(context.ApplicationInstance, null);
1455                 }
1456                 catch { }
1457             }
1458         }
1459
1460         public Task ReleaseSessionStateAsync(HttpContext context) {
1461             ReleaseSessionState(context);
1462             return TaskAsyncHelper.CompletedTask;
1463         }
1464     }
1465 }