ObjectPool.cs 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  2. // define TRACE_LEAKS to get additional diagnostics that can lead to the leak sources. note: it will
  3. // make everything about 2-3x slower
  4. //
  5. // #define TRACE_LEAKS
  6. // define DETECT_LEAKS to detect possible leaks
  7. // #if DEBUG
  8. // #define DETECT_LEAKS //for now always enable DETECT_LEAKS in debug.
  9. // #endif
  10. using System.Diagnostics;
  11. #if DETECT_LEAKS
  12. using System.Runtime.CompilerServices;
  13. #endif
  14. namespace Jint.Pooling
  15. {
  16. /// <summary>
  17. /// Generic implementation of object pooling pattern with predefined pool size limit. The main
  18. /// purpose is that limited number of frequently used objects can be kept in the pool for
  19. /// further recycling.
  20. ///
  21. /// Notes:
  22. /// 1) it is not the goal to keep all returned objects. Pool is not meant for storage. If there
  23. /// is no space in the pool, extra returned objects will be dropped.
  24. ///
  25. /// 2) it is implied that if object was obtained from a pool, the caller will return it back in
  26. /// a relatively short time. Keeping checked out objects for long durations is ok, but
  27. /// reduces usefulness of pooling. Just new up your own.
  28. ///
  29. /// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice.
  30. /// Rationale:
  31. /// If there is no intent for reusing the object, do not use pool - just use "new".
  32. /// </summary>
  33. internal sealed class ObjectPool<T> where T : class
  34. {
  35. [DebuggerDisplay("{Value,nq}")]
  36. private struct Element
  37. {
  38. internal T Value;
  39. }
  40. /// <remarks>
  41. /// Not using System.Func{T} because this file is linked into the (debugger) Formatter,
  42. /// which does not have that type (since it compiles against .NET 2.0).
  43. /// </remarks>
  44. internal delegate T Factory();
  45. // Storage for the pool objects. The first item is stored in a dedicated field because we
  46. // expect to be able to satisfy most requests from it.
  47. private T _firstItem;
  48. private readonly Element[] _items;
  49. // factory is stored for the lifetime of the pool. We will call this only when pool needs to
  50. // expand. compared to "new T()", Func gives more flexibility to implementers and faster
  51. // than "new T()".
  52. private readonly Factory _factory;
  53. #if DETECT_LEAKS
  54. private static readonly ConditionalWeakTable<T, LeakTracker> leakTrackers = new ConditionalWeakTable<T, LeakTracker>();
  55. private class LeakTracker : IDisposable
  56. {
  57. private volatile bool disposed;
  58. #if TRACE_LEAKS
  59. internal volatile object Trace = null;
  60. #endif
  61. public void Dispose()
  62. {
  63. disposed = true;
  64. GC.SuppressFinalize(this);
  65. }
  66. private string GetTrace()
  67. {
  68. #if TRACE_LEAKS
  69. return Trace == null ? "" : Trace.ToString();
  70. #else
  71. return "Leak tracing information is disabled. Define TRACE_LEAKS on ObjectPool`1.cs to get more info \n";
  72. #endif
  73. }
  74. ~LeakTracker()
  75. {
  76. if (!this.disposed && !Environment.HasShutdownStarted)
  77. {
  78. var trace = GetTrace();
  79. // If you are seeing this message it means that object has been allocated from the pool
  80. // and has not been returned back. This is not critical, but turns pool into rather
  81. // inefficient kind of "new".
  82. Debug.WriteLine($"TRACEOBJECTPOOLLEAKS_BEGIN\nPool detected potential leaking of {typeof(T)}. \n Location of the leak: \n {GetTrace()} TRACEOBJECTPOOLLEAKS_END");
  83. }
  84. }
  85. }
  86. #endif
  87. internal ObjectPool(Factory factory)
  88. : this(factory, Environment.ProcessorCount * 2)
  89. { }
  90. internal ObjectPool(Factory factory, int size)
  91. {
  92. Debug.Assert(size >= 1);
  93. _factory = factory;
  94. _items = new Element[size - 1];
  95. }
  96. private T CreateInstance()
  97. {
  98. var inst = _factory();
  99. return inst;
  100. }
  101. /// <summary>
  102. /// Produces an instance.
  103. /// </summary>
  104. /// <remarks>
  105. /// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
  106. /// Note that Free will try to store recycled objects close to the start thus statistically
  107. /// reducing how far we will typically search.
  108. /// </remarks>
  109. internal T Allocate()
  110. {
  111. // PERF: Examine the first element. If that fails, AllocateSlow will look at the remaining elements.
  112. // Note that the initial read is optimistically not synchronized. That is intentional.
  113. // We will interlock only when we have a candidate. in a worst case we may miss some
  114. // recently returned objects. Not a big deal.
  115. T inst = _firstItem;
  116. if (!ReferenceEquals(inst, null))
  117. {
  118. _firstItem = null;
  119. return inst;
  120. }
  121. inst = AllocateSlow();
  122. #if DETECT_LEAKS
  123. var tracker = new LeakTracker();
  124. leakTrackers.Add(inst, tracker);
  125. #if TRACE_LEAKS
  126. var frame = CaptureStackTrace();
  127. tracker.Trace = frame;
  128. #endif
  129. #endif
  130. return inst;
  131. }
  132. private T AllocateSlow()
  133. {
  134. var items = _items;
  135. for (int i = 0; i < items.Length; i++)
  136. {
  137. T inst = items[i].Value;
  138. if (!ReferenceEquals(inst, null))
  139. {
  140. items[i].Value = null;
  141. return inst;
  142. }
  143. }
  144. return CreateInstance();
  145. }
  146. /// <summary>
  147. /// Returns objects to the pool.
  148. /// </summary>
  149. /// <remarks>
  150. /// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
  151. /// Note that Free will try to store recycled objects close to the start thus statistically
  152. /// reducing how far we will typically search in Allocate.
  153. /// </remarks>
  154. internal void Free(T obj)
  155. {
  156. Validate(obj);
  157. ForgetTrackedObject(obj);
  158. if (ReferenceEquals(_firstItem, null))
  159. {
  160. // Intentionally not using interlocked here.
  161. // In a worst case scenario two objects may be stored into same slot.
  162. // It is very unlikely to happen and will only mean that one of the objects will get collected.
  163. _firstItem = obj;
  164. }
  165. else
  166. {
  167. FreeSlow(obj);
  168. }
  169. }
  170. private void FreeSlow(T obj)
  171. {
  172. var items = _items;
  173. for (int i = 0; i < items.Length; i++)
  174. {
  175. if (ReferenceEquals(items[i].Value, null))
  176. {
  177. // Intentionally not using interlocked here.
  178. // In a worst case scenario two objects may be stored into same slot.
  179. // It is very unlikely to happen and will only mean that one of the objects will get collected.
  180. items[i].Value = obj;
  181. break;
  182. }
  183. }
  184. }
  185. /// <summary>
  186. /// Removes an object from leak tracking.
  187. ///
  188. /// This is called when an object is returned to the pool. It may also be explicitly
  189. /// called if an object allocated from the pool is intentionally not being returned
  190. /// to the pool. This can be of use with pooled arrays if the consumer wants to
  191. /// return a larger array to the pool than was originally allocated.
  192. /// </summary>
  193. [Conditional("DEBUG")]
  194. internal void ForgetTrackedObject(T old, T replacement = null)
  195. {
  196. #if DETECT_LEAKS
  197. LeakTracker tracker;
  198. if (leakTrackers.TryGetValue(old, out tracker))
  199. {
  200. tracker.Dispose();
  201. leakTrackers.Remove(old);
  202. }
  203. else
  204. {
  205. var trace = CaptureStackTrace();
  206. Debug.WriteLine($"TRACEOBJECTPOOLLEAKS_BEGIN\nObject of type {typeof(T)} was freed, but was not from pool. \n Callstack: \n {trace} TRACEOBJECTPOOLLEAKS_END");
  207. }
  208. if (replacement != null)
  209. {
  210. tracker = new LeakTracker();
  211. leakTrackers.Add(replacement, tracker);
  212. }
  213. #endif
  214. }
  215. #if DETECT_LEAKS
  216. private static Lazy<Type> _stackTraceType = new Lazy<Type>(() => Type.GetType("System.Diagnostics.StackTrace"));
  217. private static object CaptureStackTrace()
  218. {
  219. return Activator.CreateInstance(_stackTraceType.Value);
  220. }
  221. #endif
  222. [Conditional("DEBUG")]
  223. private void Validate(object obj)
  224. {
  225. Debug.Assert(obj != null, "freeing null?");
  226. Debug.Assert(_firstItem != obj, "freeing twice?");
  227. var items = _items;
  228. for (int i = 0; i < items.Length; i++)
  229. {
  230. var value = items[i].Value;
  231. if (ReferenceEquals(value, null))
  232. {
  233. return;
  234. }
  235. Debug.Assert(value != obj, "freeing twice?");
  236. }
  237. }
  238. }
  239. }