Skip to content
This repository was archived by the owner on Oct 12, 2023. It is now read-only.

Commit 8f61919

Browse files
0xcedSeanFeldman
authored andcommitted
Add support for configuring OperationTimeout in the connection string (#644)
* Add support for configuring OperationTimeout in the connection string Like in [Microsoft.ServiceBus.Messaging (.NET Framework)][1], it's now possible to configure the `OperationTimeout` in the connection string. The `ServiceBusConnection` class constructors are adapted to read the `OperationTimeout` from the connection string and the constructor explicitly having a `TimeSpan operationTimeout` argument is obsoleted with this message: > Please use the constructor with (string namespaceConnectionString, RetryPolicy retryPolicy) arguments and define the operationTimeout in the connection string. [1]: https://docs.microsoft.com/en-us/dotnet/api/microsoft.servicebus.servicebusconnectionstringbuilder.operationtimeout * Use the operation timeout defined in the connection string for the ManagementClient Also, **actually** use the operation timeout as a timeout for the `HttpClient` responsible for the management operations instead of storing the `Constants.DefaultOperationTimeout` in a private field which is never used. * Update API Approvals * Always append key value pair delimiter when building the connection string * Improve wording as suggested by @SeanFeldman * Improve operation timeout parsing Parse as int (seconds) first, fallback on TimeSpan parsing for compatibility with WindowsAzure.ServiceBus. * Throw if the operation timeout input is neither parsable as int or TimeSpan. * Throw if the operation timeout is smaller than or equal to zero. * Throw if the operation timeout is greater than or equal to one hour.
1 parent 283a45e commit 8f61919

File tree

5 files changed

+115
-10
lines changed

5 files changed

+115
-10
lines changed

src/Microsoft.Azure.ServiceBus/Management/ManagementClient.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ public class ManagementClient
1818
private HttpClient httpClient;
1919
private readonly string endpointFQDN;
2020
private readonly ITokenProvider tokenProvider;
21-
private readonly TimeSpan operationTimeout;
2221
private readonly int port;
2322
private readonly string clientId;
2423

@@ -48,14 +47,13 @@ public ManagementClient(string endpoint, ITokenProvider tokenProvider)
4847
/// <param name="tokenProvider">Token provider which will generate security tokens for authorization.</param>
4948
public ManagementClient(ServiceBusConnectionStringBuilder connectionStringBuilder, ITokenProvider tokenProvider = default)
5049
{
51-
this.httpClient = new HttpClient();
50+
this.httpClient = new HttpClient { Timeout = connectionStringBuilder.OperationTimeout };
5251
this.endpointFQDN = connectionStringBuilder.Endpoint;
5352
this.tokenProvider = tokenProvider ?? CreateTokenProvider(connectionStringBuilder);
54-
this.operationTimeout = Constants.DefaultOperationTimeout;
5553
this.port = GetPort(connectionStringBuilder.Endpoint);
5654
this.clientId = nameof(ManagementClient) + Guid.NewGuid().ToString("N").Substring(0, 6);
5755

58-
MessagingEventSource.Log.ManagementClientCreated(this.clientId, this.operationTimeout.TotalSeconds, this.tokenProvider.ToString());
56+
MessagingEventSource.Log.ManagementClientCreated(this.clientId, this.httpClient.Timeout.TotalSeconds, this.tokenProvider.ToString());
5957
}
6058

6159
public static HttpRequestMessage CloneRequest(HttpRequestMessage req)

src/Microsoft.Azure.ServiceBus/ServiceBusConnection.cs

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,43 @@ public ServiceBusConnection(ServiceBusConnectionStringBuilder connectionStringBu
3737
/// <param name="namespaceConnectionString">Namespace connection string</param>
3838
/// <remarks>It is the responsibility of the user to close the connection after use through <see cref="CloseAsync"/></remarks>
3939
public ServiceBusConnection(string namespaceConnectionString)
40-
: this(namespaceConnectionString, Constants.DefaultOperationTimeout, RetryPolicy.Default)
40+
: this(namespaceConnectionString, RetryPolicy.Default)
4141
{
4242
}
4343

44+
/// <summary>
45+
/// Creates a new connection to service bus.
46+
/// </summary>
47+
/// <param name="namespaceConnectionString">Namespace connection string.</param>
48+
/// <param name="retryPolicy">Retry policy for operations. Defaults to <see cref="RetryPolicy.Default"/></param>
49+
/// <remarks>It is the responsibility of the user to close the connection after use through <see cref="CloseAsync"/></remarks>
50+
public ServiceBusConnection(string namespaceConnectionString, RetryPolicy retryPolicy = null)
51+
: this(retryPolicy)
52+
{
53+
if (string.IsNullOrWhiteSpace(namespaceConnectionString))
54+
{
55+
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(namespaceConnectionString));
56+
}
57+
58+
var serviceBusConnectionStringBuilder = new ServiceBusConnectionStringBuilder(namespaceConnectionString);
59+
if (!string.IsNullOrWhiteSpace(serviceBusConnectionStringBuilder.EntityPath))
60+
{
61+
throw Fx.Exception.Argument(nameof(namespaceConnectionString), "NamespaceConnectionString should not contain EntityPath.");
62+
}
63+
64+
this.InitializeConnection(serviceBusConnectionStringBuilder);
65+
}
66+
4467
/// <summary>
4568
/// Creates a new connection to service bus.
4669
/// </summary>
4770
/// <param name="namespaceConnectionString">Namespace connection string.</param>
4871
/// <param name="operationTimeout">Duration after which individual operations will timeout.</param>
4972
/// <param name="retryPolicy">Retry policy for operations. Defaults to <see cref="RetryPolicy.Default"/></param>
5073
/// <remarks>It is the responsibility of the user to close the connection after use through <see cref="CloseAsync"/></remarks>
74+
[Obsolete("This constructor is obsolete. Use ServiceBusConnection(string namespaceConnectionString, RetryPolicy retryPolicy) constructor instead, providing operationTimeout in the connection string.")]
5175
public ServiceBusConnection(string namespaceConnectionString, TimeSpan operationTimeout, RetryPolicy retryPolicy = null)
52-
: this(operationTimeout, retryPolicy)
76+
: this(retryPolicy)
5377
{
5478
if (string.IsNullOrWhiteSpace(namespaceConnectionString))
5579
{
@@ -63,6 +87,8 @@ public ServiceBusConnection(string namespaceConnectionString, TimeSpan operation
6387
}
6488

6589
this.InitializeConnection(serviceBusConnectionStringBuilder);
90+
// operationTimeout argument explicitly provided by caller should take precedence over OperationTimeout found in the connection string.
91+
this.OperationTimeout = operationTimeout;
6692
}
6793

6894
/// <summary>
@@ -72,7 +98,7 @@ public ServiceBusConnection(string namespaceConnectionString, TimeSpan operation
7298
/// <param name="transportType">Transport type.</param>
7399
/// <param name="retryPolicy">Retry policy for operations. Defaults to <see cref="RetryPolicy.Default"/></param>
74100
public ServiceBusConnection(string endpoint, TransportType transportType, RetryPolicy retryPolicy = null)
75-
: this(Constants.DefaultOperationTimeout, retryPolicy)
101+
: this(retryPolicy)
76102
{
77103
if (string.IsNullOrWhiteSpace(endpoint))
78104
{
@@ -88,9 +114,8 @@ public ServiceBusConnection(string endpoint, TransportType transportType, RetryP
88114
this.InitializeConnection(serviceBusConnectionStringBuilder);
89115
}
90116

91-
internal ServiceBusConnection(TimeSpan operationTimeout, RetryPolicy retryPolicy = null)
117+
internal ServiceBusConnection(RetryPolicy retryPolicy = null)
92118
{
93-
this.OperationTimeout = operationTimeout;
94119
this.RetryPolicy = retryPolicy ?? RetryPolicy.Default;
95120
this.syncLock = new object();
96121
}
@@ -193,6 +218,7 @@ void InitializeConnection(ServiceBusConnectionStringBuilder builder)
193218
this.TokenProvider = new SharedAccessSignatureTokenProvider(builder.SasKeyName, builder.SasKey);
194219
}
195220

221+
this.OperationTimeout = builder.OperationTimeout;
196222
this.TransportType = builder.TransportType;
197223
this.ConnectionManager = new FaultTolerantAmqpObject<AmqpConnection>(this.CreateConnectionAsync, CloseConnection);
198224
this.TransactionController = new FaultTolerantAmqpObject<Controller>(this.CreateControllerAsync, CloseController);

src/Microsoft.Azure.ServiceBus/ServiceBusConnectionStringBuilder.cs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace Microsoft.Azure.ServiceBus
55
{
66
using System;
77
using System.Collections.Generic;
8+
using System.Globalization;
89
using System.Text;
910
using Primitives;
1011

@@ -24,6 +25,8 @@ public class ServiceBusConnectionStringBuilder
2425
const string EntityPathConfigName = "EntityPath";
2526
const string TransportTypeConfigName = "TransportType";
2627

28+
const string OperationTimeoutConfigName = "OperationTimeout";
29+
2730
string entityPath, sasKeyName, sasKey, sasToken, endpoint;
2831

2932
/// <summary>
@@ -212,6 +215,12 @@ public string SasToken
212215
/// </summary>
213216
public TransportType TransportType { get; set; }
214217

218+
/// <summary>
219+
/// Duration after which individual operations will timeout.
220+
/// </summary>
221+
/// <remarks>Defaults to 1 minute.</remarks>
222+
public TimeSpan OperationTimeout { get; set; } = Constants.DefaultOperationTimeout;
223+
215224
internal Dictionary<string, string> ConnectionStringProperties = new Dictionary<string, string>(StringComparer.CurrentCultureIgnoreCase);
216225

217226
/// <summary>
@@ -243,7 +252,12 @@ public string GetNamespaceConnectionString()
243252

244253
if (this.TransportType != TransportType.Amqp)
245254
{
246-
connectionStringBuilder.Append($"{TransportTypeConfigName}{KeyValueSeparator}{this.TransportType}");
255+
connectionStringBuilder.Append($"{TransportTypeConfigName}{KeyValueSeparator}{this.TransportType}{KeyValuePairDelimiter}");
256+
}
257+
258+
if (this.OperationTimeout != Constants.DefaultOperationTimeout)
259+
{
260+
connectionStringBuilder.Append($"{OperationTimeoutConfigName}{KeyValueSeparator}{this.OperationTimeout}{KeyValuePairDelimiter}");
247261
}
248262

249263
return connectionStringBuilder.ToString().Trim(';');
@@ -319,6 +333,31 @@ void ParseConnectionString(string connectionString)
319333
this.TransportType = transportType;
320334
}
321335
}
336+
else if (key.Equals(OperationTimeoutConfigName, StringComparison.OrdinalIgnoreCase))
337+
{
338+
if (int.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var timeoutInSeconds))
339+
{
340+
this.OperationTimeout = TimeSpan.FromSeconds(timeoutInSeconds);
341+
}
342+
else if (TimeSpan.TryParse(value, NumberFormatInfo.InvariantInfo, out var operationTimeout))
343+
{
344+
this.OperationTimeout = operationTimeout;
345+
}
346+
else
347+
{
348+
throw Fx.Exception.Argument(nameof(connectionString), $"The {OperationTimeoutConfigName} ({value}) format is invalid. It must be an integer representing a number of seconds.");
349+
}
350+
351+
if (this.OperationTimeout.TotalMilliseconds <= 0)
352+
{
353+
throw Fx.Exception.Argument(nameof(connectionString), $"The {OperationTimeoutConfigName} ({value}) must be greater than zero.");
354+
}
355+
356+
if (this.OperationTimeout.TotalHours >= 1)
357+
{
358+
throw Fx.Exception.Argument(nameof(connectionString), $"The {OperationTimeoutConfigName} ({value}) must be smaller than one hour.");
359+
}
360+
}
322361
else
323362
{
324363
ConnectionStringProperties[key] = value;

test/Microsoft.Azure.ServiceBus.UnitTests/API/ApiApprovals.ApproveAzureServiceBus.approved.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,10 @@ namespace Microsoft.Azure.ServiceBus
309309
{
310310
public ServiceBusConnection(Microsoft.Azure.ServiceBus.ServiceBusConnectionStringBuilder connectionStringBuilder) { }
311311
public ServiceBusConnection(string namespaceConnectionString) { }
312+
public ServiceBusConnection(string namespaceConnectionString, Microsoft.Azure.ServiceBus.RetryPolicy retryPolicy = null) { }
313+
[System.ObsoleteAttribute("This constructor is obsolete. Use ServiceBusConnection(string namespaceConnection" +
314+
"String, RetryPolicy retryPolicy) constructor instead, providing operationTimeout" +
315+
" in the connection string.")]
312316
public ServiceBusConnection(string namespaceConnectionString, System.TimeSpan operationTimeout, Microsoft.Azure.ServiceBus.RetryPolicy retryPolicy = null) { }
313317
public ServiceBusConnection(string endpoint, Microsoft.Azure.ServiceBus.TransportType transportType, Microsoft.Azure.ServiceBus.RetryPolicy retryPolicy = null) { }
314318
public System.Uri Endpoint { get; set; }
@@ -329,6 +333,7 @@ namespace Microsoft.Azure.ServiceBus
329333
public ServiceBusConnectionStringBuilder(string endpoint, string entityPath, string sharedAccessSignature, Microsoft.Azure.ServiceBus.TransportType transportType) { }
330334
public string Endpoint { get; set; }
331335
public string EntityPath { get; set; }
336+
public System.TimeSpan OperationTimeout { get; set; }
332337
public string SasKey { get; set; }
333338
public string SasKeyName { get; set; }
334339
public string SasToken { get; set; }

test/Microsoft.Azure.ServiceBus.UnitTests/ServiceBusConnectionStringBuilderTests.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ void ConnectionStringBuilderShouldTrimTrailingSemicolon()
7575

7676
csBuilder.EntityPath = "myQ";
7777
Assert.Equal("Endpoint=amqps://contoso.servicebus.windows.net;EntityPath=myQ", csBuilder.ToString());
78+
79+
csBuilder.EntityPath = "";
80+
csBuilder.TransportType = TransportType.AmqpWebSockets;
81+
Assert.Equal("Endpoint=amqps://contoso.servicebus.windows.net;TransportType=AmqpWebSockets", csBuilder.ToString());
82+
83+
csBuilder.OperationTimeout = TimeSpan.FromSeconds(42);
84+
Assert.Equal("Endpoint=amqps://contoso.servicebus.windows.net;TransportType=AmqpWebSockets;OperationTimeout=00:00:42", csBuilder.ToString());
7885
}
7986

8087
[Fact]
@@ -126,6 +133,36 @@ void ConnectionStringBuilderShouldDefaultToAmqp()
126133
Assert.Equal(TransportType.Amqp, csBuilder.TransportType);
127134
}
128135

136+
[Fact]
137+
void ConnectionStringBuilderShouldParseOperationTimeoutAsInteger()
138+
{
139+
var csBuilder = new ServiceBusConnectionStringBuilder("Endpoint=sb://contoso.servicebus.windows.net;SharedAccessKeyName=keyname;SharedAccessKey=key;OperationTimeout=120");
140+
Assert.Equal(TimeSpan.FromMinutes(2), csBuilder.OperationTimeout);
141+
}
142+
143+
[Fact]
144+
void ConnectionStringBuilderShouldParseOperationTimeoutAsTimeSpan()
145+
{
146+
var csBuilder = new ServiceBusConnectionStringBuilder("Endpoint=sb://contoso.servicebus.windows.net;SharedAccessKeyName=keyname;SharedAccessKey=key;OperationTimeout=00:12:34");
147+
Assert.Equal(TimeSpan.FromMinutes(12).Add(TimeSpan.FromSeconds(34)), csBuilder.OperationTimeout);
148+
}
149+
150+
[Fact]
151+
void ConnectionStringBuilderOperationTimeoutShouldDefaultToOneMinute()
152+
{
153+
var csBuilder = new ServiceBusConnectionStringBuilder("Endpoint=sb://contoso.servicebus.windows.net;SharedAccessKeyName=keyname;SharedAccessKey=key");
154+
Assert.Equal(Constants.DefaultOperationTimeout, csBuilder.OperationTimeout);
155+
}
156+
157+
[Fact]
158+
void ConnectionStringBuilderShouldThrowForInvalidOperationTimeout()
159+
{
160+
var exception = Assert.Throws<ArgumentException>(() => new ServiceBusConnectionStringBuilder("Endpoint=sb://contoso.servicebus.windows.net;SharedAccessKeyName=keyname;SharedAccessKey=key;OperationTimeout=x"));
161+
Assert.Equal("connectionString", exception.ParamName);
162+
Assert.Contains("OperationTimeout", exception.Message);
163+
Assert.Contains("(x)", exception.Message);
164+
}
165+
129166
[Fact]
130167
void ConnectionStringBuilderShouldParseToken()
131168
{

0 commit comments

Comments
 (0)