Imported Upstream version 5.16.0.100

Former-commit-id: 38faa55fb9669e35e7d8448b15c25dc447f25767
This commit is contained in:
Xamarin Public Jenkins (auto-signing)
2018-08-07 15:19:03 +00:00
parent 0a9828183b
commit 7d7f676260
4419 changed files with 170950 additions and 90273 deletions

View File

@@ -3,7 +3,7 @@
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
<ItemGroup>
<ProjectReference Include="..\ref\System.Data.Odbc.csproj">
<SupportedFramework>net461;netcoreapp2.0;$(AllXamarinFrameworks)</SupportedFramework>
<SupportedFramework>uap10.0.16299;net461;netcoreapp2.0;$(AllXamarinFrameworks)</SupportedFramework>
</ProjectReference>
<ProjectReference Include="..\src\System.Data.Odbc.csproj" />
</ItemGroup>

View File

@@ -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<DbConnectionInternal> 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<DbConnectionInternal> 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<DbConnectionInternal> retry, DbConnectionOptions userOptions)
{
return TryOpenConnection(outerConnection, connectionFactory, retry, userOptions);
}
internal override bool TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource<DbConnectionInternal> 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<DbConnectionInternal> retry, DbConnectionOptions userOptions)
{
return TryOpenConnection(outerConnection, connectionFactory, retry, userOptions);
}
}
}

View File

@@ -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,143 +9,8 @@ using System.Threading.Tasks;
namespace System.Data.ProviderBase
{
internal abstract class DbConnectionFactory
internal abstract partial class DbConnectionFactory
{
private Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> _connectionPoolGroups;
private readonly List<DbConnectionPool> _poolsToRelease;
private readonly List<DbConnectionPoolGroup> _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<DbConnectionInternal>[] s_pendingOpenNonPooled = new Task<DbConnectionInternal>[Environment.ProcessorCount];
private static Task<DbConnectionInternal> s_completedTask;
protected DbConnectionFactory()
{
_connectionPoolGroups = new Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup>();
_poolsToRelease = new List<DbConnectionPool>();
_poolGroupsToRelease = new List<DbConnectionPoolGroup>();
_pruningTimer = CreatePruningTimer();
}
public abstract DbProviderFactory ProviderFactory
{
get;
}
public void ClearAllPools()
{
Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> connectionPoolGroups = _connectionPoolGroups;
foreach (KeyValuePair<DbConnectionPoolKey, DbConnectionPoolGroup> 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<DbConnectionPoolKey, DbConnectionPoolGroup> 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()
{
TimerCallback callback = new TimerCallback(PruneConnectionPoolGroups);
return new Timer(callback, null, PruningDueTime, PruningPeriod);
}
protected DbConnectionOptions FindConnectionOptions(DbConnectionPoolKey key)
{
Debug.Assert(key != null, "key cannot be null");
if (!string.IsNullOrEmpty(key.ConnectionString))
{
DbConnectionPoolGroup connectionPoolGroup;
Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> connectionPoolGroups = _connectionPoolGroups;
if (connectionPoolGroups.TryGetValue(key, out connectionPoolGroup))
{
return connectionPoolGroup.ConnectionOptions;
}
}
return null;
}
private static Task<DbConnectionInternal> GetCompletedTask()
{
Debug.Assert(Monitor.IsEntered(s_pendingOpenNonPooled), $"Expected {nameof(s_pendingOpenNonPooled)} lock to be held.");
return s_completedTask ?? (s_completedTask = Task.FromResult<DbConnectionInternal>(null));
}
internal bool TryGetConnection(DbConnection owningConnection, TaskCompletionSource<DbConnectionInternal> retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, out DbConnectionInternal connection)
{
Debug.Assert(null != owningConnection, "null owningConnection?");
@@ -309,254 +173,6 @@ namespace System.Data.ProviderBase
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<DbConnectionPoolKey, DbConnectionPoolGroup> 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<DbConnectionPoolKey, DbConnectionPoolGroup> newConnectionPoolGroups = new Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup>(1 + connectionPoolGroups.Count);
foreach (KeyValuePair<DbConnectionPoolKey, DbConnectionPoolGroup> 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<DbConnectionPoolKey, DbConnectionPoolGroup> connectionPoolGroups = _connectionPoolGroups;
Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> newConnectionPoolGroups = new Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup>(connectionPoolGroups.Count);
foreach (KeyValuePair<DbConnectionPoolKey, DbConnectionPoolGroup> 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);
}
}

View File

@@ -9,168 +9,9 @@ using System.Threading.Tasks;
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 @@ namespace System.Data.ProviderBase
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 @@ namespace System.Data.ProviderBase
}
}
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;
}
/// <devdoc>Ensure that this connection cannot be put back into the pool.</devdoc>
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);
}
}
/// <devdoc>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.</devdoc>
// 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<DbConnectionInternal> retry, DbConnectionOptions userOptions)
{
throw ADP.ConnectionAlreadyOpen(State);
}
internal virtual bool TryReplaceConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource<DbConnectionInternal> retry, DbConnectionOptions userOptions)
{
throw ADP.MethodNotImplemented();
}
protected bool TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource<DbConnectionInternal> 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);
}
}
/// <summary>
/// When overridden in a derived class, will check if the underlying connection is still actually alive
/// </summary>
/// <param name="throwOnException">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))</param>
/// <returns>True if the connection is still alive, otherwise false (If not overridden, then always true)</returns>
internal virtual bool IsConnectionAlive(bool throwOnException = false)
{
return true;
}
}
}

View File

@@ -382,9 +382,11 @@ namespace System.Data.ProviderBase
}
private Timer CreateCleanupTimer()
{
return (new Timer(new TimerCallback(this.CleanupCallback), null, _cleanupWait, _cleanupWait));
}
=> ADP.UnsafeCreateTimer(
new TimerCallback(CleanupCallback),
null,
_cleanupWait,
_cleanupWait);
private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)
{

View File

@@ -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<DbConnectionPoolIdentity, DbConnectionPool> _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<DbConnectionPoolIdentity, DbConnectionPool>();
_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<DbConnectionPoolIdentity, DbConnectionPool> oldPoolCollection = null;
lock (this)
{
if (_poolCollection.Count > 0)
{
oldPoolCollection = _poolCollection;
_poolCollection = new ConcurrentDictionary<DbConnectionPoolIdentity, DbConnectionPool>();
}
}
// 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<DbConnectionPoolIdentity, DbConnectionPool>();
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);
}
}
}
}

View File

@@ -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<CollectionEntry>(ref _items, _items.Length * 2);
_lastItemIndex++;
_items[_lastItemIndex].NewTarget(tag, value);
}
_optimisticCount++;
}
}
internal T FindItem<T>(int tag, Func<T, bool> 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);
}
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -0,0 +1,6 @@
# Exposed publicly only in implementation for serialization compat
TypesMustExist : Type 'System.Data.Odbc.ODBC32' does not exist in the implementation but it does exist in the contract.
# Cannot be exposed in the ref yet as it is new API that doesn't exist in netfx
MembersMustExist : Member 'System.Data.Odbc.OdbcParameter.Offset.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'System.Data.Odbc.OdbcParameter.Offset.set(System.Int32)' does not exist in the implementation but it does exist in the contract.

File diff suppressed because it is too large Load Diff

View File

@@ -32,6 +32,9 @@
<Compile Include="$(CommonPath)\System\Data\Common\AdapterUtil.cs">
<Link>Common\System\Data\Common\AdapterUtil.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\System\Data\Common\AdapterUtil.Drivers.cs">
<Link>System\Data\Common\AdapterUtil.Drivers.cs</Link>
</Compile>
<Compile Include="Common\System\Data\Common\AdapterUtil.Odbc.cs" />
<Compile Include="Common\System\Data\Common\DbConnectionOptions.cs" />
<Compile Include="$(CommonPath)\System\Data\Common\DbConnectionOptions.Common.cs">
@@ -49,24 +52,41 @@
<Compile Include="Common\System\Data\Common\SafeNativeMethods.cs" />
<Compile Include="Common\System\Data\DataStorage.cs" />
<Compile Include="Common\System\Data\ProviderBase\DbBuffer.cs" />
<Compile Include="Common\System\Data\ProviderBase\DbMetaDataFactory.cs" />
<Compile Include="Common\System\Data\ProviderBase\TimeoutTimer.cs" />
<Compile Include="$(CommonPath)\System\Data\Common\FieldNameLookup.cs">
<Link>Common\System\Data\ProviderBase\FieldNameLookup.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\System\Data\Common\BasicFieldNameLookup.cs">
<Link>Common\System\Data\ProviderBase\BasicFieldNameLookup.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\System\Data\ProviderBase\DbConnectionInternal.cs">
<Link>System\Data\ProviderBase\DbConnectionInternal.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\System\Data\ProviderBase\DbConnectionFactory.cs">
<Link>System\Data\ProviderBase\DbConnectionFactory.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\System\Data\ProviderBase\DbConnectionPoolGroup.cs">
<Link>System\Data\ProviderBase\DbConnectionPoolGroup.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\System\Data\ProviderBase\TimeoutTimer.cs">
<Link>System\Data\ProviderBase\TimeoutTimer.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\System\Data\ProviderBase\DbReferenceCollection.cs">
<Link>System\Data\ProviderBase\DbReferenceCollection.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\System\Data\ProviderBase\DbMetaDataFactory.cs">
<Link>System\Data\ProviderBase\DbMetaDataFactory.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\System\Data\ProviderBase\DbConnectionClosed.cs">
<Link>System\Data\ProviderBase\DbConnectionClosed.cs</Link>
</Compile>
<Compile Include="Common\System\Data\ProviderBase\DbConnectionClosed.cs" />
<Compile Include="Common\System\Data\ProviderBase\DbConnectionFactory.cs" />
<Compile Include="Common\System\Data\ProviderBase\DbConnectionInternal.cs" />
<Compile Include="Common\System\Data\ProviderBase\DbConnectionPool.cs" />
<Compile Include="Common\System\Data\ProviderBase\DbConnectionPoolGroup.cs" />
<Compile Include="Common\System\Data\ProviderBase\DbConnectionPoolGroupProviderInfo.cs" />
<Compile Include="Common\System\Data\ProviderBase\DbConnectionPoolIdentity.cs" />
<Compile Include="Common\System\Data\ProviderBase\DbConnectionPoolOptions.cs" />
<Compile Include="Common\System\Data\ProviderBase\DbConnectionPoolProviderInfo.cs" />
<Compile Include="Common\System\Data\ProviderBase\DbReferenceCollection.cs" />
<Compile Include="System\Data\Odbc\DbDataRecord.cs" />
<Compile Include="System\Data\Odbc\Odbc32.cs" />
<Compile Include="System\Data\Odbc\OdbcCommand.cs">
@@ -179,6 +199,11 @@
<ItemGroup Condition="$(TargetGroup.StartsWith('netcoreapp'))">
<Reference Include="System.Transactions.Local" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\System.Data.Odbc.OdbcMetaData.xml">
<LogicalName>System.Data.Odbc.OdbcMetaData.xml</LogicalName>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Include="DatabaseSetupInstructions.md" />
</ItemGroup>

View File

@@ -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 @@ namespace System.Data.Odbc
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);

View File

@@ -175,6 +175,22 @@ namespace System.Data.Odbc
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)
{

View File

@@ -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
{

View File

@@ -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<InvalidOperationException>(() => connection.GetSchema());
}
}
}
}

View File

@@ -16,6 +16,7 @@
<Compile Include="Helpers.cs" />
<Compile Include="IntegrationTestBase.cs" />
<Compile Include="CommandBuilderTests.cs" />
<Compile Include="OdbcConnectionSchemaTests.cs" />
<Compile Include="ReaderTests.cs" />
<Compile Include="SmokeTest.cs" />
<Compile Include="TestCommon\DataTestUtility.cs" />
@@ -50,4 +51,4 @@
<Compile Include="ConnectionStrings.Unix.cs" />
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
</Project>
</Project>