TdsParserSessionPool.cs 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. //------------------------------------------------------------------------------
  2. // <copyright file="TdsParserSessionPool.cs" company="Microsoft">
  3. // Copyright (c) Microsoft Corporation. All rights reserved.
  4. // </copyright>
  5. // <owner current="true" primary="true">[....]</owner>
  6. // <owner current="true" primary="false">[....]</owner>
  7. //------------------------------------------------------------------------------
  8. namespace System.Data.SqlClient {
  9. using System;
  10. using System.Collections.Generic;
  11. using System.Data.Common;
  12. using System.Data.ProviderBase;
  13. using System.Diagnostics;
  14. using System.Runtime.CompilerServices;
  15. using System.Runtime.InteropServices;
  16. using System.Threading;
  17. using System.Collections.Concurrent;
  18. internal class TdsParserSessionPool {
  19. // NOTE: This is a very simplistic, lightweight pooler. It wasn't
  20. // intended to handle huge number of items, just to keep track
  21. // of the session objects to ensure that they're cleaned up in
  22. // a timely manner, to avoid holding on to an unacceptible
  23. // amount of server-side resources in the event that consumers
  24. // let their data readers be GC'd, instead of explicitly
  25. // closing or disposing of them
  26. private const int MaxInactiveCount = 10; // pick something, preferably small...
  27. private static int _objectTypeCount; // Bid counter
  28. private readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
  29. private readonly TdsParser _parser; // parser that owns us
  30. private readonly List<TdsParserStateObject> _cache; // collection of all known sessions
  31. private int _cachedCount; // lock-free _cache.Count
  32. private TdsParserStateObject[] _freeStateObjects; // collection of all sessions available for reuse
  33. private int _freeStateObjectCount; // Number of available free sessions
  34. internal TdsParserSessionPool(TdsParser parser) {
  35. _parser = parser;
  36. _cache = new List<TdsParserStateObject>();
  37. _freeStateObjects = new TdsParserStateObject[MaxInactiveCount];
  38. _freeStateObjectCount = 0;
  39. if (Bid.AdvancedOn) {
  40. Bid.Trace("<sc.TdsParserSessionPool.ctor|ADV> %d# created session pool for parser %d\n", ObjectID, parser.ObjectID);
  41. }
  42. }
  43. private bool IsDisposed {
  44. get {
  45. return (null == _freeStateObjects);
  46. }
  47. }
  48. internal int ObjectID {
  49. get {
  50. return _objectID;
  51. }
  52. }
  53. internal void Deactivate() {
  54. // When being deactivated, we check all the sessions in the
  55. // cache to make sure they're cleaned up and then we dispose of
  56. // sessions that are past what we want to keep around.
  57. IntPtr hscp;
  58. Bid.ScopeEnter(out hscp, "<sc.TdsParserSessionPool.Deactivate|ADV> %d# deactivating cachedCount=%d\n", ObjectID, _cachedCount);
  59. try {
  60. lock(_cache) {
  61. // NOTE: The PutSession call below may choose to remove the
  62. // session from the cache, which will throw off our
  63. // enumerator. We avoid that by simply indexing backward
  64. // through the array.
  65. for (int i = _cache.Count - 1; i >= 0 ; i--) {
  66. TdsParserStateObject session = _cache[i];
  67. if (null != session) {
  68. if (session.IsOrphaned) {
  69. //
  70. if (Bid.AdvancedOn) {
  71. Bid.Trace("<sc.TdsParserSessionPool.Deactivate|ADV> %d# reclaiming session %d\n", ObjectID, session.ObjectID);
  72. }
  73. PutSession(session);
  74. }
  75. }
  76. }
  77. //
  78. }
  79. }
  80. finally {
  81. Bid.ScopeLeave(ref hscp);
  82. }
  83. }
  84. // This is called from a ThreadAbort - ensure that it can be run from a CER Catch
  85. internal void BestEffortCleanup() {
  86. for (int i = 0; i < _cache.Count; i++) {
  87. TdsParserStateObject session = _cache[i];
  88. if (null != session) {
  89. var sessionHandle = session.Handle;
  90. if (sessionHandle != null) {
  91. sessionHandle.Dispose();
  92. }
  93. }
  94. }
  95. }
  96. internal void Dispose() {
  97. if (Bid.AdvancedOn) {
  98. Bid.Trace("<sc.TdsParserSessionPool.Dispose|ADV> %d# disposing cachedCount=%d\n", ObjectID, _cachedCount);
  99. }
  100. lock(_cache) {
  101. // Dispose free sessions
  102. for (int i = 0; i < _freeStateObjectCount; i++) {
  103. if (_freeStateObjects[i] != null) {
  104. _freeStateObjects[i].Dispose();
  105. }
  106. }
  107. _freeStateObjects = null;
  108. _freeStateObjectCount = 0;
  109. // Dispose orphaned sessions
  110. for (int i = 0; i < _cache.Count; i++) {
  111. if (_cache[i] != null) {
  112. if (_cache[i].IsOrphaned) {
  113. _cache[i].Dispose();
  114. }
  115. else {
  116. // Remove the "initial" callback (this will allow the stateObj to be GC collected if need be)
  117. _cache[i].DecrementPendingCallbacks(false);
  118. }
  119. }
  120. }
  121. _cache.Clear();
  122. _cachedCount = 0;
  123. // Any active sessions will take care of themselves
  124. // (It's too dangerous to dispose them, as this can cause AVs)
  125. }
  126. }
  127. internal TdsParserStateObject GetSession(object owner) {
  128. TdsParserStateObject session;
  129. lock (_cache) {
  130. if (IsDisposed) {
  131. throw ADP.ClosedConnectionError();
  132. }
  133. else if (_freeStateObjectCount > 0) {
  134. // Free state object - grab it
  135. _freeStateObjectCount--;
  136. session = _freeStateObjects[_freeStateObjectCount];
  137. _freeStateObjects[_freeStateObjectCount] = null;
  138. Debug.Assert(session != null, "There was a null session in the free session list?");
  139. }
  140. else {
  141. // No free objects, create a new one
  142. session = _parser.CreateSession();
  143. if (Bid.AdvancedOn) {
  144. Bid.Trace("<sc.TdsParserSessionPool.CreateSession|ADV> %d# adding session %d to pool\n", ObjectID, session.ObjectID);
  145. }
  146. _cache.Add(session);
  147. _cachedCount = _cache.Count;
  148. }
  149. session.Activate(owner);
  150. }
  151. if (Bid.AdvancedOn) {
  152. Bid.Trace("<sc.TdsParserSessionPool.GetSession|ADV> %d# using session %d\n", ObjectID, session.ObjectID);
  153. }
  154. return session;
  155. }
  156. internal void PutSession(TdsParserStateObject session) {
  157. Debug.Assert (null != session, "null session?");
  158. //Debug.Assert(null != session.Owner, "session without owner?");
  159. bool okToReuse = session.Deactivate();
  160. lock (_cache) {
  161. if (IsDisposed) {
  162. // We're diposed - just clean out the session
  163. Debug.Assert(_cachedCount == 0, "SessionPool is disposed, but there are still sessions in the cache?");
  164. session.Dispose();
  165. }
  166. else if ((okToReuse) && (_freeStateObjectCount < MaxInactiveCount)) {
  167. // Session is good to re-use and our cache has space
  168. if (Bid.AdvancedOn) {
  169. Bid.Trace("<sc.TdsParserSessionPool.PutSession|ADV> %d# keeping session %d cachedCount=%d\n", ObjectID, session.ObjectID, _cachedCount);
  170. }
  171. Debug.Assert(!session._pendingData, "pending data on a pooled session?");
  172. _freeStateObjects[_freeStateObjectCount] = session;
  173. _freeStateObjectCount++;
  174. }
  175. else {
  176. // Either the session is bad, or we have no cache space - so dispose the session and remove it
  177. if (Bid.AdvancedOn) {
  178. Bid.Trace("<sc.TdsParserSessionPool.PutSession|ADV> %d# disposing session %d cachedCount=%d\n", ObjectID, session.ObjectID, _cachedCount);
  179. }
  180. bool removed = _cache.Remove(session);
  181. Debug.Assert(removed, "session not in pool?");
  182. _cachedCount = _cache.Count;
  183. session.Dispose();
  184. }
  185. session.RemoveOwner();
  186. }
  187. }
  188. internal string TraceString() {
  189. return String.Format(/*IFormatProvider*/ null,
  190. "(ObjID={0}, free={1}, cached={2}, total={3})",
  191. _objectID,
  192. null == _freeStateObjects ? "(null)" : _freeStateObjectCount.ToString((IFormatProvider) null),
  193. _cachedCount,
  194. _cache.Count);
  195. }
  196. internal int ActiveSessionsCount {
  197. get {
  198. return _cachedCount - _freeStateObjectCount;
  199. }
  200. }
  201. }
  202. }