**** Merged from MCS ****
[mono-project.git] / mcs / class / System.Web / System.Web.Caching / ExpiresBuckets.cs
blob7b8c139afeb83d4cee94f752f7771a4dbbe91028
1 //
2 // System.Web.Caching
3 //
4 // Author:
5 // Patrik Torstensson (Patrik.Torstensson@labs2.com)
6 // Daniel Cazzulino [DHC] (dcazzulino@users.sf.net)
7 //
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 //
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 //
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 using System;
31 using System.Collections;
32 using System.Threading;
34 namespace System.Web.Caching {
35 /// <summary>
36 /// Responsible for holding a cache entry in the linked list bucket.
37 /// </summary>
38 internal struct ExpiresEntry {
39 internal CacheEntry Entry;
40 internal long TicksExpires;
41 internal int _intNext;
44 /// <summary>
45 /// Holds cache entries that has a expiration in a bucket list.
46 /// </summary>
47 internal class ExpiresBucket {
48 private static int MIN_ENTRIES = 4;
50 private byte _byteID;
51 private int _intSize;
52 private int _intCount;
53 private int _intNext;
55 private Cache _objManager;
57 private ExpiresEntry [] _arrEntries;
59 private System.Threading.ReaderWriterLock _lock = new System.Threading.ReaderWriterLock();
61 /// <summary>
62 /// Keeps a list of indexes in the list which are available to place new items. [DHC]
63 /// </summary>
64 private Int32Collection _freeidx = new Int32Collection();
66 /// <summary>
67 /// Constructs a new bucket.
68 /// </summary>
69 /// <param name="bucket">Current bucket ID.</param>
70 /// <param name="objManager">Cache manager reponsible for the item(s) in the expires bucket.</param>
71 internal ExpiresBucket (byte bucket, Cache objManager) {
72 _objManager = objManager;
73 Initialize(bucket);
76 /// <summary>
77 /// Initializes the expires bucket, creates a linked list of MIN_ENTRIES.
78 /// </summary>
79 /// <param name="bucket">Bucket ID.</param>
80 private void Initialize (byte bucket) {
81 _byteID = bucket;
82 _intNext = 0;
83 _intCount = 0;
85 _arrEntries = new ExpiresEntry [MIN_ENTRIES];
86 _intSize = MIN_ENTRIES;
88 int intPos = 0;
89 do {
90 _arrEntries[intPos]._intNext = intPos + 1;
91 _arrEntries[intPos].TicksExpires = Cache.NoAbsoluteExpiration.Ticks;
93 intPos++;
94 } while (intPos < _intSize);
96 _arrEntries[_intSize - 1]._intNext = -1;
99 /// <summary>
100 /// Expands the bucket linked array list.
101 /// </summary>
102 private void Expand () {
103 _lock.AcquireWriterLock(-1);
104 try {
105 int oldsize = _intSize;
106 _intSize *= 2;
108 // Copy items to the new list.
109 ExpiresEntry[] newlist = new ExpiresEntry[_intSize];
110 _arrEntries.CopyTo(newlist, 0);
112 // Set last element to point to the next new empty element
113 _intNext = oldsize;
114 newlist[oldsize - 1]._intNext = oldsize;
116 // Initialize positions for the rest of new elements.
117 for (int i = oldsize; i < _intSize; i++) {
118 newlist[i]._intNext = i + 1;
119 newlist[i].TicksExpires = Cache.NoAbsoluteExpiration.Ticks;
122 // Last item signals the expansion of the list.
123 newlist[_intSize - 1]._intNext = -1;
125 // Replace the existing list.
126 _arrEntries = newlist;
128 finally {
129 _lock.ReleaseWriterLock();
134 /// <summary>
135 /// Adds a cache entry into the expires bucket.
136 /// </summary>
137 /// <param name="objEntry">Cache Entry object to be added.</param>
138 internal void Add (CacheEntry objEntry) {
139 _lock.AcquireWriterLock(-1);
140 try {
141 if (_intNext == -1) {
142 if (_freeidx.Count == 0)
143 Expand();
144 else {
145 _intNext = _freeidx[0];
146 _freeidx.Remove(_intNext);
150 _arrEntries[_intNext].TicksExpires = objEntry.Expires;
151 _arrEntries[_intNext].Entry = objEntry;
153 objEntry.ExpiresBucket = _byteID;
154 objEntry.ExpiresIndex = _intNext;
156 // If there are free indexes in the list, reuse them for the _next value.
157 if (_freeidx.Count != 0) {
158 _intNext = _freeidx [0];
159 _freeidx.Remove(_intNext);
160 } else
161 _intNext = _arrEntries[_intNext]._intNext;
163 _intCount++;
165 finally {
166 _lock.ReleaseWriterLock();
170 /// <summary>
171 /// Removes a cache entry from the expires bucket.
172 /// </summary>
173 /// <param name="objEntry">Cache entry to be removed.</param>
174 internal void Remove(CacheEntry objEntry) {
175 // Check if this is our bucket
176 if (objEntry.ExpiresBucket != _byteID) return;
177 if (objEntry.ExpiresIndex == CacheEntry.NoIndexInBucket) return;
179 _lock.AcquireWriterLock(-1);
180 try {
181 if (_arrEntries.Length < objEntry.ExpiresIndex) return;
182 _intCount--;
184 // Push the index as a free one.
185 _freeidx.Add(objEntry.ExpiresIndex);
187 _arrEntries[objEntry.ExpiresIndex].Entry = null;
188 // Clear bucket-related values from the item.
189 objEntry.ExpiresBucket = CacheEntry.NoBucketHash;
190 objEntry.ExpiresIndex = CacheEntry.NoIndexInBucket;
192 finally {
193 //Releases both reader & writer locks
194 _lock.ReleaseWriterLock();
198 /// <summary>
199 /// Updates a cache entry in the expires bucket, this is called during a hit of an item if the
200 /// cache item has a sliding expiration. The function is responsible for updating the cache
201 /// entry.
202 /// </summary>
203 /// <param name="objEntry">Cache entry to update.</param>
204 /// <param name="ticksExpires">New expiration value for the cache entry.</param>
205 internal void Update(CacheEntry objEntry, long ticksExpires) {
206 // Check if this is our bucket
207 if (objEntry.ExpiresBucket != _byteID) return;
208 if (objEntry.ExpiresIndex == CacheEntry.NoIndexInBucket) return;
210 _lock.AcquireWriterLock(-1);
211 try {
212 if (_arrEntries.Length < objEntry.ExpiresIndex) return;
214 // Proceed to update.
215 _arrEntries[objEntry.ExpiresIndex].TicksExpires = ticksExpires;
216 _arrEntries[objEntry.ExpiresIndex].Entry.Expires = ticksExpires;
218 finally {
219 //Releases both read & write locks
220 _lock.ReleaseWriterLock();
224 /// <summary>
225 /// Flushes all cache entries that has expired and removes them from the cache manager.
226 /// </summary>
227 internal void FlushExpiredItems() {
228 ExpiresEntry objEntry;
229 ArrayList removeList = null;
230 ArrayList flushList = null;
231 int intPos;
232 long ticksNow;
234 ticksNow = DateTime.UtcNow.Ticks;
236 intPos = 0;
237 // Lookup all items that needs to be removed, this is done in a two part
238 // operation to minimize the locking time.
239 _lock.AcquireReaderLock (-1);
240 try {
241 do {
242 objEntry = _arrEntries [intPos];
243 if (null != objEntry.Entry &&
244 ((objEntry.TicksExpires < ticksNow) || objEntry.Entry.ExpiresBucket != _byteID))
246 if (null == removeList)
247 removeList = new ArrayList ();
249 removeList.Add (objEntry);
252 intPos++;
253 } while (intPos < _intSize);
255 finally {
256 _lock.ReleaseReaderLock ();
259 if (null != removeList) {
260 flushList = new ArrayList (removeList.Count);
262 _lock.AcquireWriterLock (-1);
263 try {
264 foreach (ExpiresEntry entry in removeList) {
265 ExpiresEntry e = entry;
266 int id = entry.Entry.ExpiresIndex;
268 //push the index for reuse
269 _freeidx.Add (id);
271 if (entry.Entry.ExpiresBucket == _byteID) {
272 // add to our flush list
273 flushList.Add (e.Entry);
275 // Remove from bucket
276 e.Entry.ExpiresBucket = CacheEntry.NoBucketHash;
277 e.Entry.ExpiresIndex = CacheEntry.NoIndexInBucket;
280 e.Entry = null;
282 // Entries is structs, put it back
283 _arrEntries [id] = e;
286 finally {
287 _lock.ReleaseWriterLock ();
290 // We can call this without locks, it can takes time due to callbacks to user code
291 foreach (CacheEntry entry in flushList)
292 _objManager.Remove (entry.Key, CacheItemRemovedReason.Expired);
294 flushList = null;
295 removeList = null;
299 /// <summary>
300 /// Returns the current size of the expires bucket.
301 /// </summary>
302 internal int Size {
303 get {
304 return _intSize;
308 /// <summary>
309 /// Returns number of items in the bucket.
310 /// </summary>
311 internal int Count {
312 get {
313 return _intCount;
317 #region Private Int32Collection
318 /* This file has been automatically generated by TextBox -- DO NOT EDIT! */
320 Int32Collection
321 Int32Collection.Enumerator
323 These C# classes implement a strongly-typed collection of
324 Int32 objects.
326 The internal representation is an array of Int32, so the performance
327 characteristics will be more like a vector than a list, to use STL terminology.
329 The implementation is optimized for value-types, as it goes to great length to
330 avoid the overhead of boxing and unboxing. But it should also work well for
331 reference types.
333 Mad props to Nick Wienholt <sheyenne@bigpond.com> for assisting me in
334 this research, and the testing, the benchmarking, and of course, the
335 implementation!
337 Last but not least, a quick shout out to Kit George, for his generous
338 contribution to the dotnet mailing list -- a code-generator for
339 CollectionBase-derived classes:
340 http://discuss.develop.com/archives/wa.exe?A2=ind0107C&L=DOTNET&P=R35911
341 This was the original inspiration for the fine code you are now enjoying.
343 - Shawn Van Ness
345 Other folks who've contributed:
346 Ethan Smith <ethan.smith@pobox.com> (minor perf. improvements)
347 Joel Mueller <jmueller@swiftk.com> (major perf. improvements)
348 Chris Sells <csells@sellsbrothers.com> (generative programming guru)
349 Patrice Lafond <plafond@hemisphere.bm> (a bug fix -- yikes!)
352 /// <summary>
353 /// An optimized collection for holding <see cref="Int32"/> values.
354 /// </summary>
355 [System.Serializable]
356 private class Int32Collection : System.Collections.ICollection, System.Collections.IList, System.Collections.IEnumerable {
357 #region Private vars & ctors
358 private const int DefaultMinimumCapacity = 16;
360 private System.Int32[] m_array = new System.Int32[DefaultMinimumCapacity];
361 private int m_count = 0;
362 private int m_version = 0;
364 /// <summary />
365 public Int32Collection() {
368 /// <summary />
369 public Int32Collection(Int32Collection collection) {
370 AddRange(collection); }
372 /// <summary />
373 public Int32Collection(System.Int32[] array) {
374 AddRange(array); }
375 #endregion
377 #region Public members
379 /// <summary />
380 public int Count {
381 get {
382 return m_count; }
385 /// <summary />
386 public void CopyTo(System.Int32[] array) {
387 this.CopyTo(array, 0);
390 /// <summary />
391 public void CopyTo(System.Int32[] array, int start) {
392 if (m_count > array.GetUpperBound(0)+1-start)
393 throw new System.ArgumentException("Destination array was not long enough.");
395 // for (int i=0; i < m_count; ++i) array[start+i] = m_array[i];
396 System.Array.Copy(m_array, 0, array, start, m_count);
399 /// <summary />
400 public System.Int32 this[int index] {
401 get {
402 ValidateIndex(index); // throws
403 return m_array[index];
405 set {
406 ValidateIndex(index); // throws
408 ++m_version;
409 m_array[index] = value;
413 /// <summary />
414 public int Add(System.Int32 item) {
415 if (NeedsGrowth())
416 Grow();
418 ++m_version;
419 m_array[m_count] = item;
421 return m_count++;
424 /// <summary />
425 public void Clear() {
426 ++m_version;
427 m_array = new System.Int32[DefaultMinimumCapacity];
428 m_count = 0;
431 /// <summary />
432 public bool Contains(System.Int32 item) {
433 return ((IndexOf(item) == -1)?false:true);
436 /// <summary />
437 public int IndexOf(System.Int32 item) {
438 for (int i=0; i < m_count; ++i)
439 if (m_array[i].Equals(item))
440 return i;
441 return -1;
444 /// <summary />
445 public void Insert(int position, System.Int32 item) {
446 ValidateIndex(position,true); // throws
448 if (NeedsGrowth())
449 Grow();
451 ++m_version;
452 System.Array.Copy(m_array, position, m_array, position+1, m_count-position);
454 m_array[position] = item;
455 m_count++;
458 /// <summary />
459 public void Remove(System.Int32 item) {
460 int index = IndexOf(item);
461 if (index < 0)
462 throw new System.ArgumentException("Cannot remove the specified item because it was not found in the specified Collection.");
464 RemoveAt(index);
467 /// <summary />
468 public void RemoveAt(int index) {
469 ValidateIndex(index); // throws
471 ++m_version;
472 m_count--;
473 System.Array.Copy(m_array, index+1, m_array, index, m_count-index);
475 if (NeedsTrimming())
476 Trim();
479 // Public helpers (just to mimic some nice features of ArrayList)
481 /// <summary />
482 public int Capacity {
483 get {
484 return m_array.Length; }
485 set {
486 if (value < m_count) value = m_count;
487 if (value < DefaultMinimumCapacity) value = DefaultMinimumCapacity;
489 if (m_array.Length == value) return;
491 ++m_version;
493 System.Int32[] temp = new System.Int32[value];
494 System.Array.Copy(m_array, 0, temp, 0, m_count);
495 m_array = temp;
499 /// <summary />
500 public void AddRange(Int32Collection collection) {
501 ++m_version;
503 Capacity += collection.Count;
504 System.Array.Copy(collection.m_array, 0, this.m_array, m_count, collection.m_count);
505 m_count += collection.Count;
508 /// <summary />
509 public void AddRange(System.Int32[] array) {
510 ++m_version;
512 Capacity += array.Length;
513 System.Array.Copy(array, 0, this.m_array, m_count, array.Length);
514 m_count += array.Length;
516 #endregion
518 #region Private helper methods
519 private void ValidateIndex(int index) {
520 ValidateIndex(index,false);
523 private void ValidateIndex(int index, bool allowEqualEnd) {
524 int max = (allowEqualEnd)?(m_count):(m_count-1);
525 if (index < 0 || index > max)
526 throw new System.ArgumentOutOfRangeException("Index was out of range. Must be non-negative and less than the size of the collection.", (object)index, "Specified argument was out of the range of valid values.");
529 private bool NeedsGrowth() {
530 return (m_count >= Capacity);
533 private void Grow() {
534 if (NeedsGrowth())
535 Capacity = m_count*2;
538 private bool NeedsTrimming() {
539 return (m_count <= Capacity/2);
542 private void Trim() {
543 if (NeedsTrimming())
544 Capacity = m_count;
546 #endregion
548 #region System.Collections.ICollection implementation
549 bool System.Collections.ICollection.IsSynchronized {
550 get {
551 return m_array.IsSynchronized; }
554 object System.Collections.ICollection.SyncRoot {
555 get {
556 return m_array.SyncRoot; }
559 void System.Collections.ICollection.CopyTo(System.Array array, int start) {
560 this.CopyTo((System.Int32[])array, start);
562 #endregion
564 #region System.Collections.IList implementation
565 bool System.Collections.IList.IsFixedSize {
566 get {
567 return false; }
570 bool System.Collections.IList.IsReadOnly {
571 get {
572 return false; }
575 object System.Collections.IList.this[int index] {
576 get { return (object)this[index]; }
577 set { this[index] = (System.Int32)value; }
580 int System.Collections.IList.Add(object item) {
581 return this.Add((System.Int32)item);
584 bool System.Collections.IList.Contains(object item) {
585 return this.Contains((System.Int32)item);
588 int System.Collections.IList.IndexOf(object item) {
589 return this.IndexOf((System.Int32)item);
592 void System.Collections.IList.Insert(int position, object item) {
593 this.Insert(position, (System.Int32)item);
596 void System.Collections.IList.Remove(object item) {
597 this.Remove((System.Int32)item);
599 #endregion
601 #region System.Collections.IEnumerable and enumerator implementation
602 /// <summary />
603 public Enumerator GetEnumerator() {
604 return new Enumerator(this);
607 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
608 return GetEnumerator();
611 // Nested enumerator class
612 /// <summary />
613 public class Enumerator : System.Collections.IEnumerator {
614 private Int32Collection m_collection;
615 private int m_index;
616 private int m_version;
618 // Construction
620 /// <summary />
621 public Enumerator(Int32Collection tc) {
622 m_collection = tc;
623 m_index = -1;
624 m_version = tc.m_version;
627 /// <summary />
628 public System.Int32 Current {
629 get {
630 return m_collection[m_index]; }
633 /// <summary />
634 public bool MoveNext() {
635 if (m_version != m_collection.m_version)
636 throw new System.InvalidOperationException("Collection was modified; enumeration operation may not execute.");
638 ++m_index;
639 return (m_index < m_collection.Count)?true:false;
642 /// <summary />
643 public void Reset() {
644 if (m_version != m_collection.m_version)
645 throw new System.InvalidOperationException("Collection was modified; enumeration operation may not execute.");
647 m_index = -1;
650 object System.Collections.IEnumerator.Current {
651 get {
652 return (object)(this.Current); }
655 #endregion
657 #endregion