Skip to content

Commit

Permalink
Merge pull request #645 from vicancy/v1.0.13
Browse files Browse the repository at this point in the history
Releasing V1.0.13
  • Loading branch information
vicancy authored Aug 27, 2019
2 parents b58ad94 + b66cb67 commit c764ee7
Show file tree
Hide file tree
Showing 33 changed files with 568 additions and 206 deletions.
67 changes: 36 additions & 31 deletions docs/tsg.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

namespace Microsoft.Azure.SignalR
{
internal static class AckStatus
internal enum AckStatus
{
public const int Ok = 1;
public const int NotFound = 2;
public const int Timeout = 3;
Ok = 1,
NotFound = 2,
Timeout = 3
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ internal abstract class ServiceConnectionBase : IServiceConnection

private readonly SemaphoreSlim _serviceConnectionLock = new SemaphoreSlim(1, 1);

private readonly TaskCompletionSource<bool> _serviceConnectionStartTcs = new TaskCompletionSource<bool>(TaskContinuationOptions.RunContinuationsAsynchronously);
private readonly TaskCompletionSource<bool> _serviceConnectionStartTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);

private readonly ServerConnectionType _connectionType;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
Expand Down Expand Up @@ -71,11 +71,11 @@ private set

protected ServiceConnectionContainerBase(IServiceConnectionFactory serviceConnectionFactory,
int minConnectionCount, HubServiceEndpoint endpoint,
IReadOnlyList<IServiceConnection> initialConnections = null, ILogger logger = null)
IReadOnlyList<IServiceConnection> initialConnections = null, ILogger logger = null, AckHandler ackHandler = null)
{
ServiceConnectionFactory = serviceConnectionFactory;
Endpoint = endpoint;
_ackHandler = new AckHandler();
_ackHandler = ackHandler ?? new AckHandler();

// make sure it is after _endpoint is set
// init initial connections
Expand Down Expand Up @@ -134,14 +134,7 @@ protected async Task StartCoreAsync(IServiceConnection connection, string target

public void HandleAck(AckMessage ackMessage)
{
if (ackMessage.Status == AckStatus.Ok)
{
_ackHandler.TriggerAck(ackMessage.AckId, true);
}
else
{
_ackHandler.TriggerAck(ackMessage.AckId, false);
}
_ackHandler.TriggerAck(ackMessage.AckId, (AckStatus)ackMessage.Status);
}

/// <summary>
Expand Down Expand Up @@ -258,7 +251,19 @@ public async Task<bool> WriteAckableMessageAsync(ServiceMessage serviceMessage,

await WriteToRandomAvailableConnection(serviceMessage);

return await task;
var status = await task;
switch (status)
{
case AckStatus.Ok:
return true;
case AckStatus.NotFound:
return false;
case AckStatus.Timeout:
throw new TimeoutException($"Ack-able message {serviceMessage.GetType()} waiting for ack timed out.");
default:
// should not be hit.
return false;
}
}

// Ready for scalable containers
Expand Down
29 changes: 15 additions & 14 deletions src/Microsoft.Azure.SignalR.Common/Utilities/AckHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ internal sealed class AckHandler : IDisposable
{
private readonly ConcurrentDictionary<int, AckInfo> _acks = new ConcurrentDictionary<int, AckInfo>();
private readonly Timer _timer;
private readonly TimeSpan _ackInterval = TimeSpan.FromSeconds(5);
private readonly TimeSpan _ackInterval;
private readonly TimeSpan _ackTtl;
private int _currentId = 0;

public AckHandler()
public AckHandler(int ackIntervalInMilliseconds = 3000, int ackTtlInMilliseconds = 10000)
{
_ackInterval = TimeSpan.FromMilliseconds(ackIntervalInMilliseconds);
_ackTtl = TimeSpan.FromMilliseconds(ackTtlInMilliseconds);

bool restoreFlow = false;
try
{
Expand All @@ -37,19 +41,19 @@ public AckHandler()
}
}

public Task<bool> CreateAck(out int id, CancellationToken cancellationToken = default)
public Task<AckStatus> CreateAck(out int id, CancellationToken cancellationToken = default)
{
id = Interlocked.Increment(ref _currentId);
var tcs = _acks.GetOrAdd(id, _ => new AckInfo()).Tcs;
var tcs = _acks.GetOrAdd(id, _ => new AckInfo(_ackTtl)).Tcs;
cancellationToken.Register(() => tcs.TrySetCanceled());
return tcs.Task;
}

public void TriggerAck(int id, bool isSuccess)
public void TriggerAck(int id, AckStatus ackStatus)
{
if (_acks.TryRemove(id, out var ack))
{
ack.Tcs.TrySetResult(isSuccess);
ack.Tcs.TrySetResult(ackStatus);
}
}

Expand All @@ -63,8 +67,7 @@ private void CheckAcks()
{
if (_acks.TryRemove(pair.Key, out var ack))
{
// If acks not coming back in time, do not throw an exception
ack.Tcs.TrySetResult(false);
ack.Tcs.TrySetResult(AckStatus.Timeout);
}
}
}
Expand All @@ -85,16 +88,14 @@ public void Dispose()

private class AckInfo
{
private readonly TimeSpan _ttl = TimeSpan.FromSeconds(10);

public TaskCompletionSource<bool> Tcs { get; private set; }
public TaskCompletionSource<AckStatus> Tcs { get; private set; }

public DateTime Expired { get; private set; }

public AckInfo()
public AckInfo(TimeSpan ttl)
{
Expired = DateTime.UtcNow.Add(_ttl);
Tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
Expired = DateTime.UtcNow.Add(ttl);
Tcs = new TaskCompletionSource<AckStatus>(TaskCreationOptions.RunContinuationsAsynchronously);
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions src/Microsoft.Azure.SignalR.Management/ServiceManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ internal class ServiceManager : IServiceManager
private readonly ServiceEndpointProvider _endpointProvider;
private readonly IServerNameProvider _serverNameProvider;
private readonly ServiceEndpoint _endpoint;
private const int ServerConnectionCount = 1;
private readonly string _productInfo;

internal ServiceManager(ServiceManagerOptions serviceManagerOptions, string productInfo)
Expand All @@ -50,7 +49,7 @@ public async Task<IServiceHubContext> CreateHubContextAsync(string hubName, ILog
var clientConnectionFactory = new ClientConnectionFactory();
ConnectionDelegate connectionDelegate = connectionContext => Task.CompletedTask;
var serviceConnectionFactory = new ServiceConnectionFactory(serviceProtocol, clientConnectionManager, connectionFactory, loggerFactory, connectionDelegate, clientConnectionFactory);
var weakConnectionContainer = new WeakServiceConnectionContainer(serviceConnectionFactory, ServerConnectionCount, new HubServiceEndpoint(hubName, _endpointProvider, _endpoint));
var weakConnectionContainer = new WeakServiceConnectionContainer(serviceConnectionFactory, _serviceManagerOptions.ConnectionCount, new HubServiceEndpoint(hubName, _endpointProvider, _endpoint));

var serviceCollection = new ServiceCollection();
serviceCollection.AddSignalRCore();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ public class ServiceManagerOptions
/// </summary>
public string ApplicationName { get; set; }

/// <summary>
/// Gets or sets the total number of connections from SDK to Azure SignalR Service. Default value is 1.
/// </summary>
public int ConnectionCount { get; set; } = 1;

internal void ValidateOptions()
{
ValidateConnectionString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ private async Task WaitOnApplicationTask(ServiceConnectionContext connection)
if (connection.AbortOnClose)
{
// Inform the Service that we will remove the client because SignalR told us it is disconnected.
var serviceMessage = new CloseConnectionMessage(connection.ConnectionId, errorMessage: "Web application error.");
var serviceMessage = new CloseConnectionMessage(connection.ConnectionId, errorMessage: "Web application task completed, close the client.");
await WriteAsync(serviceMessage);
Log.CloseConnection(Logger, connection.ConnectionId);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Azure.SignalR.Tests.Common;
using Xunit;
using Xunit.Abstractions;

namespace Microsoft.Azure.SignalR.AspNet.Tests
Expand All @@ -12,5 +17,13 @@ public AspNetSignalRServiceE2EFacts(ITestOutputHelper output)
: base(new TestServerFactory(), new TestClientSetFactory(), output)
{
}

[ConditionalTheory]
[SkipIfConnectionStringNotPresent]
[MemberData(nameof(TestDataBase))]
public Task RunE2ETests(string methodName, int expectedMessageCount, Func<string, ITestClientSet, Task> coreTask)
{
return RunE2ETestsBase(methodName, expectedMessageCount, coreTask);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
Expand Down Expand Up @@ -423,6 +422,8 @@ public async Task TestContainerWithTwoEndpointWithPrimaryOfflineAndConnectionSta

_ = container.StartAsync();

await container.ConnectionInitializedTask;

await container.WriteAsync(DefaultGroupMessage);

var endpoints = container.GetOnlineEndpoints().ToArray();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Messaging;
using Microsoft.Azure.SignalR.Protocol;
using Microsoft.Azure.SignalR.Tests.Common;
using Xunit;

namespace Microsoft.Azure.SignalR.AspNet.Tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.Azure.SignalR.Protocol;
using Microsoft.Azure.SignalR.Tests.Common;
using Microsoft.Extensions.Logging;

namespace Microsoft.Azure.SignalR.AspNet.Tests
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,34 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Azure.SignalR.Tests.Common;
using Xunit;
using Xunit.Abstractions;

namespace Microsoft.Azure.SignalR.Tests
{
public class SignalRServiceE2EFacts : ServiceE2EFactsBase
{
public static object[][] TestData = TestDataBase.Concat(new object[][] {
new object[] { "TestClientIPEcho", DefaultClientCount, new Func<string, ITestClientSet, Task>((methodName, clients) => clients.SendAsync(methodName, sendCount : DefaultClientCount, messages : DefaultMessage)) },
new object[] { "TestClientUser", DefaultClientCount, new Func<string, ITestClientSet, Task>((methodName, clients) => clients.SendAsync(methodName, sendCount : DefaultClientCount, messages : DefaultMessage)) },
new object[] { "TestClientQueryString", DefaultClientCount, new Func<string, ITestClientSet, Task>((methodName, clients) => clients.SendAsync(methodName, sendCount : DefaultClientCount, messages : DefaultMessage)) },
}).ToArray();

public SignalRServiceE2EFacts(ITestOutputHelper output) : base(new TestServerFactory(), new TestClientSetFactory(), output)
{
}

[ConditionalTheory]
[SkipIfConnectionStringNotPresent]
[MemberData(nameof(TestData))]
public Task RunE2ETests(string methodName, int expectedMessageCount, Func<string, ITestClientSet, Task> coreTask)
{
return RunE2ETestsBase(methodName, expectedMessageCount, coreTask);
}
}
}
25 changes: 25 additions & 0 deletions test/Microsoft.Azure.SignalR.E2ETests/SignalR/TestHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,31 @@ public override Task OnDisconnectedAsync(Exception exception)
return Task.CompletedTask;
}

// Verify whether 'get client IP' is working or not
public void TestClientIPEcho(string message)
{
if (!string.IsNullOrEmpty(Context.GetHttpContext().Connection.RemoteIpAddress?.ToString()))
{
Clients.Caller.SendAsync(nameof(TestClientIPEcho), message);
}
}

public void TestClientUser(string message)
{
if (Context.User != null)
{
Clients.Caller.SendAsync(nameof(TestClientUser), message);
}
}

public void TestClientQueryString(string message)
{
if (Context.GetHttpContext().Request.QueryString != null)
{
Clients.Caller.SendAsync(nameof(TestClientQueryString), message);
}
}

public void Echo(string message)
{
Clients.Caller.SendAsync(nameof(Echo), message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ public class ServiceManagerFacts
private static readonly string[] _appNames = new string[] { "appName", "", null };
private static readonly string[] _userIds = new string[] { UserId, null };
private static readonly IEnumerable<Claim[]> _claimLists = new Claim[][] { _defaultClaims, null };
private static readonly int[] _connectionCounts = new int[] {1, 2};

public static IEnumerable<object[]> TestServiceManagerOptionData => from transport in _serviceTransportTypes
from useLoggerFactory in _useLoggerFatories
from appName in _appNames
select new object[] { transport, useLoggerFactory, appName };
from connectionCount in _connectionCounts
select new object[] { transport, useLoggerFactory, appName, connectionCount };

public static IEnumerable<object[]> TestGenerateClientEndpointData => from appName in _appNames
select new object[] { appName, GetExpectedClientEndpoint(appName) };
Expand Down Expand Up @@ -65,13 +67,14 @@ internal void GenerateClientEndpointTest(string appName, string expectedClientEn

[Theory]
[MemberData(nameof(TestServiceManagerOptionData))]
internal async Task CreateServiceHubContextTest(ServiceTransportType serviceTransportType, bool useLoggerFacory, string appName)
internal async Task CreateServiceHubContextTest(ServiceTransportType serviceTransportType, bool useLoggerFacory, string appName, int connectionCount)
{
var serviceManager = new ServiceManager(new ServiceManagerOptions
{
ConnectionString = _testConnectionString,
ServiceTransportType = serviceTransportType,
ApplicationName = appName
ApplicationName = appName,
ConnectionCount = connectionCount
}, null);

LoggerFactory loggerFactory;
Expand Down
Loading

0 comments on commit c764ee7

Please sign in to comment.