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

Add InitialConnectionCount and MaxConnectionCount and hide ConnectionCount #1653

Merged
merged 2 commits into from
Aug 5, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
26 changes: 24 additions & 2 deletions src/Microsoft.Azure.SignalR.AspNet/ServiceOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Net;
using System.Security.Claims;
Expand All @@ -20,13 +21,34 @@ public class ServiceOptions : IServiceEndpointOptions
/// Gets or sets the connection string of Azure SignalR Service instance.
/// </summary>
public string ConnectionString { get; set; }

/// <summary>
/// Gets or sets the initial number of connections per hub from SDK to Azure SignalR Service. Default value is 5.
/// Usually keep it as the default value is enough. During runtime, the SDK might start new server connections for performance tuning or load balancing.
/// When you have big number of clients, you can give it a larger number for better throughput.
/// </summary>
public int ConnectionCount { get; set; } = 5;
[EditorBrowsable(EditorBrowsableState.Never)]
vicancy marked this conversation as resolved.
Show resolved Hide resolved
public int ConnectionCount
{
get => InitialHubServerConnectionCount;
set => InitialHubServerConnectionCount = value;
}

/// <summary>
/// Gets or sets the initial number of connections per hub from SDK to Azure SignalR Service.
/// Default value is 5.
/// Usually keep it as the default value is enough. When you have big number of clients, you can give it a larger number for better throughput.
/// During runtime, the SDK might start new server connections for performance tuning or load balancing.
/// </summary>
public int InitialHubServerConnectionCount { get; set; } = 5;

/// <summary>
/// Specifies the max server connection count allowed per hub from SDK to Azure SignalR Service.
/// During runtime, the SDK might start new server connections for performance tuning or load balancing.
/// By default a new server connection starts whenever needed.
/// When the max allowed server connection count is configured, the SDK does not start new connections when server connection count reaches the limit.
/// </summary>
public int? MaxHubServerConnectionCount { get; set; }

/// <summary>
/// Gets applicationName, which will be used as a prefix to apply to each hub name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ internal interface IServiceEndpointOptions
ServiceEndpoint[] Endpoints { get; }
string ApplicationName { get; }
string ConnectionString { get; }
int ConnectionCount { get; }
int InitialHubServerConnectionCount { get; }
int? MaxHubServerConnectionCount { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,14 @@ public MultiEndpointServiceConnectionContainer(
IServiceConnectionFactory serviceConnectionFactory,
string hub,
int count,
int? maxCount,
IServiceEndpointManager endpointManager,
IMessageRouter router,
ILoggerFactory loggerFactory,
TimeSpan? scaleTimeout = null
) : this(
hub,
endpoint => CreateContainer(serviceConnectionFactory, endpoint, count, loggerFactory),
endpoint => CreateContainer(serviceConnectionFactory, endpoint, count, maxCount, loggerFactory),
endpointManager,
router,
loggerFactory,
Expand All @@ -86,11 +87,11 @@ public IEnumerable<HubServiceEndpoint> GetOnlineEndpoints()
return _routerEndpoints.endpoints.Where(s => s.Online);
}

private static IServiceConnectionContainer CreateContainer(IServiceConnectionFactory serviceConnectionFactory, HubServiceEndpoint endpoint, int count, ILoggerFactory loggerFactory)
private static IServiceConnectionContainer CreateContainer(IServiceConnectionFactory serviceConnectionFactory, HubServiceEndpoint endpoint, int count, int? maxCount, ILoggerFactory loggerFactory)
{
if (endpoint.EndpointType == EndpointType.Primary)
{
return new StrongServiceConnectionContainer(serviceConnectionFactory, count, endpoint, loggerFactory.CreateLogger<StrongServiceConnectionContainer>());
return new StrongServiceConnectionContainer(serviceConnectionFactory, count, maxCount, endpoint, loggerFactory.CreateLogger<StrongServiceConnectionContainer>());
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public ServiceConnectionContainerFactory(

public IServiceConnectionContainer Create(string hub)
{
return new MultiEndpointServiceConnectionContainer(_serviceConnectionFactory, hub, _options.ConnectionCount, _serviceEndpointManager, _router, _loggerFactory, _serviceScaleTimeout);
return new MultiEndpointServiceConnectionContainer(_serviceConnectionFactory, hub, _options.InitialHubServerConnectionCount, _options.MaxHubServerConnectionCount, _serviceEndpointManager, _router, _loggerFactory, _serviceScaleTimeout);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,23 @@ namespace Microsoft.Azure.SignalR
{
internal class StrongServiceConnectionContainer : ServiceConnectionContainerBase
{
private readonly int? _maxConnectionCount;

public StrongServiceConnectionContainer(
IServiceConnectionFactory serviceConnectionFactory,
int fixedConnectionCount,
int? maxConnectionCount,
HubServiceEndpoint endpoint,
ILogger logger) : base(serviceConnectionFactory, fixedConnectionCount, endpoint, logger: logger)
{
_maxConnectionCount = maxConnectionCount.HasValue ? (maxConnectionCount.Value > fixedConnectionCount ? maxConnectionCount.Value : fixedConnectionCount) : null;
}

public override async Task HandlePingAsync(PingMessage pingMessage)
{
await base.HandlePingAsync(pingMessage);
if (RuntimeServicePingMessage.TryGetRebalance(pingMessage, out var target) && !string.IsNullOrEmpty(target))
if (RuntimeServicePingMessage.TryGetRebalance(pingMessage, out var target) && !string.IsNullOrEmpty(target)
&& (_maxConnectionCount == null || ServiceConnections.Count < _maxConnectionCount))
{
var connection = CreateServiceConnectionCore(ServiceConnectionType.OnDemand);
AddOnDemandConnection(connection);
Expand Down
24 changes: 23 additions & 1 deletion src/Microsoft.Azure.SignalR/ServiceOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
Expand All @@ -26,7 +27,28 @@ public class ServiceOptions : IServiceEndpointOptions
/// Usually keep it as the default value is enough. During runtime, the SDK might start new server connections for performance tuning or load balancing.
/// When you have big number of clients, you can give it a larger number for better throughput.
/// </summary>
public int ConnectionCount { get; set; } = 5;
[EditorBrowsable(EditorBrowsableState.Never)]
vicancy marked this conversation as resolved.
Show resolved Hide resolved
public int ConnectionCount
{
get => InitialHubServerConnectionCount;
set => InitialHubServerConnectionCount = value;
}

/// <summary>
/// Gets or sets the initial number of connections per hub from SDK to Azure SignalR Service.
/// Default value is 5.
/// Usually keep it as the default value is enough. When you have big number of clients, you can give it a larger number for better throughput.
/// During runtime, the SDK might start new server connections for performance tuning or load balancing.
/// </summary>
public int InitialHubServerConnectionCount { get; set; } = 5;

/// <summary>
/// Specifies the max server connection count allowed per hub from SDK to Azure SignalR Service.
/// During runtime, the SDK might start new server connections for performance tuning or load balancing.
/// By default a new server connection starts whenever needed.
/// When the max allowed server connection count is configured, the SDK does not start new connections when server connection count reaches the limit.
/// </summary>
public int? MaxHubServerConnectionCount { get; set; }

/// <summary>
/// Gets applicationName, which will be used as a prefix to apply to each hub name.
Expand Down
16 changes: 14 additions & 2 deletions src/Microsoft.Azure.SignalR/ServiceOptionsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,23 @@ public static void Validate(this ServiceOptions options)
if (options.MaxPollIntervalInSeconds.HasValue &&
(options.MaxPollIntervalInSeconds < 1 || options.MaxPollIntervalInSeconds > 300))
{
throw new AzureSignalRInvalidServiceOptionsException("MaxPollIntervalInSeconds", "[1,300]");
throw new AzureSignalRInvalidServiceOptionsException(nameof(options.MaxPollIntervalInSeconds), "[1,300]");
}

// == 0 can be valid for serverless workaround
if (options.InitialHubServerConnectionCount < 0)
{
throw new AzureSignalRInvalidServiceOptionsException(nameof(options.InitialHubServerConnectionCount), "> 0");
}

if (options.MaxHubServerConnectionCount.HasValue && options.MaxHubServerConnectionCount < options.InitialHubServerConnectionCount)
{
throw new AzureSignalRInvalidServiceOptionsException(nameof(options.MaxHubServerConnectionCount), $">= {options.InitialHubServerConnectionCount}");
}

if (!string.IsNullOrEmpty(options.ApplicationName) && !AppNameRegex.IsMatch(options.ApplicationName))
{
throw new AzureSignalRInvalidServiceOptionsException("ApplicationName", "prefixed with alphabetic characters and only contain alpha-numeric characters or underscore");
throw new AzureSignalRInvalidServiceOptionsException(nameof(options.ApplicationName), "prefixed with alphabetic characters and only contain alpha-numeric characters or underscore");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public void TestStrongConnectionStatus()
var endpoint1 = new TestHubServiceEndpoint();
var conn1 = new TestServiceConnection();
var scf = new TestServiceConnectionFactory(endpoint1 => conn1);
var container = new StrongServiceConnectionContainer(scf, 5, endpoint1, loggerFactory.CreateLogger(nameof(TestStrongConnectionStatus)));
var container = new StrongServiceConnectionContainer(scf, 5, null, endpoint1, loggerFactory.CreateLogger(nameof(TestStrongConnectionStatus)));

// When init, consider the endpoint as online
// TODO: improve the logic
Expand Down
29 changes: 28 additions & 1 deletion test/Microsoft.Azure.SignalR.Tests/AddAzureSignalRFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ public void AddAzureSignalRReadsDefaultConfigurationKeyForConnectionString()
var config = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>
{
{"Azure:SignalR:ConnectionString", DefaultValue}
{"Azure:SignalR:ConnectionString", DefaultValue},
{"Azure:SignalR:ApplicationName", "Application1"}
})
.Build();
var serviceProvider = services.AddSignalR()
Expand All @@ -58,11 +59,37 @@ public void AddAzureSignalRReadsDefaultConfigurationKeyForConnectionString()

Assert.Equal(DefaultValue, options.ConnectionString);
Assert.Equal(5, options.ConnectionCount);
Assert.Equal("Application1", options.ApplicationName);
Assert.Equal(5, options.InitialHubServerConnectionCount);
Assert.Equal(TimeSpan.FromHours(1), options.AccessTokenLifetime);
Assert.Null(options.ClaimsProvider);
}
}

[Fact]
public void AddAzureSignalRReadsInvalidCongifurationThrows()
{
using (StartVerifiableLog(out var loggerFactory, LogLevel.Debug))
{
var services = new ServiceCollection();
var config = new ConfigurationBuilder()
.Build();
var serviceProvider = services.AddSignalR()
.AddAzureSignalR( o =>
{
o.InitialHubServerConnectionCount = 15;
o.MaxHubServerConnectionCount = 3;
})
.Services
.AddSingleton<IConfiguration>(config)
.AddSingleton(loggerFactory)
.BuildServiceProvider();

var ex = Assert.Throws<AzureSignalRInvalidServiceOptionsException>(() => serviceProvider.GetRequiredService<IOptions<ServiceOptions>>().Value);
Assert.Equal("Property 'MaxHubServerConnectionCount' value should be >= 15.", ex.Message);
}
}

[Fact]
public void AddAzureUsesDefaultConnectionStringIfSpecifiedAndOptionsOverridden()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public ServiceConnectionProxy(
ServerNameProvider = new DefaultServerNameProvider();

// these two lines should be located in the end of this constructor.
ServiceConnectionContainer = new StrongServiceConnectionContainer(this, connectionCount, new TestHubServiceEndpoint(), NullLogger.Instance);
ServiceConnectionContainer = new StrongServiceConnectionContainer(this, connectionCount, null, new TestHubServiceEndpoint(), NullLogger.Instance);
ServiceMessageHandler = (StrongServiceConnectionContainer) ServiceConnectionContainer;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ public async Task TestStatusPingChangesEndpointStatus()
var connectionFactory1 = new TestServiceConnectionFactory();
var connectionFactory2 = new TestServiceConnectionFactory();

var hub1 = new MultiEndpointServiceConnectionContainer(connectionFactory1, "hub1", 2, sem, router,
var hub1 = new MultiEndpointServiceConnectionContainer(connectionFactory1, "hub1", 2, null, sem, router,
loggerFactory);
var hub2 = new MultiEndpointServiceConnectionContainer(connectionFactory2, "hub2", 2, sem, router,
var hub2 = new MultiEndpointServiceConnectionContainer(connectionFactory2, "hub2", 2, null, sem, router,
loggerFactory);

var connections = connectionFactory1.CreatedConnections.SelectMany(kv => kv.Value).ToArray();
Expand Down