Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Web / State / InProcStateClientManager.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="InProcStateClientManager.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>                                                                
5 //------------------------------------------------------------------------------
6
7 namespace System.Web.SessionState {
8     using System.Threading;
9     using System.Runtime.Serialization.Formatters.Binary;
10     using System.Runtime.Serialization;
11
12     using System.Text;
13     using System.Collections;
14     using System.IO;
15     using System.Web;
16     using System.Web.Caching;
17     using System.Web.Util;
18     using System.Xml;
19     using System.Collections.Specialized; 
20     using System.Configuration.Provider;
21
22     internal sealed class InProcSessionStateStore : SessionStateStoreProviderBase {
23         internal static readonly int    CACHEKEYPREFIXLENGTH = CacheInternal.PrefixInProcSessionState.Length;
24         internal static readonly int    NewLockCookie = 1;
25
26         CacheItemRemovedCallback        _callback;
27         
28         SessionStateItemExpireCallback  _expireCallback;
29
30
31         /*
32          * Handle callbacks from the cache for session state expiry
33          */
34         public void OnCacheItemRemoved(String key, Object value, CacheItemRemovedReason reason) {
35             InProcSessionState state;
36             String id;
37
38             Debug.Trace("SessionOnEnd", "OnCacheItemRemoved called, reason = " + reason);
39
40             PerfCounters.DecrementCounter(AppPerfCounter.SESSIONS_ACTIVE);
41
42             state = (InProcSessionState) value;
43             
44             if ((state._flags & (int)SessionStateItemFlags.IgnoreCacheItemRemoved) != 0 ||
45                 (state._flags & (int)SessionStateItemFlags.Uninitialized) != 0) {
46                 Debug.Trace("SessionOnEnd", "OnCacheItemRemoved ignored");
47                 return;
48             }
49             
50             switch (reason) {
51                 case CacheItemRemovedReason.Expired: 
52                     PerfCounters.IncrementCounter(AppPerfCounter.SESSIONS_TIMED_OUT);
53                     break;
54
55                 case CacheItemRemovedReason.Removed:
56                     PerfCounters.IncrementCounter(AppPerfCounter.SESSIONS_ABANDONED);
57                     break;
58
59                 default:
60                     break;    
61             }
62
63             TraceSessionStats();
64
65             if (_expireCallback != null) {
66                 id = key.Substring(CACHEKEYPREFIXLENGTH);
67
68                 _expireCallback(id, SessionStateUtility.CreateLegitStoreData(null,
69                                                         state._sessionItems,
70                                                         state._staticObjects,
71                                                         state._timeout));
72             }
73         }
74
75         private string CreateSessionStateCacheKey(String id) {
76             return CacheInternal.PrefixInProcSessionState + id;
77         }
78
79         public override void Initialize(string name, NameValueCollection config)
80         {
81             if (String.IsNullOrEmpty(name))
82                 name = "InProc Session State Provider";
83             base.Initialize(name, config);
84
85             _callback = new CacheItemRemovedCallback(this.OnCacheItemRemoved);
86         }
87
88         public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
89         {
90             _expireCallback = expireCallback;
91             return true;
92         }
93         
94         public override void Dispose()
95         {
96         }
97
98         public override void InitializeRequest(HttpContext context)
99         {
100         }
101
102         SessionStateStoreData DoGet(HttpContext context, 
103                                         String id,
104                                         bool exclusive,
105                                         out bool locked,
106                                         out TimeSpan lockAge, 
107                                         out object lockId,
108                                         out SessionStateActions actionFlags) {
109             string  key = CreateSessionStateCacheKey(id);
110
111             // Set default return values
112             locked = false;
113             lockId = null;
114             lockAge = TimeSpan.Zero;
115             actionFlags = 0;
116
117             // Not technically necessary for InProc, but we do it to be consistent
118             // with SQL provider
119             SessionIDManager.CheckIdLength(id, true /* throwOnFail */);
120
121             InProcSessionState state = (InProcSessionState)HttpRuntime.Cache.InternalCache.Get(key);
122             if (state != null) {
123                 bool    lockedByOther;       // True if the state is locked by another session
124                 int initialFlags;
125
126                 initialFlags = (int)state._flags;
127                 if ((initialFlags & (int)SessionStateItemFlags.Uninitialized) != 0) {
128                     // It is an uninitialized item.  We have to remove that flag.
129                     // We only allow one request to do that.
130                     // For details, see inline doc for SessionStateItemFlags.Uninitialized flag.
131
132                     // If initialFlags != return value of CompareExchange, it means another request has
133                     // removed the flag.
134
135                     Debug.Trace("SessionStateClientSet", "Removing the Uninit flag for item; key = " + key);
136                     if (initialFlags == Interlocked.CompareExchange(
137                                             ref state._flags, 
138                                             initialFlags & (~((int)SessionStateItemFlags.Uninitialized)), 
139                                             initialFlags)) {
140                         actionFlags = SessionStateActions.InitializeItem;
141                     }
142                 }
143
144                 if (exclusive) {
145                     lockedByOther = true;
146                     
147                     // If unlocked, use a spinlock to test and lock the state.
148                     if (!state._locked) {
149                         state._spinLock.AcquireWriterLock();
150                         try {
151                             if (!state._locked) {
152                                 lockedByOther = false;
153                                 state._locked = true;
154                                 state._utcLockDate = DateTime.UtcNow;
155                                 state._lockCookie++;
156                             }
157                             lockId = state._lockCookie;
158                         }
159                         finally {
160                             state._spinLock.ReleaseWriterLock();
161                         }
162                     }
163                     else {
164                         // It's already locked by another request.  Return the lockCookie to caller.
165                         lockId = state._lockCookie;
166                     }
167
168                 }
169                 else {
170                     state._spinLock.AcquireReaderLock();
171                     try {
172                         lockedByOther = state._locked;
173                         lockId = state._lockCookie;
174                     }
175                     finally {
176                         state._spinLock.ReleaseReaderLock();
177                     }
178                 }
179                 
180                 if (lockedByOther) {
181                     // Item found, but locked
182                     locked = true;
183                     lockAge = DateTime.UtcNow - state._utcLockDate;
184                     return null;
185                 }
186                 else {
187                     return SessionStateUtility.CreateLegitStoreData(context, state._sessionItems,
188                                                 state._staticObjects, state._timeout);
189                 }
190             }
191
192             // Not found
193             return null;
194         }
195
196         public override SessionStateStoreData GetItem(HttpContext context, 
197                                                 String id,
198                                                 out bool locked,
199                                                 out TimeSpan lockAge, 
200                                                 out object lockId,
201                                                 out SessionStateActions actionFlags) {
202             return DoGet(context, id, false, out locked, out lockAge, out lockId, out actionFlags);
203         }
204
205
206         public override SessionStateStoreData GetItemExclusive(HttpContext context, 
207                                                 String id,
208                                                 out bool locked,
209                                                 out TimeSpan lockAge, 
210                                                 out object lockId,
211                                                 out SessionStateActions actionFlags) {
212             return DoGet(context, id, true, out locked, out lockAge, out lockId, out actionFlags);
213         }
214
215         // Unlock an item locked by GetExclusive
216         // 'lockId' is the lock context returned by previous call to GetExclusive
217         public override void ReleaseItemExclusive(HttpContext context, 
218                                 String id, 
219                                 object lockId) {
220             Debug.Assert(lockId != null, "lockId != null");
221             
222             string  key = CreateSessionStateCacheKey(id);
223             int     lockCookie = (int)lockId;
224             
225             SessionIDManager.CheckIdLength(id, true /* throwOnFail */);
226
227             InProcSessionState state = (InProcSessionState)HttpRuntime.Cache.InternalCache.Get(key);
228
229             /* If the state isn't there, we probably took too long to run. */
230             if (state == null)
231                 return;
232
233             if (state._locked) {
234                 state._spinLock.AcquireWriterLock();
235                 try {
236                     if (state._locked && lockCookie == state._lockCookie) {
237                         state._locked = false;
238                     }
239                 }
240                 finally {
241                     state._spinLock.ReleaseWriterLock();
242                 }
243             }
244         }
245
246         public override void SetAndReleaseItemExclusive(HttpContext context, 
247                                     String id, 
248                                     SessionStateStoreData item, 
249                                     object lockId, 
250                                     bool newItem) {
251             string          key = CreateSessionStateCacheKey(id);
252             bool            doInsert = true;
253             CacheStoreProvider     cacheInternal = HttpRuntime.Cache.InternalCache;
254             int             lockCookieForInsert = NewLockCookie;
255             ISessionStateItemCollection items = null;
256             HttpStaticObjectsCollection staticObjects = null;
257
258             Debug.Assert(item.Items != null, "item.Items != null");
259             Debug.Assert(item.StaticObjects != null, "item.StaticObjects != null");
260             Debug.Assert(item.Timeout <= SessionStateModule.MAX_CACHE_BASED_TIMEOUT_MINUTES, "item.Timeout <= SessionStateModule.MAX_CACHE_BASED_TIMEOUT_MINUTES");
261
262             SessionIDManager.CheckIdLength(id, true /* throwOnFail */);
263
264             if (item.Items.Count > 0) {
265                 items = item.Items;
266             }
267
268             if (!item.StaticObjects.NeverAccessed) {
269                 staticObjects = item.StaticObjects;
270             }
271
272             if (!newItem) {
273                 Debug.Assert(lockId != null, "lockId != null");
274                 InProcSessionState  stateCurrent = (InProcSessionState) cacheInternal.Get(key);
275                 int                 lockCookie = (int)lockId;
276
277                 /* If the state isn't there, we probably took too long to run. */
278                 if (stateCurrent == null)
279                     return;
280
281                 Debug.Trace("SessionStateClientSet", "state is inStorage; key = " + key);
282                 Debug.Assert((stateCurrent._flags & (int)SessionStateItemFlags.Uninitialized) == 0, "Should never set an unitialized item; key = " + key);
283                 
284                 stateCurrent._spinLock.AcquireWriterLock();
285                 
286                 try {
287                     /* Only set the state if we are the owner */
288                     if (!stateCurrent._locked || stateCurrent._lockCookie != lockCookie) {
289                         Debug.Trace("SessionStateClientSet", "Leave because we're not the owner; key = " + key);
290                         return;
291                     }
292
293                     /* We can change the state in place if the timeout hasn't changed */
294                     if (stateCurrent._timeout == item.Timeout) {
295                         stateCurrent.Copy(
296                             items,
297                             staticObjects,
298                             item.Timeout,
299                             false,
300                             DateTime.MinValue,
301                             lockCookie,
302                             stateCurrent._flags);
303
304                         // Don't need to insert into the Cache because an in-place copy is good enough.
305                         doInsert = false;
306                         Debug.Trace("SessionStateClientSet", "Changing state inplace; key = " + key);
307                     }
308                     else {
309                         /* We are going to insert a new item to replace the current one in Cache
310                            because the expiry time has changed.
311                            
312                            Pleas note that an insert will cause the Session_End to be incorrectly raised. 
313                            
314                            Please note that the item itself should not expire between now and
315                            where we do UtcInsert below because cacheInternal.Get above have just
316                            updated its expiry time.
317                         */ 
318                         stateCurrent._flags |= (int)SessionStateItemFlags.IgnoreCacheItemRemoved;
319                         
320                         /* By setting _lockCookie to 0, we prevent an overwriting by ReleaseExclusive 
321                            when we drop the lock.
322                            The scenario can happen if another request is polling and trying to prempt
323                            the lock we have on the item.
324                         */
325                         lockCookieForInsert = lockCookie;
326                         stateCurrent._lockCookie = 0;
327                     }
328                 }
329                 finally {
330                     stateCurrent._spinLock.ReleaseWriterLock();
331                 }
332             } 
333
334             if (doInsert) {
335                 Debug.Trace("SessionStateClientSet", "Inserting state into Cache; key = " + key);
336                 InProcSessionState state = new InProcSessionState(
337                         items,
338                         staticObjects,
339                         item.Timeout,
340                         false,
341                         DateTime.MinValue,
342                         lockCookieForInsert,
343                         0);
344
345                 try {
346                 }
347                 finally {
348                     // protected from ThreadAbortEx
349                     cacheInternal.Insert(key, state, new CacheInsertOptions() {
350                                                             SlidingExpiration = new TimeSpan(0, state._timeout, 0),
351                                                             Priority = CacheItemPriority.NotRemovable,
352                                                             OnRemovedCallback = _callback
353                                                         });
354                     PerfCounters.IncrementCounter(AppPerfCounter.SESSIONS_TOTAL);
355                     PerfCounters.IncrementCounter(AppPerfCounter.SESSIONS_ACTIVE);
356
357                     TraceSessionStats();
358                 }
359             }
360         }
361
362         
363         public override void CreateUninitializedItem(HttpContext context, String id, int timeout) {
364             string          key = CreateSessionStateCacheKey(id);
365
366             Debug.Assert(timeout <= SessionStateModule.MAX_CACHE_BASED_TIMEOUT_MINUTES, "item.Timeout <= SessionStateModule.MAX_CACHE_BASED_TIMEOUT_MINUTES");
367             
368             SessionIDManager.CheckIdLength(id, true /* throwOnFail */);
369
370             Debug.Trace("SessionStateClientSet", "Inserting an uninitialized item into Cache; key = " + key);
371
372             InProcSessionState state = new InProcSessionState(
373                     null,
374                     null,
375                     timeout,
376                     false,
377                     DateTime.MinValue,
378                     NewLockCookie,
379                     (int)SessionStateItemFlags.Uninitialized);
380
381             // DevDivBugs 146875
382             // We do not want to overwrite an item with an uninitialized item if it is
383             // already in the cache
384             try {
385             }
386             finally {
387                 // protected from ThreadAbortEx
388                 object existingEntry = HttpRuntime.Cache.InternalCache.Add(key, state, new CacheInsertOptions() {
389                                                                                         SlidingExpiration = new TimeSpan(0, timeout, 0),
390                                                                                         Priority = CacheItemPriority.NotRemovable,
391                                                                                         OnRemovedCallback = _callback
392                                                                                     });
393                 if (existingEntry == null) {
394                     PerfCounters.IncrementCounter(AppPerfCounter.SESSIONS_TOTAL);
395                     PerfCounters.IncrementCounter(AppPerfCounter.SESSIONS_ACTIVE);
396                 }
397             }
398         }
399         
400         // Remove an item.  Note that the item is originally obtained by GetExclusive
401         // Same note as Set on lockId
402         public override void RemoveItem(HttpContext context, 
403                                         String id, 
404                                         object lockId, 
405                                         SessionStateStoreData item) {
406             Debug.Assert(lockId != null, "lockId != null");
407                 
408             string          key = CreateSessionStateCacheKey(id);
409             CacheStoreProvider     cacheInternal = HttpRuntime.Cache.InternalCache;
410             int             lockCookie = (int)lockId;
411
412             SessionIDManager.CheckIdLength(id, true /* throwOnFail */);
413
414             InProcSessionState state = (InProcSessionState) cacheInternal.Get(key);
415
416             /* If the item isn't there, we probably took too long to run. */
417             if (state == null)
418                 return;
419
420             state._spinLock.AcquireWriterLock();
421             
422             try {
423                 /* Only remove the item if we are the owner */
424                 if (!state._locked || state._lockCookie != lockCookie)
425                     return;
426
427                 /* prevent overwriting when we drop the lock */
428                 state._lockCookie = 0;
429             }
430             finally {
431                 state._spinLock.ReleaseWriterLock();
432             }
433
434             cacheInternal.Remove(key);
435
436             TraceSessionStats();
437         }
438
439         // Reset the expire time of an item based on its timeout value
440         public override void ResetItemTimeout(HttpContext context, String id)
441         {
442             string  key = CreateSessionStateCacheKey(id);
443             
444             SessionIDManager.CheckIdLength(id, true /* throwOnFail */);
445             HttpRuntime.Cache.InternalCache.Get(key);
446         }
447
448         // Create a new SessionStateStoreData.
449         public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
450         {
451             return SessionStateUtility.CreateLegitStoreData(context, null, null, timeout);
452         }
453
454         // Called during EndRequest event
455         public override void EndRequest(HttpContext context)
456         {
457         }
458         
459         [System.Diagnostics.Conditional("DBG")]
460         internal static void TraceSessionStats() {
461 #if DBG
462             Debug.Trace("SessionState", 
463                         "sessionsTotal="          + PerfCounters.GetCounter(AppPerfCounter.SESSIONS_TOTAL) + 
464                         ", sessionsActive="       + PerfCounters.GetCounter(AppPerfCounter.SESSIONS_ACTIVE) + 
465                         ", sessionsAbandoned="    + PerfCounters.GetCounter(AppPerfCounter.SESSIONS_ABANDONED) + 
466                         ", sessionsTimedout="     + PerfCounters.GetCounter(AppPerfCounter.SESSIONS_TIMED_OUT)
467                         );
468 #endif
469         }
470     }
471
472     internal sealed class InProcSessionState {
473         internal ISessionStateItemCollection         _sessionItems;
474         internal HttpStaticObjectsCollection    _staticObjects;
475         internal int                            _timeout;        // USed to set slidingExpiration in CacheEntry
476         internal bool                           _locked;         // If it's locked by another thread
477         internal DateTime                       _utcLockDate;
478         internal int                            _lockCookie;
479         #pragma warning disable 0649
480         internal ReadWriteSpinLock              _spinLock;
481         #pragma warning restore 0649
482         internal int                            _flags;
483
484         internal InProcSessionState(
485                 ISessionStateItemCollection      sessionItems, 
486                 HttpStaticObjectsCollection staticObjects, 
487                 int                         timeout,
488                 bool                        locked,
489                 DateTime                    utcLockDate,
490                 int                         lockCookie,
491                 int                         flags) {
492
493             Copy(sessionItems, staticObjects, timeout, locked, utcLockDate, lockCookie, flags);
494         }
495
496         internal void Copy(
497                 ISessionStateItemCollection      sessionItems, 
498                 HttpStaticObjectsCollection staticObjects, 
499                 int                         timeout,
500                 bool                        locked,
501                 DateTime                    utcLockDate,
502                 int                         lockCookie,
503                 int                         flags) {
504
505             this._sessionItems = sessionItems;
506             this._staticObjects = staticObjects;
507             this._timeout = timeout;
508             this._locked = locked;
509             this._utcLockDate = utcLockDate;
510             this._lockCookie = lockCookie;
511             this._flags = flags;
512         }
513     }
514 }