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