1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 
6 
7 //------------------------------------------------------------------------------
8 
9 using System.Collections.Generic;
10 using System.Diagnostics;
11 using System.Data.Common;
12 using System.Threading;
13 using System.Threading.Tasks;
14 
15 namespace System.Data.ProviderBase
16 {
17     internal abstract class DbConnectionFactory
18     {
19         private Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> _connectionPoolGroups;
20         private readonly List<DbConnectionPool> _poolsToRelease;
21         private readonly List<DbConnectionPoolGroup> _poolGroupsToRelease;
22         private readonly Timer _pruningTimer;
23         private const int PruningDueTime = 4 * 60 * 1000;           // 4 minutes
24         private const int PruningPeriod = 30 * 1000;           // thirty seconds
25 
26 
27         // s_pendingOpenNonPooled is an array of tasks used to throttle creation of non-pooled connections to
28         // a maximum of Environment.ProcessorCount at a time.
29         private static uint s_pendingOpenNonPooledNext = 0;
30         private static Task<DbConnectionInternal>[] s_pendingOpenNonPooled = new Task<DbConnectionInternal>[Environment.ProcessorCount];
31         private static Task<DbConnectionInternal> s_completedTask;
32 
DbConnectionFactory()33         protected DbConnectionFactory()
34         {
35             _connectionPoolGroups = new Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup>();
36             _poolsToRelease = new List<DbConnectionPool>();
37             _poolGroupsToRelease = new List<DbConnectionPoolGroup>();
38             _pruningTimer = CreatePruningTimer();
39         }
40 
41 
42         abstract public DbProviderFactory ProviderFactory
43         {
44             get;
45         }
46 
47 
ClearAllPools()48         public void ClearAllPools()
49         {
50             Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> connectionPoolGroups = _connectionPoolGroups;
51             foreach (KeyValuePair<DbConnectionPoolKey, DbConnectionPoolGroup> entry in connectionPoolGroups)
52             {
53                 DbConnectionPoolGroup poolGroup = entry.Value;
54                 if (null != poolGroup)
55                 {
56                     poolGroup.Clear();
57                 }
58             }
59         }
60 
ClearPool(DbConnection connection)61         public void ClearPool(DbConnection connection)
62         {
63             ADP.CheckArgumentNull(connection, nameof(connection));
64 
65             DbConnectionPoolGroup poolGroup = GetConnectionPoolGroup(connection);
66             if (null != poolGroup)
67             {
68                 poolGroup.Clear();
69             }
70         }
71 
ClearPool(DbConnectionPoolKey key)72         public void ClearPool(DbConnectionPoolKey key)
73         {
74             Debug.Assert(key != null, "key cannot be null");
75             ADP.CheckArgumentNull(key.ConnectionString, nameof(key) + "." + nameof(key.ConnectionString));
76 
77             DbConnectionPoolGroup poolGroup;
78             Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> connectionPoolGroups = _connectionPoolGroups;
79             if (connectionPoolGroups.TryGetValue(key, out poolGroup))
80             {
81                 poolGroup.Clear();
82             }
83         }
84 
CreateConnectionPoolProviderInfo(DbConnectionOptions connectionOptions)85         internal virtual DbConnectionPoolProviderInfo CreateConnectionPoolProviderInfo(DbConnectionOptions connectionOptions)
86         {
87             return null;
88         }
89 
90 
CreateNonPooledConnection(DbConnection owningConnection, DbConnectionPoolGroup poolGroup, DbConnectionOptions userOptions)91         internal DbConnectionInternal CreateNonPooledConnection(DbConnection owningConnection, DbConnectionPoolGroup poolGroup, DbConnectionOptions userOptions)
92         {
93             Debug.Assert(null != owningConnection, "null owningConnection?");
94             Debug.Assert(null != poolGroup, "null poolGroup?");
95 
96             DbConnectionOptions connectionOptions = poolGroup.ConnectionOptions;
97             DbConnectionPoolGroupProviderInfo poolGroupProviderInfo = poolGroup.ProviderInfo;
98             DbConnectionPoolKey poolKey = poolGroup.PoolKey;
99 
100             DbConnectionInternal newConnection = CreateConnection(connectionOptions, poolKey, poolGroupProviderInfo, null, owningConnection, userOptions);
101             if (null != newConnection)
102             {
103                 newConnection.MakeNonPooledObject(owningConnection);
104             }
105             return newConnection;
106         }
107 
CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions)108         internal DbConnectionInternal CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions)
109         {
110             Debug.Assert(null != pool, "null pool?");
111             DbConnectionPoolGroupProviderInfo poolGroupProviderInfo = pool.PoolGroup.ProviderInfo;
112 
113             DbConnectionInternal newConnection = CreateConnection(options, poolKey, poolGroupProviderInfo, pool, owningObject, userOptions);
114             if (null != newConnection)
115             {
116                 newConnection.MakePooledConnection(pool);
117             }
118             return newConnection;
119         }
120 
CreateConnectionPoolGroupProviderInfo(DbConnectionOptions connectionOptions)121         virtual internal DbConnectionPoolGroupProviderInfo CreateConnectionPoolGroupProviderInfo(DbConnectionOptions connectionOptions)
122         {
123             return null;
124         }
125 
CreatePruningTimer()126         private Timer CreatePruningTimer()
127         {
128             TimerCallback callback = new TimerCallback(PruneConnectionPoolGroups);
129             return new Timer(callback, null, PruningDueTime, PruningPeriod);
130         }
131 
FindConnectionOptions(DbConnectionPoolKey key)132         protected DbConnectionOptions FindConnectionOptions(DbConnectionPoolKey key)
133         {
134             Debug.Assert(key != null, "key cannot be null");
135             if (!string.IsNullOrEmpty(key.ConnectionString))
136             {
137                 DbConnectionPoolGroup connectionPoolGroup;
138                 Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> connectionPoolGroups = _connectionPoolGroups;
139                 if (connectionPoolGroups.TryGetValue(key, out connectionPoolGroup))
140                 {
141                     return connectionPoolGroup.ConnectionOptions;
142                 }
143             }
144             return null;
145         }
146 
GetCompletedTask()147         private static Task<DbConnectionInternal> GetCompletedTask()
148         {
149             Debug.Assert(Monitor.IsEntered(s_pendingOpenNonPooled), $"Expected {nameof(s_pendingOpenNonPooled)} lock to be held.");
150             return s_completedTask ?? (s_completedTask = Task.FromResult<DbConnectionInternal>(null));
151         }
152 
TryGetConnection(DbConnection owningConnection, TaskCompletionSource<DbConnectionInternal> retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, out DbConnectionInternal connection)153         internal bool TryGetConnection(DbConnection owningConnection, TaskCompletionSource<DbConnectionInternal> retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, out DbConnectionInternal connection)
154         {
155             Debug.Assert(null != owningConnection, "null owningConnection?");
156 
157             DbConnectionPoolGroup poolGroup;
158             DbConnectionPool connectionPool;
159             connection = null;
160 
161             //  Work around race condition with clearing the pool between GetConnectionPool obtaining pool
162             //  and GetConnection on the pool checking the pool state.  Clearing the pool in this window
163             //  will switch the pool into the ShuttingDown state, and GetConnection will return null.
164             //  There is probably a better solution involving locking the pool/group, but that entails a major
165             //  re-design of the connection pooling synchronization, so is postponed for now.
166 
167             // Use retriesLeft to prevent CPU spikes with incremental sleep
168             // start with one msec, double the time every retry
169             // max time is: 1 + 2 + 4 + ... + 2^(retries-1) == 2^retries -1 == 1023ms (for 10 retries)
170             int retriesLeft = 10;
171             int timeBetweenRetriesMilliseconds = 1;
172 
173             do
174             {
175                 poolGroup = GetConnectionPoolGroup(owningConnection);
176                 // Doing this on the callers thread is important because it looks up the WindowsIdentity from the thread.
177                 connectionPool = GetConnectionPool(owningConnection, poolGroup);
178                 if (null == connectionPool)
179                 {
180                     // If GetConnectionPool returns null, we can be certain that
181                     // this connection should not be pooled via DbConnectionPool
182                     // or have a disabled pool entry.
183                     poolGroup = GetConnectionPoolGroup(owningConnection); // previous entry have been disabled
184 
185                     if (retry != null)
186                     {
187                         Task<DbConnectionInternal> newTask;
188                         CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
189                         lock (s_pendingOpenNonPooled)
190                         {
191                             // look for an available task slot (completed or empty)
192                             int idx;
193                             for (idx = 0; idx < s_pendingOpenNonPooled.Length; idx++)
194                             {
195                                 Task task = s_pendingOpenNonPooled[idx];
196                                 if (task == null)
197                                 {
198                                     s_pendingOpenNonPooled[idx] = GetCompletedTask();
199                                     break;
200                                 }
201                                 else if (task.IsCompleted)
202                                 {
203                                     break;
204                                 }
205                             }
206 
207                             // if didn't find one, pick the next one in round-robin fashion
208                             if (idx == s_pendingOpenNonPooled.Length)
209                             {
210                                 idx = (int)(s_pendingOpenNonPooledNext % s_pendingOpenNonPooled.Length);
211                                 unchecked
212                                 {
213                                     s_pendingOpenNonPooledNext++;
214                                 }
215                             }
216 
217                             // now that we have an antecedent task, schedule our work when it is completed.
218                             // If it is a new slot or a completed task, this continuation will start right away.
219                             newTask = s_pendingOpenNonPooled[idx].ContinueWith((_) =>
220                             {
221                                 Transactions.Transaction originalTransaction = ADP.GetCurrentTransaction();
222                                 try
223                                 {
224                                     ADP.SetCurrentTransaction(retry.Task.AsyncState as Transactions.Transaction);
225                                     var newConnection = CreateNonPooledConnection(owningConnection, poolGroup, userOptions);
226                                     if ((oldConnection != null) && (oldConnection.State == ConnectionState.Open))
227                                     {
228                                         oldConnection.PrepareForReplaceConnection();
229                                         oldConnection.Dispose();
230                                     }
231                                     return newConnection;
232                                 }
233                                 finally
234                                 {
235                                     ADP.SetCurrentTransaction(originalTransaction);
236                                 }
237                             }, cancellationTokenSource.Token, TaskContinuationOptions.LongRunning, TaskScheduler.Default);
238 
239                             // Place this new task in the slot so any future work will be queued behind it
240                             s_pendingOpenNonPooled[idx] = newTask;
241                         }
242 
243                         // Set up the timeout (if needed)
244                         if (owningConnection.ConnectionTimeout > 0)
245                         {
246                             int connectionTimeoutMilliseconds = owningConnection.ConnectionTimeout * 1000;
247                             cancellationTokenSource.CancelAfter(connectionTimeoutMilliseconds);
248                         }
249 
250                         // once the task is done, propagate the final results to the original caller
251                         newTask.ContinueWith((task) =>
252                         {
253                             cancellationTokenSource.Dispose();
254                             if (task.IsCanceled)
255                             {
256                                 retry.TrySetException(ADP.ExceptionWithStackTrace(ADP.NonPooledOpenTimeout()));
257                             }
258                             else if (task.IsFaulted)
259                             {
260                                 retry.TrySetException(task.Exception.InnerException);
261                             }
262                             else
263                             {
264                                 if (!retry.TrySetResult(task.Result))
265                                 {
266                                     // The outer TaskCompletionSource was already completed
267                                     // Which means that we don't know if someone has messed with the outer connection in the middle of creation
268                                     // So the best thing to do now is to destroy the newly created connection
269                                     task.Result.DoomThisConnection();
270                                     task.Result.Dispose();
271                                 }
272                             }
273                         }, TaskScheduler.Default);
274 
275                         return false;
276                     }
277 
278                     connection = CreateNonPooledConnection(owningConnection, poolGroup, userOptions);
279                 }
280                 else
281                 {
282                     if (((SqlClient.SqlConnection)owningConnection).ForceNewConnection)
283                     {
284                         Debug.Assert(!(oldConnection is DbConnectionClosed), "Force new connection, but there is no old connection");
285                         connection = connectionPool.ReplaceConnection(owningConnection, userOptions, oldConnection);
286                     }
287                     else
288                     {
289                         if (!connectionPool.TryGetConnection(owningConnection, retry, userOptions, out connection))
290                         {
291                             return false;
292                         }
293                     }
294 
295                     if (connection == null)
296                     {
297                         // connection creation failed on semaphore waiting or if max pool reached
298                         if (connectionPool.IsRunning)
299                         {
300                             // If GetConnection failed while the pool is running, the pool timeout occurred.
301                             throw ADP.PooledOpenTimeout();
302                         }
303                         else
304                         {
305                             // We've hit the race condition, where the pool was shut down after we got it from the group.
306                             // Yield time slice to allow shut down activities to complete and a new, running pool to be instantiated
307                             //  before retrying.
308                             Threading.Thread.Sleep(timeBetweenRetriesMilliseconds);
309                             timeBetweenRetriesMilliseconds *= 2; // double the wait time for next iteration
310                         }
311                     }
312                 }
313             } while (connection == null && retriesLeft-- > 0);
314 
315             if (connection == null)
316             {
317                 // exhausted all retries or timed out - give up
318                 throw ADP.PooledOpenTimeout();
319             }
320 
321             return true;
322         }
323 
GetConnectionPool(DbConnection owningObject, DbConnectionPoolGroup connectionPoolGroup)324         private DbConnectionPool GetConnectionPool(DbConnection owningObject, DbConnectionPoolGroup connectionPoolGroup)
325         {
326             // if poolgroup is disabled, it will be replaced with a new entry
327 
328             Debug.Assert(null != owningObject, "null owningObject?");
329             Debug.Assert(null != connectionPoolGroup, "null connectionPoolGroup?");
330 
331             // It is possible that while the outer connection object has
332             // been sitting around in a closed and unused state in some long
333             // running app, the pruner may have come along and remove this
334             // the pool entry from the master list.  If we were to use a
335             // pool entry in this state, we would create "unmanaged" pools,
336             // which would be bad.  To avoid this problem, we automagically
337             // re-create the pool entry whenever it's disabled.
338 
339             // however, don't rebuild connectionOptions if no pooling is involved - let new connections do that work
340             if (connectionPoolGroup.IsDisabled && (null != connectionPoolGroup.PoolGroupOptions))
341             {
342                 // reusing existing pool option in case user originally used SetConnectionPoolOptions
343                 DbConnectionPoolGroupOptions poolOptions = connectionPoolGroup.PoolGroupOptions;
344 
345                 // get the string to hash on again
346                 DbConnectionOptions connectionOptions = connectionPoolGroup.ConnectionOptions;
347                 Debug.Assert(null != connectionOptions, "prevent expansion of connectionString");
348 
349                 connectionPoolGroup = GetConnectionPoolGroup(connectionPoolGroup.PoolKey, poolOptions, ref connectionOptions);
350                 Debug.Assert(null != connectionPoolGroup, "null connectionPoolGroup?");
351                 SetConnectionPoolGroup(owningObject, connectionPoolGroup);
352             }
353             DbConnectionPool connectionPool = connectionPoolGroup.GetConnectionPool(this);
354             return connectionPool;
355         }
356 
GetConnectionPoolGroup(DbConnectionPoolKey key, DbConnectionPoolGroupOptions poolOptions, ref DbConnectionOptions userConnectionOptions)357         internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnectionPoolKey key, DbConnectionPoolGroupOptions poolOptions, ref DbConnectionOptions userConnectionOptions)
358         {
359             if (string.IsNullOrEmpty(key.ConnectionString))
360             {
361                 return (DbConnectionPoolGroup)null;
362             }
363 
364             DbConnectionPoolGroup connectionPoolGroup;
365             Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> connectionPoolGroups = _connectionPoolGroups;
366             if (!connectionPoolGroups.TryGetValue(key, out connectionPoolGroup) || (connectionPoolGroup.IsDisabled && (null != connectionPoolGroup.PoolGroupOptions)))
367             {
368                 // If we can't find an entry for the connection string in
369                 // our collection of pool entries, then we need to create a
370                 // new pool entry and add it to our collection.
371 
372                 DbConnectionOptions connectionOptions = CreateConnectionOptions(key.ConnectionString, userConnectionOptions);
373                 if (null == connectionOptions)
374                 {
375                     throw ADP.InternalConnectionError(ADP.ConnectionError.ConnectionOptionsMissing);
376                 }
377 
378                 if (null == userConnectionOptions)
379                 { // we only allow one expansion on the connection string
380                     userConnectionOptions = connectionOptions;
381                 }
382 
383                 // We don't support connection pooling on Win9x
384                 if (null == poolOptions)
385                 {
386                     if (null != connectionPoolGroup)
387                     {
388                         // reusing existing pool option in case user originally used SetConnectionPoolOptions
389                         poolOptions = connectionPoolGroup.PoolGroupOptions;
390                     }
391                     else
392                     {
393                         // Note: may return null for non-pooled connections
394                         poolOptions = CreateConnectionPoolGroupOptions(connectionOptions);
395                     }
396                 }
397 
398                 lock (this)
399                 {
400                     connectionPoolGroups = _connectionPoolGroups;
401                     if (!connectionPoolGroups.TryGetValue(key, out connectionPoolGroup))
402                     {
403                         DbConnectionPoolGroup newConnectionPoolGroup = new DbConnectionPoolGroup(connectionOptions, key, poolOptions);
404                         newConnectionPoolGroup.ProviderInfo = CreateConnectionPoolGroupProviderInfo(connectionOptions);
405 
406                         // build new dictionary with space for new connection string
407                         Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> newConnectionPoolGroups = new Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup>(1 + connectionPoolGroups.Count);
408                         foreach (KeyValuePair<DbConnectionPoolKey, DbConnectionPoolGroup> entry in connectionPoolGroups)
409                         {
410                             newConnectionPoolGroups.Add(entry.Key, entry.Value);
411                         }
412 
413                         // lock prevents race condition with PruneConnectionPoolGroups
414                         newConnectionPoolGroups.Add(key, newConnectionPoolGroup);
415                         connectionPoolGroup = newConnectionPoolGroup;
416                         _connectionPoolGroups = newConnectionPoolGroups;
417                     }
418                     else
419                     {
420                         Debug.Assert(!connectionPoolGroup.IsDisabled, "Disabled pool entry discovered");
421                     }
422                 }
423                 Debug.Assert(null != connectionPoolGroup, "how did we not create a pool entry?");
424                 Debug.Assert(null != userConnectionOptions, "how did we not have user connection options?");
425             }
426             else if (null == userConnectionOptions)
427             {
428                 userConnectionOptions = connectionPoolGroup.ConnectionOptions;
429             }
430             return connectionPoolGroup;
431         }
432 
433 
PruneConnectionPoolGroups(object state)434         private void PruneConnectionPoolGroups(object state)
435         {
436             // First, walk the pool release list and attempt to clear each
437             // pool, when the pool is finally empty, we dispose of it.  If the
438             // pool isn't empty, it's because there are active connections or
439             // distributed transactions that need it.
440             lock (_poolsToRelease)
441             {
442                 if (0 != _poolsToRelease.Count)
443                 {
444                     DbConnectionPool[] poolsToRelease = _poolsToRelease.ToArray();
445                     foreach (DbConnectionPool pool in poolsToRelease)
446                     {
447                         if (null != pool)
448                         {
449                             pool.Clear();
450 
451                             if (0 == pool.Count)
452                             {
453                                 _poolsToRelease.Remove(pool);
454                             }
455                         }
456                     }
457                 }
458             }
459 
460             // Next, walk the pool entry release list and dispose of each
461             // pool entry when it is finally empty.  If the pool entry isn't
462             // empty, it's because there are active pools that need it.
463             lock (_poolGroupsToRelease)
464             {
465                 if (0 != _poolGroupsToRelease.Count)
466                 {
467                     DbConnectionPoolGroup[] poolGroupsToRelease = _poolGroupsToRelease.ToArray();
468                     foreach (DbConnectionPoolGroup poolGroup in poolGroupsToRelease)
469                     {
470                         if (null != poolGroup)
471                         {
472                             int poolsLeft = poolGroup.Clear(); // may add entries to _poolsToRelease
473 
474                             if (0 == poolsLeft)
475                             {
476                                 _poolGroupsToRelease.Remove(poolGroup);
477                             }
478                         }
479                     }
480                 }
481             }
482 
483             // Finally, we walk through the collection of connection pool entries
484             // and prune each one.  This will cause any empty pools to be put
485             // into the release list.
486             lock (this)
487             {
488                 Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> connectionPoolGroups = _connectionPoolGroups;
489                 Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> newConnectionPoolGroups = new Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup>(connectionPoolGroups.Count);
490 
491                 foreach (KeyValuePair<DbConnectionPoolKey, DbConnectionPoolGroup> entry in connectionPoolGroups)
492                 {
493                     if (null != entry.Value)
494                     {
495                         Debug.Assert(!entry.Value.IsDisabled, "Disabled pool entry discovered");
496 
497                         // entries start active and go idle during prune if all pools are gone
498                         // move idle entries from last prune pass to a queue for pending release
499                         // otherwise process entry which may move it from active to idle
500                         if (entry.Value.Prune())
501                         { // may add entries to _poolsToRelease
502                             QueuePoolGroupForRelease(entry.Value);
503                         }
504                         else
505                         {
506                             newConnectionPoolGroups.Add(entry.Key, entry.Value);
507                         }
508                     }
509                 }
510                 _connectionPoolGroups = newConnectionPoolGroups;
511             }
512         }
513 
QueuePoolForRelease(DbConnectionPool pool, bool clearing)514         internal void QueuePoolForRelease(DbConnectionPool pool, bool clearing)
515         {
516             // Queue the pool up for release -- we'll clear it out and dispose
517             // of it as the last part of the pruning timer callback so we don't
518             // do it with the pool entry or the pool collection locked.
519             Debug.Assert(null != pool, "null pool?");
520 
521             // set the pool to the shutdown state to force all active
522             // connections to be automatically disposed when they
523             // are returned to the pool
524             pool.Shutdown();
525 
526             lock (_poolsToRelease)
527             {
528                 if (clearing)
529                 {
530                     pool.Clear();
531                 }
532                 _poolsToRelease.Add(pool);
533             }
534         }
535 
QueuePoolGroupForRelease(DbConnectionPoolGroup poolGroup)536         internal void QueuePoolGroupForRelease(DbConnectionPoolGroup poolGroup)
537         {
538             Debug.Assert(null != poolGroup, "null poolGroup?");
539 
540             lock (_poolGroupsToRelease)
541             {
542                 _poolGroupsToRelease.Add(poolGroup);
543             }
544         }
545 
CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions)546         virtual protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions)
547         {
548             return CreateConnection(options, poolKey, poolGroupProviderInfo, pool, owningConnection);
549         }
550 
GetMetaDataFactory(DbConnectionPoolGroup connectionPoolGroup, DbConnectionInternal internalConnection)551         internal DbMetaDataFactory GetMetaDataFactory(DbConnectionPoolGroup connectionPoolGroup, DbConnectionInternal internalConnection)
552         {
553             Debug.Assert(connectionPoolGroup != null, "connectionPoolGroup may not be null.");
554 
555             // get the matadatafactory from the pool entry. If it does not already have one
556             // create one and save it on the pool entry
557             DbMetaDataFactory metaDataFactory = connectionPoolGroup.MetaDataFactory;
558 
559             // consider serializing this so we don't construct multiple metadata factories
560             // if two threads happen to hit this at the same time.  One will be GC'd
561             if (metaDataFactory == null)
562             {
563                 bool allowCache = false;
564                 metaDataFactory = CreateMetaDataFactory(internalConnection, out allowCache);
565                 if (allowCache)
566                 {
567                     connectionPoolGroup.MetaDataFactory = metaDataFactory;
568                 }
569             }
570             return metaDataFactory;
571         }
572 
CreateMetaDataFactory(DbConnectionInternal internalConnection, out bool cacheMetaDataFactory)573         protected virtual DbMetaDataFactory CreateMetaDataFactory(DbConnectionInternal internalConnection, out bool cacheMetaDataFactory)
574         {
575             // providers that support GetSchema must override this with a method that creates a meta data
576             // factory appropriate for them.
577             cacheMetaDataFactory = false;
578             throw ADP.NotSupported();
579         }
580 
CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection)581         abstract protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection);
582 
CreateConnectionOptions(string connectionString, DbConnectionOptions previous)583         abstract protected DbConnectionOptions CreateConnectionOptions(string connectionString, DbConnectionOptions previous);
584 
CreateConnectionPoolGroupOptions(DbConnectionOptions options)585         abstract protected DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions(DbConnectionOptions options);
586 
GetConnectionPoolGroup(DbConnection connection)587         abstract internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnection connection);
588 
GetInnerConnection(DbConnection connection)589         abstract internal DbConnectionInternal GetInnerConnection(DbConnection connection);
590 
591 
PermissionDemand(DbConnection outerConnection)592         abstract internal void PermissionDemand(DbConnection outerConnection);
593 
SetConnectionPoolGroup(DbConnection outerConnection, DbConnectionPoolGroup poolGroup)594         abstract internal void SetConnectionPoolGroup(DbConnection outerConnection, DbConnectionPoolGroup poolGroup);
595 
SetInnerConnectionEvent(DbConnection owningObject, DbConnectionInternal to)596         abstract internal void SetInnerConnectionEvent(DbConnection owningObject, DbConnectionInternal to);
597 
SetInnerConnectionFrom(DbConnection owningObject, DbConnectionInternal to, DbConnectionInternal from)598         abstract internal bool SetInnerConnectionFrom(DbConnection owningObject, DbConnectionInternal to, DbConnectionInternal from);
599 
SetInnerConnectionTo(DbConnection owningObject, DbConnectionInternal to)600         abstract internal void SetInnerConnectionTo(DbConnection owningObject, DbConnectionInternal to);
601     }
602 }
603