2 // System.Web.Caching
\r
5 // Patrik Torstensson (Patrik.Torstensson@labs2.com)
\r
6 // Daniel Cazzulino [DHC] (dcazzulino@users.sf.net)
\r
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:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
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.
31 using System.Collections;
\r
32 using System.Threading;
\r
34 namespace System.Web.Caching {
\r
36 /// Responsible for holding a cache entry in the linked list bucket.
\r
38 internal struct ExpiresEntry {
\r
39 internal CacheEntry Entry;
\r
40 internal long TicksExpires;
\r
41 internal int _intNext;
\r
45 /// Holds cache entries that has a expiration in a bucket list.
\r
47 internal class ExpiresBucket {
\r
48 private static int MIN_ENTRIES = 4;
\r
50 private byte _byteID;
\r
51 private int _intSize;
\r
52 private int _intCount;
\r
53 private int _intNext;
\r
55 private Cache _objManager;
\r
57 private ExpiresEntry [] _arrEntries;
\r
59 private System.Threading.ReaderWriterLock _lock = new System.Threading.ReaderWriterLock();
\r
62 /// Keeps a list of indexes in the list which are available to place new items. [DHC]
\r
64 private Int32Collection _freeidx = new Int32Collection();
\r
67 /// Constructs a new bucket.
\r
69 /// <param name="bucket">Current bucket ID.</param>
\r
70 /// <param name="objManager">Cache manager reponsible for the item(s) in the expires bucket.</param>
\r
71 internal ExpiresBucket (byte bucket, Cache objManager) {
\r
72 _objManager = objManager;
\r
77 /// Initializes the expires bucket, creates a linked list of MIN_ENTRIES.
\r
79 /// <param name="bucket">Bucket ID.</param>
\r
80 private void Initialize (byte bucket) {
\r
85 _arrEntries = new ExpiresEntry [MIN_ENTRIES];
\r
86 _intSize = MIN_ENTRIES;
\r
90 _arrEntries[intPos]._intNext = intPos + 1;
\r
91 _arrEntries[intPos].TicksExpires = Cache.NoAbsoluteExpiration.Ticks;
\r
94 } while (intPos < _intSize);
\r
96 _arrEntries[_intSize - 1]._intNext = -1;
\r
100 /// Expands the bucket linked array list.
\r
102 private void Expand () {
\r
103 _lock.AcquireWriterLock(-1);
\r
105 int oldsize = _intSize;
\r
108 // Copy items to the new list.
\r
109 ExpiresEntry[] newlist = new ExpiresEntry[_intSize];
\r
110 _arrEntries.CopyTo(newlist, 0);
\r
112 // Set last element to point to the next new empty element
\r
113 _intNext = oldsize;
\r
114 newlist[oldsize - 1]._intNext = oldsize;
\r
116 // Initialize positions for the rest of new elements.
\r
117 for (int i = oldsize; i < _intSize; i++) {
\r
118 newlist[i]._intNext = i + 1;
\r
119 newlist[i].TicksExpires = Cache.NoAbsoluteExpiration.Ticks;
\r
122 // Last item signals the expansion of the list.
\r
123 newlist[_intSize - 1]._intNext = -1;
\r
125 // Replace the existing list.
\r
126 _arrEntries = newlist;
\r
129 _lock.ReleaseWriterLock();
\r
135 /// Adds a cache entry into the expires bucket.
\r
137 /// <param name="objEntry">Cache Entry object to be added.</param>
\r
138 internal void Add (CacheEntry objEntry) {
\r
139 _lock.AcquireWriterLock(-1);
\r
141 if (_intNext == -1) {
\r
142 if (_freeidx.Count == 0)
\r
145 _intNext = _freeidx[0];
\r
146 _freeidx.Remove(_intNext);
\r
150 _arrEntries[_intNext].TicksExpires = objEntry.Expires;
\r
151 _arrEntries[_intNext].Entry = objEntry;
\r
153 objEntry.ExpiresBucket = _byteID;
\r
154 objEntry.ExpiresIndex = _intNext;
\r
156 // If there are free indexes in the list, reuse them for the _next value.
\r
157 if (_freeidx.Count != 0) {
\r
158 _intNext = _freeidx [0];
\r
159 _freeidx.Remove(_intNext);
\r
161 _intNext = _arrEntries[_intNext]._intNext;
\r
166 _lock.ReleaseWriterLock();
\r
171 /// Removes a cache entry from the expires bucket.
\r
173 /// <param name="objEntry">Cache entry to be removed.</param>
\r
174 internal void Remove(CacheEntry objEntry) {
\r
175 // Check if this is our bucket
\r
176 if (objEntry.ExpiresBucket != _byteID) return;
\r
177 if (objEntry.ExpiresIndex == CacheEntry.NoIndexInBucket) return;
\r
179 _lock.AcquireWriterLock(-1);
\r
181 if (_arrEntries.Length < objEntry.ExpiresIndex) return;
\r
184 // Push the index as a free one.
\r
185 _freeidx.Add(objEntry.ExpiresIndex);
\r
187 _arrEntries[objEntry.ExpiresIndex].Entry = null;
\r
188 // Clear bucket-related values from the item.
\r
189 objEntry.ExpiresBucket = CacheEntry.NoBucketHash;
\r
190 objEntry.ExpiresIndex = CacheEntry.NoIndexInBucket;
\r
193 //Releases both reader & writer locks
\r
194 _lock.ReleaseWriterLock();
\r
199 /// Updates a cache entry in the expires bucket, this is called during a hit of an item if the
\r
200 /// cache item has a sliding expiration. The function is responsible for updating the cache
\r
203 /// <param name="objEntry">Cache entry to update.</param>
\r
204 /// <param name="ticksExpires">New expiration value for the cache entry.</param>
\r
205 internal void Update(CacheEntry objEntry, long ticksExpires) {
\r
206 // Check if this is our bucket
\r
207 if (objEntry.ExpiresBucket != _byteID) return;
\r
208 if (objEntry.ExpiresIndex == CacheEntry.NoIndexInBucket) return;
\r
210 _lock.AcquireWriterLock(-1);
\r
212 if (_arrEntries.Length < objEntry.ExpiresIndex) return;
\r
214 // Proceed to update.
\r
215 _arrEntries[objEntry.ExpiresIndex].TicksExpires = ticksExpires;
\r
216 _arrEntries[objEntry.ExpiresIndex].Entry.Expires = ticksExpires;
\r
219 //Releases both read & write locks
\r
220 _lock.ReleaseWriterLock();
\r
225 /// Flushes all cache entries that has expired and removes them from the cache manager.
\r
227 internal void FlushExpiredItems() {
\r
228 ExpiresEntry objEntry;
\r
229 ArrayList removeList = null;
\r
230 ArrayList flushList = null;
\r
234 ticksNow = DateTime.UtcNow.Ticks;
\r
237 // Lookup all items that needs to be removed, this is done in a two part
\r
238 // operation to minimize the locking time.
\r
239 _lock.AcquireReaderLock (-1);
\r
242 objEntry = _arrEntries [intPos];
\r
243 if (null != objEntry.Entry &&
\r
244 ((objEntry.TicksExpires < ticksNow) || objEntry.Entry.ExpiresBucket != _byteID))
\r
246 if (null == removeList)
\r
247 removeList = new ArrayList ();
\r
249 removeList.Add (objEntry);
\r
253 } while (intPos < _intSize);
\r
256 _lock.ReleaseReaderLock ();
\r
259 if (null != removeList) {
\r
260 flushList = new ArrayList (removeList.Count);
\r
262 _lock.AcquireWriterLock (-1);
\r
264 foreach (ExpiresEntry entry in removeList) {
\r
265 ExpiresEntry e = entry;
\r
266 int id = entry.Entry.ExpiresIndex;
\r
268 //push the index for reuse
\r
271 if (entry.Entry.ExpiresBucket == _byteID) {
\r
272 // add to our flush list
\r
273 flushList.Add (e.Entry);
\r
275 // Remove from bucket
\r
276 e.Entry.ExpiresBucket = CacheEntry.NoBucketHash;
\r
277 e.Entry.ExpiresIndex = CacheEntry.NoIndexInBucket;
\r
282 // Entries is structs, put it back
\r
283 _arrEntries [id] = e;
\r
287 _lock.ReleaseWriterLock ();
\r
290 // We can call this without locks, it can takes time due to callbacks to user code
\r
291 foreach (CacheEntry entry in flushList)
\r
292 _objManager.Remove (entry.Key, CacheItemRemovedReason.Expired);
\r
300 /// Returns the current size of the expires bucket.
\r
302 internal int Size {
\r
309 /// Returns number of items in the bucket.
\r
311 internal int Count {
\r
317 #region Private Int32Collection
\r
318 /* This file has been automatically generated by TextBox -- DO NOT EDIT! */
\r
321 Int32Collection.Enumerator
\r
323 These C# classes implement a strongly-typed collection of
\r
326 The internal representation is an array of Int32, so the performance
\r
327 characteristics will be more like a vector than a list, to use STL terminology.
\r
329 The implementation is optimized for value-types, as it goes to great length to
\r
330 avoid the overhead of boxing and unboxing. But it should also work well for
\r
333 Mad props to Nick Wienholt <sheyenne@bigpond.com> for assisting me in
\r
334 this research, and the testing, the benchmarking, and of course, the
\r
337 Last but not least, a quick shout out to Kit George, for his generous
\r
338 contribution to the dotnet mailing list -- a code-generator for
\r
339 CollectionBase-derived classes:
\r
340 http://discuss.develop.com/archives/wa.exe?A2=ind0107C&L=DOTNET&P=R35911
\r
341 This was the original inspiration for the fine code you are now enjoying.
\r
345 Other folks who've contributed:
\r
346 Ethan Smith <ethan.smith@pobox.com> (minor perf. improvements)
\r
347 Joel Mueller <jmueller@swiftk.com> (major perf. improvements)
\r
348 Chris Sells <csells@sellsbrothers.com> (generative programming guru)
\r
349 Patrice Lafond <plafond@hemisphere.bm> (a bug fix -- yikes!)
\r
353 /// An optimized collection for holding <see cref="Int32"/> values.
\r
355 [System.Serializable]
\r
356 private class Int32Collection : System.Collections.ICollection, System.Collections.IList, System.Collections.IEnumerable {
\r
357 #region Private vars & ctors
\r
358 private const int DefaultMinimumCapacity = 16;
\r
360 private System.Int32[] m_array = new System.Int32[DefaultMinimumCapacity];
\r
361 private int m_count = 0;
\r
362 private int m_version = 0;
\r
365 public Int32Collection() {
\r
369 public Int32Collection(Int32Collection collection) {
\r
370 AddRange(collection); }
\r
373 public Int32Collection(System.Int32[] array) {
\r
377 #region Public members
\r
386 public void CopyTo(System.Int32[] array) {
\r
387 this.CopyTo(array, 0);
\r
391 public void CopyTo(System.Int32[] array, int start) {
\r
392 if (m_count > array.GetUpperBound(0)+1-start)
\r
393 throw new System.ArgumentException("Destination array was not long enough.");
\r
395 // for (int i=0; i < m_count; ++i) array[start+i] = m_array[i];
\r
396 System.Array.Copy(m_array, 0, array, start, m_count);
\r
400 public System.Int32 this[int index] {
\r
402 ValidateIndex(index); // throws
\r
403 return m_array[index];
\r
406 ValidateIndex(index); // throws
\r
409 m_array[index] = value;
\r
414 public int Add(System.Int32 item) {
\r
419 m_array[m_count] = item;
\r
425 public void Clear() {
\r
427 m_array = new System.Int32[DefaultMinimumCapacity];
\r
432 public bool Contains(System.Int32 item) {
\r
433 return ((IndexOf(item) == -1)?false:true);
\r
437 public int IndexOf(System.Int32 item) {
\r
438 for (int i=0; i < m_count; ++i)
\r
439 if (m_array[i].Equals(item))
\r
445 public void Insert(int position, System.Int32 item) {
\r
446 ValidateIndex(position,true); // throws
\r
452 System.Array.Copy(m_array, position, m_array, position+1, m_count-position);
\r
454 m_array[position] = item;
\r
459 public void Remove(System.Int32 item) {
\r
460 int index = IndexOf(item);
\r
462 throw new System.ArgumentException("Cannot remove the specified item because it was not found in the specified Collection.");
\r
468 public void RemoveAt(int index) {
\r
469 ValidateIndex(index); // throws
\r
473 System.Array.Copy(m_array, index+1, m_array, index, m_count-index);
\r
475 if (NeedsTrimming())
\r
479 // Public helpers (just to mimic some nice features of ArrayList)
\r
482 public int Capacity {
\r
484 return m_array.Length; }
\r
486 if (value < m_count) value = m_count;
\r
487 if (value < DefaultMinimumCapacity) value = DefaultMinimumCapacity;
\r
489 if (m_array.Length == value) return;
\r
493 System.Int32[] temp = new System.Int32[value];
\r
494 System.Array.Copy(m_array, 0, temp, 0, m_count);
\r
500 public void AddRange(Int32Collection collection) {
\r
503 Capacity += collection.Count;
\r
504 System.Array.Copy(collection.m_array, 0, this.m_array, m_count, collection.m_count);
\r
505 m_count += collection.Count;
\r
509 public void AddRange(System.Int32[] array) {
\r
512 Capacity += array.Length;
\r
513 System.Array.Copy(array, 0, this.m_array, m_count, array.Length);
\r
514 m_count += array.Length;
\r
518 #region Private helper methods
\r
519 private void ValidateIndex(int index) {
\r
520 ValidateIndex(index,false);
\r
523 private void ValidateIndex(int index, bool allowEqualEnd) {
\r
524 int max = (allowEqualEnd)?(m_count):(m_count-1);
\r
525 if (index < 0 || index > max)
\r
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.");
\r
529 private bool NeedsGrowth() {
\r
530 return (m_count >= Capacity);
\r
533 private void Grow() {
\r
535 Capacity = m_count*2;
\r
538 private bool NeedsTrimming() {
\r
539 return (m_count <= Capacity/2);
\r
542 private void Trim() {
\r
543 if (NeedsTrimming())
\r
544 Capacity = m_count;
\r
548 #region System.Collections.ICollection implementation
\r
549 bool System.Collections.ICollection.IsSynchronized {
\r
551 return m_array.IsSynchronized; }
\r
554 object System.Collections.ICollection.SyncRoot {
\r
556 return m_array.SyncRoot; }
\r
559 void System.Collections.ICollection.CopyTo(System.Array array, int start) {
\r
560 this.CopyTo((System.Int32[])array, start);
\r
564 #region System.Collections.IList implementation
\r
565 bool System.Collections.IList.IsFixedSize {
\r
570 bool System.Collections.IList.IsReadOnly {
\r
575 object System.Collections.IList.this[int index] {
\r
576 get { return (object)this[index]; }
\r
577 set { this[index] = (System.Int32)value; }
\r
580 int System.Collections.IList.Add(object item) {
\r
581 return this.Add((System.Int32)item);
\r
584 bool System.Collections.IList.Contains(object item) {
\r
585 return this.Contains((System.Int32)item);
\r
588 int System.Collections.IList.IndexOf(object item) {
\r
589 return this.IndexOf((System.Int32)item);
\r
592 void System.Collections.IList.Insert(int position, object item) {
\r
593 this.Insert(position, (System.Int32)item);
\r
596 void System.Collections.IList.Remove(object item) {
\r
597 this.Remove((System.Int32)item);
\r
601 #region System.Collections.IEnumerable and enumerator implementation
\r
603 public Enumerator GetEnumerator() {
\r
604 return new Enumerator(this);
\r
607 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
\r
608 return GetEnumerator();
\r
611 // Nested enumerator class
\r
613 public class Enumerator : System.Collections.IEnumerator {
\r
614 private Int32Collection m_collection;
\r
615 private int m_index;
\r
616 private int m_version;
\r
621 public Enumerator(Int32Collection tc) {
\r
624 m_version = tc.m_version;
\r
628 public System.Int32 Current {
\r
630 return m_collection[m_index]; }
\r
634 public bool MoveNext() {
\r
635 if (m_version != m_collection.m_version)
\r
636 throw new System.InvalidOperationException("Collection was modified; enumeration operation may not execute.");
\r
639 return (m_index < m_collection.Count)?true:false;
\r
643 public void Reset() {
\r
644 if (m_version != m_collection.m_version)
\r
645 throw new System.InvalidOperationException("Collection was modified; enumeration operation may not execute.");
\r
650 object System.Collections.IEnumerator.Current {
\r
652 return (object)(this.Current); }
\r