2008-12-15 19:13:24 +00:00
/ *
2015-11-06 19:40:28 +01:00
* Copyright ( c ) 2006 - 2016 , openmetaverse . co
2008-12-15 19:13:24 +00:00
* All rights reserved .
*
* - Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions are met :
*
* - Redistributions of source code must retain the above copyright notice , this
* list of conditions and the following disclaimer .
2015-11-06 19:00:05 +01:00
* - Neither the name of the openmetaverse . co nor the names
2008-12-15 19:13:24 +00:00
* of its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR
* CONSEQUENTIAL DAMAGES ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS
* INTERRUPTION ) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN
* CONTRACT , STRICT LIABILITY , OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE )
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE , EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE .
* /
using System ;
using System.Threading ;
using System.Collections.Generic ;
namespace OpenMetaverse
{
#region TimedCacheKey Class
2016-09-25 20:36:12 -05:00
internal class TimedCacheKey < TKey > : IComparable < TKey >
2008-12-15 19:13:24 +00:00
{
2016-09-25 20:36:12 -05:00
public DateTime ExpirationDate { get ; private set ; }
public TKey Key { get ; }
public bool SlidingExpiration { get ; }
public TimeSpan SlidingExpirationWindowSize { get ; }
2008-12-15 19:13:24 +00:00
public TimedCacheKey ( TKey key , DateTime expirationDate )
{
2016-09-25 20:36:12 -05:00
Key = key ;
SlidingExpiration = false ;
ExpirationDate = expirationDate ;
2008-12-15 19:13:24 +00:00
}
public TimedCacheKey ( TKey key , TimeSpan slidingExpirationWindowSize )
{
2016-09-25 20:36:12 -05:00
Key = key ;
SlidingExpiration = true ;
SlidingExpirationWindowSize = slidingExpirationWindowSize ;
2008-12-15 19:13:24 +00:00
Accessed ( ) ;
}
public void Accessed ( )
{
2017-01-04 19:40:23 -05:00
if ( SlidingExpiration )
ExpirationDate = DateTime . UtcNow . Add ( SlidingExpirationWindowSize ) ;
2008-12-15 19:13:24 +00:00
}
public int CompareTo ( TKey other )
{
2016-09-25 20:36:12 -05:00
return Key . GetHashCode ( ) . CompareTo ( other . GetHashCode ( ) ) ;
2008-12-15 19:13:24 +00:00
}
}
#endregion
public sealed class ExpiringCache < TKey , TValue >
{
2009-06-10 06:49:52 +00:00
const double CACHE_PURGE_HZ = 1.0 ;
const int MAX_LOCK_WAIT = 5000 ; // milliseconds
2008-12-15 19:13:24 +00:00
#region Private fields
/// <summary>For thread safety</summary>
2009-06-10 06:49:52 +00:00
object syncRoot = new object ( ) ;
/// <summary>For thread safety</summary>
object isPurging = new object ( ) ;
2008-12-15 19:13:24 +00:00
2016-09-25 20:36:12 -05:00
readonly Dictionary < TimedCacheKey < TKey > , TValue > timedStorage = new Dictionary < TimedCacheKey < TKey > , TValue > ( ) ;
readonly Dictionary < TKey , TimedCacheKey < TKey > > timedStorageIndex = new Dictionary < TKey , TimedCacheKey < TKey > > ( ) ;
2008-12-15 19:13:24 +00:00
private System . Timers . Timer timer = new System . Timers . Timer ( TimeSpan . FromSeconds ( CACHE_PURGE_HZ ) . TotalMilliseconds ) ;
#endregion
#region Constructor
public ExpiringCache ( )
{
timer . Elapsed + = PurgeCache ;
timer . Start ( ) ;
}
#endregion
#region Public methods
2010-08-13 23:06:35 +00:00
public bool Add ( TKey key , TValue value , double expirationSeconds )
2008-12-15 19:13:24 +00:00
{
2009-06-10 06:49:52 +00:00
if ( ! Monitor . TryEnter ( syncRoot , MAX_LOCK_WAIT ) )
throw new ApplicationException ( "Lock could not be acquired after " + MAX_LOCK_WAIT + "ms" ) ;
2008-12-15 19:13:24 +00:00
try
{
// This is the actual adding of the key
if ( timedStorageIndex . ContainsKey ( key ) )
{
return false ;
}
else
{
2016-09-25 20:36:12 -05:00
var internalKey = new TimedCacheKey < TKey > ( key , DateTime . UtcNow + TimeSpan . FromSeconds ( expirationSeconds ) ) ;
2008-12-15 19:13:24 +00:00
timedStorage . Add ( internalKey , value ) ;
timedStorageIndex . Add ( key , internalKey ) ;
return true ;
}
}
2009-06-10 06:49:52 +00:00
finally { Monitor . Exit ( syncRoot ) ; }
2008-12-15 19:13:24 +00:00
}
public bool Add ( TKey key , TValue value , TimeSpan slidingExpiration )
{
2009-06-10 06:49:52 +00:00
if ( ! Monitor . TryEnter ( syncRoot , MAX_LOCK_WAIT ) )
throw new ApplicationException ( "Lock could not be acquired after " + MAX_LOCK_WAIT + "ms" ) ;
2008-12-15 19:13:24 +00:00
try
{
// This is the actual adding of the key
if ( timedStorageIndex . ContainsKey ( key ) )
{
return false ;
}
else
{
2016-09-25 20:36:12 -05:00
var internalKey = new TimedCacheKey < TKey > ( key , slidingExpiration ) ;
2008-12-15 19:13:24 +00:00
timedStorage . Add ( internalKey , value ) ;
timedStorageIndex . Add ( key , internalKey ) ;
return true ;
}
}
2009-06-10 06:49:52 +00:00
finally { Monitor . Exit ( syncRoot ) ; }
2008-12-15 19:13:24 +00:00
}
2010-08-13 23:06:35 +00:00
public bool AddOrUpdate ( TKey key , TValue value , double expirationSeconds )
2008-12-15 19:13:24 +00:00
{
2009-06-10 06:49:52 +00:00
if ( ! Monitor . TryEnter ( syncRoot , MAX_LOCK_WAIT ) )
throw new ApplicationException ( "Lock could not be acquired after " + MAX_LOCK_WAIT + "ms" ) ;
2008-12-15 19:13:24 +00:00
try
{
if ( Contains ( key ) )
{
2010-08-13 23:06:35 +00:00
Update ( key , value , expirationSeconds ) ;
2008-12-15 19:13:24 +00:00
return false ;
}
else
{
2010-08-13 23:06:35 +00:00
Add ( key , value , expirationSeconds ) ;
2008-12-15 19:13:24 +00:00
return true ;
}
}
2009-06-10 06:49:52 +00:00
finally { Monitor . Exit ( syncRoot ) ; }
2008-12-15 19:13:24 +00:00
}
public bool AddOrUpdate ( TKey key , TValue value , TimeSpan slidingExpiration )
{
2009-06-10 06:49:52 +00:00
if ( ! Monitor . TryEnter ( syncRoot , MAX_LOCK_WAIT ) )
throw new ApplicationException ( "Lock could not be acquired after " + MAX_LOCK_WAIT + "ms" ) ;
2008-12-15 19:13:24 +00:00
try
{
if ( Contains ( key ) )
{
Update ( key , value , slidingExpiration ) ;
return false ;
}
else
{
Add ( key , value , slidingExpiration ) ;
return true ;
}
}
2009-06-10 06:49:52 +00:00
finally { Monitor . Exit ( syncRoot ) ; }
2008-12-15 19:13:24 +00:00
}
public void Clear ( )
{
2009-06-10 06:49:52 +00:00
if ( ! Monitor . TryEnter ( syncRoot , MAX_LOCK_WAIT ) )
throw new ApplicationException ( "Lock could not be acquired after " + MAX_LOCK_WAIT + "ms" ) ;
2008-12-15 19:13:24 +00:00
try
{
timedStorage . Clear ( ) ;
timedStorageIndex . Clear ( ) ;
}
2009-06-10 06:49:52 +00:00
finally { Monitor . Exit ( syncRoot ) ; }
2008-12-15 19:13:24 +00:00
}
public bool Contains ( TKey key )
{
2009-06-10 06:49:52 +00:00
if ( ! Monitor . TryEnter ( syncRoot , MAX_LOCK_WAIT ) )
throw new ApplicationException ( "Lock could not be acquired after " + MAX_LOCK_WAIT + "ms" ) ;
2008-12-15 19:13:24 +00:00
try
{
return timedStorageIndex . ContainsKey ( key ) ;
}
2009-06-10 06:49:52 +00:00
finally { Monitor . Exit ( syncRoot ) ; }
2008-12-15 19:13:24 +00:00
}
2016-09-25 20:36:12 -05:00
public int Count = > timedStorage . Count ;
2008-12-15 19:13:24 +00:00
public object this [ TKey key ]
{
get
{
2009-06-10 06:49:52 +00:00
if ( ! Monitor . TryEnter ( syncRoot , MAX_LOCK_WAIT ) )
throw new ApplicationException ( "Lock could not be acquired after " + MAX_LOCK_WAIT + "ms" ) ;
2008-12-15 19:13:24 +00:00
try
{
2025-05-28 19:34:27 -05:00
if ( timedStorageIndex . TryGetValue ( key , out var tkey ) )
2008-12-15 19:13:24 +00:00
{
2016-09-25 20:36:12 -05:00
var o = timedStorage [ tkey ] ;
2008-12-15 19:13:24 +00:00
timedStorage . Remove ( tkey ) ;
tkey . Accessed ( ) ;
timedStorage . Add ( tkey , o ) ;
return o ;
}
else
{
throw new ArgumentException ( "Key not found in the cache" ) ;
}
}
2009-06-10 06:49:52 +00:00
finally { Monitor . Exit ( syncRoot ) ; }
2008-12-15 19:13:24 +00:00
}
}
public bool Remove ( TKey key )
{
2009-06-10 06:49:52 +00:00
if ( ! Monitor . TryEnter ( syncRoot , MAX_LOCK_WAIT ) )
throw new ApplicationException ( "Lock could not be acquired after " + MAX_LOCK_WAIT + "ms" ) ;
2008-12-15 19:13:24 +00:00
try
{
if ( timedStorageIndex . ContainsKey ( key ) )
{
timedStorage . Remove ( timedStorageIndex [ key ] ) ;
timedStorageIndex . Remove ( key ) ;
return true ;
}
else
{
return false ;
}
}
2009-06-10 06:49:52 +00:00
finally { Monitor . Exit ( syncRoot ) ; }
2008-12-15 19:13:24 +00:00
}
public bool TryGetValue ( TKey key , out TValue value )
{
TValue o ;
2009-06-10 06:49:52 +00:00
if ( ! Monitor . TryEnter ( syncRoot , MAX_LOCK_WAIT ) )
throw new ApplicationException ( "Lock could not be acquired after " + MAX_LOCK_WAIT + "ms" ) ;
2008-12-15 19:13:24 +00:00
try
{
2025-05-28 19:34:27 -05:00
if ( timedStorageIndex . TryGetValue ( key , out var tkey ) )
2008-12-15 19:13:24 +00:00
{
o = timedStorage [ tkey ] ;
timedStorage . Remove ( tkey ) ;
tkey . Accessed ( ) ;
timedStorage . Add ( tkey , o ) ;
value = o ;
return true ;
}
}
2009-06-10 06:49:52 +00:00
finally { Monitor . Exit ( syncRoot ) ; }
2008-12-15 19:13:24 +00:00
2009-06-10 06:49:52 +00:00
value = default ( TValue ) ;
return false ;
2008-12-15 19:13:24 +00:00
}
public bool Update ( TKey key , TValue value )
{
2009-06-10 06:49:52 +00:00
if ( ! Monitor . TryEnter ( syncRoot , MAX_LOCK_WAIT ) )
throw new ApplicationException ( "Lock could not be acquired after " + MAX_LOCK_WAIT + "ms" ) ;
2008-12-15 19:13:24 +00:00
try
{
if ( timedStorageIndex . ContainsKey ( key ) )
{
timedStorage . Remove ( timedStorageIndex [ key ] ) ;
timedStorageIndex [ key ] . Accessed ( ) ;
timedStorage . Add ( timedStorageIndex [ key ] , value ) ;
return true ;
}
else
{
return false ;
}
}
2009-06-10 06:49:52 +00:00
finally { Monitor . Exit ( syncRoot ) ; }
2008-12-15 19:13:24 +00:00
}
2010-08-13 23:06:35 +00:00
public bool Update ( TKey key , TValue value , double expirationSeconds )
2008-12-15 19:13:24 +00:00
{
2009-06-10 06:49:52 +00:00
if ( ! Monitor . TryEnter ( syncRoot , MAX_LOCK_WAIT ) )
throw new ApplicationException ( "Lock could not be acquired after " + MAX_LOCK_WAIT + "ms" ) ;
2008-12-15 19:13:24 +00:00
try
{
if ( timedStorageIndex . ContainsKey ( key ) )
{
timedStorage . Remove ( timedStorageIndex [ key ] ) ;
timedStorageIndex . Remove ( key ) ;
}
else
{
return false ;
}
2010-08-13 23:06:35 +00:00
TimedCacheKey < TKey > internalKey = new TimedCacheKey < TKey > ( key , DateTime . UtcNow + TimeSpan . FromSeconds ( expirationSeconds ) ) ;
2008-12-15 19:13:24 +00:00
timedStorage . Add ( internalKey , value ) ;
timedStorageIndex . Add ( key , internalKey ) ;
return true ;
}
2009-06-10 06:49:52 +00:00
finally { Monitor . Exit ( syncRoot ) ; }
2008-12-15 19:13:24 +00:00
}
public bool Update ( TKey key , TValue value , TimeSpan slidingExpiration )
{
2009-06-10 06:49:52 +00:00
if ( ! Monitor . TryEnter ( syncRoot , MAX_LOCK_WAIT ) )
throw new ApplicationException ( "Lock could not be acquired after " + MAX_LOCK_WAIT + "ms" ) ;
2008-12-15 19:13:24 +00:00
try
{
if ( timedStorageIndex . ContainsKey ( key ) )
{
timedStorage . Remove ( timedStorageIndex [ key ] ) ;
timedStorageIndex . Remove ( key ) ;
}
else
{
return false ;
}
2016-09-25 20:36:12 -05:00
var internalKey = new TimedCacheKey < TKey > ( key , slidingExpiration ) ;
2008-12-15 19:13:24 +00:00
timedStorage . Add ( internalKey , value ) ;
timedStorageIndex . Add ( key , internalKey ) ;
return true ;
}
2009-06-10 06:49:52 +00:00
finally { Monitor . Exit ( syncRoot ) ; }
2008-12-15 19:13:24 +00:00
}
public void CopyTo ( Array array , int startIndex )
{
// Error checking
2016-09-25 20:36:12 -05:00
if ( array = = null ) { throw new ArgumentNullException ( nameof ( array ) ) ; }
2008-12-15 19:13:24 +00:00
2016-09-25 20:36:12 -05:00
if ( startIndex < 0 ) { throw new ArgumentOutOfRangeException ( nameof ( startIndex ) , "startIndex must be >= 0." ) ; }
2008-12-15 19:13:24 +00:00
2016-09-25 20:36:12 -05:00
if ( array . Rank > 1 ) { throw new ArgumentException ( "array must be of Rank 1 (one-dimensional)" , nameof ( array ) ) ; }
if ( startIndex > = array . Length ) { throw new ArgumentException ( "startIndex must be less than the length of the array." , nameof ( startIndex ) ) ; }
2008-12-15 19:13:24 +00:00
if ( Count > array . Length - startIndex ) { throw new ArgumentException ( "There is not enough space from startIndex to the end of the array to accomodate all items in the cache." ) ; }
// Copy the data to the array (in a thread-safe manner)
2009-06-10 06:49:52 +00:00
if ( ! Monitor . TryEnter ( syncRoot , MAX_LOCK_WAIT ) )
throw new ApplicationException ( "Lock could not be acquired after " + MAX_LOCK_WAIT + "ms" ) ;
2008-12-15 19:13:24 +00:00
try
{
2016-09-25 20:36:12 -05:00
foreach ( var o in timedStorage )
2008-12-15 19:13:24 +00:00
{
array . SetValue ( o , startIndex ) ;
startIndex + + ;
}
}
2009-06-10 06:49:52 +00:00
finally { Monitor . Exit ( syncRoot ) ; }
2008-12-15 19:13:24 +00:00
}
#endregion
#region Private methods
/// <summary>
/// Purges expired objects from the cache. Called automatically by the purge timer.
/// </summary>
private void PurgeCache ( object sender , System . Timers . ElapsedEventArgs e )
{
// Only let one thread purge at once - a buildup could cause a crash
// This could cause the purge to be delayed while there are lots of read/write ops
// happening on the cache
if ( ! Monitor . TryEnter ( isPurging ) )
return ;
2010-08-13 23:06:35 +00:00
2016-09-25 20:36:12 -05:00
var signalTime = DateTime . UtcNow ;
2010-08-13 23:06:35 +00:00
2008-12-15 19:13:24 +00:00
try
{
2009-06-10 06:49:52 +00:00
// If we fail to acquire a lock on the synchronization root after MAX_LOCK_WAIT, skip this purge cycle
if ( ! Monitor . TryEnter ( syncRoot , MAX_LOCK_WAIT ) )
return ;
2008-12-15 19:13:24 +00:00
try
{
2016-09-25 20:36:12 -05:00
var expiredItems = new Lazy < List < object > > ( ) ;
2008-12-15 19:13:24 +00:00
2016-09-25 20:36:12 -05:00
foreach ( var timedKey in timedStorage . Keys )
2008-12-15 19:13:24 +00:00
{
2010-08-13 23:06:35 +00:00
if ( timedKey . ExpirationDate < signalTime )
2008-12-15 19:13:24 +00:00
{
// Mark the object for purge
2010-08-13 23:06:35 +00:00
expiredItems . Value . Add ( timedKey . Key ) ;
2008-12-15 19:13:24 +00:00
}
else
{
break ;
}
}
2010-08-13 23:06:35 +00:00
if ( expiredItems . IsValueCreated )
2008-12-15 19:13:24 +00:00
{
2010-08-13 23:06:35 +00:00
foreach ( TKey key in expiredItems . Value )
{
2016-09-25 20:36:12 -05:00
var timedKey = timedStorageIndex [ key ] ;
2010-08-13 23:06:35 +00:00
timedStorageIndex . Remove ( timedKey . Key ) ;
timedStorage . Remove ( timedKey ) ;
}
2008-12-15 19:13:24 +00:00
}
}
2009-06-10 06:49:52 +00:00
finally { Monitor . Exit ( syncRoot ) ; }
2008-12-15 19:13:24 +00:00
}
finally { Monitor . Exit ( isPurging ) ; }
}
#endregion
}
}