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