*BREAKING CHANGES* Merging jhurliman branch with the trunk. The old AssetManager, ImageManager, and InventoryManager systems have been phased out and replaced by the new AssetManager and InventoryManager classes. The lowest network layer has been abstracted out and replaced, new networking callbacks added, and many small bugfixes made. AppearanceManager.Wear* commands have been commented out until they are ported to the new inventory system
git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@1296 52acb1d6-8a22-11de-b505-999d5b087335
This commit is contained in:
525
libsecondlife/ObjectPoolBase.cs
Normal file
525
libsecondlife/ObjectPoolBase.cs
Normal file
@@ -0,0 +1,525 @@
|
||||
/*
|
||||
* Copyright (c) 2007, Second Life Reverse Engineering Team
|
||||
* 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.
|
||||
* - Neither the name of the Second Life Reverse Engineering Team nor the names
|
||||
* 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace libsecondlife
|
||||
{
|
||||
public sealed class WrappedObject<T> : IDisposable where T : class
|
||||
{
|
||||
private T _instance;
|
||||
internal readonly ObjectPoolSegment<T> _owningSegment;
|
||||
internal readonly ObjectPoolBase<T> _owningObjectPool;
|
||||
private bool _disposed = false;
|
||||
|
||||
internal WrappedObject(ObjectPoolBase<T> owningPool, ObjectPoolSegment<T> ownerSegment, T activeInstance)
|
||||
{
|
||||
_owningObjectPool = owningPool;
|
||||
_owningSegment = ownerSegment;
|
||||
_instance = activeInstance;
|
||||
}
|
||||
|
||||
~WrappedObject()
|
||||
{
|
||||
// If the AppDomain is being unloaded, or the CLR is
|
||||
// shutting down, just exit gracefully
|
||||
if (Environment.HasShutdownStarted)
|
||||
return;
|
||||
|
||||
//Object Reserrection in Action!
|
||||
GC.ReRegisterForFinalize(this);
|
||||
|
||||
// return this instance back to the owning queue.
|
||||
_owningObjectPool.CheckIn(_owningSegment, _instance);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Checkes the instance back into the object pool.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
_disposed = true;
|
||||
_owningObjectPool.CheckIn(_owningSegment, _instance);
|
||||
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)
|
||||
{
|
||||
ObjectPoolSegment<T> segment = CreateSegment(false);
|
||||
_segments.Add(segment.SegmentNumber, segment);
|
||||
}
|
||||
}
|
||||
|
||||
// This forces a compact, to make sure our objects fill in any holes in the heap.
|
||||
if (_gc)
|
||||
{
|
||||
System.GC.Collect();
|
||||
}
|
||||
|
||||
_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");
|
||||
|
||||
|
||||
// This method is called inside a lock, so no interlocked stuff required.
|
||||
int segmentToAdd = _activeSegment;
|
||||
_activeSegment++;
|
||||
|
||||
Queue<T> buffers = new Queue<T>();
|
||||
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)
|
||||
DateTime cleanupTime = (allowSegmentToBeCleanedUp) ? DateTime.Now.Add(this._minimumAgeToCleanup) : DateTime.MaxValue;
|
||||
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)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
_timer.Dispose();
|
||||
_disposed = true;
|
||||
|
||||
foreach (KeyValuePair<int, ObjectPoolSegment<T>> kvp in _segments)
|
||||
{
|
||||
try
|
||||
{
|
||||
kvp.Value.Dispose();
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
_segments.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal class ObjectPoolSegment<T> : IDisposable where T : class
|
||||
{
|
||||
private Queue<T> _liveInstances = new Queue<T>();
|
||||
private int _segmentNumber;
|
||||
private int _originalCount;
|
||||
private bool _isDisposed = false;
|
||||
private DateTime _eligibleForDeletionAt;
|
||||
|
||||
public int SegmentNumber { get { return _segmentNumber; } }
|
||||
public int AvailableItems { get { return _liveInstances.Count; } }
|
||||
public DateTime DateEligibleForDeletion { get { return _eligibleForDeletionAt; } }
|
||||
|
||||
public ObjectPoolSegment(int segmentNumber, Queue<T> liveInstances, DateTime eligibleForDeletionAt)
|
||||
{
|
||||
_segmentNumber = segmentNumber;
|
||||
_liveInstances = liveInstances;
|
||||
_originalCount = liveInstances.Count;
|
||||
_eligibleForDeletionAt = eligibleForDeletionAt;
|
||||
}
|
||||
|
||||
public bool CanBeCleanedUp()
|
||||
{
|
||||
if (_isDisposed == true)
|
||||
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)
|
||||
{
|
||||
T instance = _liveInstances.Dequeue();
|
||||
if (shouldDispose)
|
||||
{
|
||||
try
|
||||
{
|
||||
(instance as IDisposable).Dispose();
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void CheckInObject(T o)
|
||||
{
|
||||
if (_isDisposed == true)
|
||||
throw new ObjectDisposedException("ObjectPoolSegment");
|
||||
|
||||
_liveInstances.Enqueue(o);
|
||||
}
|
||||
|
||||
internal T CheckOutObject()
|
||||
{
|
||||
if (_isDisposed == true)
|
||||
throw new ObjectDisposedException("ObjectPoolSegment");
|
||||
|
||||
if (0 == _liveInstances.Count)
|
||||
throw new InvalidOperationException("No Objects Available for Checkout");
|
||||
|
||||
T o = _liveInstances.Dequeue();
|
||||
return o;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user