corrade-vassal – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 1 /*
2 * Copyright (c) 2006-2014, openmetaverse.org
3 * All rights reserved.
4 *
5 * - Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * - Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 * - Neither the name of the openmetaverse.org nor the names
11 * of its contributors may be used to endorse or promote products derived from
12 * this software without specific prior written permission.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 * POSSIBILITY OF SUCH DAMAGE.
25 */
26  
27 using System;
28 using System.Collections;
29 using System.Collections.Generic;
30 using System.Threading;
31  
32 namespace OpenMetaverse
33 {
34 public sealed class WrappedObject<T> : IDisposable where T : class
35 {
36 private T _instance;
37 internal readonly ObjectPoolSegment<T> _owningSegment;
38 internal readonly ObjectPoolBase<T> _owningObjectPool;
39 private bool _disposed = false;
40  
41 internal WrappedObject(ObjectPoolBase<T> owningPool, ObjectPoolSegment<T> ownerSegment, T activeInstance)
42 {
43 _owningObjectPool = owningPool;
44 _owningSegment = ownerSegment;
45 _instance = activeInstance;
46 }
47  
48 ~WrappedObject()
49 {
50 #if !PocketPC
51 // If the AppDomain is being unloaded, or the CLR is
52 // shutting down, just exit gracefully
53 if (Environment.HasShutdownStarted)
54 return;
55 #endif
56  
57 // Object Resurrection in Action!
58 GC.ReRegisterForFinalize(this);
59  
60 // Return this instance back to the owning queue
61 _owningObjectPool.CheckIn(_owningSegment, _instance);
62 }
63  
64 /// <summary>
65 /// Returns an instance of the class that has been checked out of the Object Pool.
66 /// </summary>
67 public T Instance
68 {
69 get
70 {
71 if (_disposed)
72 throw new ObjectDisposedException("WrappedObject");
73 return _instance;
74 }
75 }
76  
77 /// <summary>
78 /// Checks the instance back into the object pool
79 /// </summary>
80 public void Dispose()
81 {
82 if (_disposed)
83 return;
84  
85 _disposed = true;
86 _owningObjectPool.CheckIn(_owningSegment, _instance);
87 GC.SuppressFinalize(this);
88 }
89 }
90  
91 public abstract class ObjectPoolBase<T> : IDisposable where T : class
92 {
93 private int _itemsPerSegment = 32;
94 private int _minimumSegmentCount = 1;
95  
96 // A segment won't be eligible for cleanup unless it's at least this old...
97 private TimeSpan _minimumAgeToCleanup = new TimeSpan(0, 5, 0);
98  
99 // ever increasing segment counter
100 private int _activeSegment = 0;
101  
102 private bool _gc = true;
103  
104 private volatile bool _disposed = false;
105  
106 private Dictionary<int, ObjectPoolSegment<T>> _segments = new Dictionary<int, ObjectPoolSegment<T>>();
107 private object _syncRoot = new object();
108 private object _timerLock = new object();
109  
110 // create a timer that starts in 5 minutes, and gets called every 5 minutes.
111 System.Threading.Timer _timer;
112 int _cleanupFrequency;
113  
114 /// <summary>
115 /// Creates a new instance of the ObjectPoolBase class. Initialize MUST be called
116 /// after using this constructor.
117 /// </summary>
118 protected ObjectPoolBase()
119 {
120 }
121  
122 /// <summary>
123 /// Creates a new instance of the ObjectPool Base class.
124 /// </summary>
125 /// <param name="itemsPerSegment">The object pool is composed of segments, which
126 /// are allocated whenever the size of the pool is exceeded. The number of items
127 /// in a segment should be large enough that allocating a new segmeng is a rare
128 /// thing. For example, on a server that will have 10k people logged in at once,
129 /// the receive buffer object pool should have segment sizes of at least 1000
130 /// byte arrays per segment.
131 /// </param>
132 /// <param name="minimumSegmentCount">The minimun number of segments that may exist.</param>
133 /// <param name="gcOnPoolGrowth">Perform a full GC.Collect whenever a segment is allocated, and then again after allocation to compact the heap.</param>
134 /// <param name="cleanupFrequenceMS">The frequency which segments are checked to see if they're eligible for cleanup.</param>
135 protected ObjectPoolBase(int itemsPerSegment, int minimumSegmentCount, bool gcOnPoolGrowth, int cleanupFrequenceMS)
136 {
137 Initialize(itemsPerSegment, minimumSegmentCount, gcOnPoolGrowth, cleanupFrequenceMS);
138 }
139  
140 protected void Initialize(int itemsPerSegment, int minimumSegmentCount, bool gcOnPoolGrowth, int cleanupFrequenceMS)
141 {
142 _itemsPerSegment = itemsPerSegment;
143 _minimumSegmentCount = minimumSegmentCount;
144 _gc = gcOnPoolGrowth;
145  
146 // force garbage collection to make sure these new long lived objects
147 // cause as little fragmentation as possible
148 if (_gc)
149 System.GC.Collect();
150  
151 lock (_syncRoot)
152 {
153 while (_segments.Count < this.MinimumSegmentCount)
154 {
155 ObjectPoolSegment<T> segment = CreateSegment(false);
156 _segments.Add(segment.SegmentNumber, segment);
157 }
158 }
159  
160 // This forces a compact, to make sure our objects fill in any holes in the heap.
161 if (_gc)
162 {
163 System.GC.Collect();
164 }
165  
166 _timer = new Timer(CleanupThreadCallback, null, cleanupFrequenceMS, cleanupFrequenceMS);
167 }
168  
169 /// <summary>
170 /// Forces the segment cleanup algorithm to be run. This method is intended
171 /// primarly for use from the Unit Test libraries.
172 /// </summary>
173 internal void ForceCleanup()
174 {
175 CleanupThreadCallback(null);
176 }
177  
178 private void CleanupThreadCallback(object state)
179 {
180 if (_disposed)
181 return;
182  
183 if (Monitor.TryEnter(_timerLock) == false)
184 return;
185  
186 try
187 {
188 lock (_syncRoot)
189 {
190 // If we're below, or at, or minimum segment count threshold,
191 // there's no point in going any further.
192 if (_segments.Count <= _minimumSegmentCount)
193 return;
194  
195 for (int i = _activeSegment; i > 0; i--)
196 {
197 ObjectPoolSegment<T> segment;
198 if (_segments.TryGetValue(i, out segment) == true)
199 {
200 // For the "old" segments that were allocated at startup, this will
201 // always be false, as their expiration dates are set at infinity.
202 if (segment.CanBeCleanedUp())
203 {
204 _segments.Remove(i);
205 segment.Dispose();
206 }
207 }
208 }
209 }
210 }
211 finally
212 {
213 Monitor.Exit(_timerLock);
214 }
215 }
216  
217 /// <summary>
218 /// Responsible for allocate 1 instance of an object that will be stored in a segment.
219 /// </summary>
220 /// <returns>An instance of whatever objec the pool is pooling.</returns>
221 protected abstract T GetObjectInstance();
222  
223  
224 private ObjectPoolSegment<T> CreateSegment(bool allowSegmentToBeCleanedUp)
225 {
226 if (_disposed)
227 throw new ObjectDisposedException("ObjectPoolBase");
228  
229 if (allowSegmentToBeCleanedUp)
230 Logger.Log("Creating new object pool segment", Helpers.LogLevel.Info);
231  
232 // This method is called inside a lock, so no interlocked stuff required.
233 int segmentToAdd = _activeSegment;
234 _activeSegment++;
235  
236 Queue<T> buffers = new Queue<T>();
237 for (int i = 1; i <= this._itemsPerSegment; i++)
238 {
239 T obj = GetObjectInstance();
240 buffers.Enqueue(obj);
241 }
242  
243 // certain segments we don't want to ever be cleaned up (the initial segments)
244 DateTime cleanupTime = (allowSegmentToBeCleanedUp) ? DateTime.Now.Add(this._minimumAgeToCleanup) : DateTime.MaxValue;
245 ObjectPoolSegment<T> segment = new ObjectPoolSegment<T>(segmentToAdd, buffers, cleanupTime);
246  
247 return segment;
248 }
249  
250  
251 /// <summary>
252 /// Checks in an instance of T owned by the object pool. This method is only intended to be called
253 /// by the <c>WrappedObject</c> class.
254 /// </summary>
255 /// <param name="owningSegment">The segment from which the instance is checked out.</param>
256 /// <param name="instance">The instance of <c>T</c> to check back into the segment.</param>
257 internal void CheckIn(ObjectPoolSegment<T> owningSegment, T instance)
258 {
259 lock (_syncRoot)
260 {
261 owningSegment.CheckInObject(instance);
262 }
263 }
264  
265 /// <summary>
266 /// Checks an instance of <c>T</c> from the pool. If the pool is not sufficient to
267 /// allow the checkout, a new segment is created.
268 /// </summary>
269 /// <returns>A <c>WrappedObject</c> around the instance of <c>T</c>. To check
270 /// the instance back into the segment, be sureto dispose the WrappedObject
271 /// when finished. </returns>
272 public WrappedObject<T> CheckOut()
273 {
274 if (_disposed)
275 throw new ObjectDisposedException("ObjectPoolBase");
276  
277 // It's key that this CheckOut always, always, uses a pooled object
278 // from the oldest available segment. This will help keep the "newer"
279 // segments from being used - which in turn, makes them eligible
280 // for deletion.
281  
282  
283 lock (_syncRoot)
284 {
285 ObjectPoolSegment<T> targetSegment = null;
286  
287 // find the oldest segment that has items available for checkout
288 for (int i = 0; i < _activeSegment; i++)
289 {
290 ObjectPoolSegment<T> segment;
291 if (_segments.TryGetValue(i, out segment) == true)
292 {
293 if (segment.AvailableItems > 0)
294 {
295 targetSegment = segment;
296 break;
297 }
298 }
299 }
300  
301 if (targetSegment == null)
302 {
303 // We couldn't find a sigment that had any available space in it,
304 // so it's time to create a new segment.
305  
306 // Before creating the segment, do a GC to make sure the heap
307 // is compacted.
308 if (_gc) GC.Collect();
309  
310 targetSegment = CreateSegment(true);
311  
312 if (_gc) GC.Collect();
313  
314 _segments.Add(targetSegment.SegmentNumber, targetSegment);
315 }
316  
317 WrappedObject<T> obj = new WrappedObject<T>(this, targetSegment, targetSegment.CheckOutObject());
318 return obj;
319 }
320 }
321  
322 /// <summary>
323 /// The total number of segments created. Intended to be used by the Unit Tests.
324 /// </summary>
325 public int TotalSegments
326 {
327 get
328 {
329 if (_disposed)
330 throw new ObjectDisposedException("ObjectPoolBase");
331  
332 lock (_syncRoot)
333 {
334 return _segments.Count;
335 }
336 }
337 }
338  
339 /// <summary>
340 /// The number of items that are in a segment. Items in a segment
341 /// are all allocated at the same time, and are hopefully close to
342 /// each other in the managed heap.
343 /// </summary>
344 public int ItemsPerSegment
345 {
346 get
347 {
348 if (_disposed)
349 throw new ObjectDisposedException("ObjectPoolBase");
350  
351 return _itemsPerSegment;
352 }
353 }
354  
355 /// <summary>
356 /// The minimum number of segments. When segments are reclaimed,
357 /// this number of segments will always be left alone. These
358 /// segments are allocated at startup.
359 /// </summary>
360 public int MinimumSegmentCount
361 {
362 get
363 {
364 if (_disposed)
365 throw new ObjectDisposedException("ObjectPoolBase");
366  
367 return _minimumSegmentCount;
368 }
369 }
370  
371 /// <summary>
372 /// The age a segment must be before it's eligible for cleanup.
373 /// This is used to prevent thrash, and typical values are in
374 /// the 5 minute range.
375 /// </summary>
376 public TimeSpan MinimumSegmentAgePriorToCleanup
377 {
378 get
379 {
380 if (_disposed)
381 throw new ObjectDisposedException("ObjectPoolBase");
382  
383 return _minimumAgeToCleanup;
384 }
385 set
386 {
387 if (_disposed)
388 throw new ObjectDisposedException("ObjectPoolBase");
389  
390 _minimumAgeToCleanup = value;
391 }
392 }
393  
394 /// <summary>
395 /// The frequence which the cleanup thread runs. This is typically
396 /// expected to be in the 5 minute range.
397 /// </summary>
398 public int CleanupFrequencyMilliseconds
399 {
400 get
401 {
402 if (_disposed)
403 throw new ObjectDisposedException("ObjectPoolBase");
404  
405 return _cleanupFrequency;
406 }
407 set
408 {
409 if (_disposed)
410 throw new ObjectDisposedException("ObjectPoolBase");
411  
412 Interlocked.Exchange(ref _cleanupFrequency, value);
413  
414 _timer.Change(_cleanupFrequency, _cleanupFrequency);
415 }
416 }
417  
418 #region IDisposable Members
419  
420 public void Dispose()
421 {
422 if (_disposed)
423 return;
424  
425 Dispose(true);
426  
427 GC.SuppressFinalize(this);
428 }
429  
430 protected virtual void Dispose(bool disposing)
431 {
432 if (disposing)
433 {
434 lock (_syncRoot)
435 {
436 if (_disposed)
437 return;
438  
439 _timer.Dispose();
440 _disposed = true;
441  
442 foreach (KeyValuePair<int, ObjectPoolSegment<T>> kvp in _segments)
443 {
444 try
445 {
446 kvp.Value.Dispose();
447 }
448 catch (Exception) { }
449 }
450  
451 _segments.Clear();
452 }
453 }
454 }
455  
456 #endregion
457 }
458  
459 internal class ObjectPoolSegment<T> : IDisposable where T : class
460 {
461 private Queue<T> _liveInstances = new Queue<T>();
462 private int _segmentNumber;
463 private int _originalCount;
464 private bool _isDisposed = false;
465 private DateTime _eligibleForDeletionAt;
466  
467 public int SegmentNumber { get { return _segmentNumber; } }
468 public int AvailableItems { get { return _liveInstances.Count; } }
469 public DateTime DateEligibleForDeletion { get { return _eligibleForDeletionAt; } }
470  
471 public ObjectPoolSegment(int segmentNumber, Queue<T> liveInstances, DateTime eligibleForDeletionAt)
472 {
473 _segmentNumber = segmentNumber;
474 _liveInstances = liveInstances;
475 _originalCount = liveInstances.Count;
476 _eligibleForDeletionAt = eligibleForDeletionAt;
477 }
478  
479 public bool CanBeCleanedUp()
480 {
481 if (_isDisposed == true)
482 throw new ObjectDisposedException("ObjectPoolSegment");
483  
484 return ((_originalCount == _liveInstances.Count) && (DateTime.Now > _eligibleForDeletionAt));
485 }
486  
487 public void Dispose()
488 {
489 if (_isDisposed)
490 return;
491  
492 _isDisposed = true;
493  
494 bool shouldDispose = (typeof(T) is IDisposable);
495 while (_liveInstances.Count != 0)
496 {
497 T instance = _liveInstances.Dequeue();
498 if (shouldDispose)
499 {
500 try
501 {
502 (instance as IDisposable).Dispose();
503 }
504 catch (Exception) { }
505 }
506 }
507 }
508  
509 internal void CheckInObject(T o)
510 {
511 if (_isDisposed == true)
512 throw new ObjectDisposedException("ObjectPoolSegment");
513  
514 _liveInstances.Enqueue(o);
515 }
516  
517 internal T CheckOutObject()
518 {
519 if (_isDisposed == true)
520 throw new ObjectDisposedException("ObjectPoolSegment");
521  
522 if (0 == _liveInstances.Count)
523 throw new InvalidOperationException("No Objects Available for Checkout");
524  
525 T o = _liveInstances.Dequeue();
526 return o;
527 }
528 }
529 }