Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Feature | Introduce "Pool Idle Timeout" connection property #348

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -585,13 +585,24 @@ False
<format type="text/markdown"><![CDATA[

## Remarks
When connection pooling is enabled and a timeout error or other login error occurs, an exception will be thrown and subsequent connection attempts will fail for the next five seconds, the "blocking period". If the application attempts to connect within the blocking period, the first exception will be thrown again. Subsequent failures after a blocking period ends will result in a new blocking period that is twice as long as the previous blocking period, up to a maximum of one minute.
When connection pooling is enabled and a timeout error or other login error occurs, an exception will be thrown and subsequent connection attempts will fail for the next five seconds, the "blocking period". If the application attempts to connect within the blocking period, the first exception will be thrown again. Subsequent failures after a blocking period ends will result in a new blocking period that is twice as long as the previous blocking period, up to a maximum of one minute.

Attempting to connect to Azure SQL databases can fail with transient errors which are typically recovered within a few seconds. However, with the connection pool blocking period behavior, you may not be able to reach your database for extensive periods even though the database is available. This is especially problematic for apps that need to render fast. The **PoolBlockingPeriod** enables you to select the blocking period best suited for your app. See the <xref:Microsoft.Data.SqlClient.PoolBlockingPeriod> enumeration for available settings.

]]></format>
</remarks>
</PoolBlockingPeriod>
<PoolIdleTimeout>
<summary>Gets or sets the maximum length of time (in seconds) a connection can be idle in the pool before it is automatically closed.</summary>
<value>The value of the <see cref="P:Microsoft.Data.SqlClient.SqlConnectionStringBuilder.PoolIdleTimeout" /> property, or randomly generated value between 4-8 minutes if no value has been supplied. Acceptable value range: 0 to <see cref="P:System.Int32.MaxValue" /></value>
<remarks>
<format type="text/markdown"><![CDATA[

## Remarks
This property corresponds to the "Pool Idle Timeout" or "pool idle timeout" keys within the connection string.
]]></format>
</remarks>
</PoolIdleTimeout>
<Pooling>
<summary>Gets or sets a Boolean value that indicates whether the connection will be pooled or explicitly opened every time that the connection is requested.</summary>
<value>The value of the <see cref="P:Microsoft.Data.SqlClient.SqlConnectionStringBuilder.Pooling" /> property, or <see langword="true" /> if none has been supplied.</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,10 @@ public SqlConnectionStringBuilder(string connectionString) { }
[System.ComponentModel.DisplayNameAttribute("Min Pool Size")]
[System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)]
public int MinPoolSize { get { throw null; } set { } }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml' path='docs/members[@name="SqlConnectionStringBuilder"]/PoolIdleTimeout/*'/>
[System.ComponentModel.DisplayNameAttribute("Pool Idle Timeout")]
[System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)]
public int PoolIdleTimeout { get { throw null; } set { } }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml' path='docs/members[@name="SqlConnectionStringBuilder"]/MultipleActiveResultSets/*'/>
[System.ComponentModel.DisplayNameAttribute("MultipleActiveResultSets")]
[System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ internal static partial class ADP

internal const CompareOptions DefaultCompareOptions = CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase;
internal const int DefaultConnectionTimeout = DbConnectionStringDefaults.ConnectTimeout;

internal const int DefaultPoolIdleTimeout = DbConnectionStringDefaults.PoolIdleTimeout;

static partial void TraceException(string trace, Exception e);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,7 @@ internal static partial class DbConnectionStringDefaults
internal const bool MultiSubnetFailover = false;
internal const int MaxPoolSize = 100;
internal const int MinPoolSize = 0;
internal const int PoolIdleTimeout = -1;
internal const int PacketSize = 8000;
internal const string Password = "";
internal const bool PersistSecurityInfo = false;
Expand Down Expand Up @@ -698,6 +699,7 @@ internal static partial class DbConnectionStringKeywords
internal const string MaxPoolSize = "Max Pool Size";
internal const string Pooling = "Pooling";
internal const string MinPoolSize = "Min Pool Size";
internal const string PoolIdleTimeout = "Pool Idle Timeout";
#if netcoreapp
internal const string PoolBlockingPeriod = "PoolBlockingPeriod";
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ internal WaitHandle[] GetHandles(bool withCreate)
private static readonly Random s_random = new Random(5101977); // Value obtained from Dave Driver

private readonly int _cleanupWait;
private readonly int _poolIdleTimeout;
private readonly DbConnectionPoolIdentity _identity;

private readonly DbConnectionFactory _connectionFactory;
Expand Down Expand Up @@ -399,17 +400,27 @@ internal DbConnectionPool(

_state = State.Initializing;

lock (s_random)
{ // Random.Next is not thread-safe
_cleanupWait = s_random.Next(12, 24) * 10 * 1000; // 2-4 minutes in 10 sec intervals
}

_connectionFactory = connectionFactory;
_connectionPoolGroup = connectionPoolGroup;
_connectionPoolGroupOptions = connectionPoolGroup.PoolGroupOptions;
_connectionPoolProviderInfo = connectionPoolProviderInfo;
_identity = identity;

_poolIdleTimeout = _connectionPoolGroupOptions.PoolIdleTimeout;

if (_poolIdleTimeout > -1)
{
// Use PoolIdleTimout (in seconds) to create _cleanupWait timer
_cleanupWait = _poolIdleTimeout * 1000;
}
else
{
lock (s_random)
{ // Random.Next is not thread-safe
_cleanupWait = s_random.Next(12, 24) * 10 * 1000; // 2-4 minutes in 10 sec intervals
}
}

_waitHandles = new PoolWaitHandles();

_errorWait = ERROR_WAIT_DEFAULT;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ internal sealed class DbConnectionPoolGroupOptions
private readonly bool _poolByIdentity;
private readonly int _minPoolSize;
private readonly int _maxPoolSize;
private readonly int _poolIdleTimeout;
private readonly int _creationTimeout;
private readonly TimeSpan _loadBalanceTimeout;
private readonly bool _hasTransactionAffinity;
Expand All @@ -20,6 +21,7 @@ public DbConnectionPoolGroupOptions(
bool poolByIdentity,
int minPoolSize,
int maxPoolSize,
int poolIdleTimeout,
int creationTimeout,
int loadBalanceTimeout,
bool hasTransactionAffinity
Expand All @@ -28,6 +30,7 @@ bool hasTransactionAffinity
_poolByIdentity = poolByIdentity;
_minPoolSize = minPoolSize;
_maxPoolSize = maxPoolSize;
_poolIdleTimeout = poolIdleTimeout;
_creationTimeout = creationTimeout;

if (0 != loadBalanceTimeout)
Expand Down Expand Up @@ -59,6 +62,10 @@ public int MinPoolSize
{
get { return _minPoolSize; }
}
public int PoolIdleTimeout
{
get { return _poolIdleTimeout; }
}
public bool PoolByIdentity
{
get { return _poolByIdentity; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ override protected DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions
opt.IntegratedSecurity,
opt.MinPoolSize,
opt.MaxPoolSize,
opt.PoolIdleTimeout,
connectionTimeout,
opt.LoadBalanceTimeout,
opt.Enlist);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ internal static partial class DEFAULT
internal const bool MARS = false;
internal const int Max_Pool_Size = 100;
internal const int Min_Pool_Size = 0;
internal const int Pool_Idle_Timeout = ADP.DefaultPoolIdleTimeout;
internal const bool MultiSubnetFailover = DbConnectionStringDefaults.MultiSubnetFailover;
internal const int Packet_Size = 8000;
internal const string Password = "";
Expand Down Expand Up @@ -81,6 +82,7 @@ internal static class KEY
internal const string MARS = "multipleactiveresultsets";
internal const string Max_Pool_Size = "max pool size";
internal const string Min_Pool_Size = "min pool size";
internal const string Pool_Idle_Timeout = "pool idle timeout";
internal const string MultiSubnetFailover = "multisubnetfailover";
internal const string Network_Library = "network library";
internal const string Packet_Size = "packet size";
Expand Down Expand Up @@ -196,6 +198,7 @@ internal static class TRANSACTIONBINDING
private readonly int _loadBalanceTimeout;
private readonly int _maxPoolSize;
private readonly int _minPoolSize;
private readonly int _poolIdleTimeout;
private readonly int _packetSize;
private readonly int _connectRetryCount;
private readonly int _connectRetryInterval;
Expand Down Expand Up @@ -251,6 +254,7 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G
_loadBalanceTimeout = ConvertValueToInt32(KEY.Load_Balance_Timeout, DEFAULT.Load_Balance_Timeout);
_maxPoolSize = ConvertValueToInt32(KEY.Max_Pool_Size, DEFAULT.Max_Pool_Size);
_minPoolSize = ConvertValueToInt32(KEY.Min_Pool_Size, DEFAULT.Min_Pool_Size);
_poolIdleTimeout = ConvertValueToInt32(KEY.Pool_Idle_Timeout, DEFAULT.Pool_Idle_Timeout);
_packetSize = ConvertValueToInt32(KEY.Packet_Size, DEFAULT.Packet_Size);
_connectRetryCount = ConvertValueToInt32(KEY.Connect_Retry_Count, DEFAULT.Connect_Retry_Count);
_connectRetryInterval = ConvertValueToInt32(KEY.Connect_Retry_Interval, DEFAULT.Connect_Retry_Interval);
Expand Down Expand Up @@ -302,13 +306,16 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G
{
throw ADP.InvalidMinMaxPoolSizeValues();
}
if (_poolIdleTimeout < -1)
{
throw ADP.InvalidConnectionOptionValue(KEY.Pool_Idle_Timeout);
}

if ((_packetSize < TdsEnums.MIN_PACKET_SIZE) || (TdsEnums.MAX_PACKET_SIZE < _packetSize))
{
throw SQL.InvalidPacketSizeValue();
}


ValidateValueLength(_applicationName, TdsEnums.MAXLEN_APPNAME, KEY.Application_Name);
ValidateValueLength(_currentLanguage, TdsEnums.MAXLEN_LANGUAGE, KEY.Current_Language);
ValidateValueLength(_dataSource, TdsEnums.MAXLEN_SERVERNAME, KEY.Data_Source);
Expand Down Expand Up @@ -479,6 +486,7 @@ internal SqlConnectionString(SqlConnectionString connectionOptions, string dataS
#endif
_maxPoolSize = connectionOptions._maxPoolSize;
_minPoolSize = connectionOptions._minPoolSize;
_poolIdleTimeout = connectionOptions._poolIdleTimeout;
_multiSubnetFailover = connectionOptions._multiSubnetFailover;
_packetSize = connectionOptions._packetSize;
_applicationName = connectionOptions._applicationName;
Expand Down Expand Up @@ -531,6 +539,7 @@ internal SqlConnectionString(SqlConnectionString connectionOptions, string dataS
internal int LoadBalanceTimeout { get { return _loadBalanceTimeout; } }
internal int MaxPoolSize { get { return _maxPoolSize; } }
internal int MinPoolSize { get { return _minPoolSize; } }
internal int PoolIdleTimeout { get { return _poolIdleTimeout; } }
internal int PacketSize { get { return _packetSize; } }
internal int ConnectRetryCount { get { return _connectRetryCount; } }
internal int ConnectRetryInterval { get { return _connectRetryInterval; } }
Expand Down Expand Up @@ -566,7 +575,7 @@ protected internal override string Expand()
{
if (null != _expandedAttachDBFilename)
{
return ExpandAttachDbFileName(_expandedAttachDBFilename);
return ExpandAttachDbFileName(_expandedAttachDBFilename);
}
else
{
Expand Down Expand Up @@ -629,6 +638,7 @@ internal static Dictionary<string, string> GetParseSynonyms()
{ KEY.MARS, KEY.MARS },
{ KEY.Max_Pool_Size, KEY.Max_Pool_Size },
{ KEY.Min_Pool_Size, KEY.Min_Pool_Size },
{ KEY.Pool_Idle_Timeout, KEY.Pool_Idle_Timeout },
{ KEY.MultiSubnetFailover, KEY.MultiSubnetFailover },
{ KEY.Network_Library, KEY.Network_Library },
{ KEY.Packet_Size, KEY.Packet_Size },
Expand Down Expand Up @@ -843,7 +853,7 @@ internal SqlConnectionAttestationProtocol ConvertValueToAttestationProtocol()
}
catch (FormatException e)
{
throw ADP.InvalidConnectionOptionValue(KEY.AttestationProtocol, e);
throw ADP.InvalidConnectionOptionValue(KEY.AttestationProtocol, e);
}
catch (OverflowException e)
{
Expand Down
Loading