Skip to content

Commit 07abb0d

Browse files
SNOW-834807: Allow users to configure ServicePointManager.ConnectionLimit property (#1224)
Co-authored-by: Marcin Gemra <[email protected]>
1 parent 6129aec commit 07abb0d

File tree

8 files changed

+131
-5
lines changed

8 files changed

+131
-5
lines changed

Snowflake.Data.Tests/IntegrationTests/CertificateRevocationIT.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public void TestCertificate()
2626
false,
2727
false,
2828
3,
29+
20,
2930
true,
3031
CertRevocationCheckMode.Enabled.ToString(),
3132
true,

Snowflake.Data.Tests/UnitTests/HttpUtilTest.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public async Task TestNonRetryableHttpExceptionThrowsError()
3535
.ThrowsAsync(new HttpRequestException("", new AuthenticationException()));
3636

3737
var httpClient = HttpUtil.Instance.GetHttpClient(
38-
new HttpClientConfig("fakeHost", "fakePort", "user", "password", "fakeProxyList", false, false, 7, certRevocationCheckMode: "ENABLED"),
38+
new HttpClientConfig("fakeHost", "fakePort", "user", "password", "fakeProxyList", false, false, 7, 20, certRevocationCheckMode: "ENABLED"),
3939
handler.Object);
4040

4141
try
@@ -150,7 +150,8 @@ public void TestCreateHttpClientHandlerWithProxy()
150150
"localhost",
151151
false,
152152
false,
153-
7
153+
7,
154+
20
154155
);
155156

156157
// act
@@ -173,6 +174,7 @@ public void TestCreateHttpClientHandlerWithoutProxy()
173174
null,
174175
false,
175176
false,
177+
20,
176178
0
177179
);
178180

Snowflake.Data.Tests/UnitTests/Revocation/CertificateRevocationVerifierTest.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ private HttpClientConfig GetHttpConfig(CertRevocationCheckMode checkMode = CertR
277277
false,
278278
false,
279279
3,
280+
20,
280281
true,
281282
checkMode.ToString(),
282283
false,

Snowflake.Data.Tests/UnitTests/Session/SFHttpClientPropertiesTest.cs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,86 @@ public void TestConvertToMapOnly2Properties(
5353
Assert.AreEqual(clientStoreTemporaryCredential, parameterMap[SFSessionParameter.CLIENT_STORE_TEMPORARY_CREDENTIAL]);
5454
}
5555

56+
[Test]
57+
[TestCase(1)]
58+
[TestCase(10)]
59+
[TestCase(100)]
60+
public void TestSettingConnectionLimitProperty(int expectedConnectionLimit)
61+
{
62+
// arrange
63+
var connectionString = $"ACCOUNT=account;USER=test;PASSWORD=test;SERVICE_POINT_CONNECTION_LIMIT={expectedConnectionLimit}";
64+
var properties = SFSessionProperties.ParseConnectionString(connectionString, new SessionPropertiesContext());
65+
66+
// act
67+
var extractedProperties = SFSessionHttpClientProperties.ExtractAndValidate(properties);
68+
69+
// assert
70+
Assert.AreEqual(expectedConnectionLimit, extractedProperties._servicePointConnectionLimit);
71+
}
72+
73+
[Test]
74+
public void TestSettingConnectionLimitPropertyToLessThan1()
75+
{
76+
// arrange
77+
var connectionString = $"ACCOUNT=account;USER=test;PASSWORD=test;SERVICE_POINT_CONNECTION_LIMIT={0}";
78+
var properties = SFSessionProperties.ParseConnectionString(connectionString, new SessionPropertiesContext());
79+
80+
// act
81+
var extractedProperties = SFSessionHttpClientProperties.ExtractAndValidate(properties);
82+
83+
// assert
84+
Assert.AreEqual(SFSessionHttpClientProperties.DefaultConnectionLimit, extractedProperties._servicePointConnectionLimit);
85+
}
86+
87+
[Test]
88+
public void TestSettingConnectionLimitPropertyToGreaterThanMaxConnectionLimit()
89+
{
90+
// arrange
91+
var connectionString = $"ACCOUNT=account;USER=test;PASSWORD=test;SERVICE_POINT_CONNECTION_LIMIT={SFSessionHttpClientProperties.MaxConnectionLimit + 1}";
92+
var properties = SFSessionProperties.ParseConnectionString(connectionString, new SessionPropertiesContext());
93+
94+
// act
95+
var extractedProperties = SFSessionHttpClientProperties.ExtractAndValidate(properties);
96+
97+
// assert
98+
Assert.AreEqual(SFSessionHttpClientProperties.DefaultConnectionLimit, extractedProperties._servicePointConnectionLimit);
99+
}
100+
101+
[Test]
102+
public void TestSettingConnectionLimitPropertyToNoValue()
103+
{
104+
// arrange
105+
var connectionString = $"ACCOUNT=account;USER=test;PASSWORD=test;SERVICE_POINT_CONNECTION_LIMIT=";
106+
var properties = SFSessionProperties.ParseConnectionString(connectionString, new SessionPropertiesContext());
107+
108+
// act
109+
var extractedProperties = SFSessionHttpClientProperties.ExtractAndValidate(properties);
110+
111+
// assert
112+
Assert.AreEqual(SFSessionHttpClientProperties.DefaultConnectionLimit, extractedProperties._servicePointConnectionLimit);
113+
}
114+
115+
[Test]
116+
[TestCase("abc")]
117+
[TestCase("1.5")]
118+
[TestCase("true")]
119+
[TestCase("-2.3")]
120+
[TestCase("null")]
121+
public void TestThrowsExceptionWhenSettingConnectionLimitPropertyToNonIntegerValue(string nonIntegerValue)
122+
{
123+
// arrange
124+
var parameterName = "SERVICE_POINT_CONNECTION_LIMIT";
125+
var expectedErrorMessage = $"Error: Invalid parameter value for {parameterName}";
126+
var connectionString = $"ACCOUNT=account;USER=test;PASSWORD=test;{parameterName}={nonIntegerValue}";
127+
128+
// act
129+
var thrown = Assert.Throws<SnowflakeDbException>(() => SFSessionProperties.ParseConnectionString(connectionString, new SessionPropertiesContext()));
130+
131+
// assert
132+
Assert.AreEqual(SFError.INVALID_CONNECTION_PARAMETER_VALUE.GetAttribute<SFErrorAttr>().errorCode, thrown.ErrorCode);
133+
Assert.IsTrue(thrown.Message.Contains(expectedErrorMessage));
134+
}
135+
56136
[Test]
57137
public void TestBuildHttpClientConfig()
58138
{

Snowflake.Data/Core/HttpUtil.cs

100755100644
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public HttpClientConfig(
2727
bool disableRetry,
2828
bool forceRetryOn404,
2929
int maxHttpRetries,
30+
int connectionLimit,
3031
bool includeRetryReason = true,
3132
string certRevocationCheckMode = "DISABLED",
3233
bool enableCRLDiskCaching = true,
@@ -46,6 +47,7 @@ public HttpClientConfig(
4647
ForceRetryOn404 = forceRetryOn404;
4748
MaxHttpRetries = maxHttpRetries;
4849
IncludeRetryReason = includeRetryReason;
50+
ConnectionLimit = connectionLimit;
4951
CertRevocationCheckMode = (CertRevocationCheckMode)Enum.Parse(typeof(CertRevocationCheckMode), certRevocationCheckMode, true);
5052
EnableCRLDiskCaching = enableCRLDiskCaching;
5153
EnableCRLInMemoryCaching = enableCRLInMemoryCaching;
@@ -65,6 +67,7 @@ public HttpClientConfig(
6567
forceRetryOn404.ToString(),
6668
maxHttpRetries.ToString(),
6769
includeRetryReason.ToString(),
70+
connectionLimit.ToString(),
6871
certRevocationCheckMode,
6972
enableCRLDiskCaching.ToString(),
7073
enableCRLInMemoryCaching.ToString(),
@@ -84,6 +87,7 @@ public HttpClientConfig(
8487
public readonly bool ForceRetryOn404;
8588
public readonly int MaxHttpRetries;
8689
public readonly bool IncludeRetryReason;
90+
public readonly int ConnectionLimit;
8791
internal readonly CertRevocationCheckMode CertRevocationCheckMode;
8892
internal readonly bool EnableCRLDiskCaching;
8993
internal readonly bool EnableCRLInMemoryCaching;
@@ -178,7 +182,7 @@ private HttpClient RegisterNewHttpClientIfNecessary(HttpClientConfig config, Del
178182

179183
internal HttpClient CreateNewHttpClient(HttpClientConfig config, DelegatingHandler customHandler = null) =>
180184
new HttpClient(
181-
new RetryHandler(SetupCustomHttpHandler(config, customHandler), config.DisableRetry, config.ForceRetryOn404, config.MaxHttpRetries, config.IncludeRetryReason))
185+
new RetryHandler(SetupCustomHttpHandler(config, customHandler), config.DisableRetry, config.ForceRetryOn404, config.MaxHttpRetries, config.IncludeRetryReason, config.ConnectionLimit))
182186
{
183187
Timeout = Timeout.InfiniteTimeSpan
184188
};
@@ -451,13 +455,15 @@ private class RetryHandler : DelegatingHandler
451455
private bool forceRetryOn404;
452456
private int maxRetryCount;
453457
private bool includeRetryReason;
458+
private int connectionLimit;
454459

455-
internal RetryHandler(HttpMessageHandler innerHandler, bool disableRetry, bool forceRetryOn404, int maxRetryCount, bool includeRetryReason) : base(innerHandler)
460+
internal RetryHandler(HttpMessageHandler innerHandler, bool disableRetry, bool forceRetryOn404, int maxRetryCount, bool includeRetryReason, int connectionLimit) : base(innerHandler)
456461
{
457462
this.disableRetry = disableRetry;
458463
this.forceRetryOn404 = forceRetryOn404;
459464
this.maxRetryCount = maxRetryCount;
460465
this.includeRetryReason = includeRetryReason;
466+
this.connectionLimit = connectionLimit;
461467
}
462468

463469
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage requestMessage,
@@ -474,7 +480,7 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
474480
ServicePoint p = ServicePointManager.FindServicePoint(requestMessage.RequestUri);
475481
p.Expect100Continue = false; // Saves about 100 ms per request
476482
p.UseNagleAlgorithm = false; // Saves about 200 ms per request
477-
p.ConnectionLimit = 20; // Default value is 2, we need more connections for performing multiple parallel queries
483+
p.ConnectionLimit = connectionLimit; // Default value is 2, we need more connections for performing multiple parallel queries
478484

479485
TimeSpan httpTimeout = (TimeSpan)requestMessage.Properties[BaseRestRequest.HTTP_REQUEST_TIMEOUT_KEY];
480486
TimeSpan restTimeout = (TimeSpan)requestMessage.Properties[BaseRestRequest.REST_REQUEST_TIMEOUT_KEY];

Snowflake.Data/Core/Session/SFSessionHttpClientProperties.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ internal class SFSessionHttpClientProperties
2121
public static readonly TimeSpan DefaultExpirationTimeout = TimeSpan.FromHours(1);
2222
public const bool DefaultPoolingEnabled = true;
2323
public const int DefaultMaxHttpRetries = 7;
24+
public const int DefaultConnectionLimit = 20;
2425
public static readonly TimeSpan DefaultRetryTimeout = TimeSpan.FromSeconds(300);
2526
private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger<SFSessionHttpClientProperties>();
27+
internal static readonly int MaxConnectionLimit = 1000;
2628

2729
internal bool validateDefaultParameters;
2830
internal bool clientSessionKeepAlive;
@@ -41,6 +43,7 @@ internal class SFSessionHttpClientProperties
4143
private TimeSpan _expirationTimeout;
4244
private bool _poolingEnabled;
4345
internal bool _clientStoreTemporaryCredential;
46+
internal int _servicePointConnectionLimit;
4447
internal CertRevocationCheckMode _certRevocationCheckMode;
4548
internal bool _enableCrlDiskCaching;
4649
internal bool _enableCrlInMemoryCaching;
@@ -99,6 +102,7 @@ private void CheckPropertiesAreValid()
99102
ValidateHttpRetries();
100103
ValidateMinMaxPoolSize();
101104
ValidateWaitingForSessionIdleTimeout();
105+
ValidateConnectionLimit();
102106
}
103107
catch (SnowflakeDbException)
104108
{
@@ -188,6 +192,15 @@ private void ValidateWaitingForSessionIdleTimeout()
188192
}
189193
}
190194

195+
private void ValidateConnectionLimit()
196+
{
197+
if (_servicePointConnectionLimit < 1 || _servicePointConnectionLimit > MaxConnectionLimit)
198+
{
199+
s_logger.Warn($"Connection limit must be between 1 and {MaxConnectionLimit}. Using the default value of {DefaultConnectionLimit}");
200+
_servicePointConnectionLimit = DefaultConnectionLimit;
201+
}
202+
}
203+
191204
public HttpClientConfig BuildHttpClientConfig()
192205
{
193206
return new HttpClientConfig(
@@ -199,6 +212,7 @@ public HttpClientConfig BuildHttpClientConfig()
199212
disableRetry,
200213
forceRetryOn404,
201214
maxHttpRetries,
215+
_servicePointConnectionLimit,
202216
includeRetryReason,
203217
_certRevocationCheckMode.ToString(),
204218
_enableCrlDiskCaching,
@@ -267,6 +281,7 @@ public SFSessionHttpClientProperties ExtractProperties(SFSessionProperties prope
267281
_poolingEnabled = extractor.ExtractBooleanWithDefaultValue(SFSessionProperty.POOLINGENABLED),
268282
_disableSamlUrlCheck = extractor.ExtractBooleanWithDefaultValue(SFSessionProperty.DISABLE_SAML_URL_CHECK),
269283
_clientStoreTemporaryCredential = Boolean.Parse(propertiesDictionary[SFSessionProperty.CLIENT_STORE_TEMPORARY_CREDENTIAL]),
284+
_servicePointConnectionLimit = int.Parse(propertiesDictionary[SFSessionProperty.SERVICE_POINT_CONNECTION_LIMIT]),
270285
_certRevocationCheckMode = (CertRevocationCheckMode)Enum.Parse(typeof(CertRevocationCheckMode), propertiesDictionary[SFSessionProperty.CERTREVOCATIONCHECKMODE], true),
271286
_enableCrlDiskCaching = Boolean.Parse(propertiesDictionary[SFSessionProperty.ENABLECRLDISKCACHING]),
272287
_enableCrlInMemoryCaching = Boolean.Parse(propertiesDictionary[SFSessionProperty.ENABLECRLINMEMORYCACHING]),

Snowflake.Data/Core/Session/SFSessionProperty.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ internal enum SFSessionProperty
132132
WORKLOAD_IDENTITY_ENTRA_RESOURCE,
133133
[SFSessionPropertyAttr(required = false, defaultValue = "false")]
134134
OAUTHENABLESINGLEUSEREFRESHTOKENS,
135+
[SFSessionPropertyAttr(required = false, defaultValue = "20")]
136+
SERVICE_POINT_CONNECTION_LIMIT,
135137
[SFSessionPropertyAttr(required = false, defaultValue = "disabled")]
136138
CERTREVOCATIONCHECKMODE,
137139
[SFSessionPropertyAttr(required = false, defaultValue = "true")]
@@ -302,6 +304,7 @@ internal static SFSessionProperties ParseConnectionString(string connectionStrin
302304
ValidateAccountDomain(properties);
303305
WarnIfHttpUsed(properties);
304306
ValidateAuthenticatorFlowsProperties(properties);
307+
ValidateServicePointConnectionLimit(properties);
305308
ValidateCrlParameters(properties);
306309
ValidateTlsParameters(properties);
307310

@@ -860,6 +863,23 @@ private static void ValidateFileTransferMaxBytesInMemoryProperty(SFSessionProper
860863
}
861864
}
862865

866+
private static void ValidateServicePointConnectionLimit(SFSessionProperties properties)
867+
{
868+
if (properties.TryGetValue(SFSessionProperty.SERVICE_POINT_CONNECTION_LIMIT, out var servicePointConnectionLimit))
869+
{
870+
if (!int.TryParse(servicePointConnectionLimit, out _))
871+
{
872+
var errorMessage = $"Invalid value of {SFSessionProperty.SERVICE_POINT_CONNECTION_LIMIT.ToString()} parameter";
873+
logger.Error(errorMessage);
874+
throw new SnowflakeDbException(
875+
new Exception(errorMessage),
876+
SFError.INVALID_CONNECTION_PARAMETER_VALUE,
877+
"",
878+
SFSessionProperty.SERVICE_POINT_CONNECTION_LIMIT.ToString());
879+
}
880+
}
881+
}
882+
863883
private static bool IsRequired(SFSessionProperty sessionProperty, SFSessionProperties properties)
864884
{
865885
if (sessionProperty.Equals(SFSessionProperty.PASSWORD))

0 commit comments

Comments
 (0)