Merge branch 'xml-fixes' of https://github.com/myeisha/mono into myeisha-xml-fixes
[mono.git] / mcs / class / System.Web / System.Web.SessionState_2.0 / SessionInProcHandler.cs
1 //
2 // System.Web.SessionState.SessionInProcHandler
3 //
4 // Authors:
5 //      Marek Habersack <grendello@gmail.com>
6 //
7 // (C) 2006 Marek Habersack
8 // (C) 2010 Novell, Inc (http://novell.com/)
9 //
10
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31 #if NET_2_0
32 using System;
33 using System.IO;
34 using System.Collections;
35 using System.Collections.Specialized;
36 using System.Configuration.Provider;
37 using System.Web.Caching;
38 using System.Web.Configuration;
39 using System.Threading;
40
41 namespace System.Web.SessionState
42 {
43         internal sealed class InProcSessionItem
44         {
45                 public bool locked;
46                 public bool cookieless;
47                 public ISessionStateItemCollection items;
48                 public DateTime lockedTime;
49                 public DateTime expiresAt;
50                 public ReaderWriterLockSlim rwlock;
51                 public Int32 lockId;
52                 public int timeout;
53                 public bool resettingTimeout;
54                 public HttpStaticObjectsCollection staticItems;
55                 
56                 internal InProcSessionItem ()
57                 {
58                         this.locked = false;
59                         this.cookieless = false;
60                         this.items = null;
61                         this.staticItems = null;
62                         this.lockedTime = DateTime.MinValue;
63                         this.expiresAt = DateTime.MinValue;
64                         this.rwlock = new ReaderWriterLockSlim ();
65                         this.lockId = Int32.MinValue;
66                         this.timeout = 0;
67                         this.resettingTimeout = false;
68                 }
69
70                 public void Dispose ()
71                 {
72                         if (rwlock != null) {
73                                 rwlock.Dispose ();
74                                 rwlock = null;
75                         }
76                         staticItems = null;
77                         items = null;
78                 }
79                 
80                 ~InProcSessionItem ()
81                 {
82                         Dispose ();
83                 }
84         }
85         
86         internal class SessionInProcHandler : SessionStateStoreProviderBase
87         {
88                 const string CachePrefix = "@@@InProc@";
89                 const int CachePrefixLength = 10;
90                 
91                 const Int32 lockAcquireTimeout = 30000;
92                 
93                 CacheItemRemovedCallback removedCB;
94                 //NameValueCollection privateConfig;
95                 SessionStateItemExpireCallback expireCallback;
96                 HttpStaticObjectsCollection staticObjects;
97                 
98                 public override SessionStateStoreData CreateNewStoreData (HttpContext context, int timeout)
99                 {
100                         return new SessionStateStoreData (new SessionStateItemCollection (),
101                                                           staticObjects, timeout);
102                 }
103
104                 void InsertSessionItem (InProcSessionItem item, int timeout, string id)
105                 {
106                         if (item == null || String.IsNullOrEmpty (id))
107                                 return;
108
109                         HttpRuntime.InternalCache.Insert (id,
110                                                           item,
111                                                           null,
112                                                           Cache.NoAbsoluteExpiration,
113                                                           TimeSpan.FromMinutes (timeout),
114                                                           CacheItemPriority.AboveNormal,
115                                                           removedCB);
116                 }
117                 
118                 public override void CreateUninitializedItem (HttpContext context, string id, int timeout)
119                 {
120                         EnsureGoodId (id, true);
121                         InProcSessionItem item = new InProcSessionItem ();
122                         item.expiresAt = DateTime.UtcNow.AddMinutes (timeout);
123                         item.timeout = timeout;
124                         InsertSessionItem (item, timeout, CachePrefix + id);
125                 }
126                 
127                 public override void Dispose ()
128                 {
129                 }
130                 
131                 public override void EndRequest (HttpContext context)
132                 {
133                         if (staticObjects != null) {
134                                 staticObjects.GetObjects ().Clear ();
135                                 staticObjects = null;
136                         }
137                 }
138
139                 SessionStateStoreData GetItemInternal (HttpContext context,
140                                                        string id,
141                                                        out bool locked,
142                                                        out TimeSpan lockAge,
143                                                        out object lockId,
144                                                        out SessionStateActions actions,
145                                                        bool exclusive)
146                 {
147                         locked = false;
148                         lockAge = TimeSpan.MinValue;
149                         lockId = Int32.MinValue;
150                         actions = SessionStateActions.None;
151
152                         if (id == null)
153                                 return null;
154                         
155                         Cache cache = HttpRuntime.InternalCache;
156                         string CacheId = CachePrefix + id;
157                         InProcSessionItem item = cache [CacheId] as InProcSessionItem;
158                         
159                         if (item == null)
160                                 return null;
161
162                         bool readLocked = false, writeLocked = false;
163                         try {
164                                 if (item.rwlock.TryEnterUpgradeableReadLock (lockAcquireTimeout))
165                                         readLocked = true;
166                                 else
167                                         throw new ApplicationException ("Failed to acquire lock");
168                                 
169                                 if (item.locked) {
170                                         locked = true;
171                                         lockAge = DateTime.UtcNow.Subtract (item.lockedTime);
172                                         lockId = item.lockId;
173                                         return null;
174                                 }
175                                 
176                                 if (exclusive) {
177                                         if (item.rwlock.TryEnterWriteLock (lockAcquireTimeout))
178                                                 writeLocked = true;
179                                         else
180                                                 throw new ApplicationException ("Failed to acquire lock");
181                                         item.locked = true;
182                                         item.lockedTime = DateTime.UtcNow;
183                                         item.lockId++;
184                                         lockId = item.lockId;
185                                 }
186                                 if (item.items == null) {
187                                         actions = SessionStateActions.InitializeItem;
188                                         item.items = new SessionStateItemCollection ();
189                                 }
190                                 if (item.staticItems == null)
191                                         item.staticItems = staticObjects;
192                                 
193                                 return new SessionStateStoreData (item.items,
194                                                                   item.staticItems,
195                                                                   item.timeout);
196                         } catch {
197                                 // we want such errors to be passed to the application.
198                                 throw;
199                         } finally {
200                                 if (writeLocked)
201                                         item.rwlock.ExitWriteLock ();
202                                 if (readLocked)
203                                         item.rwlock.ExitUpgradeableReadLock ();
204                         }
205                 }
206                 
207                 public override SessionStateStoreData GetItem (HttpContext context,
208                                                                string id,
209                                                                out bool locked,
210                                                                out TimeSpan lockAge,
211                                                                out object lockId,
212                                                                out SessionStateActions actions)
213                 {
214                         EnsureGoodId (id, false);
215                         return GetItemInternal (context, id, out locked, out lockAge, out lockId, out actions, false);
216                 }
217                 
218                 public override SessionStateStoreData GetItemExclusive (HttpContext context,
219                                                                         string id,
220                                                                         out bool locked,
221                                                                         out TimeSpan lockAge,
222                                                                         out object lockId,
223                                                                         out SessionStateActions actions)
224                 {
225                         EnsureGoodId (id, false);
226                         return GetItemInternal (context, id, out locked, out lockAge, out lockId, out actions, true);
227                 }
228
229                 public override void Initialize (string name, NameValueCollection config)
230                 {
231                         if (String.IsNullOrEmpty (name))
232                                 name = "Session InProc handler";
233                         removedCB = new CacheItemRemovedCallback (OnSessionRemoved);
234                         //privateConfig = config;
235                         base.Initialize (name, config);
236                 }
237                 
238                 public override void InitializeRequest (HttpContext context)
239                 {
240                         staticObjects = HttpApplicationFactory.ApplicationState.SessionObjects.Clone ();
241                 }
242                 
243                 public override void ReleaseItemExclusive (HttpContext context,
244                                                            string id,
245                                                            object lockId)
246                 {
247                         EnsureGoodId (id, true);
248                         string CacheId = CachePrefix + id;
249                         InProcSessionItem item = HttpRuntime.InternalCache [CacheId] as InProcSessionItem;
250                         
251                         if (item == null || lockId == null || lockId.GetType() != typeof(Int32) || item.lockId != (Int32)lockId)
252                                 return;
253
254                         bool locked = false;
255                         try {
256                                 if (item.rwlock.TryEnterWriteLock (lockAcquireTimeout))
257                                         locked = true;
258                                 else
259                                         throw new ApplicationException ("Failed to acquire lock");
260                                 item.locked = false;
261                         } catch {
262                                 throw;
263                         } finally {
264                                 if (locked)
265                                         item.rwlock.ExitWriteLock ();
266                         }
267                 }
268                 
269                 public override void RemoveItem (HttpContext context,
270                                                  string id,
271                                                  object lockId,
272                                                  SessionStateStoreData item)
273                 {
274                         EnsureGoodId (id, true);
275                         string CacheId = CachePrefix + id;
276                         Cache cache = HttpRuntime.InternalCache;
277                         InProcSessionItem inProcItem = cache [CacheId] as InProcSessionItem;
278
279                         if (inProcItem == null || lockId == null || lockId.GetType() != typeof(Int32) || inProcItem.lockId != (Int32)lockId)
280                                 return;
281
282                         bool locked = false;
283                         try {
284                                 if (inProcItem.rwlock.TryEnterWriteLock (lockAcquireTimeout))
285                                         locked = true;
286                                 else
287                                         throw new ApplicationException ("Failed to acquire lock after");
288                                 cache.Remove (CacheId);
289                         } catch {
290                                 throw;
291                         } finally {
292                                 if (locked)
293                                         inProcItem.rwlock.ExitWriteLock ();
294                         }
295                 }
296                 
297                 public override void ResetItemTimeout (HttpContext context, string id)
298                 {
299                         EnsureGoodId (id, true);
300                         string CacheId = CachePrefix + id;
301                         Cache cache = HttpRuntime.InternalCache;
302                         InProcSessionItem item = cache [CacheId] as InProcSessionItem;
303                         
304                         if (item == null)
305                                 return;
306
307                         bool locked = false;
308                         try {
309                                 if (item.rwlock.TryEnterWriteLock (lockAcquireTimeout))
310                                         locked = true;
311                                 else
312                                         throw new ApplicationException ("Failed to acquire lock after");
313                                 item.resettingTimeout = true;
314                                 cache.Remove (CacheId);
315                                 InsertSessionItem (item, item.timeout, CacheId);
316                         } catch {
317                                 throw;
318                         } finally {
319                                 if (locked)
320                                         item.rwlock.ExitWriteLock ();
321                         }
322                 }
323
324                 /* In certain situations the 'item' parameter passed to SetAndReleaseItemExclusive
325                    may be null. The issue was reported in bug #333898, but the reporter cannot
326                    provide a test case that triggers the issue. Added work around the problem
327                    in the way that should have the least impact on the rest of the code. If 'item'
328                    is null, then the new session item is created without the items and staticItems
329                    collections - they will be initialized to defaults when retrieving the session
330                    item. This is not a correct fix, but since there is no test case this is the best
331                    what can be done right now.
332                 */
333                 public override void SetAndReleaseItemExclusive (HttpContext context,
334                                                                  string id,
335                                                                  SessionStateStoreData item,
336                                                                  object lockId,
337                                                                  bool newItem)
338                 {
339                         EnsureGoodId (id, true);
340                         string CacheId = CachePrefix + id;
341                         Cache cache = HttpRuntime.InternalCache;
342                         InProcSessionItem inProcItem = cache [CacheId] as InProcSessionItem;
343                         ISessionStateItemCollection itemItems = null;
344                         int itemTimeout = 20;
345                         HttpStaticObjectsCollection itemStaticItems = null;
346
347                         if (item != null) {
348                                 itemItems = item.Items;
349                                 itemTimeout = item.Timeout;
350                                 itemStaticItems = item.StaticObjects;
351                         }
352                         
353                         if (newItem || inProcItem == null) {
354                                 inProcItem = new InProcSessionItem ();
355                                 inProcItem.timeout = itemTimeout;
356                                 inProcItem.expiresAt = DateTime.UtcNow.AddMinutes (itemTimeout);
357                                 if (lockId.GetType() == typeof(Int32))
358                                         inProcItem.lockId = (Int32)lockId;
359                         } else {
360                                 if (lockId == null || lockId.GetType() != typeof(Int32) || inProcItem.lockId != (Int32)lockId)
361                                         return;
362                                 inProcItem.resettingTimeout = true;
363                                 cache.Remove (CacheId);
364                         }
365
366                         bool locked = false;
367                         try {
368                                 if (inProcItem.rwlock.TryEnterWriteLock (lockAcquireTimeout))
369                                         locked = true;
370                                 else
371                                         throw new ApplicationException ("Failed to acquire lock");
372                                 inProcItem.locked = false;
373                                 inProcItem.items = itemItems;
374                                 inProcItem.staticItems = itemStaticItems;
375                                 InsertSessionItem (inProcItem, itemTimeout, CacheId);
376                         } catch {
377                                 throw;
378                         } finally {
379                                 if (locked)
380                                         inProcItem.rwlock.ExitWriteLock ();
381                         }
382                 }
383                 
384                 public override bool SetItemExpireCallback (SessionStateItemExpireCallback expireCallback)
385                 {
386                         this.expireCallback = expireCallback;
387                         return true;
388                 }
389
390                 void EnsureGoodId (string id, bool throwOnNull)
391                 {
392                         if (id == null)
393                                 if (throwOnNull)
394                                         throw new HttpException ("Session ID is invalid");
395                                 else
396                                         return;
397                         
398                         if (id.Length > SessionIDManager.SessionIDMaxLength)
399                                 throw new HttpException ("Session ID too long");
400                 }
401
402                 void OnSessionRemoved (string key, object value, CacheItemRemovedReason reason)
403                 {
404                         if (expireCallback != null) {
405                                 if (key.StartsWith (CachePrefix, StringComparison.OrdinalIgnoreCase))
406                                         key = key.Substring (CachePrefixLength);
407                                 
408                                 if (value is SessionStateStoreData)
409                                         expireCallback (key, (SessionStateStoreData)value);
410                                 else if (value is InProcSessionItem) {
411                                         InProcSessionItem item = (InProcSessionItem)value;
412                                         if (item.resettingTimeout) {
413                                                 item.resettingTimeout = false;
414                                                 return;
415                                         }
416                                         
417                                         expireCallback (key,
418                                                         new SessionStateStoreData (
419                                                                 item.items,
420                                                                 item.staticItems,
421                                                                 item.timeout));
422                                         item.Dispose ();
423                                 } else
424                                         expireCallback (key, null);
425                         } else if (value is InProcSessionItem)
426                                 ((InProcSessionItem)value).Dispose ();
427                 }
428         }
429 }
430 #endif