2007-07-13 14:49:36 +00:00
/ *
2015-11-06 19:40:28 +01:00
* Copyright ( c ) 2006 - 2016 , openmetaverse . co
2007-07-13 14:49:36 +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
2007-07-13 14:49:36 +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.Collections.Generic ;
using System.Threading ;
2008-07-21 21:12:59 +00:00
namespace OpenMetaverse
2007-07-13 14:49:36 +00:00
{
public sealed class WrappedObject < T > : IDisposable where T : class
{
private T _instance ;
2016-09-25 19:52:04 -05:00
internal readonly ObjectPoolSegment < T > OwningSegment ;
internal readonly ObjectPoolBase < T > OwningObjectPool ;
2007-07-13 14:49:36 +00:00
private bool _disposed = false ;
internal WrappedObject ( ObjectPoolBase < T > owningPool , ObjectPoolSegment < T > ownerSegment , T activeInstance )
{
2016-09-25 19:52:04 -05:00
OwningObjectPool = owningPool ;
OwningSegment = ownerSegment ;
2007-07-13 14:49:36 +00:00
_instance = activeInstance ;
}
~ WrappedObject ( )
{
2007-11-06 09:26:10 +00:00
#if ! PocketPC
2007-07-13 14:49:36 +00:00
// If the AppDomain is being unloaded, or the CLR is
// shutting down, just exit gracefully
if ( Environment . HasShutdownStarted )
return ;
2007-11-06 09:26:10 +00:00
#endif
2007-07-13 14:49:36 +00:00
2007-11-06 09:26:10 +00:00
// Object Resurrection in Action!
2007-07-13 14:49:36 +00:00
GC . ReRegisterForFinalize ( this ) ;
2007-11-06 09:26:10 +00:00
// Return this instance back to the owning queue
2016-09-25 19:52:04 -05:00
OwningObjectPool . CheckIn ( OwningSegment , _instance ) ;
2007-07-13 14:49:36 +00:00
}
/// <summary>
/// Returns an instance of the class that has been checked out of the Object Pool.
/// </summary>
public T Instance
{
get
{
if ( _disposed )
throw new ObjectDisposedException ( "WrappedObject" ) ;
return _instance ;
}
}
/// <summary>
2007-12-06 02:07:26 +00:00
/// Checks the instance back into the object pool
2007-07-13 14:49:36 +00:00
/// </summary>
public void Dispose ( )
{
if ( _disposed )
return ;
_disposed = true ;
2016-09-25 19:52:04 -05:00
OwningObjectPool . CheckIn ( OwningSegment , _instance ) ;
2007-07-13 14:49:36 +00:00
GC . SuppressFinalize ( this ) ;
}
}
public abstract class ObjectPoolBase < T > : IDisposable where T : class
{
private int _itemsPerSegment = 32 ;
private int _minimumSegmentCount = 1 ;
// A segment won't be eligible for cleanup unless it's at least this old...
private TimeSpan _minimumAgeToCleanup = new TimeSpan ( 0 , 5 , 0 ) ;
// ever increasing segment counter
private int _activeSegment = 0 ;
private bool _gc = true ;
private volatile bool _disposed = false ;
private Dictionary < int , ObjectPoolSegment < T > > _segments = new Dictionary < int , ObjectPoolSegment < T > > ( ) ;
private object _syncRoot = new object ( ) ;
private object _timerLock = new object ( ) ;
// create a timer that starts in 5 minutes, and gets called every 5 minutes.
System . Threading . Timer _timer ;
int _cleanupFrequency ;
/// <summary>
/// Creates a new instance of the ObjectPoolBase class. Initialize MUST be called
/// after using this constructor.
/// </summary>
protected ObjectPoolBase ( )
{
}
/// <summary>
/// Creates a new instance of the ObjectPool Base class.
/// </summary>
/// <param name="itemsPerSegment">The object pool is composed of segments, which
/// are allocated whenever the size of the pool is exceeded. The number of items
/// in a segment should be large enough that allocating a new segmeng is a rare
/// thing. For example, on a server that will have 10k people logged in at once,
/// the receive buffer object pool should have segment sizes of at least 1000
/// byte arrays per segment.
/// </param>
/// <param name="minimumSegmentCount">The minimun number of segments that may exist.</param>
/// <param name="gcOnPoolGrowth">Perform a full GC.Collect whenever a segment is allocated, and then again after allocation to compact the heap.</param>
/// <param name="cleanupFrequenceMS">The frequency which segments are checked to see if they're eligible for cleanup.</param>
protected ObjectPoolBase ( int itemsPerSegment , int minimumSegmentCount , bool gcOnPoolGrowth , int cleanupFrequenceMS )
{
Initialize ( itemsPerSegment , minimumSegmentCount , gcOnPoolGrowth , cleanupFrequenceMS ) ;
}
protected void Initialize ( int itemsPerSegment , int minimumSegmentCount , bool gcOnPoolGrowth , int cleanupFrequenceMS )
{
_itemsPerSegment = itemsPerSegment ;
_minimumSegmentCount = minimumSegmentCount ;
_gc = gcOnPoolGrowth ;
// force garbage collection to make sure these new long lived objects
// cause as little fragmentation as possible
if ( _gc )
System . GC . Collect ( ) ;
lock ( _syncRoot )
{
while ( _segments . Count < this . MinimumSegmentCount )
{
2016-09-25 19:52:04 -05:00
var segment = CreateSegment ( false ) ;
2007-07-13 14:49:36 +00:00
_segments . Add ( segment . SegmentNumber , segment ) ;
}
}
// This forces a compact, to make sure our objects fill in any holes in the heap.
if ( _gc )
{
2016-09-25 19:52:04 -05:00
GC . Collect ( ) ;
2007-07-13 14:49:36 +00:00
}
_timer = new Timer ( CleanupThreadCallback , null , cleanupFrequenceMS , cleanupFrequenceMS ) ;
}
/// <summary>
/// Forces the segment cleanup algorithm to be run. This method is intended
/// primarly for use from the Unit Test libraries.
/// </summary>
internal void ForceCleanup ( )
{
CleanupThreadCallback ( null ) ;
}
private void CleanupThreadCallback ( object state )
{
if ( _disposed )
return ;
if ( Monitor . TryEnter ( _timerLock ) = = false )
return ;
try
{
lock ( _syncRoot )
{
// If we're below, or at, or minimum segment count threshold,
// there's no point in going any further.
if ( _segments . Count < = _minimumSegmentCount )
return ;
for ( int i = _activeSegment ; i > 0 ; i - - )
{
ObjectPoolSegment < T > segment ;
if ( _segments . TryGetValue ( i , out segment ) = = true )
{
// For the "old" segments that were allocated at startup, this will
// always be false, as their expiration dates are set at infinity.
if ( segment . CanBeCleanedUp ( ) )
{
_segments . Remove ( i ) ;
segment . Dispose ( ) ;
}
}
}
}
}
finally
{
Monitor . Exit ( _timerLock ) ;
}
}
/// <summary>
/// Responsible for allocate 1 instance of an object that will be stored in a segment.
/// </summary>
/// <returns>An instance of whatever objec the pool is pooling.</returns>
protected abstract T GetObjectInstance ( ) ;
private ObjectPoolSegment < T > CreateSegment ( bool allowSegmentToBeCleanedUp )
{
if ( _disposed )
throw new ObjectDisposedException ( "ObjectPoolBase" ) ;
2008-07-30 19:24:15 +00:00
if ( allowSegmentToBeCleanedUp )
Logger . Log ( "Creating new object pool segment" , Helpers . LogLevel . Info ) ;
2007-07-13 14:49:36 +00:00
// This method is called inside a lock, so no interlocked stuff required.
int segmentToAdd = _activeSegment ;
_activeSegment + + ;
2016-09-25 19:52:04 -05:00
var buffers = new Queue < T > ( ) ;
2007-07-13 14:49:36 +00:00
for ( int i = 1 ; i < = this . _itemsPerSegment ; i + + )
{
T obj = GetObjectInstance ( ) ;
buffers . Enqueue ( obj ) ;
}
// certain segments we don't want to ever be cleaned up (the initial segments)
2016-09-25 19:52:04 -05:00
DateTime cleanupTime = ( allowSegmentToBeCleanedUp ) ? DateTime . Now . Add ( _minimumAgeToCleanup ) : DateTime . MaxValue ;
2007-07-13 14:49:36 +00:00
ObjectPoolSegment < T > segment = new ObjectPoolSegment < T > ( segmentToAdd , buffers , cleanupTime ) ;
return segment ;
}
/// <summary>
/// Checks in an instance of T owned by the object pool. This method is only intended to be called
/// by the <c>WrappedObject</c> class.
/// </summary>
/// <param name="owningSegment">The segment from which the instance is checked out.</param>
/// <param name="instance">The instance of <c>T</c> to check back into the segment.</param>
internal void CheckIn ( ObjectPoolSegment < T > owningSegment , T instance )
{
lock ( _syncRoot )
{
owningSegment . CheckInObject ( instance ) ;
}
}
/// <summary>
/// Checks an instance of <c>T</c> from the pool. If the pool is not sufficient to
/// allow the checkout, a new segment is created.
/// </summary>
/// <returns>A <c>WrappedObject</c> around the instance of <c>T</c>. To check
/// the instance back into the segment, be sureto dispose the WrappedObject
/// when finished. </returns>
public WrappedObject < T > CheckOut ( )
{
if ( _disposed )
throw new ObjectDisposedException ( "ObjectPoolBase" ) ;
// It's key that this CheckOut always, always, uses a pooled object
// from the oldest available segment. This will help keep the "newer"
// segments from being used - which in turn, makes them eligible
// for deletion.
lock ( _syncRoot )
{
ObjectPoolSegment < T > targetSegment = null ;
// find the oldest segment that has items available for checkout
for ( int i = 0 ; i < _activeSegment ; i + + )
{
ObjectPoolSegment < T > segment ;
if ( _segments . TryGetValue ( i , out segment ) = = true )
{
if ( segment . AvailableItems > 0 )
{
targetSegment = segment ;
break ;
}
}
}
if ( targetSegment = = null )
{
// We couldn't find a sigment that had any available space in it,
// so it's time to create a new segment.
// Before creating the segment, do a GC to make sure the heap
// is compacted.
if ( _gc ) GC . Collect ( ) ;
targetSegment = CreateSegment ( true ) ;
if ( _gc ) GC . Collect ( ) ;
_segments . Add ( targetSegment . SegmentNumber , targetSegment ) ;
}
WrappedObject < T > obj = new WrappedObject < T > ( this , targetSegment , targetSegment . CheckOutObject ( ) ) ;
return obj ;
}
}
/// <summary>
/// The total number of segments created. Intended to be used by the Unit Tests.
/// </summary>
public int TotalSegments
{
get
{
if ( _disposed )
throw new ObjectDisposedException ( "ObjectPoolBase" ) ;
lock ( _syncRoot )
{
return _segments . Count ;
}
}
}
/// <summary>
/// The number of items that are in a segment. Items in a segment
/// are all allocated at the same time, and are hopefully close to
/// each other in the managed heap.
/// </summary>
public int ItemsPerSegment
{
get
{
if ( _disposed )
throw new ObjectDisposedException ( "ObjectPoolBase" ) ;
return _itemsPerSegment ;
}
}
/// <summary>
/// The minimum number of segments. When segments are reclaimed,
/// this number of segments will always be left alone. These
/// segments are allocated at startup.
/// </summary>
public int MinimumSegmentCount
{
get
{
if ( _disposed )
throw new ObjectDisposedException ( "ObjectPoolBase" ) ;
return _minimumSegmentCount ;
}
}
/// <summary>
/// The age a segment must be before it's eligible for cleanup.
/// This is used to prevent thrash, and typical values are in
/// the 5 minute range.
/// </summary>
public TimeSpan MinimumSegmentAgePriorToCleanup
{
get
{
if ( _disposed )
throw new ObjectDisposedException ( "ObjectPoolBase" ) ;
return _minimumAgeToCleanup ;
}
set
{
if ( _disposed )
throw new ObjectDisposedException ( "ObjectPoolBase" ) ;
_minimumAgeToCleanup = value ;
}
}
/// <summary>
/// The frequence which the cleanup thread runs. This is typically
/// expected to be in the 5 minute range.
/// </summary>
public int CleanupFrequencyMilliseconds
{
get
{
if ( _disposed )
throw new ObjectDisposedException ( "ObjectPoolBase" ) ;
return _cleanupFrequency ;
}
set
{
if ( _disposed )
throw new ObjectDisposedException ( "ObjectPoolBase" ) ;
Interlocked . Exchange ( ref _cleanupFrequency , value ) ;
_timer . Change ( _cleanupFrequency , _cleanupFrequency ) ;
}
}
#region IDisposable Members
public void Dispose ( )
{
if ( _disposed )
return ;
Dispose ( true ) ;
GC . SuppressFinalize ( this ) ;
}
protected virtual void Dispose ( bool disposing )
{
2016-09-25 19:52:04 -05:00
if ( ! disposing ) return ;
lock ( _syncRoot )
2007-07-13 14:49:36 +00:00
{
2016-09-25 19:52:04 -05:00
if ( _disposed )
return ;
2007-07-13 14:49:36 +00:00
2016-09-25 19:52:04 -05:00
_timer . Dispose ( ) ;
_disposed = true ;
2007-07-13 14:49:36 +00:00
2016-09-25 19:52:04 -05:00
foreach ( var kvp in _segments )
{
try
2007-07-13 14:49:36 +00:00
{
2016-09-25 19:52:04 -05:00
kvp . Value . Dispose ( ) ;
2007-07-13 14:49:36 +00:00
}
2016-09-25 19:52:04 -05:00
catch ( Exception ) { } // TODO: Investigate, this is smelly.
2007-07-13 14:49:36 +00:00
}
2016-09-25 19:52:04 -05:00
_segments . Clear ( ) ;
2007-07-13 14:49:36 +00:00
}
}
#endregion
}
internal class ObjectPoolSegment < T > : IDisposable where T : class
{
private Queue < T > _liveInstances = new Queue < T > ( ) ;
private int _segmentNumber ;
private int _originalCount ;
2016-09-25 19:52:04 -05:00
private bool _isDisposed ;
2007-07-13 14:49:36 +00:00
private DateTime _eligibleForDeletionAt ;
2016-09-25 19:52:04 -05:00
public int SegmentNumber = > _segmentNumber ;
public int AvailableItems = > _liveInstances . Count ;
public DateTime DateEligibleForDeletion = > _eligibleForDeletionAt ;
2007-07-13 14:49:36 +00:00
public ObjectPoolSegment ( int segmentNumber , Queue < T > liveInstances , DateTime eligibleForDeletionAt )
{
_segmentNumber = segmentNumber ;
_liveInstances = liveInstances ;
_originalCount = liveInstances . Count ;
_eligibleForDeletionAt = eligibleForDeletionAt ;
}
public bool CanBeCleanedUp ( )
{
2016-09-25 19:52:04 -05:00
if ( _isDisposed )
2007-07-13 14:49:36 +00:00
throw new ObjectDisposedException ( "ObjectPoolSegment" ) ;
return ( ( _originalCount = = _liveInstances . Count ) & & ( DateTime . Now > _eligibleForDeletionAt ) ) ;
}
public void Dispose ( )
{
if ( _isDisposed )
return ;
_isDisposed = true ;
bool shouldDispose = ( typeof ( T ) is IDisposable ) ;
while ( _liveInstances . Count ! = 0 )
{
2016-09-25 19:52:04 -05:00
var instance = _liveInstances . Dequeue ( ) ;
2007-07-13 14:49:36 +00:00
if ( shouldDispose )
{
try
{
( instance as IDisposable ) . Dispose ( ) ;
}
catch ( Exception ) { }
}
}
}
internal void CheckInObject ( T o )
{
2016-09-25 19:52:04 -05:00
if ( _isDisposed )
2007-07-13 14:49:36 +00:00
throw new ObjectDisposedException ( "ObjectPoolSegment" ) ;
_liveInstances . Enqueue ( o ) ;
}
internal T CheckOutObject ( )
{
2016-09-25 19:52:04 -05:00
if ( _isDisposed )
2007-07-13 14:49:36 +00:00
throw new ObjectDisposedException ( "ObjectPoolSegment" ) ;
if ( 0 = = _liveInstances . Count )
throw new InvalidOperationException ( "No Objects Available for Checkout" ) ;
2016-09-25 19:52:04 -05:00
var o = _liveInstances . Dequeue ( ) ;
2007-07-13 14:49:36 +00:00
return o ;
}
}
}