diff --git a/src/Common/src/System/Data/ProviderBase/DbConnectionClosed.cs b/src/Common/src/System/Data/ProviderBase/DbConnectionClosed.cs new file mode 100644 index 000000000000..1238beb3cc87 --- /dev/null +++ b/src/Common/src/System/Data/ProviderBase/DbConnectionClosed.cs @@ -0,0 +1,138 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Data.Common; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace System.Data.ProviderBase +{ + abstract internal partial class DbConnectionClosed : DbConnectionInternal + { + // Construct an "empty" connection + protected DbConnectionClosed(ConnectionState state, bool hidePassword, bool allowSetConnectionString) : base(state, hidePassword, allowSetConnectionString) + { + } + + public override string ServerVersion => throw ADP.ClosedConnectionError(); + + public override DbTransaction BeginTransaction(IsolationLevel il) => throw ADP.ClosedConnectionError(); + + public override void ChangeDatabase(string database) => throw ADP.ClosedConnectionError(); + + internal override void CloseConnection(DbConnection owningObject, DbConnectionFactory connectionFactory) + { + // not much to do here... + } + + protected override void Deactivate() => ADP.ClosedConnectionError(); + + protected internal override DataTable GetSchema(DbConnectionFactory factory, DbConnectionPoolGroup poolGroup, DbConnection outerConnection, string collectionName, string[] restrictions) + => throw ADP.ClosedConnectionError(); + + protected override DbReferenceCollection CreateReferenceCollection() => throw ADP.ClosedConnectionError(); + + internal override bool TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) + => base.TryOpenConnectionInternal(outerConnection, connectionFactory, retry, userOptions); + } + + abstract internal class DbConnectionBusy : DbConnectionClosed + { + protected DbConnectionBusy(ConnectionState state) : base(state, true, false) + { + } + + internal override bool TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) + => throw ADP.ConnectionAlreadyOpen(State); + } + + sealed internal class DbConnectionClosedBusy : DbConnectionBusy + { + // Closed Connection, Currently Busy - changing connection string + internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionClosedBusy(); // singleton object + + private DbConnectionClosedBusy() : base(ConnectionState.Closed) + { + } + } + + sealed internal class DbConnectionOpenBusy : DbConnectionBusy + { + // Open Connection, Currently Busy - closing connection + internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionOpenBusy(); // singleton object + + private DbConnectionOpenBusy() : base(ConnectionState.Open) + { + } + } + + sealed internal class DbConnectionClosedConnecting : DbConnectionBusy + { + // Closed Connection, Currently Connecting + + internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionClosedConnecting(); // singleton object + + private DbConnectionClosedConnecting() : base(ConnectionState.Connecting) + { + } + + internal override void CloseConnection(DbConnection owningObject, DbConnectionFactory connectionFactory) + { + connectionFactory.SetInnerConnectionTo(owningObject, DbConnectionClosedPreviouslyOpened.SingletonInstance); + } + + internal override bool TryReplaceConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) + => TryOpenConnection(outerConnection, connectionFactory, retry, userOptions); + + internal override bool TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) + { + if (retry == null || !retry.Task.IsCompleted) + { + // retry is null if this is a synchronous call + + // if someone calls Open or OpenAsync while in this state, + // then the retry task will not be completed + + throw ADP.ConnectionAlreadyOpen(State); + } + + // we are completing an asynchronous open + Debug.Assert(retry.Task.Status == TaskStatus.RanToCompletion, "retry task must be completed successfully"); + DbConnectionInternal openConnection = retry.Task.Result; + if (null == openConnection) + { + connectionFactory.SetInnerConnectionTo(outerConnection, this); + throw ADP.InternalConnectionError(ADP.ConnectionError.GetConnectionReturnsNull); + } + connectionFactory.SetInnerConnectionEvent(outerConnection, openConnection); + + return true; + } + } + + sealed internal class DbConnectionClosedNeverOpened : DbConnectionClosed + { + // Closed Connection, Has Never Been Opened + + internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionClosedNeverOpened(); // singleton object + + private DbConnectionClosedNeverOpened() : base(ConnectionState.Closed, false, true) + { + } + } + + sealed internal class DbConnectionClosedPreviouslyOpened : DbConnectionClosed + { + // Closed Connection, Has Previously Been Opened + + internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionClosedPreviouslyOpened(); // singleton object + + private DbConnectionClosedPreviouslyOpened() : base(ConnectionState.Closed, true, true) + { + } + + internal override bool TryReplaceConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) + => TryOpenConnection(outerConnection, connectionFactory, retry, userOptions); + } +} diff --git a/src/Common/src/System/Data/ProviderBase/DbConnectionFactory.cs b/src/Common/src/System/Data/ProviderBase/DbConnectionFactory.cs new file mode 100644 index 000000000000..9ac3330b4ac2 --- /dev/null +++ b/src/Common/src/System/Data/ProviderBase/DbConnectionFactory.cs @@ -0,0 +1,427 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Data.ProviderBase +{ + internal abstract partial class DbConnectionFactory + { + private Dictionary _connectionPoolGroups; + private readonly List _poolsToRelease; + private readonly List _poolGroupsToRelease; + private readonly Timer _pruningTimer; + private const int PruningDueTime = 4 * 60 * 1000; // 4 minutes + private const int PruningPeriod = 30 * 1000; // thirty seconds + + + // s_pendingOpenNonPooled is an array of tasks used to throttle creation of non-pooled connections to + // a maximum of Environment.ProcessorCount at a time. + private static uint s_pendingOpenNonPooledNext = 0; + private static Task[] s_pendingOpenNonPooled = new Task[Environment.ProcessorCount]; + private static Task s_completedTask; + + protected DbConnectionFactory() + { + _connectionPoolGroups = new Dictionary(); + _poolsToRelease = new List(); + _poolGroupsToRelease = new List(); + _pruningTimer = CreatePruningTimer(); + } + + + abstract public DbProviderFactory ProviderFactory + { + get; + } + + + public void ClearAllPools() + { + Dictionary connectionPoolGroups = _connectionPoolGroups; + foreach (KeyValuePair entry in connectionPoolGroups) + { + DbConnectionPoolGroup poolGroup = entry.Value; + if (null != poolGroup) + { + poolGroup.Clear(); + } + } + } + + public void ClearPool(DbConnection connection) + { + ADP.CheckArgumentNull(connection, nameof(connection)); + + DbConnectionPoolGroup poolGroup = GetConnectionPoolGroup(connection); + if (null != poolGroup) + { + poolGroup.Clear(); + } + } + + public void ClearPool(DbConnectionPoolKey key) + { + Debug.Assert(key != null, "key cannot be null"); + ADP.CheckArgumentNull(key.ConnectionString, nameof(key) + "." + nameof(key.ConnectionString)); + + DbConnectionPoolGroup poolGroup; + Dictionary connectionPoolGroups = _connectionPoolGroups; + if (connectionPoolGroups.TryGetValue(key, out poolGroup)) + { + poolGroup.Clear(); + } + } + + internal virtual DbConnectionPoolProviderInfo CreateConnectionPoolProviderInfo(DbConnectionOptions connectionOptions) + { + return null; + } + + + internal DbConnectionInternal CreateNonPooledConnection(DbConnection owningConnection, DbConnectionPoolGroup poolGroup, DbConnectionOptions userOptions) + { + Debug.Assert(null != owningConnection, "null owningConnection?"); + Debug.Assert(null != poolGroup, "null poolGroup?"); + + DbConnectionOptions connectionOptions = poolGroup.ConnectionOptions; + DbConnectionPoolGroupProviderInfo poolGroupProviderInfo = poolGroup.ProviderInfo; + DbConnectionPoolKey poolKey = poolGroup.PoolKey; + + DbConnectionInternal newConnection = CreateConnection(connectionOptions, poolKey, poolGroupProviderInfo, null, owningConnection, userOptions); + if (null != newConnection) + { + newConnection.MakeNonPooledObject(owningConnection); + } + return newConnection; + } + + internal DbConnectionInternal CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions) + { + Debug.Assert(null != pool, "null pool?"); + DbConnectionPoolGroupProviderInfo poolGroupProviderInfo = pool.PoolGroup.ProviderInfo; + + DbConnectionInternal newConnection = CreateConnection(options, poolKey, poolGroupProviderInfo, pool, owningObject, userOptions); + if (null != newConnection) + { + newConnection.MakePooledConnection(pool); + } + return newConnection; + } + + virtual internal DbConnectionPoolGroupProviderInfo CreateConnectionPoolGroupProviderInfo(DbConnectionOptions connectionOptions) + { + return null; + } + + private Timer CreatePruningTimer() => + ADP.UnsafeCreateTimer( + new TimerCallback(PruneConnectionPoolGroups), + null, + PruningDueTime, + PruningPeriod); + + protected DbConnectionOptions FindConnectionOptions(DbConnectionPoolKey key) + { + Debug.Assert(key != null, "key cannot be null"); + if (!string.IsNullOrEmpty(key.ConnectionString)) + { + DbConnectionPoolGroup connectionPoolGroup; + Dictionary connectionPoolGroups = _connectionPoolGroups; + if (connectionPoolGroups.TryGetValue(key, out connectionPoolGroup)) + { + return connectionPoolGroup.ConnectionOptions; + } + } + return null; + } + + private static Task GetCompletedTask() + { + Debug.Assert(Monitor.IsEntered(s_pendingOpenNonPooled), $"Expected {nameof(s_pendingOpenNonPooled)} lock to be held."); + return s_completedTask ?? (s_completedTask = Task.FromResult(null)); + } + + private DbConnectionPool GetConnectionPool(DbConnection owningObject, DbConnectionPoolGroup connectionPoolGroup) + { + // if poolgroup is disabled, it will be replaced with a new entry + + Debug.Assert(null != owningObject, "null owningObject?"); + Debug.Assert(null != connectionPoolGroup, "null connectionPoolGroup?"); + + // It is possible that while the outer connection object has + // been sitting around in a closed and unused state in some long + // running app, the pruner may have come along and remove this + // the pool entry from the master list. If we were to use a + // pool entry in this state, we would create "unmanaged" pools, + // which would be bad. To avoid this problem, we automagically + // re-create the pool entry whenever it's disabled. + + // however, don't rebuild connectionOptions if no pooling is involved - let new connections do that work + if (connectionPoolGroup.IsDisabled && (null != connectionPoolGroup.PoolGroupOptions)) + { + // reusing existing pool option in case user originally used SetConnectionPoolOptions + DbConnectionPoolGroupOptions poolOptions = connectionPoolGroup.PoolGroupOptions; + + // get the string to hash on again + DbConnectionOptions connectionOptions = connectionPoolGroup.ConnectionOptions; + Debug.Assert(null != connectionOptions, "prevent expansion of connectionString"); + + connectionPoolGroup = GetConnectionPoolGroup(connectionPoolGroup.PoolKey, poolOptions, ref connectionOptions); + Debug.Assert(null != connectionPoolGroup, "null connectionPoolGroup?"); + SetConnectionPoolGroup(owningObject, connectionPoolGroup); + } + DbConnectionPool connectionPool = connectionPoolGroup.GetConnectionPool(this); + return connectionPool; + } + + internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnectionPoolKey key, DbConnectionPoolGroupOptions poolOptions, ref DbConnectionOptions userConnectionOptions) + { + if (string.IsNullOrEmpty(key.ConnectionString)) + { + return (DbConnectionPoolGroup)null; + } + + DbConnectionPoolGroup connectionPoolGroup; + Dictionary connectionPoolGroups = _connectionPoolGroups; + if (!connectionPoolGroups.TryGetValue(key, out connectionPoolGroup) || (connectionPoolGroup.IsDisabled && (null != connectionPoolGroup.PoolGroupOptions))) + { + // If we can't find an entry for the connection string in + // our collection of pool entries, then we need to create a + // new pool entry and add it to our collection. + + DbConnectionOptions connectionOptions = CreateConnectionOptions(key.ConnectionString, userConnectionOptions); + if (null == connectionOptions) + { + throw ADP.InternalConnectionError(ADP.ConnectionError.ConnectionOptionsMissing); + } + + if (null == userConnectionOptions) + { // we only allow one expansion on the connection string + userConnectionOptions = connectionOptions; + } + + // We don't support connection pooling on Win9x + if (null == poolOptions) + { + if (null != connectionPoolGroup) + { + // reusing existing pool option in case user originally used SetConnectionPoolOptions + poolOptions = connectionPoolGroup.PoolGroupOptions; + } + else + { + // Note: may return null for non-pooled connections + poolOptions = CreateConnectionPoolGroupOptions(connectionOptions); + } + } + + lock (this) + { + connectionPoolGroups = _connectionPoolGroups; + if (!connectionPoolGroups.TryGetValue(key, out connectionPoolGroup)) + { + DbConnectionPoolGroup newConnectionPoolGroup = new DbConnectionPoolGroup(connectionOptions, key, poolOptions); + newConnectionPoolGroup.ProviderInfo = CreateConnectionPoolGroupProviderInfo(connectionOptions); + + // build new dictionary with space for new connection string + Dictionary newConnectionPoolGroups = new Dictionary(1 + connectionPoolGroups.Count); + foreach (KeyValuePair entry in connectionPoolGroups) + { + newConnectionPoolGroups.Add(entry.Key, entry.Value); + } + + // lock prevents race condition with PruneConnectionPoolGroups + newConnectionPoolGroups.Add(key, newConnectionPoolGroup); + connectionPoolGroup = newConnectionPoolGroup; + _connectionPoolGroups = newConnectionPoolGroups; + } + else + { + Debug.Assert(!connectionPoolGroup.IsDisabled, "Disabled pool entry discovered"); + } + } + Debug.Assert(null != connectionPoolGroup, "how did we not create a pool entry?"); + Debug.Assert(null != userConnectionOptions, "how did we not have user connection options?"); + } + else if (null == userConnectionOptions) + { + userConnectionOptions = connectionPoolGroup.ConnectionOptions; + } + return connectionPoolGroup; + } + + + private void PruneConnectionPoolGroups(object state) + { + // First, walk the pool release list and attempt to clear each + // pool, when the pool is finally empty, we dispose of it. If the + // pool isn't empty, it's because there are active connections or + // distributed transactions that need it. + lock (_poolsToRelease) + { + if (0 != _poolsToRelease.Count) + { + DbConnectionPool[] poolsToRelease = _poolsToRelease.ToArray(); + foreach (DbConnectionPool pool in poolsToRelease) + { + if (null != pool) + { + pool.Clear(); + + if (0 == pool.Count) + { + _poolsToRelease.Remove(pool); + } + } + } + } + } + + // Next, walk the pool entry release list and dispose of each + // pool entry when it is finally empty. If the pool entry isn't + // empty, it's because there are active pools that need it. + lock (_poolGroupsToRelease) + { + if (0 != _poolGroupsToRelease.Count) + { + DbConnectionPoolGroup[] poolGroupsToRelease = _poolGroupsToRelease.ToArray(); + foreach (DbConnectionPoolGroup poolGroup in poolGroupsToRelease) + { + if (null != poolGroup) + { + int poolsLeft = poolGroup.Clear(); // may add entries to _poolsToRelease + + if (0 == poolsLeft) + { + _poolGroupsToRelease.Remove(poolGroup); + } + } + } + } + } + + // Finally, we walk through the collection of connection pool entries + // and prune each one. This will cause any empty pools to be put + // into the release list. + lock (this) + { + Dictionary connectionPoolGroups = _connectionPoolGroups; + Dictionary newConnectionPoolGroups = new Dictionary(connectionPoolGroups.Count); + + foreach (KeyValuePair entry in connectionPoolGroups) + { + if (null != entry.Value) + { + Debug.Assert(!entry.Value.IsDisabled, "Disabled pool entry discovered"); + + // entries start active and go idle during prune if all pools are gone + // move idle entries from last prune pass to a queue for pending release + // otherwise process entry which may move it from active to idle + if (entry.Value.Prune()) + { // may add entries to _poolsToRelease + QueuePoolGroupForRelease(entry.Value); + } + else + { + newConnectionPoolGroups.Add(entry.Key, entry.Value); + } + } + } + _connectionPoolGroups = newConnectionPoolGroups; + } + } + + internal void QueuePoolForRelease(DbConnectionPool pool, bool clearing) + { + // Queue the pool up for release -- we'll clear it out and dispose + // of it as the last part of the pruning timer callback so we don't + // do it with the pool entry or the pool collection locked. + Debug.Assert(null != pool, "null pool?"); + + // set the pool to the shutdown state to force all active + // connections to be automatically disposed when they + // are returned to the pool + pool.Shutdown(); + + lock (_poolsToRelease) + { + if (clearing) + { + pool.Clear(); + } + _poolsToRelease.Add(pool); + } + } + + internal void QueuePoolGroupForRelease(DbConnectionPoolGroup poolGroup) + { + Debug.Assert(null != poolGroup, "null poolGroup?"); + + lock (_poolGroupsToRelease) + { + _poolGroupsToRelease.Add(poolGroup); + } + } + + virtual protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) + { + return CreateConnection(options, poolKey, poolGroupProviderInfo, pool, owningConnection); + } + + internal DbMetaDataFactory GetMetaDataFactory(DbConnectionPoolGroup connectionPoolGroup, DbConnectionInternal internalConnection) + { + Debug.Assert(connectionPoolGroup != null, "connectionPoolGroup may not be null."); + + // get the matadatafactory from the pool entry. If it does not already have one + // create one and save it on the pool entry + DbMetaDataFactory metaDataFactory = connectionPoolGroup.MetaDataFactory; + + // consider serializing this so we don't construct multiple metadata factories + // if two threads happen to hit this at the same time. One will be GC'd + if (metaDataFactory == null) + { + bool allowCache = false; + metaDataFactory = CreateMetaDataFactory(internalConnection, out allowCache); + if (allowCache) + { + connectionPoolGroup.MetaDataFactory = metaDataFactory; + } + } + return metaDataFactory; + } + + protected virtual DbMetaDataFactory CreateMetaDataFactory(DbConnectionInternal internalConnection, out bool cacheMetaDataFactory) + { + // providers that support GetSchema must override this with a method that creates a meta data + // factory appropriate for them. + cacheMetaDataFactory = false; + throw ADP.NotSupported(); + } + + abstract protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection); + + abstract protected DbConnectionOptions CreateConnectionOptions(string connectionString, DbConnectionOptions previous); + + abstract protected DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions(DbConnectionOptions options); + + abstract internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnection connection); + + abstract internal DbConnectionInternal GetInnerConnection(DbConnection connection); + + abstract internal void PermissionDemand(DbConnection outerConnection); + + abstract internal void SetConnectionPoolGroup(DbConnection outerConnection, DbConnectionPoolGroup poolGroup); + + abstract internal void SetInnerConnectionEvent(DbConnection owningObject, DbConnectionInternal to); + + abstract internal bool SetInnerConnectionFrom(DbConnection owningObject, DbConnectionInternal to, DbConnectionInternal from); + + abstract internal void SetInnerConnectionTo(DbConnection owningObject, DbConnectionInternal to); + } +} diff --git a/src/Common/src/System/Data/ProviderBase/DbConnectionInternal.cs b/src/Common/src/System/Data/ProviderBase/DbConnectionInternal.cs new file mode 100644 index 000000000000..945a1c5ed5c0 --- /dev/null +++ b/src/Common/src/System/Data/ProviderBase/DbConnectionInternal.cs @@ -0,0 +1,434 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Data.Common; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using System.Transactions; + + +namespace System.Data.ProviderBase +{ + internal abstract partial class DbConnectionInternal + { + internal static readonly StateChangeEventArgs StateChangeClosed = new StateChangeEventArgs(ConnectionState.Open, ConnectionState.Closed); + internal static readonly StateChangeEventArgs StateChangeOpen = new StateChangeEventArgs(ConnectionState.Closed, ConnectionState.Open); + + private readonly bool _allowSetConnectionString; + private readonly bool _hidePassword; + private readonly ConnectionState _state; + + private readonly WeakReference _owningObject = new WeakReference(null, false); // [usage must be thread safe] the owning object, when not in the pool. (both Pooled and Non-Pooled connections) + + private DbConnectionPool _connectionPool; // the pooler that the connection came from (Pooled connections only) + private DbReferenceCollection _referenceCollection; // collection of objects that we need to notify in some way when we're being deactivated + private int _pooledCount; // [usage must be thread safe] the number of times this object has been pushed into the pool less the number of times it's been popped (0 != inPool) + + private bool _connectionIsDoomed; // true when the connection should no longer be used. + private bool _cannotBePooled; // true when the connection should no longer be pooled. + + private DateTime _createTime; // when the connection was created. + +#if DEBUG + private int _activateCount; // debug only counter to verify activate/deactivates are in sync. +#endif //DEBUG + + protected DbConnectionInternal() : this(ConnectionState.Open, true, false) + { + } + + // Constructor for internal connections + internal DbConnectionInternal(ConnectionState state, bool hidePassword, bool allowSetConnectionString) + { + _allowSetConnectionString = allowSetConnectionString; + _hidePassword = hidePassword; + _state = state; + } + + internal bool AllowSetConnectionString + { + get + { + return _allowSetConnectionString; + } + } + + internal bool CanBePooled + { + get + { + bool flag = (!_connectionIsDoomed && !_cannotBePooled && !_owningObject.IsAlive); + return flag; + } + } + + protected internal bool IsConnectionDoomed + { + get + { + return _connectionIsDoomed; + } + } + + internal bool IsEmancipated + { + get + { + // NOTE: There are race conditions between PrePush, PostPop and this + // property getter -- only use this while this object is locked; + // (DbConnectionPool.Clear and ReclaimEmancipatedObjects + // do this for us) + + // The functionality is as follows: + // + // _pooledCount is incremented when the connection is pushed into the pool + // _pooledCount is decremented when the connection is popped from the pool + // _pooledCount is set to -1 when the connection is not pooled (just in case...) + // + // That means that: + // + // _pooledCount > 1 connection is in the pool multiple times (This should not happen) + // _pooledCount == 1 connection is in the pool + // _pooledCount == 0 connection is out of the pool + // _pooledCount == -1 connection is not a pooled connection; we shouldn't be here for non-pooled connections. + // _pooledCount < -1 connection out of the pool multiple times + // + // Now, our job is to return TRUE when the connection is out + // of the pool and it's owning object is no longer around to + // return it. + + bool value = (_pooledCount < 1) && !_owningObject.IsAlive; + return value; + } + } + + internal bool IsInPool + { + get + { + Debug.Assert(_pooledCount <= 1 && _pooledCount >= -1, "Pooled count for object is invalid"); + return (_pooledCount == 1); + } + } + + + protected internal object Owner + { + // We use a weak reference to the owning object so we can identify when + // it has been garbage collected without thowing exceptions. + get + { + return _owningObject.Target; + } + } + + internal DbConnectionPool Pool + { + get + { + return _connectionPool; + } + } + + protected internal DbReferenceCollection ReferenceCollection + { + get + { + return _referenceCollection; + } + } + + abstract public string ServerVersion + { + get; + } + + // this should be abstract but until it is added to all the providers virtual will have to do + virtual public string ServerVersionNormalized + { + get + { + throw ADP.NotSupported(); + } + } + + public bool ShouldHidePassword + { + get + { + return _hidePassword; + } + } + + public ConnectionState State + { + get + { + return _state; + } + } + + internal void AddWeakReference(object value, int tag) + { + if (null == _referenceCollection) + { + _referenceCollection = CreateReferenceCollection(); + if (null == _referenceCollection) + { + throw ADP.InternalError(ADP.InternalErrorCode.CreateReferenceCollectionReturnedNull); + } + } + _referenceCollection.Add(value, tag); + } + + abstract public DbTransaction BeginTransaction(IsolationLevel il); + + virtual public void ChangeDatabase(string value) + { + throw ADP.MethodNotImplemented(); + } + + virtual internal void PrepareForReplaceConnection() + { + // By default, there is no preparation required + } + + virtual protected void PrepareForCloseConnection() + { + // By default, there is no preparation required + } + + virtual protected object ObtainAdditionalLocksForClose() + { + return null; // no additional locks in default implementation + } + + virtual protected void ReleaseAdditionalLocksForClose(object lockToken) + { + // no additional locks in default implementation + } + + virtual protected DbReferenceCollection CreateReferenceCollection() + { + throw ADP.InternalError(ADP.InternalErrorCode.AttemptingToConstructReferenceCollectionOnStaticObject); + } + + abstract protected void Deactivate(); + + internal void DeactivateConnection() + { + // Internal method called from the connection pooler so we don't expose + // the Deactivate method publicly. + +#if DEBUG + int activateCount = Interlocked.Decrement(ref _activateCount); +#endif // DEBUG + + + if (!_connectionIsDoomed && Pool.UseLoadBalancing) + { + // If we're not already doomed, check the connection's lifetime and + // doom it if it's lifetime has elapsed. + + DateTime now = DateTime.UtcNow; + if ((now.Ticks - _createTime.Ticks) > Pool.LoadBalanceTimeout.Ticks) + { + DoNotPoolThisConnection(); + } + } + Deactivate(); + } + + protected internal void DoNotPoolThisConnection() + { + _cannotBePooled = true; + } + + /// Ensure that this connection cannot be put back into the pool. + protected internal void DoomThisConnection() + { + _connectionIsDoomed = true; + } + + protected internal virtual DataTable GetSchema(DbConnectionFactory factory, DbConnectionPoolGroup poolGroup, DbConnection outerConnection, string collectionName, string[] restrictions) + { + Debug.Assert(outerConnection != null, "outerConnection may not be null."); + + DbMetaDataFactory metaDataFactory = factory.GetMetaDataFactory(poolGroup, this); + Debug.Assert(metaDataFactory != null, "metaDataFactory may not be null."); + + return metaDataFactory.GetSchema(outerConnection, collectionName, restrictions); + } + + internal void MakeNonPooledObject(object owningObject) + { + // Used by DbConnectionFactory to indicate that this object IS NOT part of + // a connection pool. + + _connectionPool = null; + _owningObject.Target = owningObject; + _pooledCount = -1; + } + + internal void MakePooledConnection(DbConnectionPool connectionPool) + { + // Used by DbConnectionFactory to indicate that this object IS part of + // a connection pool. + _createTime = DateTime.UtcNow; + + _connectionPool = connectionPool; + } + + internal void NotifyWeakReference(int message) + { + DbReferenceCollection referenceCollection = ReferenceCollection; + if (null != referenceCollection) + { + referenceCollection.Notify(message); + } + } + + internal virtual void OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory) + { + if (!TryOpenConnection(outerConnection, connectionFactory, null, null)) + { + throw ADP.InternalError(ADP.InternalErrorCode.SynchronousConnectReturnedPending); + } + } + + /// The default implementation is for the open connection objects, and + /// it simply throws. Our private closed-state connection objects + /// override this and do the correct thing. + // User code should either override DbConnectionInternal.Activate when it comes out of the pool + // or override DbConnectionFactory.CreateConnection when the connection is created for non-pooled connections + internal virtual bool TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) + { + throw ADP.ConnectionAlreadyOpen(State); + } + + internal virtual bool TryReplaceConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) + { + throw ADP.MethodNotImplemented(); + } + + protected bool TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) + { + // ?->Connecting: prevent set_ConnectionString during Open + if (connectionFactory.SetInnerConnectionFrom(outerConnection, DbConnectionClosedConnecting.SingletonInstance, this)) + { + DbConnectionInternal openConnection = null; + try + { + connectionFactory.PermissionDemand(outerConnection); + if (!connectionFactory.TryGetConnection(outerConnection, retry, userOptions, this, out openConnection)) + { + return false; + } + } + catch + { + // This should occur for all exceptions, even ADP.UnCatchableExceptions. + connectionFactory.SetInnerConnectionTo(outerConnection, this); + throw; + } + if (null == openConnection) + { + connectionFactory.SetInnerConnectionTo(outerConnection, this); + throw ADP.InternalConnectionError(ADP.ConnectionError.GetConnectionReturnsNull); + } + connectionFactory.SetInnerConnectionEvent(outerConnection, openConnection); + } + + return true; + } + + internal void PrePush(object expectedOwner) + { + // Called by DbConnectionPool when we're about to be put into it's pool, we + // take this opportunity to ensure ownership and pool counts are legit. + + // IMPORTANT NOTE: You must have taken a lock on the object before + // you call this method to prevent race conditions with Clear and + // ReclaimEmancipatedObjects. + + //3 // The following tests are retail assertions of things we can't allow to happen. + if (null == expectedOwner) + { + if (null != _owningObject.Target) + { + throw ADP.InternalError(ADP.InternalErrorCode.UnpooledObjectHasOwner); // new unpooled object has an owner + } + } + else if (_owningObject.Target != expectedOwner) + { + throw ADP.InternalError(ADP.InternalErrorCode.UnpooledObjectHasWrongOwner); // unpooled object has incorrect owner + } + if (0 != _pooledCount) + { + throw ADP.InternalError(ADP.InternalErrorCode.PushingObjectSecondTime); // pushing object onto stack a second time + } + _pooledCount++; + _owningObject.Target = null; // NOTE: doing this and checking for InternalError.PooledObjectHasOwner degrades the close by 2% + } + + internal void PostPop(object newOwner) + { + // Called by DbConnectionPool right after it pulls this from it's pool, we + // take this opportunity to ensure ownership and pool counts are legit. + + Debug.Assert(!IsEmancipated, "pooled object not in pool"); + + // When another thread is clearing this pool, it + // will doom all connections in this pool without prejudice which + // causes the following assert to fire, which really mucks up stress + // against checked bits. The assert is benign, so we're commenting + // it out. + //Debug.Assert(CanBePooled, "pooled object is not poolable"); + + // IMPORTANT NOTE: You must have taken a lock on the object before + // you call this method to prevent race conditions with Clear and + // ReclaimEmancipatedObjects. + + if (null != _owningObject.Target) + { + throw ADP.InternalError(ADP.InternalErrorCode.PooledObjectHasOwner); // pooled connection already has an owner! + } + _owningObject.Target = newOwner; + _pooledCount--; + //3 // The following tests are retail assertions of things we can't allow to happen. + if (null != Pool) + { + if (0 != _pooledCount) + { + throw ADP.InternalError(ADP.InternalErrorCode.PooledObjectInPoolMoreThanOnce); // popping object off stack with multiple pooledCount + } + } + else if (-1 != _pooledCount) + { + throw ADP.InternalError(ADP.InternalErrorCode.NonPooledObjectUsedMoreThanOnce); // popping object off stack with multiple pooledCount + } + } + + internal void RemoveWeakReference(object value) + { + DbReferenceCollection referenceCollection = ReferenceCollection; + if (null != referenceCollection) + { + referenceCollection.Remove(value); + } + } + + /// + /// When overridden in a derived class, will check if the underlying connection is still actually alive + /// + /// If true an exception will be thrown if the connection is dead instead of returning true\false + /// (this allows the caller to have the real reason that the connection is not alive (e.g. network error, etc)) + /// True if the connection is still alive, otherwise false (If not overridden, then always true) + internal virtual bool IsConnectionAlive(bool throwOnException = false) + { + return true; + } + } +} diff --git a/src/System.Data.SqlClient/src/System/Data/ProviderBase/DbConnectionPoolGroup.cs b/src/Common/src/System/Data/ProviderBase/DbConnectionPoolGroup.cs similarity index 94% rename from src/System.Data.SqlClient/src/System/Data/ProviderBase/DbConnectionPoolGroup.cs rename to src/Common/src/System/Data/ProviderBase/DbConnectionPoolGroup.cs index b28ade14d237..f32c4d0d99da 100644 --- a/src/System.Data.SqlClient/src/System/Data/ProviderBase/DbConnectionPoolGroup.cs +++ b/src/Common/src/System/Data/ProviderBase/DbConnectionPoolGroup.cs @@ -3,8 +3,6 @@ // See the LICENSE file in the project root for more information. -//------------------------------------------------------------------------------ - using System.Collections.Concurrent; using System.Data.Common; using System.Diagnostics; @@ -62,21 +60,9 @@ internal DbConnectionPoolGroup(DbConnectionOptions connectionOptions, DbConnecti _state = PoolGroupStateActive; } - internal DbConnectionOptions ConnectionOptions - { - get - { - return _connectionOptions; - } - } + internal DbConnectionOptions ConnectionOptions => _connectionOptions; - internal DbConnectionPoolKey PoolKey - { - get - { - return _poolKey; - } - } + internal DbConnectionPoolKey PoolKey => _poolKey; internal DbConnectionPoolGroupProviderInfo ProviderInfo { @@ -94,22 +80,9 @@ internal DbConnectionPoolGroupProviderInfo ProviderInfo } } - internal bool IsDisabled - { - get - { - return (PoolGroupStateDisabled == _state); - } - } - + internal bool IsDisabled => (PoolGroupStateDisabled == _state); - internal DbConnectionPoolGroupOptions PoolGroupOptions - { - get - { - return _poolGroupOptions; - } - } + internal DbConnectionPoolGroupOptions PoolGroupOptions => _poolGroupOptions; internal DbMetaDataFactory MetaDataFactory { @@ -190,7 +163,7 @@ internal DbConnectionPool GetConnectionPool(DbConnectionFactory connectionFactor if (null != currentIdentity) { if (!_poolCollection.TryGetValue(currentIdentity, out pool)) // find the pool - { + { lock (this) { // Did someone already add it to the list? diff --git a/src/System.Data.SqlClient/src/System/Data/ProviderBase/DbMetaDataFactory.cs b/src/Common/src/System/Data/ProviderBase/DbMetaDataFactory.cs similarity index 96% rename from src/System.Data.SqlClient/src/System/Data/ProviderBase/DbMetaDataFactory.cs rename to src/Common/src/System/Data/ProviderBase/DbMetaDataFactory.cs index 3da59e4470c2..8233dc984902 100644 --- a/src/System.Data.SqlClient/src/System/Data/ProviderBase/DbMetaDataFactory.cs +++ b/src/Common/src/System/Data/ProviderBase/DbMetaDataFactory.cs @@ -46,29 +46,11 @@ public DbMetaDataFactory(Stream xmlStream, string serverVersion, string normaliz _normalizedServerVersion = normalizedServerVersion; } - protected DataSet CollectionDataSet - { - get - { - return _metaDataCollectionsDataSet; - } - } + protected DataSet CollectionDataSet => _metaDataCollectionsDataSet; - protected string ServerVersion - { - get - { - return _serverVersionString; - } - } + protected string ServerVersion => _serverVersionString; - protected string ServerVersionNormalized - { - get - { - return _normalizedServerVersion; - } - } + protected string ServerVersionNormalized => _normalizedServerVersion; protected DataTable CloneAndFilterCollection(string collectionName, string[] hiddenColumnNames) { @@ -109,10 +91,7 @@ protected DataTable CloneAndFilterCollection(string collectionName, string[] hid return destinationTable; } - public void Dispose() - { - Dispose(true); - } + public void Dispose() => Dispose(true); protected virtual void Dispose(bool disposing) { @@ -216,11 +195,6 @@ private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restri private DataColumn[] FilterColumns(DataTable sourceTable, string[] hiddenColumnNames, DataColumnCollection destinationColumns) { - - DataColumn newDestinationColumn; - int currentColumn; - DataColumn[] filteredSourceColumns = null; - int columnCount = 0; foreach (DataColumn sourceColumn in sourceTable.Columns) { @@ -235,14 +209,14 @@ private DataColumn[] FilterColumns(DataTable sourceTable, string[] hiddenColumnN throw ADP.NoColumns(); } - currentColumn = 0; - filteredSourceColumns = new DataColumn[columnCount]; + int currentColumn = 0; + DataColumn[] filteredSourceColumns = new DataColumn[columnCount]; foreach (DataColumn sourceColumn in sourceTable.Columns) { if (IncludeThisColumn(sourceColumn, hiddenColumnNames) == true) { - newDestinationColumn = new DataColumn(sourceColumn.ColumnName, sourceColumn.DataType); + DataColumn newDestinationColumn = new DataColumn(sourceColumn.ColumnName, sourceColumn.DataType); destinationColumns.Add(newDestinationColumn); filteredSourceColumns[currentColumn] = sourceColumn; currentColumn++; @@ -375,7 +349,7 @@ private string GetParameterName(string neededCollectionName, int neededRestricti DataColumn parameterName = null; DataColumn restrictionName = null; DataColumn restrictionNumber = null; - ; + string result = null; restrictionsTable = _metaDataCollectionsDataSet.Tables[DbMetaDataCollectionNames.Restrictions]; @@ -587,6 +561,3 @@ private bool SupportedByCurrentVersion(DataRow requestedCollectionRow) } } } - - - diff --git a/src/System.Data.SqlClient/src/System/Data/ProviderBase/DbReferenceCollection.cs b/src/Common/src/System/Data/ProviderBase/DbReferenceCollection.cs similarity index 100% rename from src/System.Data.SqlClient/src/System/Data/ProviderBase/DbReferenceCollection.cs rename to src/Common/src/System/Data/ProviderBase/DbReferenceCollection.cs diff --git a/src/System.Data.Odbc/src/Common/System/Data/ProviderBase/TimeoutTimer.cs b/src/Common/src/System/Data/ProviderBase/TimeoutTimer.cs similarity index 100% rename from src/System.Data.Odbc/src/Common/System/Data/ProviderBase/TimeoutTimer.cs rename to src/Common/src/System/Data/ProviderBase/TimeoutTimer.cs diff --git a/src/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbConnectionClosed.cs b/src/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbConnectionClosed.cs index 635d3d6b78f6..619341d2bf5a 100644 --- a/src/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbConnectionClosed.cs +++ b/src/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbConnectionClosed.cs @@ -3,165 +3,13 @@ // See the LICENSE file in the project root for more information. using System.Data.Common; -using System.Diagnostics; -using System.Threading.Tasks; namespace System.Data.ProviderBase { - internal abstract class DbConnectionClosed : DbConnectionInternal + internal abstract partial class DbConnectionClosed : DbConnectionInternal { - // Construct an "empty" connection - protected DbConnectionClosed(ConnectionState state, bool hidePassword, bool allowSetConnectionString) : base(state, hidePassword, allowSetConnectionString) - { - } + protected override void Activate() => throw ADP.ClosedConnectionError(); - public override string ServerVersion - { - get - { - throw ADP.ClosedConnectionError(); - } - } - - protected override void Activate() - { - throw ADP.ClosedConnectionError(); - } - - public override DbTransaction BeginTransaction(IsolationLevel il) - { - throw ADP.ClosedConnectionError(); - } - - public override void ChangeDatabase(string database) - { - throw ADP.ClosedConnectionError(); - } - - internal override void CloseConnection(DbConnection owningObject, DbConnectionFactory connectionFactory) - { - // not much to do here... - } - - protected override void Deactivate() - { - throw ADP.ClosedConnectionError(); - } - - - protected override DbReferenceCollection CreateReferenceCollection() - { - throw ADP.ClosedConnectionError(); - } - - internal override bool TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) - { - return base.TryOpenConnectionInternal(outerConnection, connectionFactory, retry, userOptions); - } - } - - internal abstract class DbConnectionBusy : DbConnectionClosed - { - protected DbConnectionBusy(ConnectionState state) : base(state, true, false) - { - } - - internal override bool TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) - { - throw ADP.ConnectionAlreadyOpen(State); - } - } - - internal sealed class DbConnectionClosedBusy : DbConnectionBusy - { - // Closed Connection, Currently Busy - changing connection string - internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionClosedBusy(); // singleton object - - private DbConnectionClosedBusy() : base(ConnectionState.Closed) - { - } - } - - internal sealed class DbConnectionOpenBusy : DbConnectionBusy - { - // Open Connection, Currently Busy - closing connection - internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionOpenBusy(); // singleton object - - private DbConnectionOpenBusy() : base(ConnectionState.Open) - { - } - } - - internal sealed class DbConnectionClosedConnecting : DbConnectionBusy - { - // Closed Connection, Currently Connecting - - internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionClosedConnecting(); // singleton object - - private DbConnectionClosedConnecting() : base(ConnectionState.Connecting) - { - } - - internal override void CloseConnection(DbConnection owningObject, DbConnectionFactory connectionFactory) - { - connectionFactory.SetInnerConnectionTo(owningObject, DbConnectionClosedPreviouslyOpened.SingletonInstance); - } - - internal override bool TryReplaceConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) - { - return TryOpenConnection(outerConnection, connectionFactory, retry, userOptions); - } - - internal override bool TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) - { - if (retry == null || !retry.Task.IsCompleted) - { - // retry is null if this is a synchronous call - - // if someone calls Open or OpenAsync while in this state, - // then the retry task will not be completed - - throw ADP.ConnectionAlreadyOpen(State); - } - - // we are completing an asynchronous open - Debug.Assert(retry.Task.Status == TaskStatus.RanToCompletion, "retry task must be completed successfully"); - DbConnectionInternal openConnection = retry.Task.Result; - if (null == openConnection) - { - connectionFactory.SetInnerConnectionTo(outerConnection, this); - throw ADP.InternalConnectionError(ADP.ConnectionError.GetConnectionReturnsNull); - } - connectionFactory.SetInnerConnectionEvent(outerConnection, openConnection); - - return true; - } } - internal sealed class DbConnectionClosedNeverOpened : DbConnectionClosed - { - // Closed Connection, Has Never Been Opened - - internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionClosedNeverOpened(); // singleton object - - private DbConnectionClosedNeverOpened() : base(ConnectionState.Closed, false, true) - { - } - } - - internal sealed class DbConnectionClosedPreviouslyOpened : DbConnectionClosed - { - // Closed Connection, Has Previously Been Opened - - internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionClosedPreviouslyOpened(); // singleton object - - private DbConnectionClosedPreviouslyOpened() : base(ConnectionState.Closed, true, true) - { - } - - internal override bool TryReplaceConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) - { - return TryOpenConnection(outerConnection, connectionFactory, retry, userOptions); - } - } } diff --git a/src/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbConnectionFactory.cs b/src/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbConnectionFactory.cs index a41ce8f07f2f..d7d43f4c554f 100644 --- a/src/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbConnectionFactory.cs +++ b/src/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbConnectionFactory.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Generic; using System.Diagnostics; using System.Data.Common; using System.Threading; @@ -10,144 +9,8 @@ namespace System.Data.ProviderBase { - internal abstract class DbConnectionFactory + internal abstract partial class DbConnectionFactory { - private Dictionary _connectionPoolGroups; - private readonly List _poolsToRelease; - private readonly List _poolGroupsToRelease; - private readonly Timer _pruningTimer; - - private const int PruningDueTime = 4 * 60 * 1000; // 4 minutes - private const int PruningPeriod = 30 * 1000; // thirty seconds - - - // s_pendingOpenNonPooled is an array of tasks used to throttle creation of non-pooled connections to - // a maximum of Environment.ProcessorCount at a time. - private static uint s_pendingOpenNonPooledNext = 0; - private static Task[] s_pendingOpenNonPooled = new Task[Environment.ProcessorCount]; - private static Task s_completedTask; - - protected DbConnectionFactory() - { - _connectionPoolGroups = new Dictionary(); - _poolsToRelease = new List(); - _poolGroupsToRelease = new List(); - _pruningTimer = CreatePruningTimer(); - } - - - public abstract DbProviderFactory ProviderFactory - { - get; - } - - - public void ClearAllPools() - { - Dictionary connectionPoolGroups = _connectionPoolGroups; - foreach (KeyValuePair entry in connectionPoolGroups) - { - DbConnectionPoolGroup poolGroup = entry.Value; - if (null != poolGroup) - { - poolGroup.Clear(); - } - } - } - - public void ClearPool(DbConnection connection) - { - ADP.CheckArgumentNull(connection, nameof(connection)); - - DbConnectionPoolGroup poolGroup = GetConnectionPoolGroup(connection); - if (null != poolGroup) - { - poolGroup.Clear(); - } - } - - public void ClearPool(DbConnectionPoolKey key) - { - Debug.Assert(key != null, "key cannot be null"); - ADP.CheckArgumentNull(key.ConnectionString, nameof(key) + "." + nameof(key.ConnectionString)); - - DbConnectionPoolGroup poolGroup; - Dictionary connectionPoolGroups = _connectionPoolGroups; - if (connectionPoolGroups.TryGetValue(key, out poolGroup)) - { - poolGroup.Clear(); - } - } - - internal virtual DbConnectionPoolProviderInfo CreateConnectionPoolProviderInfo(DbConnectionOptions connectionOptions) - { - return null; - } - - - internal DbConnectionInternal CreateNonPooledConnection(DbConnection owningConnection, DbConnectionPoolGroup poolGroup, DbConnectionOptions userOptions) - { - Debug.Assert(null != owningConnection, "null owningConnection?"); - Debug.Assert(null != poolGroup, "null poolGroup?"); - - DbConnectionOptions connectionOptions = poolGroup.ConnectionOptions; - DbConnectionPoolGroupProviderInfo poolGroupProviderInfo = poolGroup.ProviderInfo; - DbConnectionPoolKey poolKey = poolGroup.PoolKey; - - DbConnectionInternal newConnection = CreateConnection(connectionOptions, poolKey, poolGroupProviderInfo, null, owningConnection, userOptions); - if (null != newConnection) - { - newConnection.MakeNonPooledObject(owningConnection); - } - return newConnection; - } - - internal DbConnectionInternal CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions) - { - Debug.Assert(null != pool, "null pool?"); - DbConnectionPoolGroupProviderInfo poolGroupProviderInfo = pool.PoolGroup.ProviderInfo; - - DbConnectionInternal newConnection = CreateConnection(options, poolKey, poolGroupProviderInfo, pool, owningObject, userOptions); - if (null != newConnection) - { - newConnection.MakePooledConnection(pool); - } - return newConnection; - } - - internal virtual DbConnectionPoolGroupProviderInfo CreateConnectionPoolGroupProviderInfo(DbConnectionOptions connectionOptions) - { - return null; - } - - private Timer CreatePruningTimer() - => ADP.UnsafeCreateTimer( - new TimerCallback(PruneConnectionPoolGroups), - null, - PruningDueTime, - PruningPeriod); - - protected DbConnectionOptions FindConnectionOptions(DbConnectionPoolKey key) - { - Debug.Assert(key != null, "key cannot be null"); - if (!string.IsNullOrEmpty(key.ConnectionString)) - { - DbConnectionPoolGroup connectionPoolGroup; - Dictionary connectionPoolGroups = _connectionPoolGroups; - if (connectionPoolGroups.TryGetValue(key, out connectionPoolGroup)) - { - return connectionPoolGroup.ConnectionOptions; - } - } - return null; - } - - private static Task GetCompletedTask() - { - Debug.Assert(Monitor.IsEntered(s_pendingOpenNonPooled), $"Expected {nameof(s_pendingOpenNonPooled)} lock to be held."); - return s_completedTask ?? (s_completedTask = Task.FromResult(null)); - } - internal bool TryGetConnection(DbConnection owningConnection, TaskCompletionSource retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, out DbConnectionInternal connection) { Debug.Assert(null != owningConnection, "null owningConnection?"); @@ -310,254 +173,6 @@ internal bool TryGetConnection(DbConnection owningConnection, TaskCompletionSour return true; } - - private DbConnectionPool GetConnectionPool(DbConnection owningObject, DbConnectionPoolGroup connectionPoolGroup) - { - // if poolgroup is disabled, it will be replaced with a new entry - - Debug.Assert(null != owningObject, "null owningObject?"); - Debug.Assert(null != connectionPoolGroup, "null connectionPoolGroup?"); - - // It is possible that while the outer connection object has - // been sitting around in a closed and unused state in some long - // running app, the pruner may have come along and remove this - // the pool entry from the master list. If we were to use a - // pool entry in this state, we would create "unmanaged" pools, - // which would be bad. To avoid this problem, we automagically - // re-create the pool entry whenever it's disabled. - - // however, don't rebuild connectionOptions if no pooling is involved - let new connections do that work - if (connectionPoolGroup.IsDisabled && (null != connectionPoolGroup.PoolGroupOptions)) - { - // reusing existing pool option in case user originally used SetConnectionPoolOptions - DbConnectionPoolGroupOptions poolOptions = connectionPoolGroup.PoolGroupOptions; - - // get the string to hash on again - DbConnectionOptions connectionOptions = connectionPoolGroup.ConnectionOptions; - Debug.Assert(null != connectionOptions, "prevent expansion of connectionString"); - - connectionPoolGroup = GetConnectionPoolGroup(connectionPoolGroup.PoolKey, poolOptions, ref connectionOptions); - Debug.Assert(null != connectionPoolGroup, "null connectionPoolGroup?"); - SetConnectionPoolGroup(owningObject, connectionPoolGroup); - } - DbConnectionPool connectionPool = connectionPoolGroup.GetConnectionPool(this); - return connectionPool; - } - - internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnectionPoolKey key, DbConnectionPoolGroupOptions poolOptions, ref DbConnectionOptions userConnectionOptions) - { - if (string.IsNullOrEmpty(key.ConnectionString)) - { - return (DbConnectionPoolGroup)null; - } - - DbConnectionPoolGroup connectionPoolGroup; - Dictionary connectionPoolGroups = _connectionPoolGroups; - if (!connectionPoolGroups.TryGetValue(key, out connectionPoolGroup) || (connectionPoolGroup.IsDisabled && (null != connectionPoolGroup.PoolGroupOptions))) - { - // If we can't find an entry for the connection string in - // our collection of pool entries, then we need to create a - // new pool entry and add it to our collection. - - DbConnectionOptions connectionOptions = CreateConnectionOptions(key.ConnectionString, userConnectionOptions); - if (null == connectionOptions) - { - throw ADP.InternalConnectionError(ADP.ConnectionError.ConnectionOptionsMissing); - } - - if (null == userConnectionOptions) - { // we only allow one expansion on the connection string - userConnectionOptions = connectionOptions; - } - - // We don't support connection pooling on Win9x - if (null == poolOptions) - { - if (null != connectionPoolGroup) - { - // reusing existing pool option in case user originally used SetConnectionPoolOptions - poolOptions = connectionPoolGroup.PoolGroupOptions; - } - else - { - // Note: may return null for non-pooled connections - poolOptions = CreateConnectionPoolGroupOptions(connectionOptions); - } - } - - - DbConnectionPoolGroup newConnectionPoolGroup = new DbConnectionPoolGroup(connectionOptions, key, poolOptions); - newConnectionPoolGroup.ProviderInfo = CreateConnectionPoolGroupProviderInfo(connectionOptions); - - lock (this) - { - connectionPoolGroups = _connectionPoolGroups; - if (!connectionPoolGroups.TryGetValue(key, out connectionPoolGroup)) - { - // build new dictionary with space for new connection string - Dictionary newConnectionPoolGroups = new Dictionary(1 + connectionPoolGroups.Count); - foreach (KeyValuePair entry in connectionPoolGroups) - { - newConnectionPoolGroups.Add(entry.Key, entry.Value); - } - - // lock prevents race condition with PruneConnectionPoolGroups - newConnectionPoolGroups.Add(key, newConnectionPoolGroup); - connectionPoolGroup = newConnectionPoolGroup; - _connectionPoolGroups = newConnectionPoolGroups; - } - else - { - Debug.Assert(!connectionPoolGroup.IsDisabled, "Disabled pool entry discovered"); - } - } - Debug.Assert(null != connectionPoolGroup, "how did we not create a pool entry?"); - Debug.Assert(null != userConnectionOptions, "how did we not have user connection options?"); - } - else if (null == userConnectionOptions) - { - userConnectionOptions = connectionPoolGroup.ConnectionOptions; - } - return connectionPoolGroup; - } - - - private void PruneConnectionPoolGroups(object state) - { - // First, walk the pool release list and attempt to clear each - // pool, when the pool is finally empty, we dispose of it. If the - // pool isn't empty, it's because there are active connections or - // distributed transactions that need it. - lock (_poolsToRelease) - { - if (0 != _poolsToRelease.Count) - { - DbConnectionPool[] poolsToRelease = _poolsToRelease.ToArray(); - foreach (DbConnectionPool pool in poolsToRelease) - { - if (null != pool) - { - pool.Clear(); - - if (0 == pool.Count) - { - _poolsToRelease.Remove(pool); - } - } - } - } - } - - // Next, walk the pool entry release list and dispose of each - // pool entry when it is finally empty. If the pool entry isn't - // empty, it's because there are active pools that need it. - lock (_poolGroupsToRelease) - { - if (0 != _poolGroupsToRelease.Count) - { - DbConnectionPoolGroup[] poolGroupsToRelease = _poolGroupsToRelease.ToArray(); - foreach (DbConnectionPoolGroup poolGroup in poolGroupsToRelease) - { - if (null != poolGroup) - { - int poolsLeft = poolGroup.Clear(); // may add entries to _poolsToRelease - - if (0 == poolsLeft) - { - _poolGroupsToRelease.Remove(poolGroup); - } - } - } - } - } - - // Finally, we walk through the collection of connection pool entries - // and prune each one. This will cause any empty pools to be put - // into the release list. - lock (this) - { - Dictionary connectionPoolGroups = _connectionPoolGroups; - Dictionary newConnectionPoolGroups = new Dictionary(connectionPoolGroups.Count); - - foreach (KeyValuePair entry in connectionPoolGroups) - { - if (null != entry.Value) - { - Debug.Assert(!entry.Value.IsDisabled, "Disabled pool entry discovered"); - - // entries start active and go idle during prune if all pools are gone - // move idle entries from last prune pass to a queue for pending release - // otherwise process entry which may move it from active to idle - if (entry.Value.Prune()) - { // may add entries to _poolsToRelease - QueuePoolGroupForRelease(entry.Value); - } - else - { - newConnectionPoolGroups.Add(entry.Key, entry.Value); - } - } - } - _connectionPoolGroups = newConnectionPoolGroups; - } - } - - internal void QueuePoolForRelease(DbConnectionPool pool, bool clearing) - { - // Queue the pool up for release -- we'll clear it out and dispose - // of it as the last part of the pruning timer callback so we don't - // do it with the pool entry or the pool collection locked. - Debug.Assert(null != pool, "null pool?"); - - // set the pool to the shutdown state to force all active - // connections to be automatically disposed when they - // are returned to the pool - pool.Shutdown(); - - lock (_poolsToRelease) - { - if (clearing) - { - pool.Clear(); - } - _poolsToRelease.Add(pool); - } - } - - internal void QueuePoolGroupForRelease(DbConnectionPoolGroup poolGroup) - { - Debug.Assert(null != poolGroup, "null poolGroup?"); - - lock (_poolGroupsToRelease) - { - _poolGroupsToRelease.Add(poolGroup); - } - } - - protected virtual DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) - { - return CreateConnection(options, poolKey, poolGroupProviderInfo, pool, owningConnection); - } - - protected abstract DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection); - - protected abstract DbConnectionOptions CreateConnectionOptions(string connectionString, DbConnectionOptions previous); - - protected abstract DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions(DbConnectionOptions options); - - internal abstract DbConnectionPoolGroup GetConnectionPoolGroup(DbConnection connection); - - internal abstract DbConnectionInternal GetInnerConnection(DbConnection connection); - - - internal abstract void PermissionDemand(DbConnection outerConnection); - - internal abstract void SetConnectionPoolGroup(DbConnection outerConnection, DbConnectionPoolGroup poolGroup); - - internal abstract void SetInnerConnectionEvent(DbConnection owningObject, DbConnectionInternal to); - - internal abstract bool SetInnerConnectionFrom(DbConnection owningObject, DbConnectionInternal to, DbConnectionInternal from); - - internal abstract void SetInnerConnectionTo(DbConnection owningObject, DbConnectionInternal to); + } } diff --git a/src/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbConnectionInternal.cs b/src/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbConnectionInternal.cs index d58b31ba40d2..9fd4a2ebcee8 100644 --- a/src/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbConnectionInternal.cs +++ b/src/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbConnectionInternal.cs @@ -9,168 +9,9 @@ namespace System.Data.ProviderBase { - internal abstract class DbConnectionInternal + internal abstract partial class DbConnectionInternal { - internal static readonly StateChangeEventArgs StateChangeClosed = new StateChangeEventArgs(ConnectionState.Open, ConnectionState.Closed); - internal static readonly StateChangeEventArgs StateChangeOpen = new StateChangeEventArgs(ConnectionState.Closed, ConnectionState.Open); - - private readonly bool _allowSetConnectionString; - private readonly bool _hidePassword; - private readonly ConnectionState _state; - - private readonly WeakReference _owningObject = new WeakReference(null, false); // [usage must be thread safe] the owning object, when not in the pool. (both Pooled and Non-Pooled connections) - - private DbConnectionPool _connectionPool; // the pooler that the connection came from (Pooled connections only) - private DbReferenceCollection _referenceCollection; // collection of objects that we need to notify in some way when we're being deactivated - private int _pooledCount; // [usage must be thread safe] the number of times this object has been pushed into the pool less the number of times it's been popped (0 != inPool) - - private bool _connectionIsDoomed; // true when the connection should no longer be used. - private bool _cannotBePooled; // true when the connection should no longer be pooled. - - private DateTime _createTime; // when the connection was created. - - -#if DEBUG - private int _activateCount; // debug only counter to verify activate/deactivates are in sync. -#endif //DEBUG - - protected DbConnectionInternal() : this(ConnectionState.Open, true, false) - { - } - - // Constructor for internal connections - internal DbConnectionInternal(ConnectionState state, bool hidePassword, bool allowSetConnectionString) - { - _allowSetConnectionString = allowSetConnectionString; - _hidePassword = hidePassword; - _state = state; - } - - internal bool AllowSetConnectionString - { - get - { - return _allowSetConnectionString; - } - } - - internal bool CanBePooled - { - get - { - bool flag = (!_connectionIsDoomed && !_cannotBePooled && !_owningObject.IsAlive); - return flag; - } - } - - - protected internal bool IsConnectionDoomed - { - get - { - return _connectionIsDoomed; - } - } - - internal bool IsEmancipated - { - get - { - // NOTE: There are race conditions between PrePush, PostPop and this - // property getter -- only use this while this object is locked; - // (DbConnectionPool.Clear and ReclaimEmancipatedObjects - // do this for us) - - // The functionality is as follows: - // - // _pooledCount is incremented when the connection is pushed into the pool - // _pooledCount is decremented when the connection is popped from the pool - // _pooledCount is set to -1 when the connection is not pooled (just in case...) - // - // That means that: - // - // _pooledCount > 1 connection is in the pool multiple times (This should not happen) - // _pooledCount == 1 connection is in the pool - // _pooledCount == 0 connection is out of the pool - // _pooledCount == -1 connection is not a pooled connection; we shouldn't be here for non-pooled connections. - // _pooledCount < -1 connection out of the pool multiple times - // - // Now, our job is to return TRUE when the connection is out - // of the pool and it's owning object is no longer around to - // return it. - - bool value = (_pooledCount < 1) && !_owningObject.IsAlive; - return value; - } - } - - internal bool IsInPool - { - get - { - Debug.Assert(_pooledCount <= 1 && _pooledCount >= -1, "Pooled count for object is invalid"); - return (_pooledCount == 1); - } - } - - - protected internal object Owner - { - // We use a weak reference to the owning object so we can identify when - // it has been garbage collected without thowing exceptions. - get - { - return _owningObject.Target; - } - } - - internal DbConnectionPool Pool - { - get - { - return _connectionPool; - } - } - - - protected internal DbReferenceCollection ReferenceCollection - { - get - { - return _referenceCollection; - } - } - - public abstract string ServerVersion - { - get; - } - - // this should be abstract but until it is added to all the providers virtual will have to do - public virtual string ServerVersionNormalized - { - get - { - throw ADP.NotSupported(); - } - } - - public bool ShouldHidePassword - { - get - { - return _hidePassword; - } - } - - public ConnectionState State - { - get - { - return _state; - } - } - + protected abstract void Activate(); internal void ActivateConnection() @@ -187,26 +28,6 @@ internal void ActivateConnection() Activate(); } - internal void AddWeakReference(object value, int tag) - { - if (null == _referenceCollection) - { - _referenceCollection = CreateReferenceCollection(); - if (null == _referenceCollection) - { - throw ADP.InternalError(ADP.InternalErrorCode.CreateReferenceCollectionReturnedNull); - } - } - _referenceCollection.Add(value, tag); - } - - public abstract DbTransaction BeginTransaction(IsolationLevel il); - - public virtual void ChangeDatabase(string value) - { - throw ADP.MethodNotImplemented(); - } - internal virtual void CloseConnection(DbConnection owningObject, DbConnectionFactory connectionFactory) { // The implementation here is the implementation required for the @@ -304,245 +125,10 @@ internal virtual void CloseConnection(DbConnection owningObject, DbConnectionFac } } - internal virtual void PrepareForReplaceConnection() - { - // By default, there is no preparation required - } - - protected virtual void PrepareForCloseConnection() - { - // By default, there is no preparation required - } - - protected virtual object ObtainAdditionalLocksForClose() - { - return null; // no additional locks in default implementation - } - - protected virtual void ReleaseAdditionalLocksForClose(object lockToken) - { - // no additional locks in default implementation - } - - protected virtual DbReferenceCollection CreateReferenceCollection() - { - throw ADP.InternalError(ADP.InternalErrorCode.AttemptingToConstructReferenceCollectionOnStaticObject); - } - - protected abstract void Deactivate(); - - internal void DeactivateConnection() - { - // Internal method called from the connection pooler so we don't expose - // the Deactivate method publicly. - -#if DEBUG - int activateCount = Interlocked.Decrement(ref _activateCount); - Debug.Assert(0 == activateCount, "activated multiple times?"); -#endif // DEBUG - - - if (!_connectionIsDoomed && Pool.UseLoadBalancing) - { - // If we're not already doomed, check the connection's lifetime and - // doom it if it's lifetime has elapsed. - - DateTime now = DateTime.UtcNow; - if ((now.Ticks - _createTime.Ticks) > Pool.LoadBalanceTimeout.Ticks) - { - DoNotPoolThisConnection(); - } - } - Deactivate(); - } - - public virtual void Dispose() { _connectionPool = null; _connectionIsDoomed = true; } - - protected internal void DoNotPoolThisConnection() - { - _cannotBePooled = true; - } - - /// Ensure that this connection cannot be put back into the pool. - protected internal void DoomThisConnection() - { - _connectionIsDoomed = true; - } - - - internal void MakeNonPooledObject(object owningObject) - { - // Used by DbConnectionFactory to indicate that this object IS NOT part of - // a connection pool. - - _connectionPool = null; - _owningObject.Target = owningObject; - _pooledCount = -1; - } - - internal void MakePooledConnection(DbConnectionPool connectionPool) - { - // Used by DbConnectionFactory to indicate that this object IS part of - // a connection pool. - _createTime = DateTime.UtcNow; - - _connectionPool = connectionPool; - } - - internal void NotifyWeakReference(int message) - { - DbReferenceCollection referenceCollection = ReferenceCollection; - if (null != referenceCollection) - { - referenceCollection.Notify(message); - } - } - - internal virtual void OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory) - { - if (!TryOpenConnection(outerConnection, connectionFactory, null, null)) - { - throw ADP.InternalError(ADP.InternalErrorCode.SynchronousConnectReturnedPending); - } - } - - /// The default implementation is for the open connection objects, and - /// it simply throws. Our private closed-state connection objects - /// override this and do the correct thing. - // User code should either override DbConnectionInternal.Activate when it comes out of the pool - // or override DbConnectionFactory.CreateConnection when the connection is created for non-pooled connections - internal virtual bool TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) - { - throw ADP.ConnectionAlreadyOpen(State); - } - - internal virtual bool TryReplaceConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) - { - throw ADP.MethodNotImplemented(); - } - - protected bool TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) - { - // ?->Connecting: prevent set_ConnectionString during Open - if (connectionFactory.SetInnerConnectionFrom(outerConnection, DbConnectionClosedConnecting.SingletonInstance, this)) - { - DbConnectionInternal openConnection = null; - try - { - connectionFactory.PermissionDemand(outerConnection); - if (!connectionFactory.TryGetConnection(outerConnection, retry, userOptions, this, out openConnection)) - { - return false; - } - } - catch - { - // This should occur for all exceptions, even ADP.UnCatchableExceptions. - connectionFactory.SetInnerConnectionTo(outerConnection, this); - throw; - } - if (null == openConnection) - { - connectionFactory.SetInnerConnectionTo(outerConnection, this); - throw ADP.InternalConnectionError(ADP.ConnectionError.GetConnectionReturnsNull); - } - connectionFactory.SetInnerConnectionEvent(outerConnection, openConnection); - } - - return true; - } - - internal void PrePush(object expectedOwner) - { - // Called by DbConnectionPool when we're about to be put into it's pool, we - // take this opportunity to ensure ownership and pool counts are legit. - - // IMPORTANT NOTE: You must have taken a lock on the object before - // you call this method to prevent race conditions with Clear and - // ReclaimEmancipatedObjects. - - //3 // The following tests are retail assertions of things we can't allow to happen. - if (null == expectedOwner) - { - if (null != _owningObject.Target) - { - throw ADP.InternalError(ADP.InternalErrorCode.UnpooledObjectHasOwner); // new unpooled object has an owner - } - } - else if (_owningObject.Target != expectedOwner) - { - throw ADP.InternalError(ADP.InternalErrorCode.UnpooledObjectHasWrongOwner); // unpooled object has incorrect owner - } - if (0 != _pooledCount) - { - throw ADP.InternalError(ADP.InternalErrorCode.PushingObjectSecondTime); // pushing object onto stack a second time - } - _pooledCount++; - _owningObject.Target = null; // NOTE: doing this and checking for InternalError.PooledObjectHasOwner degrades the close by 2% - } - - internal void PostPop(object newOwner) - { - // Called by DbConnectionPool right after it pulls this from it's pool, we - // take this opportunity to ensure ownership and pool counts are legit. - - Debug.Assert(!IsEmancipated, "pooled object not in pool"); - - // When another thread is clearing this pool, it - // will doom all connections in this pool without prejudice which - // causes the following assert to fire, which really mucks up stress - // against checked bits. The assert is benign, so we're commenting - // it out. - //Debug.Assert(CanBePooled, "pooled object is not poolable"); - - // IMPORTANT NOTE: You must have taken a lock on the object before - // you call this method to prevent race conditions with Clear and - // ReclaimEmancipatedObjects. - - if (null != _owningObject.Target) - { - throw ADP.InternalError(ADP.InternalErrorCode.PooledObjectHasOwner); // pooled connection already has an owner! - } - _owningObject.Target = newOwner; - _pooledCount--; - //3 // The following tests are retail assertions of things we can't allow to happen. - if (null != Pool) - { - if (0 != _pooledCount) - { - throw ADP.InternalError(ADP.InternalErrorCode.PooledObjectInPoolMoreThanOnce); // popping object off stack with multiple pooledCount - } - } - else if (-1 != _pooledCount) - { - throw ADP.InternalError(ADP.InternalErrorCode.NonPooledObjectUsedMoreThanOnce); // popping object off stack with multiple pooledCount - } - } - - internal void RemoveWeakReference(object value) - { - DbReferenceCollection referenceCollection = ReferenceCollection; - if (null != referenceCollection) - { - referenceCollection.Remove(value); - } - } - - - /// - /// When overridden in a derived class, will check if the underlying connection is still actually alive - /// - /// If true an exception will be thrown if the connection is dead instead of returning true\false - /// (this allows the caller to have the real reason that the connection is not alive (e.g. network error, etc)) - /// True if the connection is still alive, otherwise false (If not overridden, then always true) - internal virtual bool IsConnectionAlive(bool throwOnException = false) - { - return true; - } } } diff --git a/src/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbConnectionPoolGroup.cs b/src/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbConnectionPoolGroup.cs deleted file mode 100644 index 9e0a10c200df..000000000000 --- a/src/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbConnectionPoolGroup.cs +++ /dev/null @@ -1,302 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Concurrent; -using System.Data.Common; -using System.Diagnostics; - -namespace System.Data.ProviderBase -{ - // set_ConnectionString calls DbConnectionFactory.GetConnectionPoolGroup - // when not found a new pool entry is created and potentially added - // DbConnectionPoolGroup starts in the Active state - - // Open calls DbConnectionFactory.GetConnectionPool - // if the existing pool entry is Disabled, GetConnectionPoolGroup is called for a new entry - // DbConnectionFactory.GetConnectionPool calls DbConnectionPoolGroup.GetConnectionPool - - // DbConnectionPoolGroup.GetConnectionPool will return pool for the current identity - // or null if identity is restricted or pooling is disabled or state is disabled at time of add - // state changes are Active->Active, Idle->Active - - // DbConnectionFactory.PruneConnectionPoolGroups calls Prune - // which will QueuePoolForRelease on all empty pools - // and once no pools remain, change state from Active->Idle->Disabled - // Once Disabled, factory can remove its reference to the pool entry - - internal sealed class DbConnectionPoolGroup - { - private readonly DbConnectionOptions _connectionOptions; - private readonly DbConnectionPoolKey _poolKey; - private readonly DbConnectionPoolGroupOptions _poolGroupOptions; - private ConcurrentDictionary _poolCollection; - - private int _state; // see PoolGroupState* below - - private DbConnectionPoolGroupProviderInfo _providerInfo; - - // always lock this before changing _state, we don't want to move out of the 'Disabled' state - // PoolGroupStateUninitialized = 0; - private const int PoolGroupStateActive = 1; // initial state, GetPoolGroup from cache, connection Open - private const int PoolGroupStateIdle = 2; // all pools are pruned via Clear - private const int PoolGroupStateDisabled = 4; // factory pool entry pruning method - - internal DbConnectionPoolGroup(DbConnectionOptions connectionOptions, DbConnectionPoolKey key, DbConnectionPoolGroupOptions poolGroupOptions) - { - Debug.Assert(null != connectionOptions, "null connection options"); - - _connectionOptions = connectionOptions; - _poolKey = key; - _poolGroupOptions = poolGroupOptions; - - // always lock this object before changing state - // HybridDictionary does not create any sub-objects until add - // so it is safe to use for non-pooled connection as long as - // we check _poolGroupOptions first - _poolCollection = new ConcurrentDictionary(); - _state = PoolGroupStateActive; - } - - internal DbConnectionOptions ConnectionOptions - { - get - { - return _connectionOptions; - } - } - - internal DbConnectionPoolKey PoolKey - { - get - { - return _poolKey; - } - } - - internal DbConnectionPoolGroupProviderInfo ProviderInfo - { - get - { - return _providerInfo; - } - set - { - _providerInfo = value; - if (null != value) - { - _providerInfo.PoolGroup = this; - } - } - } - - internal bool IsDisabled - { - get - { - return (PoolGroupStateDisabled == _state); - } - } - - - internal DbConnectionPoolGroupOptions PoolGroupOptions - { - get - { - return _poolGroupOptions; - } - } - - - internal int Clear() - { - // must be multi-thread safe with competing calls by Clear and Prune via background thread - // will return the number of connections in the group after clearing has finished - - // First, note the old collection and create a new collection to be used - ConcurrentDictionary oldPoolCollection = null; - lock (this) - { - if (_poolCollection.Count > 0) - { - oldPoolCollection = _poolCollection; - _poolCollection = new ConcurrentDictionary(); - } - } - - // Then, if a new collection was created, release the pools from the old collection - if (oldPoolCollection != null) - { - foreach (var entry in oldPoolCollection) - { - DbConnectionPool pool = entry.Value; - if (pool != null) - { - DbConnectionFactory connectionFactory = pool.ConnectionFactory; - connectionFactory.QueuePoolForRelease(pool, true); - } - } - } - - // Finally, return the pool collection count - this may be non-zero if something was added while we were clearing - return _poolCollection.Count; - } - - internal DbConnectionPool GetConnectionPool(DbConnectionFactory connectionFactory) - { - // When this method returns null it indicates that the connection - // factory should not use pooling. - - // We don't support connection pooling on Win9x; - // PoolGroupOptions will only be null when we're not supposed to pool - // connections. - DbConnectionPool pool = null; - if (null != _poolGroupOptions) - { - DbConnectionPoolIdentity currentIdentity = DbConnectionPoolIdentity.NoIdentity; - - if (_poolGroupOptions.PoolByIdentity) - { - // if we're pooling by identity (because integrated security is - // being used for these connections) then we need to go out and - // search for the connectionPool that matches the current identity. - - currentIdentity = DbConnectionPoolIdentity.GetCurrent(); - - // If the current token is restricted in some way, then we must - // not attempt to pool these connections. - if (currentIdentity.IsRestricted) - { - currentIdentity = null; - } - } - if (null != currentIdentity) - { - if (!_poolCollection.TryGetValue(currentIdentity, out pool)) - { // find the pool - DbConnectionPoolProviderInfo connectionPoolProviderInfo = connectionFactory.CreateConnectionPoolProviderInfo(this.ConnectionOptions); - - // optimistically create pool, but its callbacks are delayed until after actual add - DbConnectionPool newPool = new DbConnectionPool(connectionFactory, this, currentIdentity, connectionPoolProviderInfo); - - lock (this) - { - // Did someone already add it to the list? - if (!_poolCollection.TryGetValue(currentIdentity, out pool)) - { - if (MarkPoolGroupAsActive()) - { - // If we get here, we know for certain that we there isn't - // a pool that matches the current identity, so we have to - // add the optimistically created one - newPool.Startup(); // must start pool before usage - bool addResult = _poolCollection.TryAdd(currentIdentity, newPool); - Debug.Assert(addResult, "No other pool with current identity should exist at this point"); - pool = newPool; - newPool = null; - } - else - { - // else pool entry has been disabled so don't create new pools - Debug.Assert(PoolGroupStateDisabled == _state, "state should be disabled"); - } - } - else - { - // else found an existing pool to use instead - Debug.Assert(PoolGroupStateActive == _state, "state should be active since a pool exists and lock holds"); - } - } - - if (null != newPool) - { - // don't need to call connectionFactory.QueuePoolForRelease(newPool) because - // pool callbacks were delayed and no risk of connections being created - newPool.Shutdown(); - } - } - // the found pool could be in any state - } - } - - if (null == pool) - { - lock (this) - { - // keep the pool entry state active when not pooling - MarkPoolGroupAsActive(); - } - } - return pool; - } - - private bool MarkPoolGroupAsActive() - { - // when getting a connection, make the entry active if it was idle (but not disabled) - // must always lock this before calling - - if (PoolGroupStateIdle == _state) - { - _state = PoolGroupStateActive; - } - return (PoolGroupStateActive == _state); - } - - internal bool Prune() - { - // must only call from DbConnectionFactory.PruneConnectionPoolGroups on background timer thread - // must lock(DbConnectionFactory._connectionPoolGroups.SyncRoot) before calling ReadyToRemove - // to avoid conflict with DbConnectionFactory.CreateConnectionPoolGroup replacing pool entry - lock (this) - { - if (_poolCollection.Count > 0) - { - var newPoolCollection = new ConcurrentDictionary(); - - foreach (var entry in _poolCollection) - { - DbConnectionPool pool = entry.Value; - if (pool != null) - { - // Actually prune the pool if there are no connections in the pool and no errors occurred. - // Empty pool during pruning indicates zero or low activity, but - // an error state indicates the pool needs to stay around to - // throttle new connection attempts. - if ((!pool.ErrorOccurred) && (0 == pool.Count)) - { - // Order is important here. First we remove the pool - // from the collection of pools so no one will try - // to use it while we're processing and finally we put the - // pool into a list of pools to be released when they - // are completely empty. - DbConnectionFactory connectionFactory = pool.ConnectionFactory; - - connectionFactory.QueuePoolForRelease(pool, false); - } - else - { - newPoolCollection.TryAdd(entry.Key, entry.Value); - } - } - } - _poolCollection = newPoolCollection; - } - - // must be pruning thread to change state and no connections - // otherwise pruning thread risks making entry disabled soon after user calls ClearPool - if (0 == _poolCollection.Count) - { - if (PoolGroupStateActive == _state) - { - _state = PoolGroupStateIdle; - } - else if (PoolGroupStateIdle == _state) - { - _state = PoolGroupStateDisabled; - } - } - return (PoolGroupStateDisabled == _state); - } - } - } -} diff --git a/src/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbMetaDataFactory.cs b/src/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbMetaDataFactory.cs deleted file mode 100644 index 8c941a5a67d0..000000000000 --- a/src/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbMetaDataFactory.cs +++ /dev/null @@ -1,583 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Data.Common; -using System.Diagnostics; -using System.Globalization; -using System.IO; - -namespace System.Data.ProviderBase -{ - internal class DbMetaDataFactory - { // V1.2.3300 - private DataSet _metaDataCollectionsDataSet; - private string _normalizedServerVersion; - private string _serverVersionString; - // well known column names - private const string _collectionName = "CollectionName"; - private const string _populationMechanism = "PopulationMechanism"; - private const string _populationString = "PopulationString"; - private const string _maximumVersion = "MaximumVersion"; - private const string _minimumVersion = "MinimumVersion"; - private const string _dataSourceProductVersionNormalized = "DataSourceProductVersionNormalized"; - private const string _dataSourceProductVersion = "DataSourceProductVersion"; - private const string _restrictionDefault = "RestrictionDefault"; - private const string _restrictionNumber = "RestrictionNumber"; - private const string _numberOfRestrictions = "NumberOfRestrictions"; - private const string _restrictionName = "RestrictionName"; - private const string _parameterName = "ParameterName"; - - // population mechanisms - private const string _dataTable = "DataTable"; - private const string _sqlCommand = "SQLCommand"; - private const string _prepareCollection = "PrepareCollection"; - - - public DbMetaDataFactory(Stream xmlStream, string serverVersion, string normalizedServerVersion) - { - ADP.CheckArgumentNull(xmlStream, "xmlStream"); - ADP.CheckArgumentNull(serverVersion, "serverVersion"); - ADP.CheckArgumentNull(normalizedServerVersion, "normalizedServerVersion"); - - LoadDataSetFromXml(xmlStream); - - _serverVersionString = serverVersion; - _normalizedServerVersion = normalizedServerVersion; - } - - protected DataSet CollectionDataSet - { - get - { - return _metaDataCollectionsDataSet; - } - } - - protected string ServerVersion - { - get - { - return _serverVersionString; - } - } - - - protected string ServerVersionNormalized - { - get - { - return _normalizedServerVersion; - } - } - - protected DataTable CloneAndFilterCollection(string collectionName, string[] hiddenColumnNames) - { - DataTable sourceTable; - DataTable destinationTable; - DataColumn[] filteredSourceColumns; - DataColumnCollection destinationColumns; - DataRow newRow; - - sourceTable = _metaDataCollectionsDataSet.Tables[collectionName]; - - if ((sourceTable == null) || (collectionName != sourceTable.TableName)) - { - throw ADP.DataTableDoesNotExist(collectionName); - } - - destinationTable = new DataTable(collectionName); - destinationTable.Locale = CultureInfo.InvariantCulture; - destinationColumns = destinationTable.Columns; - - filteredSourceColumns = FilterColumns(sourceTable, hiddenColumnNames, destinationColumns); - - foreach (DataRow row in sourceTable.Rows) - { - if (SupportedByCurrentVersion(row) == true) - { - newRow = destinationTable.NewRow(); - for (int i = 0; i < destinationColumns.Count; i++) - { - newRow[destinationColumns[i]] = row[filteredSourceColumns[i], DataRowVersion.Current]; - } - destinationTable.Rows.Add(newRow); - newRow.AcceptChanges(); - } - } - - return destinationTable; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _normalizedServerVersion = null; - _serverVersionString = null; - _metaDataCollectionsDataSet.Dispose(); - } - } - - private DataTable ExecuteCommand(DataRow requestedCollectionRow, String[] restrictions, DbConnection connection) - { - DataTable metaDataCollectionsTable = _metaDataCollectionsDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; - DataColumn populationStringColumn = metaDataCollectionsTable.Columns[_populationString]; - DataColumn numberOfRestrictionsColumn = metaDataCollectionsTable.Columns[_numberOfRestrictions]; - DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[_collectionName]; - //DataColumn restrictionNameColumn = metaDataCollectionsTable.Columns[_restrictionName]; - - DataTable resultTable = null; - DbCommand command = null; - DataTable schemaTable = null; - - Debug.Assert(requestedCollectionRow != null); - String sqlCommand = requestedCollectionRow[populationStringColumn, DataRowVersion.Current] as string; - int numberOfRestrictions = (int)requestedCollectionRow[numberOfRestrictionsColumn, DataRowVersion.Current]; - String collectionName = requestedCollectionRow[collectionNameColumn, DataRowVersion.Current] as string; - - if ((restrictions != null) && (restrictions.Length > numberOfRestrictions)) - { - throw ADP.TooManyRestrictions(collectionName); - } - - command = connection.CreateCommand(); - command.CommandText = sqlCommand; - command.CommandTimeout = System.Math.Max(command.CommandTimeout, 180); - - for (int i = 0; i < numberOfRestrictions; i++) - { - DbParameter restrictionParameter = command.CreateParameter(); - - - if ((restrictions != null) && (restrictions.Length > i) && (restrictions[i] != null)) - { - restrictionParameter.Value = restrictions[i]; - } - else - { - // This is where we have to assign null to the value of the parameter. - restrictionParameter.Value = DBNull.Value; - } - - restrictionParameter.ParameterName = GetParameterName(collectionName, i + 1); - restrictionParameter.Direction = ParameterDirection.Input; - command.Parameters.Add(restrictionParameter); - } - - DbDataReader reader = null; - try - { - try - { - reader = command.ExecuteReader(); - } - catch (Exception e) - { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - throw ADP.QueryFailed(collectionName, e); - } - - // - - // Build a DataTable from the reader - resultTable = new DataTable(collectionName); - resultTable.Locale = CultureInfo.InvariantCulture; - - schemaTable = reader.GetSchemaTable(); - foreach (DataRow row in schemaTable.Rows) - { - resultTable.Columns.Add(row["ColumnName"] as string, (Type)row["DataType"] as Type); - } - object[] values = new object[resultTable.Columns.Count]; - while (reader.Read()) - { - reader.GetValues(values); - resultTable.Rows.Add(values); - } - } - finally - { - if (reader != null) - { - reader.Dispose(); - reader = null; - } - } - return resultTable; - } - - private DataColumn[] FilterColumns(DataTable sourceTable, string[] hiddenColumnNames, DataColumnCollection destinationColumns) - { - DataColumn newDestinationColumn; - int currentColumn; - DataColumn[] filteredSourceColumns = null; - - int columnCount = 0; - foreach (DataColumn sourceColumn in sourceTable.Columns) - { - if (IncludeThisColumn(sourceColumn, hiddenColumnNames) == true) - { - columnCount++; - } - } - - if (columnCount == 0) - { - throw ADP.NoColumns(); - } - - currentColumn = 0; - filteredSourceColumns = new DataColumn[columnCount]; - - foreach (DataColumn sourceColumn in sourceTable.Columns) - { - if (IncludeThisColumn(sourceColumn, hiddenColumnNames) == true) - { - newDestinationColumn = new DataColumn(sourceColumn.ColumnName, sourceColumn.DataType); - destinationColumns.Add(newDestinationColumn); - filteredSourceColumns[currentColumn] = sourceColumn; - currentColumn++; - } - } - return filteredSourceColumns; - } - - internal DataRow FindMetaDataCollectionRow(string collectionName) - { - bool versionFailure; - bool haveExactMatch; - bool haveMultipleInexactMatches; - string candidateCollectionName; - - DataTable metaDataCollectionsTable = _metaDataCollectionsDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; - if (metaDataCollectionsTable == null) - { - throw ADP.InvalidXml(); - } - - DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[DbMetaDataColumnNames.CollectionName]; - - if ((null == collectionNameColumn) || (typeof(System.String) != collectionNameColumn.DataType)) - { - throw ADP.InvalidXmlMissingColumn(DbMetaDataCollectionNames.MetaDataCollections, DbMetaDataColumnNames.CollectionName); - } - - DataRow requestedCollectionRow = null; - String exactCollectionName = null; - - // find the requested collection - versionFailure = false; - haveExactMatch = false; - haveMultipleInexactMatches = false; - - foreach (DataRow row in metaDataCollectionsTable.Rows) - { - candidateCollectionName = row[collectionNameColumn, DataRowVersion.Current] as string; - if (string.IsNullOrEmpty(candidateCollectionName)) - { - throw ADP.InvalidXmlInvalidValue(DbMetaDataCollectionNames.MetaDataCollections, DbMetaDataColumnNames.CollectionName); - } - - if (ADP.CompareInsensitiveInvariant(candidateCollectionName, collectionName)) - { - if (SupportedByCurrentVersion(row) == false) - { - versionFailure = true; - } - else - { - if (collectionName == candidateCollectionName) - { - if (haveExactMatch == true) - { - throw ADP.CollectionNameIsNotUnique(collectionName); - } - requestedCollectionRow = row; - exactCollectionName = candidateCollectionName; - haveExactMatch = true; - } - else - { - // have an inexact match - ok only if it is the only one - if (exactCollectionName != null) - { - // can't fail here becasue we may still find an exact match - haveMultipleInexactMatches = true; - } - requestedCollectionRow = row; - exactCollectionName = candidateCollectionName; - } - } - } - } - - if (requestedCollectionRow == null) - { - if (versionFailure == false) - { - throw ADP.UndefinedCollection(collectionName); - } - else - { - throw ADP.UnsupportedVersion(collectionName); - } - } - - if ((haveExactMatch == false) && (haveMultipleInexactMatches == true)) - { - throw ADP.AmbigousCollectionName(collectionName); - } - - return requestedCollectionRow; - } - - private void FixUpVersion(DataTable dataSourceInfoTable) - { - Debug.Assert(dataSourceInfoTable.TableName == DbMetaDataCollectionNames.DataSourceInformation); - DataColumn versionColumn = dataSourceInfoTable.Columns[_dataSourceProductVersion]; - DataColumn normalizedVersionColumn = dataSourceInfoTable.Columns[_dataSourceProductVersionNormalized]; - - if ((versionColumn == null) || (normalizedVersionColumn == null)) - { - throw ADP.MissingDataSourceInformationColumn(); - } - - if (dataSourceInfoTable.Rows.Count != 1) - { - throw ADP.IncorrectNumberOfDataSourceInformationRows(); - } - - DataRow dataSourceInfoRow = dataSourceInfoTable.Rows[0]; - - dataSourceInfoRow[versionColumn] = _serverVersionString; - dataSourceInfoRow[normalizedVersionColumn] = _normalizedServerVersion; - dataSourceInfoRow.AcceptChanges(); - } - - - private string GetParameterName(string neededCollectionName, int neededRestrictionNumber) - { - DataTable restrictionsTable = null; - DataColumnCollection restrictionColumns = null; - DataColumn collectionName = null; - DataColumn parameterName = null; - DataColumn restrictionName = null; - DataColumn restrictionNumber = null; ; - string result = null; - - restrictionsTable = _metaDataCollectionsDataSet.Tables[DbMetaDataCollectionNames.Restrictions]; - if (restrictionsTable != null) - { - restrictionColumns = restrictionsTable.Columns; - if (restrictionColumns != null) - { - collectionName = restrictionColumns[_collectionName]; - parameterName = restrictionColumns[_parameterName]; - restrictionName = restrictionColumns[_restrictionName]; - restrictionNumber = restrictionColumns[_restrictionNumber]; - } - } - - if ((parameterName == null) || (collectionName == null) || (restrictionName == null) || (restrictionNumber == null)) - { - throw ADP.MissingRestrictionColumn(); - } - - foreach (DataRow restriction in restrictionsTable.Rows) - { - if (((string)restriction[collectionName] == neededCollectionName) && - ((int)restriction[restrictionNumber] == neededRestrictionNumber) && - (SupportedByCurrentVersion(restriction))) - { - result = (string)restriction[parameterName]; - break; - } - } - - if (result == null) - { - throw ADP.MissingRestrictionRow(); - } - - return result; - } - - public virtual DataTable GetSchema(DbConnection connection, string collectionName, string[] restrictions) - { - Debug.Assert(_metaDataCollectionsDataSet != null); - - // - DataTable metaDataCollectionsTable = _metaDataCollectionsDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; - DataColumn populationMechanismColumn = metaDataCollectionsTable.Columns[_populationMechanism]; - DataColumn collectionNameColumn = metaDataCollectionsTable.Columns[DbMetaDataColumnNames.CollectionName]; - DataRow requestedCollectionRow = null; - DataTable requestedSchema = null; - string[] hiddenColumns; - string exactCollectionName = null; - - requestedCollectionRow = FindMetaDataCollectionRow(collectionName); - exactCollectionName = requestedCollectionRow[collectionNameColumn, DataRowVersion.Current] as string; - - if (ADP.IsEmptyArray(restrictions) == false) - { - for (int i = 0; i < restrictions.Length; i++) - { - if ((restrictions[i] != null) && (restrictions[i].Length > 4096)) - { - // use a non-specific error because no new beta 2 error messages are allowed - // - throw ADP.NotSupported(); - } - } - } - - string populationMechanism = requestedCollectionRow[populationMechanismColumn, DataRowVersion.Current] as string; - switch (populationMechanism) - { - case _dataTable: - if (exactCollectionName == DbMetaDataCollectionNames.MetaDataCollections) - { - hiddenColumns = new string[2]; - hiddenColumns[0] = _populationMechanism; - hiddenColumns[1] = _populationString; - } - else - { - hiddenColumns = null; - } - // none of the datatable collections support restrictions - if (ADP.IsEmptyArray(restrictions) == false) - { - throw ADP.TooManyRestrictions(exactCollectionName); - } - - - requestedSchema = CloneAndFilterCollection(exactCollectionName, hiddenColumns); - - // - - // for the data source infomation table we need to fix up the version columns at run time - // since the version is determined at run time - if (exactCollectionName == DbMetaDataCollectionNames.DataSourceInformation) - { - FixUpVersion(requestedSchema); - } - break; - - case _sqlCommand: - requestedSchema = ExecuteCommand(requestedCollectionRow, restrictions, connection); - break; - - case _prepareCollection: - requestedSchema = PrepareCollection(exactCollectionName, restrictions, connection); - break; - - default: - throw ADP.UndefinedPopulationMechanism(populationMechanism); - } - - return requestedSchema; - } - - private bool IncludeThisColumn(DataColumn sourceColumn, string[] hiddenColumnNames) - { - bool result = true; - string sourceColumnName = sourceColumn.ColumnName; - - switch (sourceColumnName) - { - case _minimumVersion: - case _maximumVersion: - result = false; - break; - - default: - if (hiddenColumnNames == null) - { - break; - } - for (int i = 0; i < hiddenColumnNames.Length; i++) - { - if (hiddenColumnNames[i] == sourceColumnName) - { - result = false; - break; - } - } - break; - } - - return result; - } - - private void LoadDataSetFromXml(Stream XmlStream) - { - _metaDataCollectionsDataSet = new DataSet(); - _metaDataCollectionsDataSet.Locale = System.Globalization.CultureInfo.InvariantCulture; - _metaDataCollectionsDataSet.ReadXml(XmlStream); - } - - protected virtual DataTable PrepareCollection(String collectionName, String[] restrictions, DbConnection connection) - { - throw ADP.NotSupported(); - } - - private bool SupportedByCurrentVersion(DataRow requestedCollectionRow) - { - bool result = true; - DataColumnCollection tableColumns = requestedCollectionRow.Table.Columns; - DataColumn versionColumn; - Object version; - - // check the minimum version first - versionColumn = tableColumns[_minimumVersion]; - if (versionColumn != null) - { - version = requestedCollectionRow[versionColumn]; - if (version != null) - { - if (version != DBNull.Value) - { - if (0 > string.Compare(_normalizedServerVersion, (string)version, StringComparison.OrdinalIgnoreCase)) - { - result = false; - } - } - } - } - - // if the minmum version was ok what about the maximum version - if (result == true) - { - versionColumn = tableColumns[_maximumVersion]; - if (versionColumn != null) - { - version = requestedCollectionRow[versionColumn]; - if (version != null) - { - if (version != DBNull.Value) - { - if (0 < string.Compare(_normalizedServerVersion, (string)version, StringComparison.OrdinalIgnoreCase)) - { - result = false; - } - } - } - } - } - - return result; - } - } -} - - diff --git a/src/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbReferenceCollection.cs b/src/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbReferenceCollection.cs deleted file mode 100644 index 442ca95301f4..000000000000 --- a/src/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbReferenceCollection.cs +++ /dev/null @@ -1,282 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Diagnostics; -using System.Threading; - -namespace System.Data.ProviderBase -{ - internal abstract class DbReferenceCollection - { - private struct CollectionEntry - { - private int _tag; // information about the reference - private WeakReference _weak; // the reference itself. - - public void NewTarget(int tag, object target) - { - Debug.Assert(!HasTarget, "Entry already has a valid target"); - Debug.Assert(tag != 0, "Bad tag"); - Debug.Assert(target != null, "Invalid target"); - - if (_weak == null) - { - _weak = new WeakReference(target, false); - } - else - { - _weak.Target = target; - } - _tag = tag; - } - - public void RemoveTarget() - { - _tag = 0; - } - - public bool HasTarget - { - get - { - return ((_tag != 0) && (_weak.IsAlive)); - } - } - - public int Tag - { - get - { - return _tag; - } - } - - public object Target - { - get - { - return (_tag == 0 ? null : _weak.Target); - } - } - } - - private const int LockPollTime = 100; // Time to wait (in ms) between attempting to get the _itemLock - private const int DefaultCollectionSize = 20; // Default size for the collection, and the amount to grow every time the collection is full - private CollectionEntry[] _items; // The collection of items we are keeping track of - private readonly object _itemLock; // Used to synchronize access to the _items collection - private int _optimisticCount; // (#ItemsAdded - #ItemsRemoved) - This estimates the number of items that we *should* have (but doesn't take into account item targets being GC'd) - private int _lastItemIndex; // Location of the last item in _items - private volatile bool _isNotifying; // Indicates that the collection is currently being notified (and, therefore, about to be cleared) - - protected DbReferenceCollection() - { - _items = new CollectionEntry[DefaultCollectionSize]; - _itemLock = new object(); - _optimisticCount = 0; - _lastItemIndex = 0; - } - - public abstract void Add(object value, int tag); - - protected void AddItem(object value, int tag) - { - Debug.Assert(null != value && 0 != tag, "AddItem with null value or 0 tag"); - bool itemAdded = false; - - lock (_itemLock) - { - // Try to find a free spot - for (int i = 0; i <= _lastItemIndex; ++i) - { - if (_items[i].Tag == 0) - { - _items[i].NewTarget(tag, value); - Debug.Assert(_items[i].HasTarget, "missing expected target"); - itemAdded = true; - break; - } - } - - // No free spots, can we just add on to the end? - if ((!itemAdded) && (_lastItemIndex + 1 < _items.Length)) - { - _lastItemIndex++; - _items[_lastItemIndex].NewTarget(tag, value); - itemAdded = true; - } - - // If no free spots and no space at the end, try to find a dead item - if (!itemAdded) - { - for (int i = 0; i <= _lastItemIndex; ++i) - { - if (!_items[i].HasTarget) - { - _items[i].NewTarget(tag, value); - Debug.Assert(_items[i].HasTarget, "missing expected target"); - itemAdded = true; - break; - } - } - } - - // If nothing was free, then resize and add to the end - if (!itemAdded) - { - Array.Resize(ref _items, _items.Length * 2); - _lastItemIndex++; - _items[_lastItemIndex].NewTarget(tag, value); - } - - _optimisticCount++; - } - } - - internal T FindItem(int tag, Func filterMethod) where T : class - { - bool lockObtained = false; - try - { - TryEnterItemLock(ref lockObtained); - if (lockObtained) - { - if (_optimisticCount > 0) - { - // Loop through the items - for (int counter = 0; counter <= _lastItemIndex; counter++) - { - // Check tag (should be easiest and quickest) - if (_items[counter].Tag == tag) - { - // NOTE: Check if the returned value is null twice may seem wasteful, but this if for performance - // Since checking for null twice is cheaper than calling both HasTarget and Target OR always attempting to typecast - object value = _items[counter].Target; - if (value != null) - { - // Make sure the item has the correct type and passes the filtering - T tempItem = value as T; - if ((tempItem != null) && (filterMethod(tempItem))) - { - return tempItem; - } - } - } - } - } - } - } - finally - { - ExitItemLockIfNeeded(lockObtained); - } - - // If we got to here, then no item was found, so return null - return null; - } - - public void Notify(int message) - { - bool lockObtained = false; - try - { - TryEnterItemLock(ref lockObtained); - if (lockObtained) - { - try - { - _isNotifying = true; - - // Loop through each live item and notify it - if (_optimisticCount > 0) - { - for (int index = 0; index <= _lastItemIndex; ++index) - { - object value = _items[index].Target; // checks tag & gets target - if (null != value) - { - NotifyItem(message, _items[index].Tag, value); - _items[index].RemoveTarget(); - } - Debug.Assert(!_items[index].HasTarget, "Unexpected target after notifying"); - } - _optimisticCount = 0; - } - - // Shrink collection (if needed) - if (_items.Length > 100) - { - _lastItemIndex = 0; - _items = new CollectionEntry[DefaultCollectionSize]; - } - } - finally - { - _isNotifying = false; - } - } - } - finally - { - ExitItemLockIfNeeded(lockObtained); - } - } - - protected abstract void NotifyItem(int message, int tag, object value); - - public abstract void Remove(object value); - - protected void RemoveItem(object value) - { - Debug.Assert(null != value, "RemoveItem with null"); - - bool lockObtained = false; - try - { - TryEnterItemLock(ref lockObtained); - - if (lockObtained) - { - // Find the value, and then remove the target from our collection - if (_optimisticCount > 0) - { - for (int index = 0; index <= _lastItemIndex; ++index) - { - if (value == _items[index].Target) - { // checks tag & gets target - _items[index].RemoveTarget(); - _optimisticCount--; - break; - } - } - } - } - } - finally - { - ExitItemLockIfNeeded(lockObtained); - } - } - - // This is polling lock that will abandon getting the lock if _isNotifying is set to true - private void TryEnterItemLock(ref bool lockObtained) - { - // Assume that we couldn't take the lock - lockObtained = false; - // Keep trying to take the lock until either we've taken it, or the collection is being notified - while ((!_isNotifying) && (!lockObtained)) - { - Monitor.TryEnter(_itemLock, LockPollTime, ref lockObtained); - } - } - - private void ExitItemLockIfNeeded(bool lockObtained) - { - if (lockObtained) - { - Monitor.Exit(_itemLock); - } - } - } -} - diff --git a/src/System.Data.Odbc/src/Resources/System.Data.Odbc.OdbcMetaData.xml b/src/System.Data.Odbc/src/Resources/System.Data.Odbc.OdbcMetaData.xml new file mode 100644 index 000000000000..122abae60698 --- /dev/null +++ b/src/System.Data.Odbc/src/Resources/System.Data.Odbc.OdbcMetaData.xml @@ -0,0 +1,1013 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MetaDataCollections + 0 + 0 + DataTable + MetaDataCollections + + + DataSourceInformation + 0 + 0 + PrepareCollection + DataSourceInformation + + + DataTypes + 0 + 0 + PrepareCollection + DataTypes + + + Restrictions + 0 + 0 + DataTable + Restrictions + + + ReservedWords + 0 + 0 + PrepareCollection + ReservedWords + + + Columns + 4 + 4 + PrepareCollection + + + Indexes + 4 + 4 + PrepareCollection + + + Procedures + 4 + 3 + PrepareCollection + + + ProcedureColumns + 4 + 4 + PrepareCollection + + + ProcedureParameters + 4 + 4 + PrepareCollection + + + Tables + 3 + 3 + PrepareCollection + + + Views + 3 + 3 + PrepareCollection + + + Columns + TABLE_CAT + 1 + + + Columns + TABLE_SCHEM + 2 + + + Columns + TABLE_NAME + 3 + + + Columns + COLUMN_NAME + 4 + + + Indexes + TABLE_CAT + 1 + + + Indexes + TABLE_SCHEM + 2 + + + Indexes + TABLE_NAME + 3 + + + Indexes + INDEX_NAME + 4 + + + Procedures + PROCEDURE_CAT + 1 + + + Procedures + PROCEDURE_SCHEM + 2 + + + Procedures + PROCEDURE_NAME + 3 + + + Procedures + PROCEDURE_TYPE + 4 + + + ProcedureColumns + PROCEDURE_CAT + 1 + + + ProcedureColumns + PROCEDURE_SCHE + 2 + + + ProcedureColumns + PROCEDURE_NAME + 3 + + + ProcedureColumns + COLUMN_NAME + 4 + + + ProcedureParameters + PROCEDURE_CAT + 1 + + + ProcedureParameters + PROCEDURE_SCHEM + 2 + + + ProcedureParameters + PROCEDURE_NAME + 3 + + + ProcedureParameters + COLUMN_NAME + 4 + + + Tables + TABLE_CAT + 1 + + + Tables + TABLE_SCHEM + 2 + + + Tables + TABLE_NAME + 3 + + + Views + TABLE_CAT + 1 + + + Views + TABLE_SCHEM + 2 + + + Views + TABLE_NAME + 3 + + + + + + ABSOLUTE + + + ACTION + + + ADA + + + ADD + + + ALL + + + ALLOCATE + + + ALTER + + + AND + + + ANY + + + ARE + + + AS + + + ASC + + + ASSERTION + + + AT + + + AUTHORIZATION + + + AVG + + + BEGIN + + + BETWEEN + + + BIT + + + BIT_LENGTH + + + BOTH + + + BY + + + CASCADE + + + CASCADED + + + CASE + + + CAST + + + CATALOG + + + CHAR + + + CHAR_LENGTH + + + CHARACTER + + + CHARACTER_LENGTH + + + CHECK + + + CLOSE + + + COALESCE + + + COLLATE + + + COLLATION + + + COLUMN + + + COMMIT + + + CONNECT + + + CONNECTION + + + CONSTRAINT + + + CONSTRAINTS + + + CONTINUE + + + CONVERT + + + CORRESPONDING + + + COUNT + + + CREATE + + + CROSS + + + CURRENT + + + CURRENT_DATE + + + CURRENT_TIME + + + CURRENT_TIMESTAMP + + + CURRENT_USER + + + CURSOR + + + DATE + + + DAY + + + DEALLOCATE + + + DEC + + + DECIMAL + + + DECLARE + + + DEFAULT + + + DEFERRABLE + + + DEFERRED + + + DELETE + + + DESC + + + DESCRIBE + + + DESCRIPTOR + + + DIAGNOSTICS + + + DISCONNECT + + + DISTINCT + + + DOMAIN + + + DOUBLE + + + DROP + + + ELSE + + + END + + + END-EXEC + + + ESCAPE + + + EXCEPT + + + EXCEPTION + + + EXEC + + + EXECUTE + + + EXISTS + + + EXTERNAL + + + EXTRACT + + + FALSE + + + FETCH + + + FIRST + + + FLOAT + + + FOR + + + FOREIGN + + + FORTRAN + + + FOUND + + + FROM + + + FULL + + + GET + + + GLOBAL + + + GO + + + GOTO + + + GRANT + + + GROUP + + + HAVING + + + HOUR + + + IDENTITY + + + IMMEDIATE + + + IN + + + INCLUDE + + + INDEX + + + INDICATOR + + + INITIALLY + + + INNER + + + INPUT + + + INSENSITIVE + + + INSERT + + + INT + + + INTEGER + + + INTERSECT + + + INTERVAL + + + INTO + + + IS + + + ISOLATION + + + JOIN + + + KEY + + + LANGUAGE + + + LAST + + + LEADING + + + LEFT + + + LEVEL + + + LIKE + + + LOCAL + + + LOWER + + + MATCH + + + MAX + + + MIN + + + MINUTE + + + MODULE + + + MONTH + + + NAMES + + + NATIONAL + + + NATURAL + + + NCHAR + + + NEXT + + + NO + + + NONE + + + NOT + + + NULL + + + NULLIF + + + NUMERIC + + + OCTET_LENGTH + + + OF + + + ON + + + ONLY + + + OPEN + + + OPTION + + + OR + + + ORDER + + + OUTER + + + OUTPUT + + + OVERLAPS + + + PAD + + + PARTIAL + + + PASCAL + + + POSITION + + + PRECISION + + + PREPARE + + + PRESERVE + + + PRIMARY + + + PRIOR + + + PRIVILEGES + + + PROCEDURE + + + PUBLIC + + + READ + + + REAL + + + REFERENCES + + + RELATIVE + + + RESTRICT + + + REVOKE + + + RIGHT + + + ROLLBACK + + + ROWS + + + SCHEMA + + + SCROLL + + + SECOND + + + SECTION + + + SELECT + + + SESSION + + + SESSION_USER + + + SET + + + SIZE + + + SMALLINT + + + SOME + + + SPACE + + + SQL + + + SQLCA + + + SQLCODE + + + SQLERROR + + + SQLSTATE + + + SQLWARNING + + + SUBSTRING + + + SUM + + + SYSTEM_USER + + + TABLE + + + TEMPORARY + + + THEN + + + TIME + + + TIMESTAMP + + + TIMEZONE_HOUR + + + TIMEZONE_MINUTE + + + TO + + + TRAILING + + + TRANSACTION + + + TRANSLATE + + + TRANSLATION + + + TRIM + + + TRUE + + + UNION + + + UNIQUE + + + UNKNOWN + + + UPDATE + + + UPPER + + + USAGE + + + USER + + + USING + + + VALUE + + + VALUES + + + VARCHAR + + + VARYING + + + VIEW + + + WHEN + + + WHENEVER + + + WHERE + + + WITH + + + WORK + + + WRITE + + + YEAR + + + ZONE + + diff --git a/src/System.Data.Odbc/src/System.Data.Odbc.csproj b/src/System.Data.Odbc/src/System.Data.Odbc.csproj index e436c86eddad..2a3f098d988f 100644 --- a/src/System.Data.Odbc/src/System.Data.Odbc.csproj +++ b/src/System.Data.Odbc/src/System.Data.Odbc.csproj @@ -52,24 +52,41 @@ - - Common\System\Data\ProviderBase\FieldNameLookup.cs Common\System\Data\ProviderBase\BasicFieldNameLookup.cs + + System\Data\ProviderBase\DbConnectionInternal.cs + + + System\Data\ProviderBase\DbConnectionFactory.cs + + + System\Data\ProviderBase\DbConnectionPoolGroup.cs + + + System\Data\ProviderBase\TimeoutTimer.cs + + + System\Data\ProviderBase\DbReferenceCollection.cs + + + System\Data\ProviderBase\DbMetaDataFactory.cs + + + System\Data\ProviderBase\DbConnectionClosed.cs + - - @@ -182,6 +199,11 @@ + + + System.Data.Odbc.OdbcMetaData.xml + + diff --git a/src/System.Data.Odbc/src/System/Data/Odbc/OdbcConnectionFactory.cs b/src/System.Data.Odbc/src/System/Data/Odbc/OdbcConnectionFactory.cs index 9d910e748d44..1287c5403326 100644 --- a/src/System.Data.Odbc/src/System/Data/Odbc/OdbcConnectionFactory.cs +++ b/src/System.Data.Odbc/src/System/Data/Odbc/OdbcConnectionFactory.cs @@ -2,9 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Specialized; using System.Data.Common; using System.Data.ProviderBase; using System.Diagnostics; +using System.IO; namespace System.Data.Odbc { @@ -50,6 +52,36 @@ internal override DbConnectionPoolGroupProviderInfo CreateConnectionPoolGroupPro return new OdbcConnectionPoolGroupProviderInfo(); } + protected override DbMetaDataFactory CreateMetaDataFactory(DbConnectionInternal internalConnection, out bool cacheMetaDataFactory) + { + Debug.Assert(internalConnection != null, "internalConnection may not be null."); + cacheMetaDataFactory = false; + + OdbcConnection odbcOuterConnection = ((OdbcConnectionOpen)internalConnection).OuterConnection; + Debug.Assert(odbcOuterConnection != null, "outer connection may not be null."); + + // get the DBMS Name + object driverName = null; + string stringValue = odbcOuterConnection.GetInfoStringUnhandled(ODBC32.SQL_INFO.DRIVER_NAME); + if (stringValue != null) + { + driverName = stringValue; + } + + Stream XMLStream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("System.Data.Odbc.OdbcMetaData.xml"); + cacheMetaDataFactory = true; + + + Debug.Assert(XMLStream != null, "XMLstream may not be null."); + + String versionString = odbcOuterConnection.GetInfoStringUnhandled(ODBC32.SQL_INFO.DBMS_VER); + + return new OdbcMetaDataFactory(XMLStream, + versionString, + versionString, + odbcOuterConnection); + } + internal override DbConnectionPoolGroup GetConnectionPoolGroup(DbConnection connection) { OdbcConnection c = (connection as OdbcConnection); diff --git a/src/System.Data.Odbc/src/System/Data/Odbc/OdbcConnectionHelper.cs b/src/System.Data.Odbc/src/System/Data/Odbc/OdbcConnectionHelper.cs index 481e9e437366..6927b923697b 100644 --- a/src/System.Data.Odbc/src/System/Data/Odbc/OdbcConnectionHelper.cs +++ b/src/System.Data.Odbc/src/System/Data/Odbc/OdbcConnectionHelper.cs @@ -175,6 +175,22 @@ protected override void Dispose(bool disposing) partial void RepairInnerConnection(); + override public DataTable GetSchema() + { + return this.GetSchema(DbMetaDataCollectionNames.MetaDataCollections, null); + } + + override public DataTable GetSchema(string collectionName) + { + return this.GetSchema(collectionName, null); + } + + override public DataTable GetSchema(string collectionName, string[] restrictionValues) + { + // NOTE: This is virtual because not all providers may choose to support + // returning schema data + return InnerConnection.GetSchema(ConnectionFactory, PoolGroup, this, collectionName, restrictionValues); + } internal void NotifyWeakReference(int message) { diff --git a/src/System.Data.Odbc/tests/Helpers.cs b/src/System.Data.Odbc/tests/Helpers.cs index 0ab71a191055..126b8657fc44 100644 --- a/src/System.Data.Odbc/tests/Helpers.cs +++ b/src/System.Data.Odbc/tests/Helpers.cs @@ -2,12 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; namespace System.Data.Odbc.Tests { diff --git a/src/System.Data.Odbc/tests/OdbcConnectionSchemaTests.cs b/src/System.Data.Odbc/tests/OdbcConnectionSchemaTests.cs new file mode 100644 index 000000000000..caf10e341539 --- /dev/null +++ b/src/System.Data.Odbc/tests/OdbcConnectionSchemaTests.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Xunit; + +namespace System.Data.Odbc.Tests +{ + public class OdbcConnectionSchemaTests + { + [CheckConnStrSetupFact] + public void TestConnectionSchemaOnOpenConnection() + { + string connectionString = DataTestUtility.OdbcConnStr; + + using (OdbcConnection connection = new OdbcConnection(connectionString)) + { + connection.GetSchema(); + connection.Open(); + DataTable schema = connection.GetSchema(); + Assert.NotNull(schema); + + DataTable tableSchema = connection.GetSchema("Tables"); + Assert.NotNull(tableSchema); + } + } + + [Fact] + public void TestConnectionSchemaOnNonOpenConnection() + { + using (OdbcConnection connection = new OdbcConnection(string.Empty)) + { + Assert.Throws(() => connection.GetSchema()); + } + } + } +} diff --git a/src/System.Data.Odbc/tests/System.Data.Odbc.Tests.csproj b/src/System.Data.Odbc/tests/System.Data.Odbc.Tests.csproj index 287eec667cb9..361e88a376ca 100644 --- a/src/System.Data.Odbc/tests/System.Data.Odbc.Tests.csproj +++ b/src/System.Data.Odbc/tests/System.Data.Odbc.Tests.csproj @@ -16,6 +16,7 @@ + @@ -50,4 +51,4 @@ - + \ No newline at end of file diff --git a/src/System.Data.SqlClient/src/System.Data.SqlClient.csproj b/src/System.Data.SqlClient/src/System.Data.SqlClient.csproj index 2269393d815e..7e2717d7ac4f 100644 --- a/src/System.Data.SqlClient/src/System.Data.SqlClient.csproj +++ b/src/System.Data.SqlClient/src/System.Data.SqlClient.csproj @@ -80,18 +80,35 @@ System\Data\Common\NameValuePair.cs + + Common\System\Data\ProviderBase\DbConnectionInternal.cs + + + Common\System\Data\ProviderBase\DbConnectionFactory.cs + + + Common\System\Data\ProviderBase\DbConnectionPoolGroup.cs + + + Common\System\Data\ProviderBase\TimeoutTimer.cs + + + Common\System\Data\ProviderBase\DbReferenceCollection.cs + + + Common\System\Data\ProviderBase\DbMetaDataFactory.cs + + + Common\System\Data\ProviderBase\DbConnectionClosed.cs + - - - - diff --git a/src/System.Data.SqlClient/src/System/Data/ProviderBase/DbConnectionClosed.cs b/src/System.Data.SqlClient/src/System/Data/ProviderBase/DbConnectionClosed.cs index 1eed07c005a1..74f48b852f37 100644 --- a/src/System.Data.SqlClient/src/System/Data/ProviderBase/DbConnectionClosed.cs +++ b/src/System.Data.SqlClient/src/System/Data/ProviderBase/DbConnectionClosed.cs @@ -2,179 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. - - -//------------------------------------------------------------------------------ - using System.Data.Common; -using System.Diagnostics; -using System.Threading.Tasks; using System.Transactions; - namespace System.Data.ProviderBase { - abstract internal class DbConnectionClosed : DbConnectionInternal - { - // Construct an "empty" connection - protected DbConnectionClosed(ConnectionState state, bool hidePassword, bool allowSetConnectionString) : base(state, hidePassword, allowSetConnectionString) - { - } - - public override string ServerVersion - { - get - { - throw ADP.ClosedConnectionError(); - } - } - - protected override void Activate(Transaction transaction) - { - throw ADP.ClosedConnectionError(); - } - - public override DbTransaction BeginTransaction(IsolationLevel il) - { - throw ADP.ClosedConnectionError(); - } - - public override void ChangeDatabase(string database) - { - throw ADP.ClosedConnectionError(); - } - - internal override void CloseConnection(DbConnection owningObject, DbConnectionFactory connectionFactory) - { - // not much to do here... - } - - protected override void Deactivate() - { - throw ADP.ClosedConnectionError(); - } - - public override void EnlistTransaction(Transaction transaction) - { - throw ADP.ClosedConnectionError(); - } - - protected internal override DataTable GetSchema(DbConnectionFactory factory, DbConnectionPoolGroup poolGroup, DbConnection outerConnection, string collectionName, string[] restrictions) - => throw ADP.ClosedConnectionError(); - - protected override DbReferenceCollection CreateReferenceCollection() - { - throw ADP.ClosedConnectionError(); - } - - internal override bool TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) - { - return base.TryOpenConnectionInternal(outerConnection, connectionFactory, retry, userOptions); - } - } - - abstract internal class DbConnectionBusy : DbConnectionClosed - { - protected DbConnectionBusy(ConnectionState state) : base(state, true, false) - { - } - - internal override bool TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) - { - throw ADP.ConnectionAlreadyOpen(State); - } - } - - sealed internal class DbConnectionClosedBusy : DbConnectionBusy - { - // Closed Connection, Currently Busy - changing connection string - internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionClosedBusy(); // singleton object - - private DbConnectionClosedBusy() : base(ConnectionState.Closed) - { - } - } - - sealed internal class DbConnectionOpenBusy : DbConnectionBusy - { - // Open Connection, Currently Busy - closing connection - internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionOpenBusy(); // singleton object - - private DbConnectionOpenBusy() : base(ConnectionState.Open) - { - } - } - - sealed internal class DbConnectionClosedConnecting : DbConnectionBusy - { - // Closed Connection, Currently Connecting - - internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionClosedConnecting(); // singleton object - - private DbConnectionClosedConnecting() : base(ConnectionState.Connecting) - { - } - - internal override void CloseConnection(DbConnection owningObject, DbConnectionFactory connectionFactory) - { - connectionFactory.SetInnerConnectionTo(owningObject, DbConnectionClosedPreviouslyOpened.SingletonInstance); - } - - internal override bool TryReplaceConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) - { - return TryOpenConnection(outerConnection, connectionFactory, retry, userOptions); - } - - internal override bool TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) - { - if (retry == null || !retry.Task.IsCompleted) - { - // retry is null if this is a synchronous call - - // if someone calls Open or OpenAsync while in this state, - // then the retry task will not be completed - - throw ADP.ConnectionAlreadyOpen(State); - } - - // we are completing an asynchronous open - Debug.Assert(retry.Task.Status == TaskStatus.RanToCompletion, "retry task must be completed successfully"); - DbConnectionInternal openConnection = retry.Task.Result; - if (null == openConnection) - { - connectionFactory.SetInnerConnectionTo(outerConnection, this); - throw ADP.InternalConnectionError(ADP.ConnectionError.GetConnectionReturnsNull); - } - connectionFactory.SetInnerConnectionEvent(outerConnection, openConnection); - - return true; - } - } - - sealed internal class DbConnectionClosedNeverOpened : DbConnectionClosed - { - // Closed Connection, Has Never Been Opened - - internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionClosedNeverOpened(); // singleton object - - private DbConnectionClosedNeverOpened() : base(ConnectionState.Closed, false, true) - { - } - } - - sealed internal class DbConnectionClosedPreviouslyOpened : DbConnectionClosed + internal abstract partial class DbConnectionClosed : DbConnectionInternal { - // Closed Connection, Has Previously Been Opened - - internal static readonly DbConnectionInternal SingletonInstance = new DbConnectionClosedPreviouslyOpened(); // singleton object - - private DbConnectionClosedPreviouslyOpened() : base(ConnectionState.Closed, true, true) - { - } + protected override void Activate(Transaction transaction) => throw ADP.ClosedConnectionError(); - internal override bool TryReplaceConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) - { - return TryOpenConnection(outerConnection, connectionFactory, retry, userOptions); - } + public override void EnlistTransaction(Transaction transaction) => throw ADP.ClosedConnectionError(); } } diff --git a/src/System.Data.SqlClient/src/System/Data/ProviderBase/DbConnectionFactory.cs b/src/System.Data.SqlClient/src/System/Data/ProviderBase/DbConnectionFactory.cs index bf448231e3e9..c343a914dbaf 100644 --- a/src/System.Data.SqlClient/src/System/Data/ProviderBase/DbConnectionFactory.cs +++ b/src/System.Data.SqlClient/src/System/Data/ProviderBase/DbConnectionFactory.cs @@ -2,11 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. - - -//------------------------------------------------------------------------------ - -using System.Collections.Generic; using System.Diagnostics; using System.Data.Common; using System.Threading; @@ -14,142 +9,8 @@ namespace System.Data.ProviderBase { - internal abstract class DbConnectionFactory + internal abstract partial class DbConnectionFactory { - private Dictionary _connectionPoolGroups; - private readonly List _poolsToRelease; - private readonly List _poolGroupsToRelease; - private readonly Timer _pruningTimer; - private const int PruningDueTime = 4 * 60 * 1000; // 4 minutes - private const int PruningPeriod = 30 * 1000; // thirty seconds - - - // s_pendingOpenNonPooled is an array of tasks used to throttle creation of non-pooled connections to - // a maximum of Environment.ProcessorCount at a time. - private static uint s_pendingOpenNonPooledNext = 0; - private static Task[] s_pendingOpenNonPooled = new Task[Environment.ProcessorCount]; - private static Task s_completedTask; - - protected DbConnectionFactory() - { - _connectionPoolGroups = new Dictionary(); - _poolsToRelease = new List(); - _poolGroupsToRelease = new List(); - _pruningTimer = CreatePruningTimer(); - } - - - abstract public DbProviderFactory ProviderFactory - { - get; - } - - - public void ClearAllPools() - { - Dictionary connectionPoolGroups = _connectionPoolGroups; - foreach (KeyValuePair entry in connectionPoolGroups) - { - DbConnectionPoolGroup poolGroup = entry.Value; - if (null != poolGroup) - { - poolGroup.Clear(); - } - } - } - - public void ClearPool(DbConnection connection) - { - ADP.CheckArgumentNull(connection, nameof(connection)); - - DbConnectionPoolGroup poolGroup = GetConnectionPoolGroup(connection); - if (null != poolGroup) - { - poolGroup.Clear(); - } - } - - public void ClearPool(DbConnectionPoolKey key) - { - Debug.Assert(key != null, "key cannot be null"); - ADP.CheckArgumentNull(key.ConnectionString, nameof(key) + "." + nameof(key.ConnectionString)); - - DbConnectionPoolGroup poolGroup; - Dictionary connectionPoolGroups = _connectionPoolGroups; - if (connectionPoolGroups.TryGetValue(key, out poolGroup)) - { - poolGroup.Clear(); - } - } - - internal virtual DbConnectionPoolProviderInfo CreateConnectionPoolProviderInfo(DbConnectionOptions connectionOptions) - { - return null; - } - - - internal DbConnectionInternal CreateNonPooledConnection(DbConnection owningConnection, DbConnectionPoolGroup poolGroup, DbConnectionOptions userOptions) - { - Debug.Assert(null != owningConnection, "null owningConnection?"); - Debug.Assert(null != poolGroup, "null poolGroup?"); - - DbConnectionOptions connectionOptions = poolGroup.ConnectionOptions; - DbConnectionPoolGroupProviderInfo poolGroupProviderInfo = poolGroup.ProviderInfo; - DbConnectionPoolKey poolKey = poolGroup.PoolKey; - - DbConnectionInternal newConnection = CreateConnection(connectionOptions, poolKey, poolGroupProviderInfo, null, owningConnection, userOptions); - if (null != newConnection) - { - newConnection.MakeNonPooledObject(owningConnection); - } - return newConnection; - } - - internal DbConnectionInternal CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions) - { - Debug.Assert(null != pool, "null pool?"); - DbConnectionPoolGroupProviderInfo poolGroupProviderInfo = pool.PoolGroup.ProviderInfo; - - DbConnectionInternal newConnection = CreateConnection(options, poolKey, poolGroupProviderInfo, pool, owningObject, userOptions); - if (null != newConnection) - { - newConnection.MakePooledConnection(pool); - } - return newConnection; - } - - virtual internal DbConnectionPoolGroupProviderInfo CreateConnectionPoolGroupProviderInfo(DbConnectionOptions connectionOptions) - { - return null; - } - - private Timer CreatePruningTimer() => - ADP.UnsafeCreateTimer( - new TimerCallback(PruneConnectionPoolGroups), - null, - PruningDueTime, - PruningPeriod); - - protected DbConnectionOptions FindConnectionOptions(DbConnectionPoolKey key) - { - Debug.Assert(key != null, "key cannot be null"); - if (!string.IsNullOrEmpty(key.ConnectionString)) - { - DbConnectionPoolGroup connectionPoolGroup; - Dictionary connectionPoolGroups = _connectionPoolGroups; - if (connectionPoolGroups.TryGetValue(key, out connectionPoolGroup)) - { - return connectionPoolGroup.ConnectionOptions; - } - } - return null; - } - - private static Task GetCompletedTask() - { - Debug.Assert(Monitor.IsEntered(s_pendingOpenNonPooled), $"Expected {nameof(s_pendingOpenNonPooled)} lock to be held."); - return s_completedTask ?? (s_completedTask = Task.FromResult(null)); - } internal bool TryGetConnection(DbConnection owningConnection, TaskCompletionSource retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, out DbConnectionInternal connection) { @@ -321,283 +182,5 @@ internal bool TryGetConnection(DbConnection owningConnection, TaskCompletionSour return true; } - - private DbConnectionPool GetConnectionPool(DbConnection owningObject, DbConnectionPoolGroup connectionPoolGroup) - { - // if poolgroup is disabled, it will be replaced with a new entry - - Debug.Assert(null != owningObject, "null owningObject?"); - Debug.Assert(null != connectionPoolGroup, "null connectionPoolGroup?"); - - // It is possible that while the outer connection object has - // been sitting around in a closed and unused state in some long - // running app, the pruner may have come along and remove this - // the pool entry from the master list. If we were to use a - // pool entry in this state, we would create "unmanaged" pools, - // which would be bad. To avoid this problem, we automagically - // re-create the pool entry whenever it's disabled. - - // however, don't rebuild connectionOptions if no pooling is involved - let new connections do that work - if (connectionPoolGroup.IsDisabled && (null != connectionPoolGroup.PoolGroupOptions)) - { - // reusing existing pool option in case user originally used SetConnectionPoolOptions - DbConnectionPoolGroupOptions poolOptions = connectionPoolGroup.PoolGroupOptions; - - // get the string to hash on again - DbConnectionOptions connectionOptions = connectionPoolGroup.ConnectionOptions; - Debug.Assert(null != connectionOptions, "prevent expansion of connectionString"); - - connectionPoolGroup = GetConnectionPoolGroup(connectionPoolGroup.PoolKey, poolOptions, ref connectionOptions); - Debug.Assert(null != connectionPoolGroup, "null connectionPoolGroup?"); - SetConnectionPoolGroup(owningObject, connectionPoolGroup); - } - DbConnectionPool connectionPool = connectionPoolGroup.GetConnectionPool(this); - return connectionPool; - } - - internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnectionPoolKey key, DbConnectionPoolGroupOptions poolOptions, ref DbConnectionOptions userConnectionOptions) - { - if (string.IsNullOrEmpty(key.ConnectionString)) - { - return (DbConnectionPoolGroup)null; - } - - DbConnectionPoolGroup connectionPoolGroup; - Dictionary connectionPoolGroups = _connectionPoolGroups; - if (!connectionPoolGroups.TryGetValue(key, out connectionPoolGroup) || (connectionPoolGroup.IsDisabled && (null != connectionPoolGroup.PoolGroupOptions))) - { - // If we can't find an entry for the connection string in - // our collection of pool entries, then we need to create a - // new pool entry and add it to our collection. - - DbConnectionOptions connectionOptions = CreateConnectionOptions(key.ConnectionString, userConnectionOptions); - if (null == connectionOptions) - { - throw ADP.InternalConnectionError(ADP.ConnectionError.ConnectionOptionsMissing); - } - - if (null == userConnectionOptions) - { // we only allow one expansion on the connection string - userConnectionOptions = connectionOptions; - } - - // We don't support connection pooling on Win9x - if (null == poolOptions) - { - if (null != connectionPoolGroup) - { - // reusing existing pool option in case user originally used SetConnectionPoolOptions - poolOptions = connectionPoolGroup.PoolGroupOptions; - } - else - { - // Note: may return null for non-pooled connections - poolOptions = CreateConnectionPoolGroupOptions(connectionOptions); - } - } - - lock (this) - { - connectionPoolGroups = _connectionPoolGroups; - if (!connectionPoolGroups.TryGetValue(key, out connectionPoolGroup)) - { - DbConnectionPoolGroup newConnectionPoolGroup = new DbConnectionPoolGroup(connectionOptions, key, poolOptions); - newConnectionPoolGroup.ProviderInfo = CreateConnectionPoolGroupProviderInfo(connectionOptions); - - // build new dictionary with space for new connection string - Dictionary newConnectionPoolGroups = new Dictionary(1 + connectionPoolGroups.Count); - foreach (KeyValuePair entry in connectionPoolGroups) - { - newConnectionPoolGroups.Add(entry.Key, entry.Value); - } - - // lock prevents race condition with PruneConnectionPoolGroups - newConnectionPoolGroups.Add(key, newConnectionPoolGroup); - connectionPoolGroup = newConnectionPoolGroup; - _connectionPoolGroups = newConnectionPoolGroups; - } - else - { - Debug.Assert(!connectionPoolGroup.IsDisabled, "Disabled pool entry discovered"); - } - } - Debug.Assert(null != connectionPoolGroup, "how did we not create a pool entry?"); - Debug.Assert(null != userConnectionOptions, "how did we not have user connection options?"); - } - else if (null == userConnectionOptions) - { - userConnectionOptions = connectionPoolGroup.ConnectionOptions; - } - return connectionPoolGroup; - } - - - private void PruneConnectionPoolGroups(object state) - { - // First, walk the pool release list and attempt to clear each - // pool, when the pool is finally empty, we dispose of it. If the - // pool isn't empty, it's because there are active connections or - // distributed transactions that need it. - lock (_poolsToRelease) - { - if (0 != _poolsToRelease.Count) - { - DbConnectionPool[] poolsToRelease = _poolsToRelease.ToArray(); - foreach (DbConnectionPool pool in poolsToRelease) - { - if (null != pool) - { - pool.Clear(); - - if (0 == pool.Count) - { - _poolsToRelease.Remove(pool); - } - } - } - } - } - - // Next, walk the pool entry release list and dispose of each - // pool entry when it is finally empty. If the pool entry isn't - // empty, it's because there are active pools that need it. - lock (_poolGroupsToRelease) - { - if (0 != _poolGroupsToRelease.Count) - { - DbConnectionPoolGroup[] poolGroupsToRelease = _poolGroupsToRelease.ToArray(); - foreach (DbConnectionPoolGroup poolGroup in poolGroupsToRelease) - { - if (null != poolGroup) - { - int poolsLeft = poolGroup.Clear(); // may add entries to _poolsToRelease - - if (0 == poolsLeft) - { - _poolGroupsToRelease.Remove(poolGroup); - } - } - } - } - } - - // Finally, we walk through the collection of connection pool entries - // and prune each one. This will cause any empty pools to be put - // into the release list. - lock (this) - { - Dictionary connectionPoolGroups = _connectionPoolGroups; - Dictionary newConnectionPoolGroups = new Dictionary(connectionPoolGroups.Count); - - foreach (KeyValuePair entry in connectionPoolGroups) - { - if (null != entry.Value) - { - Debug.Assert(!entry.Value.IsDisabled, "Disabled pool entry discovered"); - - // entries start active and go idle during prune if all pools are gone - // move idle entries from last prune pass to a queue for pending release - // otherwise process entry which may move it from active to idle - if (entry.Value.Prune()) - { // may add entries to _poolsToRelease - QueuePoolGroupForRelease(entry.Value); - } - else - { - newConnectionPoolGroups.Add(entry.Key, entry.Value); - } - } - } - _connectionPoolGroups = newConnectionPoolGroups; - } - } - - internal void QueuePoolForRelease(DbConnectionPool pool, bool clearing) - { - // Queue the pool up for release -- we'll clear it out and dispose - // of it as the last part of the pruning timer callback so we don't - // do it with the pool entry or the pool collection locked. - Debug.Assert(null != pool, "null pool?"); - - // set the pool to the shutdown state to force all active - // connections to be automatically disposed when they - // are returned to the pool - pool.Shutdown(); - - lock (_poolsToRelease) - { - if (clearing) - { - pool.Clear(); - } - _poolsToRelease.Add(pool); - } - } - - internal void QueuePoolGroupForRelease(DbConnectionPoolGroup poolGroup) - { - Debug.Assert(null != poolGroup, "null poolGroup?"); - - lock (_poolGroupsToRelease) - { - _poolGroupsToRelease.Add(poolGroup); - } - } - - virtual protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) - { - return CreateConnection(options, poolKey, poolGroupProviderInfo, pool, owningConnection); - } - - internal DbMetaDataFactory GetMetaDataFactory(DbConnectionPoolGroup connectionPoolGroup, DbConnectionInternal internalConnection) - { - Debug.Assert(connectionPoolGroup != null, "connectionPoolGroup may not be null."); - - // get the matadatafactory from the pool entry. If it does not already have one - // create one and save it on the pool entry - DbMetaDataFactory metaDataFactory = connectionPoolGroup.MetaDataFactory; - - // consider serializing this so we don't construct multiple metadata factories - // if two threads happen to hit this at the same time. One will be GC'd - if (metaDataFactory == null) - { - bool allowCache = false; - metaDataFactory = CreateMetaDataFactory(internalConnection, out allowCache); - if (allowCache) - { - connectionPoolGroup.MetaDataFactory = metaDataFactory; - } - } - return metaDataFactory; - } - - protected virtual DbMetaDataFactory CreateMetaDataFactory(DbConnectionInternal internalConnection, out bool cacheMetaDataFactory) - { - // providers that support GetSchema must override this with a method that creates a meta data - // factory appropriate for them. - cacheMetaDataFactory = false; - throw ADP.NotSupported(); - } - - abstract protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection); - - abstract protected DbConnectionOptions CreateConnectionOptions(string connectionString, DbConnectionOptions previous); - - abstract protected DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions(DbConnectionOptions options); - - abstract internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnection connection); - - abstract internal DbConnectionInternal GetInnerConnection(DbConnection connection); - - - abstract internal void PermissionDemand(DbConnection outerConnection); - - abstract internal void SetConnectionPoolGroup(DbConnection outerConnection, DbConnectionPoolGroup poolGroup); - - abstract internal void SetInnerConnectionEvent(DbConnection owningObject, DbConnectionInternal to); - - abstract internal bool SetInnerConnectionFrom(DbConnection owningObject, DbConnectionInternal to, DbConnectionInternal from); - - abstract internal void SetInnerConnectionTo(DbConnection owningObject, DbConnectionInternal to); } } diff --git a/src/System.Data.SqlClient/src/System/Data/ProviderBase/DbConnectionInternal.cs b/src/System.Data.SqlClient/src/System/Data/ProviderBase/DbConnectionInternal.cs index 3ac471cac9c9..0b08a9c102f7 100644 --- a/src/System.Data.SqlClient/src/System/Data/ProviderBase/DbConnectionInternal.cs +++ b/src/System.Data.SqlClient/src/System/Data/ProviderBase/DbConnectionInternal.cs @@ -3,40 +3,18 @@ // See the LICENSE file in the project root for more information. - -//------------------------------------------------------------------------------ - using System.Data.Common; -using System.Data.SqlClient; using System.Diagnostics; using System.Threading; -using System.Threading.Tasks; using System.Transactions; namespace System.Data.ProviderBase { - internal abstract class DbConnectionInternal + internal abstract partial class DbConnectionInternal { - internal static readonly StateChangeEventArgs StateChangeClosed = new StateChangeEventArgs(ConnectionState.Open, ConnectionState.Closed); - internal static readonly StateChangeEventArgs StateChangeOpen = new StateChangeEventArgs(ConnectionState.Closed, ConnectionState.Open); - - private readonly bool _allowSetConnectionString; - private readonly bool _hidePassword; - private readonly ConnectionState _state; - - private readonly WeakReference _owningObject = new WeakReference(null, false); // [usage must be thread safe] the owning object, when not in the pool. (both Pooled and Non-Pooled connections) - - private DbConnectionPool _connectionPool; // the pooler that the connection came from (Pooled connections only) - private DbReferenceCollection _referenceCollection; // collection of objects that we need to notify in some way when we're being deactivated - private int _pooledCount; // [usage must be thread safe] the number of times this object has been pushed into the pool less the number of times it's been popped (0 != inPool) - - private bool _connectionIsDoomed; // true when the connection should no longer be used. - private bool _cannotBePooled; // true when the connection should no longer be pooled. private bool _isInStasis; - private DateTime _createTime; // when the connection was created. - private Transaction _enlistedTransaction; // [usage must be thread-safe] the transaction that we're enlisted in, either manually or automatically // _enlistedTransaction is a clone, so that transaction information can be queried even if the original transaction object is disposed. @@ -45,39 +23,6 @@ internal abstract class DbConnectionInternal // Also, this reference should not be disposed, since we aren't taking ownership of it. private Transaction _enlistedTransactionOriginal; -#if DEBUG - private int _activateCount; // debug only counter to verify activate/deactivates are in sync. -#endif //DEBUG - - protected DbConnectionInternal() : this(ConnectionState.Open, true, false) - { - } - - // Constructor for internal connections - internal DbConnectionInternal(ConnectionState state, bool hidePassword, bool allowSetConnectionString) - { - _allowSetConnectionString = allowSetConnectionString; - _hidePassword = hidePassword; - _state = state; - } - - internal bool AllowSetConnectionString - { - get - { - return _allowSetConnectionString; - } - } - - internal bool CanBePooled - { - get - { - bool flag = (!_connectionIsDoomed && !_cannotBePooled && !_owningObject.IsAlive); - return flag; - } - } - protected internal Transaction EnlistedTransaction { get @@ -237,74 +182,6 @@ virtual internal bool IsTransactionRoot } } - protected internal bool IsConnectionDoomed - { - get - { - return _connectionIsDoomed; - } - } - - internal bool IsEmancipated - { - get - { - // NOTE: There are race conditions between PrePush, PostPop and this - // property getter -- only use this while this object is locked; - // (DbConnectionPool.Clear and ReclaimEmancipatedObjects - // do this for us) - - // The functionality is as follows: - // - // _pooledCount is incremented when the connection is pushed into the pool - // _pooledCount is decremented when the connection is popped from the pool - // _pooledCount is set to -1 when the connection is not pooled (just in case...) - // - // That means that: - // - // _pooledCount > 1 connection is in the pool multiple times (This should not happen) - // _pooledCount == 1 connection is in the pool - // _pooledCount == 0 connection is out of the pool - // _pooledCount == -1 connection is not a pooled connection; we shouldn't be here for non-pooled connections. - // _pooledCount < -1 connection out of the pool multiple times - // - // Now, our job is to return TRUE when the connection is out - // of the pool and it's owning object is no longer around to - // return it. - - bool value = (_pooledCount < 1) && !_owningObject.IsAlive; - return value; - } - } - - internal bool IsInPool - { - get - { - Debug.Assert(_pooledCount <= 1 && _pooledCount >= -1, "Pooled count for object is invalid"); - return (_pooledCount == 1); - } - } - - - protected internal object Owner - { - // We use a weak reference to the owning object so we can identify when - // it has been garbage collected without thowing exceptions. - get - { - return _owningObject.Target; - } - } - - internal DbConnectionPool Pool - { - get - { - return _connectionPool; - } - } - virtual protected bool ReadyToPrepareTransaction { get @@ -313,44 +190,6 @@ virtual protected bool ReadyToPrepareTransaction } } - protected internal DbReferenceCollection ReferenceCollection - { - get - { - return _referenceCollection; - } - } - - abstract public string ServerVersion - { - get; - } - - // this should be abstract but until it is added to all the providers virtual will have to do - virtual public string ServerVersionNormalized - { - get - { - throw ADP.NotSupported(); - } - } - - public bool ShouldHidePassword - { - get - { - return _hidePassword; - } - } - - public ConnectionState State - { - get - { - return _state; - } - } - abstract protected void Activate(Transaction transaction); internal void ActivateConnection(Transaction transaction) @@ -361,26 +200,6 @@ internal void ActivateConnection(Transaction transaction) Activate(transaction); } - internal void AddWeakReference(object value, int tag) - { - if (null == _referenceCollection) - { - _referenceCollection = CreateReferenceCollection(); - if (null == _referenceCollection) - { - throw ADP.InternalError(ADP.InternalErrorCode.CreateReferenceCollectionReturnedNull); - } - } - _referenceCollection.Add(value, tag); - } - - abstract public DbTransaction BeginTransaction(IsolationLevel il); - - virtual public void ChangeDatabase(string value) - { - throw ADP.MethodNotImplemented(); - } - internal virtual void CloseConnection(DbConnection owningObject, DbConnectionFactory connectionFactory) { // The implementation here is the implementation required for the @@ -487,57 +306,6 @@ internal virtual void CloseConnection(DbConnection owningObject, DbConnectionFac } } - virtual internal void PrepareForReplaceConnection() - { - // By default, there is no preparation required - } - - virtual protected void PrepareForCloseConnection() - { - // By default, there is no preparation required - } - - virtual protected object ObtainAdditionalLocksForClose() - { - return null; // no additional locks in default implementation - } - - virtual protected void ReleaseAdditionalLocksForClose(object lockToken) - { - // no additional locks in default implementation - } - - virtual protected DbReferenceCollection CreateReferenceCollection() - { - throw ADP.InternalError(ADP.InternalErrorCode.AttemptingToConstructReferenceCollectionOnStaticObject); - } - - abstract protected void Deactivate(); - - internal void DeactivateConnection() - { - // Internal method called from the connection pooler so we don't expose - // the Deactivate method publicly. - -#if DEBUG - int activateCount = Interlocked.Decrement(ref _activateCount); -#endif // DEBUG - - - if (!_connectionIsDoomed && Pool.UseLoadBalancing) - { - // If we're not already doomed, check the connection's lifetime and - // doom it if it's lifetime has elapsed. - - DateTime now = DateTime.UtcNow; - if ((now.Ticks - _createTime.Ticks) > Pool.LoadBalanceTimeout.Ticks) - { - DoNotPoolThisConnection(); - } - } - Deactivate(); - } - virtual internal void DelegatedTransactionEnded() { // Called by System.Transactions when the delegated transaction has @@ -604,188 +372,9 @@ public virtual void Dispose() enlistedTransaction.Dispose(); } } - - protected internal void DoNotPoolThisConnection() - { - _cannotBePooled = true; - } - - /// Ensure that this connection cannot be put back into the pool. - protected internal void DoomThisConnection() - { - _connectionIsDoomed = true; - } - + abstract public void EnlistTransaction(Transaction transaction); - protected internal virtual DataTable GetSchema(DbConnectionFactory factory, DbConnectionPoolGroup poolGroup, DbConnection outerConnection, string collectionName, string[] restrictions) - { - Debug.Assert(outerConnection != null, "outerConnection may not be null."); - - DbMetaDataFactory metaDataFactory = factory.GetMetaDataFactory(poolGroup, this); - Debug.Assert(metaDataFactory != null, "metaDataFactory may not be null."); - - return metaDataFactory.GetSchema(outerConnection, collectionName, restrictions); - } - - internal void MakeNonPooledObject(object owningObject) - { - // Used by DbConnectionFactory to indicate that this object IS NOT part of - // a connection pool. - - _connectionPool = null; - _owningObject.Target = owningObject; - _pooledCount = -1; - } - - internal void MakePooledConnection(DbConnectionPool connectionPool) - { - // Used by DbConnectionFactory to indicate that this object IS part of - // a connection pool. - _createTime = DateTime.UtcNow; - - _connectionPool = connectionPool; - } - - internal void NotifyWeakReference(int message) - { - DbReferenceCollection referenceCollection = ReferenceCollection; - if (null != referenceCollection) - { - referenceCollection.Notify(message); - } - } - - internal virtual void OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory) - { - if (!TryOpenConnection(outerConnection, connectionFactory, null, null)) - { - throw ADP.InternalError(ADP.InternalErrorCode.SynchronousConnectReturnedPending); - } - } - - /// The default implementation is for the open connection objects, and - /// it simply throws. Our private closed-state connection objects - /// override this and do the correct thing. - // User code should either override DbConnectionInternal.Activate when it comes out of the pool - // or override DbConnectionFactory.CreateConnection when the connection is created for non-pooled connections - internal virtual bool TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) - { - throw ADP.ConnectionAlreadyOpen(State); - } - - internal virtual bool TryReplaceConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) - { - throw ADP.MethodNotImplemented(); - } - - protected bool TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource retry, DbConnectionOptions userOptions) - { - // ?->Connecting: prevent set_ConnectionString during Open - if (connectionFactory.SetInnerConnectionFrom(outerConnection, DbConnectionClosedConnecting.SingletonInstance, this)) - { - DbConnectionInternal openConnection = null; - try - { - connectionFactory.PermissionDemand(outerConnection); - if (!connectionFactory.TryGetConnection(outerConnection, retry, userOptions, this, out openConnection)) - { - return false; - } - } - catch - { - // This should occur for all exceptions, even ADP.UnCatchableExceptions. - connectionFactory.SetInnerConnectionTo(outerConnection, this); - throw; - } - if (null == openConnection) - { - connectionFactory.SetInnerConnectionTo(outerConnection, this); - throw ADP.InternalConnectionError(ADP.ConnectionError.GetConnectionReturnsNull); - } - connectionFactory.SetInnerConnectionEvent(outerConnection, openConnection); - } - - return true; - } - - internal void PrePush(object expectedOwner) - { - // Called by DbConnectionPool when we're about to be put into it's pool, we - // take this opportunity to ensure ownership and pool counts are legit. - - // IMPORTANT NOTE: You must have taken a lock on the object before - // you call this method to prevent race conditions with Clear and - // ReclaimEmancipatedObjects. - - //3 // The following tests are retail assertions of things we can't allow to happen. - if (null == expectedOwner) - { - if (null != _owningObject.Target) - { - throw ADP.InternalError(ADP.InternalErrorCode.UnpooledObjectHasOwner); // new unpooled object has an owner - } - } - else if (_owningObject.Target != expectedOwner) - { - throw ADP.InternalError(ADP.InternalErrorCode.UnpooledObjectHasWrongOwner); // unpooled object has incorrect owner - } - if (0 != _pooledCount) - { - throw ADP.InternalError(ADP.InternalErrorCode.PushingObjectSecondTime); // pushing object onto stack a second time - } - _pooledCount++; - _owningObject.Target = null; // NOTE: doing this and checking for InternalError.PooledObjectHasOwner degrades the close by 2% - } - - internal void PostPop(object newOwner) - { - // Called by DbConnectionPool right after it pulls this from it's pool, we - // take this opportunity to ensure ownership and pool counts are legit. - - Debug.Assert(!IsEmancipated, "pooled object not in pool"); - - // When another thread is clearing this pool, it - // will doom all connections in this pool without prejudice which - // causes the following assert to fire, which really mucks up stress - // against checked bits. The assert is benign, so we're commenting - // it out. - //Debug.Assert(CanBePooled, "pooled object is not poolable"); - - // IMPORTANT NOTE: You must have taken a lock on the object before - // you call this method to prevent race conditions with Clear and - // ReclaimEmancipatedObjects. - - if (null != _owningObject.Target) - { - throw ADP.InternalError(ADP.InternalErrorCode.PooledObjectHasOwner); // pooled connection already has an owner! - } - _owningObject.Target = newOwner; - _pooledCount--; - //3 // The following tests are retail assertions of things we can't allow to happen. - if (null != Pool) - { - if (0 != _pooledCount) - { - throw ADP.InternalError(ADP.InternalErrorCode.PooledObjectInPoolMoreThanOnce); // popping object off stack with multiple pooledCount - } - } - else if (-1 != _pooledCount) - { - throw ADP.InternalError(ADP.InternalErrorCode.NonPooledObjectUsedMoreThanOnce); // popping object off stack with multiple pooledCount - } - } - - internal void RemoveWeakReference(object value) - { - DbReferenceCollection referenceCollection = ReferenceCollection; - if (null != referenceCollection) - { - referenceCollection.Remove(value); - } - } - // Cleanup connection's transaction-specific structures (currently used by Delegated transaction). // This is a separate method because cleanup can be triggered in multiple ways for a delegated // transaction. @@ -876,16 +465,5 @@ private void TerminateStasis(bool returningToPool) { _isInStasis = false; } - - /// - /// When overridden in a derived class, will check if the underlying connection is still actually alive - /// - /// If true an exception will be thrown if the connection is dead instead of returning true\false - /// (this allows the caller to have the real reason that the connection is not alive (e.g. network error, etc)) - /// True if the connection is still alive, otherwise false (If not overridden, then always true) - internal virtual bool IsConnectionAlive(bool throwOnException = false) - { - return true; - } } } diff --git a/src/System.Data.SqlClient/src/System/Data/ProviderBase/TimeoutTimer.cs b/src/System.Data.SqlClient/src/System/Data/ProviderBase/TimeoutTimer.cs deleted file mode 100644 index 969f399d3470..000000000000 --- a/src/System.Data.SqlClient/src/System/Data/ProviderBase/TimeoutTimer.cs +++ /dev/null @@ -1,170 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// -// Class used to manage timeouts in complex system operations. -// - -using System.Data.Common; -using System.Diagnostics; - -namespace System.Data.ProviderBase -{ - // Purpose: - // Manages determining and tracking timeouts - // - // Intended use: - // Call StartXXXXTimeout() to get a timer with the given expiration point - // Get remaining time in appropriate format to pass to subsystem timeouts - // Check for timeout via IsExpired for checks in managed code. - // Simply abandon to GC when done. - internal class TimeoutTimer - { - //------------------- - // Fields - //------------------- - private long _timerExpire; - private bool _isInfiniteTimeout; - - //------------------- - // Timeout-setting methods - //------------------- - - // Get a new timer that will expire in the given number of seconds - // For input, a value of zero seconds indicates infinite timeout - internal static TimeoutTimer StartSecondsTimeout(int seconds) - { - //-------------------- - // Preconditions: None (seconds must conform to SetTimeoutSeconds requirements) - - //-------------------- - // Method body - var timeout = new TimeoutTimer(); - timeout.SetTimeoutSeconds(seconds); - - //--------------------- - // Postconditions - Debug.Assert(timeout != null); // Need a valid timeouttimer if no error - - return timeout; - } - - // Get a new timer that will expire in the given number of milliseconds - // No current need to support infinite milliseconds timeout - internal static TimeoutTimer StartMillisecondsTimeout(long milliseconds) - { - //-------------------- - // Preconditions - Debug.Assert(0 <= milliseconds); - - //-------------------- - // Method body - var timeout = new TimeoutTimer(); - timeout._timerExpire = checked(ADP.TimerCurrent() + (milliseconds * TimeSpan.TicksPerMillisecond)); - timeout._isInfiniteTimeout = false; - - //--------------------- - // Postconditions - Debug.Assert(timeout != null); // Need a valid timeouttimer if no error - - return timeout; - } - - //------------------- - // Methods for changing timeout - //------------------- - - internal void SetTimeoutSeconds(int seconds) - { - //-------------------- - // Preconditions - Debug.Assert(0 <= seconds || InfiniteTimeout == seconds); // no need to support negative seconds at present - - //-------------------- - // Method body - if (InfiniteTimeout == seconds) - { - _isInfiniteTimeout = true; - } - else - { - // Stash current time + timeout - _timerExpire = checked(ADP.TimerCurrent() + ADP.TimerFromSeconds(seconds)); - _isInfiniteTimeout = false; - } - //--------------------- - // Postconditions:None - } - - //------------------- - // Timeout info properties - //------------------- - - // Indicator for infinite timeout when starting a timer - internal static readonly long InfiniteTimeout = 0; - - // Is this timer in an expired state? - internal bool IsExpired - { - get - { - return !IsInfinite && ADP.TimerHasExpired(_timerExpire); - } - } - - // is this an infinite-timeout timer? - internal bool IsInfinite - { - get - { - return _isInfiniteTimeout; - } - } - - // Special accessor for TimerExpire for use when thunking to legacy timeout methods. - internal long LegacyTimerExpire - { - get - { - return (_isInfiniteTimeout) ? Int64.MaxValue : _timerExpire; - } - } - - // Returns milliseconds remaining trimmed to zero for none remaining - // and long.MaxValue for infinite - // This method should be preferred for internal calculations that are not - // yet common enough to code into the TimeoutTimer class itself. - internal long MillisecondsRemaining - { - get - { - //------------------- - // Preconditions: None - - //------------------- - // Method Body - long milliseconds; - if (_isInfiniteTimeout) - { - milliseconds = long.MaxValue; - } - else - { - milliseconds = ADP.TimerRemainingMilliseconds(_timerExpire); - if (0 > milliseconds) - { - milliseconds = 0; - } - } - - //-------------------- - // Postconditions - Debug.Assert(0 <= milliseconds); // This property guarantees no negative return values - - return milliseconds; - } - } - } -} -