ConcurrentObjectPool.cs 9.7 KB

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