@@ -59,22 +59,6 @@ internal sealed class WaitHandleDbConnectionPool : IDbConnectionPool
5959 // This class is a way to stash our cloned Tx key for later disposal when it's no longer needed.
6060 // We can't get at the key in the dictionary without enumerating entries, so we stash an extra
6161 // copy as part of the value.
62- private sealed class TransactedConnectionList : List < DbConnectionInternal >
63- {
64- private Transaction _transaction ;
65- internal TransactedConnectionList ( int initialAllocation , Transaction tx ) : base ( initialAllocation )
66- {
67- _transaction = tx ;
68- }
69-
70- internal void Dispose ( )
71- {
72- if ( _transaction != null )
73- {
74- _transaction . Dispose ( ) ;
75- }
76- }
77- }
7862
7963 private sealed class PendingGetConnection
8064 {
@@ -91,250 +75,6 @@ public PendingGetConnection(long dueTime, DbConnection owner, TaskCompletionSour
9175 public DbConnectionOptions UserOptions { get ; private set ; }
9276 }
9377
94- private sealed class TransactedConnectionPool
95- {
96- Dictionary < Transaction , TransactedConnectionList > _transactedCxns ;
97-
98- IDbConnectionPool _pool ;
99-
100- private static int _objectTypeCount ; // EventSource Counter
101- internal readonly int _objectID = System . Threading . Interlocked . Increment ( ref _objectTypeCount ) ;
102-
103- internal TransactedConnectionPool ( IDbConnectionPool pool )
104- {
105- Debug . Assert ( pool != null , "null pool?" ) ;
106-
107- _pool = pool ;
108- _transactedCxns = new Dictionary < Transaction , TransactedConnectionList > ( ) ;
109- SqlClientEventSource . Log . TryPoolerTraceEvent ( "<prov.DbConnectionPool.TransactedConnectionPool.TransactedConnectionPool|RES|CPOOL> {0}, Constructed for connection pool {1}" , ObjectID , _pool . Id ) ;
110- }
111-
112- internal int ObjectID
113- {
114- get
115- {
116- return _objectID ;
117- }
118- }
119-
120- internal IDbConnectionPool Pool
121- {
122- get
123- {
124- return _pool ;
125- }
126- }
127-
128- internal DbConnectionInternal GetTransactedObject ( Transaction transaction )
129- {
130- Debug . Assert ( transaction != null , "null transaction?" ) ;
131-
132- DbConnectionInternal transactedObject = null ;
133-
134- TransactedConnectionList connections ;
135- bool txnFound = false ;
136-
137- lock ( _transactedCxns )
138- {
139- txnFound = _transactedCxns . TryGetValue ( transaction , out connections ) ;
140- }
141-
142- // NOTE: GetTransactedObject is only used when AutoEnlist = True and the ambient transaction
143- // (Sys.Txns.Txn.Current) is still valid/non-null. This, in turn, means that we don't need
144- // to worry about a pending asynchronous TransactionCompletedEvent to trigger processing in
145- // TransactionEnded below and potentially wipe out the connections list underneath us. It
146- // is similarly alright if a pending addition to the connections list in PutTransactedObject
147- // below is not completed prior to the lock on the connections object here...getting a new
148- // connection is probably better than unnecessarily locking
149- if ( txnFound )
150- {
151- Debug . Assert ( connections != null ) ;
152-
153- // synchronize multi-threaded access with PutTransactedObject (TransactionEnded should
154- // not be a concern, see comments above)
155- lock ( connections )
156- {
157- int i = connections . Count - 1 ;
158- if ( 0 <= i )
159- {
160- transactedObject = connections [ i ] ;
161- connections . RemoveAt ( i ) ;
162- }
163- }
164- }
165-
166- if ( transactedObject != null )
167- {
168- SqlClientEventSource . Log . TryPoolerTraceEvent ( "<prov.DbConnectionPool.TransactedConnectionPool.GetTransactedObject|RES|CPOOL> {0}, Transaction {1}, Connection {2}, Popped." , ObjectID , transaction . GetHashCode ( ) , transactedObject . ObjectID ) ;
169- }
170- return transactedObject ;
171- }
172-
173- internal void PutTransactedObject ( Transaction transaction , DbConnectionInternal transactedObject )
174- {
175- Debug . Assert ( transaction != null , "null transaction?" ) ;
176- Debug . Assert ( transactedObject != null , "null transactedObject?" ) ;
177-
178- TransactedConnectionList connections ;
179- bool txnFound = false ;
180-
181- // NOTE: because TransactionEnded is an asynchronous notification, there's no guarantee
182- // around the order in which PutTransactionObject and TransactionEnded are called.
183-
184- lock ( _transactedCxns )
185- {
186- // Check if a transacted pool has been created for this transaction
187- if ( txnFound = _transactedCxns . TryGetValue ( transaction , out connections ) )
188- {
189- Debug . Assert ( connections != null ) ;
190-
191- // synchronize multi-threaded access with GetTransactedObject
192- lock ( connections )
193- {
194- Debug . Assert ( 0 > connections . IndexOf ( transactedObject ) , "adding to pool a second time?" ) ;
195- SqlClientEventSource . Log . TryPoolerTraceEvent ( "<prov.DbConnectionPool.TransactedConnectionPool.PutTransactedObject|RES|CPOOL> {0}, Transaction {1}, Connection {2}, Pushing." , ObjectID , transaction . GetHashCode ( ) , transactedObject . ObjectID ) ;
196- connections . Add ( transactedObject ) ;
197- }
198- }
199- }
200-
201- // CONSIDER: the following code is more complicated than it needs to be to avoid cloning the
202- // transaction and allocating memory within a lock. Is that complexity really necessary?
203- if ( ! txnFound )
204- {
205- // create the transacted pool, making sure to clone the associated transaction
206- // for use as a key in our internal dictionary of transactions and connections
207- Transaction transactionClone = null ;
208- TransactedConnectionList newConnections = null ;
209-
210- try
211- {
212- transactionClone = transaction . Clone ( ) ;
213- newConnections = new TransactedConnectionList ( 2 , transactionClone ) ; // start with only two connections in the list; most times we won't need that many.
214-
215- lock ( _transactedCxns )
216- {
217- // NOTE: in the interim between the locks on the transacted pool (this) during
218- // execution of this method, another thread (threadB) may have attempted to
219- // add a different connection to the transacted pool under the same
220- // transaction. As a result, threadB may have completed creating the
221- // transacted pool while threadA was processing the above instructions.
222- if ( txnFound = _transactedCxns . TryGetValue ( transaction , out connections ) )
223- {
224- Debug . Assert ( connections != null ) ;
225-
226- // synchronize multi-threaded access with GetTransactedObject
227- lock ( connections )
228- {
229- Debug . Assert ( 0 > connections . IndexOf ( transactedObject ) , "adding to pool a second time?" ) ;
230- SqlClientEventSource . Log . TryPoolerTraceEvent ( "<prov.DbConnectionPool.TransactedConnectionPool.PutTransactedObject|RES|CPOOL> {0}, Transaction {1}, Connection {2}, Pushing." , ObjectID , transaction . GetHashCode ( ) , transactedObject . ObjectID ) ;
231- connections . Add ( transactedObject ) ;
232- }
233- }
234- else
235- {
236- SqlClientEventSource . Log . TryPoolerTraceEvent ( "<prov.DbConnectionPool.TransactedConnectionPool.PutTransactedObject|RES|CPOOL> {0}, Transaction {1}, Connection {2}, Adding List to transacted pool." , ObjectID , transaction . GetHashCode ( ) , transactedObject . ObjectID ) ;
237-
238- // add the connection/transacted object to the list
239- newConnections . Add ( transactedObject ) ;
240-
241- _transactedCxns . Add ( transactionClone , newConnections ) ;
242- transactionClone = null ; // we've used it -- don't throw it or the TransactedConnectionList that references it away.
243- }
244- }
245- }
246- finally
247- {
248- if ( transactionClone != null )
249- {
250- if ( newConnections != null )
251- {
252- // another thread created the transaction pool and thus the new
253- // TransactedConnectionList was not used, so dispose of it and
254- // the transaction clone that it incorporates.
255- newConnections . Dispose ( ) ;
256- }
257- else
258- {
259- // memory allocation for newConnections failed...clean up unused transactionClone
260- transactionClone . Dispose ( ) ;
261- }
262- }
263- }
264- SqlClientEventSource . Log . TryPoolerTraceEvent ( "<prov.DbConnectionPool.TransactedConnectionPool.PutTransactedObject|RES|CPOOL> {0}, Transaction {1}, Connection {2}, Added." , ObjectID , transaction . GetHashCode ( ) , transactedObject . ObjectID ) ;
265- }
266-
267- SqlClientEventSource . Metrics . EnterFreeConnection ( ) ;
268- }
269-
270- internal void TransactionEnded ( Transaction transaction , DbConnectionInternal transactedObject )
271- {
272- SqlClientEventSource . Log . TryPoolerTraceEvent ( "<prov.DbConnectionPool.TransactedConnectionPool.TransactionEnded|RES|CPOOL> {0}, Transaction {1}, Connection {2}, Transaction Completed" , ObjectID , transaction . GetHashCode ( ) , transactedObject . ObjectID ) ;
273- TransactedConnectionList connections ;
274- int entry = - 1 ;
275-
276- // NOTE: because TransactionEnded is an asynchronous notification, there's no guarantee
277- // around the order in which PutTransactionObject and TransactionEnded are called. As
278- // such, it is possible that the transaction does not yet have a pool created.
279-
280- // TODO: is this a plausible and/or likely scenario? Do we need to have a mechanism to ensure
281- // TODO: that the pending creation of a transacted pool for this transaction is aborted when
282- // TODO: PutTransactedObject finally gets some CPU time?
283-
284- lock ( _transactedCxns )
285- {
286- if ( _transactedCxns . TryGetValue ( transaction , out connections ) )
287- {
288- Debug . Assert ( connections != null ) ;
289-
290- bool shouldDisposeConnections = false ;
291-
292- // Lock connections to avoid conflict with GetTransactionObject
293- lock ( connections )
294- {
295- entry = connections . IndexOf ( transactedObject ) ;
296-
297- if ( entry >= 0 )
298- {
299- connections . RemoveAt ( entry ) ;
300- }
301-
302- // Once we've completed all the ended notifications, we can
303- // safely remove the list from the transacted pool.
304- if ( 0 >= connections . Count )
305- {
306- SqlClientEventSource . Log . TryPoolerTraceEvent ( "<prov.DbConnectionPool.TransactedConnectionPool.TransactionEnded|RES|CPOOL> {0}, Transaction {1}, Removing List from transacted pool." , ObjectID , transaction . GetHashCode ( ) ) ;
307- _transactedCxns . Remove ( transaction ) ;
308-
309- // we really need to dispose our connection list; it may have
310- // native resources via the tx and GC may not happen soon enough.
311- shouldDisposeConnections = true ;
312- }
313- }
314-
315- if ( shouldDisposeConnections )
316- {
317- connections . Dispose ( ) ;
318- }
319- }
320- else
321- {
322- SqlClientEventSource . Log . TryPoolerTraceEvent ( "<prov.DbConnectionPool.TransactedConnectionPool.TransactionEnded|RES|CPOOL> {0}, Transaction {1}, Connection {2}, Transacted pool not yet created prior to transaction completing. Connection may be leaked." , ObjectID , transaction . GetHashCode ( ) , transactedObject . ObjectID ) ;
323- }
324- }
325-
326- // If (and only if) we found the connection in the list of
327- // connections, we'll put it back...
328- if ( 0 <= entry )
329- {
330-
331- SqlClientEventSource . Metrics . ExitFreeConnection ( ) ;
332- Pool . PutObjectFromTransactedPool ( transactedObject ) ;
333- }
334- }
335-
336- }
337-
33878 private sealed class PoolWaitHandles
33979 {
34080 private readonly Semaphore _poolSemaphore ;
0 commit comments