123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- #pragma warning disable CA1822
- #nullable disable
- // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
- // define TRACE_LEAKS to get additional diagnostics that can lead to the leak sources. note: it will
- // make everything about 2-3x slower
- //
- // #define TRACE_LEAKS
- // define DETECT_LEAKS to detect possible leaks
- // #if DEBUG
- // #define DETECT_LEAKS //for now always enable DETECT_LEAKS in debug.
- // #endif
- using System.Diagnostics;
- using System.Threading;
- #if DETECT_LEAKS
- using System.Runtime.CompilerServices;
- #endif
- namespace Jint.Pooling;
- /// <summary>
- /// Generic implementation of object pooling pattern with predefined pool size limit. The main
- /// purpose is that limited number of frequently used objects can be kept in the pool for
- /// further recycling.
- ///
- /// Notes:
- /// 1) it is not the goal to keep all returned objects. Pool is not meant for storage. If there
- /// is no space in the pool, extra returned objects will be dropped.
- ///
- /// 2) it is implied that if object was obtained from a pool, the caller will return it back in
- /// a relatively short time. Keeping checked out objects for long durations is ok, but
- /// reduces usefulness of pooling. Just new up your own.
- ///
- /// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice.
- /// Rationale:
- /// If there is no intent for reusing the object, do not use pool - just use "new".
- /// </summary>
- internal sealed class ConcurrentObjectPool<T> where T : class
- {
- [DebuggerDisplay("{Value,nq}")]
- private struct Element
- {
- internal T Value;
- }
- /// <remarks>
- /// Not using System.Func{T} because this file is linked into the (debugger) Formatter,
- /// which does not have that type (since it compiles against .NET 2.0).
- /// </remarks>
- internal delegate T Factory();
- // Storage for the pool objects. The first item is stored in a dedicated field because we
- // expect to be able to satisfy most requests from it.
- private T _firstItem;
- private readonly Element[] _items;
- // factory is stored for the lifetime of the pool. We will call this only when pool needs to
- // expand. compared to "new T()", Func gives more flexibility to implementers and faster
- // than "new T()".
- private readonly Factory _factory;
- #if DETECT_LEAKS
- private static readonly ConditionalWeakTable<T, LeakTracker> leakTrackers = new ConditionalWeakTable<T, LeakTracker>();
- private class LeakTracker : IDisposable
- {
- private volatile bool disposed;
- #if TRACE_LEAKS
- internal volatile object Trace = null;
- #endif
- public void Dispose()
- {
- disposed = true;
- GC.SuppressFinalize(this);
- }
- private string GetTrace()
- {
- #if TRACE_LEAKS
- return Trace == null ? "" : Trace.ToString();
- #else
- return "Leak tracing information is disabled. Define TRACE_LEAKS on ObjectPool`1.cs to get more info \n";
- #endif
- }
- ~LeakTracker()
- {
- if (!this.disposed && !Environment.HasShutdownStarted)
- {
- var trace = GetTrace();
- // If you are seeing this message it means that object has been allocated from the pool
- // and has not been returned back. This is not critical, but turns pool into rather
- // inefficient kind of "new".
- Debug.WriteLine($"TRACEOBJECTPOOLLEAKS_BEGIN\nPool detected potential leaking of {typeof(T)}. \n Location of the leak: \n {GetTrace()} TRACEOBJECTPOOLLEAKS_END");
- }
- }
- }
- #endif
- internal ConcurrentObjectPool(Factory factory)
- : this(factory, Environment.ProcessorCount * 2)
- { }
- internal ConcurrentObjectPool(Factory factory, int size)
- {
- Debug.Assert(size >= 1);
- _factory = factory;
- _items = new Element[size - 1];
- }
- private T CreateInstance()
- {
- var inst = _factory();
- return inst;
- }
- /// <summary>
- /// Produces an instance.
- /// </summary>
- /// <remarks>
- /// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
- /// Note that Free will try to store recycled objects close to the start thus statistically
- /// reducing how far we will typically search.
- /// </remarks>
- internal T Allocate()
- {
- // PERF: Examine the first element. If that fails, AllocateSlow will look at the remaining elements.
- // Note that the initial read is optimistically not synchronized. That is intentional.
- // We will interlock only when we have a candidate. in a worst case we may miss some
- // recently returned objects. Not a big deal.
- T inst = _firstItem;
- if (inst == null || inst != Interlocked.CompareExchange(ref _firstItem, null, inst))
- {
- inst = AllocateSlow();
- }
- #if DETECT_LEAKS
- var tracker = new LeakTracker();
- leakTrackers.Add(inst, tracker);
- #if TRACE_LEAKS
- var frame = CaptureStackTrace();
- tracker.Trace = frame;
- #endif
- #endif
- return inst;
- }
- private T AllocateSlow()
- {
- var items = _items;
- for (int i = 0; i < items.Length; i++)
- {
- // Note that the initial read is optimistically not synchronized. That is intentional.
- // We will interlock only when we have a candidate. in a worst case we may miss some
- // recently returned objects. Not a big deal.
- T inst = items[i].Value;
- if (inst != null)
- {
- if (inst == Interlocked.CompareExchange(ref items[i].Value, null, inst))
- {
- return inst;
- }
- }
- }
- return CreateInstance();
- }
- /// <summary>
- /// Returns objects to the pool.
- /// </summary>
- /// <remarks>
- /// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
- /// Note that Free will try to store recycled objects close to the start thus statistically
- /// reducing how far we will typically search in Allocate.
- /// </remarks>
- internal void Free(T obj)
- {
- Validate(obj);
- ForgetTrackedObject(obj);
- if (_firstItem == null)
- {
- // Intentionally not using interlocked here.
- // In a worst case scenario two objects may be stored into same slot.
- // It is very unlikely to happen and will only mean that one of the objects will get collected.
- _firstItem = obj;
- }
- else
- {
- FreeSlow(obj);
- }
- }
- private void FreeSlow(T obj)
- {
- var items = _items;
- for (int i = 0; i < items.Length; i++)
- {
- if (items[i].Value == null)
- {
- // Intentionally not using interlocked here.
- // In a worst case scenario two objects may be stored into same slot.
- // It is very unlikely to happen and will only mean that one of the objects will get collected.
- items[i].Value = obj;
- break;
- }
- }
- }
- /// <summary>
- /// Removes an object from leak tracking.
- ///
- /// This is called when an object is returned to the pool. It may also be explicitly
- /// called if an object allocated from the pool is intentionally not being returned
- /// to the pool. This can be of use with pooled arrays if the consumer wants to
- /// return a larger array to the pool than was originally allocated.
- /// </summary>
- [Conditional("DEBUG")]
- internal void ForgetTrackedObject(T old, T replacement = null)
- {
- #if DETECT_LEAKS
- LeakTracker tracker;
- if (leakTrackers.TryGetValue(old, out tracker))
- {
- tracker.Dispose();
- leakTrackers.Remove(old);
- }
- else
- {
- var trace = CaptureStackTrace();
- Debug.WriteLine($"TRACEOBJECTPOOLLEAKS_BEGIN\nObject of type {typeof(T)} was freed, but was not from pool. \n Callstack: \n {trace} TRACEOBJECTPOOLLEAKS_END");
- }
- if (replacement != null)
- {
- tracker = new LeakTracker();
- leakTrackers.Add(replacement, tracker);
- }
- #endif
- }
- #if DETECT_LEAKS
- private static Lazy<Type> _stackTraceType = new Lazy<Type>(() => Type.GetType("System.Diagnostics.StackTrace"));
- private static object CaptureStackTrace()
- {
- return Activator.CreateInstance(_stackTraceType.Value);
- }
- #endif
- [Conditional("DEBUG")]
- private void Validate(object obj)
- {
- Debug.Assert(obj != null, "freeing null?");
- Debug.Assert(_firstItem != obj, "freeing twice?");
- var items = _items;
- for (int i = 0; i < items.Length; i++)
- {
- var value = items[i].Value;
- if (value == null)
- {
- return;
- }
- Debug.Assert(value != obj, "freeing twice?");
- }
- }
- }
|