diff --git a/SignalR.sln b/SignalR.sln
index f53f22d4ca..7f925af865 100644
--- a/SignalR.sln
+++ b/SignalR.sln
@@ -89,7 +89,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Crankier", "benchmarkapps\C
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarkapps", "benchmarkapps", "{43F352F3-4E2B-4ED7-901B-36E6671251F5}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.SignalR.Specification.Tests", "src\Microsoft.AspNetCore.SignalR.Specification.Tests\Microsoft.AspNetCore.SignalR.Specification.Tests.csproj", "{2B03333F-3ACD-474C-862B-FA97D3BA03B5}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.SignalR.Specification.Tests", "src\Microsoft.AspNetCore.SignalR.Specification.Tests\Microsoft.AspNetCore.SignalR.Specification.Tests.csproj", "{2B03333F-3ACD-474C-862B-FA97D3BA03B5}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.SignalR.StackExchangeRedis", "src\Microsoft.AspNetCore.SignalR.StackExchangeRedis\Microsoft.AspNetCore.SignalR.StackExchangeRedis.csproj", "{D1334F29-5C19-4C7B-B62D-0A2F23AFB31C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.SignalR.StackExchangeRedis.Tests", "test\Microsoft.AspNetCore.SignalR.StackExchangeRedis.Tests\Microsoft.AspNetCore.SignalR.StackExchangeRedis.Tests.csproj", "{A5006087-81B0-4C62-B847-50ED5C37069D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -213,6 +217,14 @@ Global
{2B03333F-3ACD-474C-862B-FA97D3BA03B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B03333F-3ACD-474C-862B-FA97D3BA03B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B03333F-3ACD-474C-862B-FA97D3BA03B5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D1334F29-5C19-4C7B-B62D-0A2F23AFB31C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D1334F29-5C19-4C7B-B62D-0A2F23AFB31C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D1334F29-5C19-4C7B-B62D-0A2F23AFB31C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D1334F29-5C19-4C7B-B62D-0A2F23AFB31C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A5006087-81B0-4C62-B847-50ED5C37069D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A5006087-81B0-4C62-B847-50ED5C37069D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A5006087-81B0-4C62-B847-50ED5C37069D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A5006087-81B0-4C62-B847-50ED5C37069D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -247,6 +259,8 @@ Global
{8C75AC94-C980-4FE1-9F79-6CED3C8665CE} = {43F352F3-4E2B-4ED7-901B-36E6671251F5}
{8D3E3E7D-452B-44F4-86CA-111003EA11ED} = {43F352F3-4E2B-4ED7-901B-36E6671251F5}
{2B03333F-3ACD-474C-862B-FA97D3BA03B5} = {DA69F624-5398-4884-87E4-B816698CDE65}
+ {D1334F29-5C19-4C7B-B62D-0A2F23AFB31C} = {DA69F624-5398-4884-87E4-B816698CDE65}
+ {A5006087-81B0-4C62-B847-50ED5C37069D} = {6A35B453-52EC-48AF-89CA-D4A69800F131}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7945A4E4-ACDB-4F6E-95CA-6AC6E7C2CD59}
diff --git a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj
index d391a18436..0143f5ffae 100644
--- a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj
+++ b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj
@@ -17,8 +17,9 @@
-
+
+
diff --git a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/RedisHubLifetimeManagerBenchmark.cs b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/RedisHubLifetimeManagerBenchmark.cs
index 3e333ddd74..852fbdfa3c 100644
--- a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/RedisHubLifetimeManagerBenchmark.cs
+++ b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/RedisHubLifetimeManagerBenchmark.cs
@@ -10,7 +10,7 @@ using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.SignalR.Internal;
using Microsoft.AspNetCore.SignalR.Protocol;
-using Microsoft.AspNetCore.SignalR.Redis;
+using Microsoft.AspNetCore.SignalR.StackExchangeRedis;
using Microsoft.AspNetCore.SignalR.Tests;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
@@ -34,7 +34,8 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
[Params(2, 20)]
public int ProtocolCount { get; set; }
- [GlobalSetup]
+ // Re-enable micro-benchmark when https://github.com/aspnet/SignalR/issues/3088 is fixed
+ // [GlobalSetup]
public void GlobalSetup()
{
var server = new TestRedisServer();
@@ -90,7 +91,7 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
_users.Add("EvenUser");
_users.Add("OddUser");
- _args = new object[] {"Foo"};
+ _args = new object[] { "Foo" };
}
private IEnumerable GenerateProtocols(int protocolCount)
@@ -111,55 +112,55 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
}
}
- [Benchmark]
+ //[Benchmark]
public async Task SendAll()
{
await _manager1.SendAllAsync("Test", _args);
}
- [Benchmark]
+ //[Benchmark]
public async Task SendGroup()
{
await _manager1.SendGroupAsync("Everyone", "Test", _args);
}
- [Benchmark]
+ //[Benchmark]
public async Task SendUser()
{
await _manager1.SendUserAsync("EvenUser", "Test", _args);
}
- [Benchmark]
+ //[Benchmark]
public async Task SendConnection()
{
await _manager1.SendConnectionAsync(_clients[0].Connection.ConnectionId, "Test", _args);
}
- [Benchmark]
+ //[Benchmark]
public async Task SendConnections()
{
await _manager1.SendConnectionsAsync(_sendIds, "Test", _args);
}
- [Benchmark]
+ //[Benchmark]
public async Task SendAllExcept()
{
await _manager1.SendAllExceptAsync("Test", _args, _excludedConnectionIds);
}
- [Benchmark]
+ //[Benchmark]
public async Task SendGroupExcept()
{
await _manager1.SendGroupExceptAsync("Everyone", "Test", _args, _excludedConnectionIds);
}
- [Benchmark]
+ //[Benchmark]
public async Task SendGroups()
{
await _manager1.SendGroupsAsync(_groups, "Test", _args);
}
- [Benchmark]
+ //[Benchmark]
public async Task SendUsers()
{
await _manager1.SendUsersAsync(_users, "Test", _args);
diff --git a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/RedisProtocolBenchmark.cs b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/RedisProtocolBenchmark.cs
index 3008ed999c..f5e02e489b 100644
--- a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/RedisProtocolBenchmark.cs
+++ b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/RedisProtocolBenchmark.cs
@@ -7,7 +7,7 @@ using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.SignalR.Protocol;
-using Microsoft.AspNetCore.SignalR.Redis.Internal;
+using Microsoft.AspNetCore.SignalR.StackExchangeRedis.Internal;
namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
{
diff --git a/build/dependencies.props b/build/dependencies.props
index 826da88432..13a42ca540 100644
--- a/build/dependencies.props
+++ b/build/dependencies.props
@@ -62,6 +62,7 @@
2.0.3
11.0.2
1.2.6
+ 2.0.513
4.5.0
4.5.0
4.5.1
diff --git a/src/Microsoft.AspNetCore.SignalR.Redis/Internal/MessagePackUtil.cs b/src/Microsoft.AspNetCore.SignalR.Redis/Internal/MessagePackUtil.cs
index d190bb74e8..b824d90394 100644
--- a/src/Microsoft.AspNetCore.SignalR.Redis/Internal/MessagePackUtil.cs
+++ b/src/Microsoft.AspNetCore.SignalR.Redis/Internal/MessagePackUtil.cs
@@ -1,3 +1,6 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
diff --git a/src/Microsoft.AspNetCore.SignalR.Redis/Internal/RedisGroupCommand.cs b/src/Microsoft.AspNetCore.SignalR.Redis/Internal/RedisGroupCommand.cs
index a2ef82f373..3759da98ae 100644
--- a/src/Microsoft.AspNetCore.SignalR.Redis/Internal/RedisGroupCommand.cs
+++ b/src/Microsoft.AspNetCore.SignalR.Redis/Internal/RedisGroupCommand.cs
@@ -1,3 +1,6 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
namespace Microsoft.AspNetCore.SignalR.Redis.Internal
{
public readonly struct RedisGroupCommand
diff --git a/src/Microsoft.AspNetCore.SignalR.Redis/Internal/RedisInvocation.cs b/src/Microsoft.AspNetCore.SignalR.Redis/Internal/RedisInvocation.cs
index e9cedbd5b0..a1a8a3ee07 100644
--- a/src/Microsoft.AspNetCore.SignalR.Redis/Internal/RedisInvocation.cs
+++ b/src/Microsoft.AspNetCore.SignalR.Redis/Internal/RedisInvocation.cs
@@ -1,5 +1,7 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
using System.Collections.Generic;
-using Microsoft.AspNetCore.SignalR.Internal;
using Microsoft.AspNetCore.SignalR.Protocol;
namespace Microsoft.AspNetCore.SignalR.Redis.Internal
diff --git a/src/Microsoft.AspNetCore.SignalR.Redis/Internal/RedisProtocol.cs b/src/Microsoft.AspNetCore.SignalR.Redis/Internal/RedisProtocol.cs
index 6d3c51659b..6eaeb2ee79 100644
--- a/src/Microsoft.AspNetCore.SignalR.Redis/Internal/RedisProtocol.cs
+++ b/src/Microsoft.AspNetCore.SignalR.Redis/Internal/RedisProtocol.cs
@@ -8,7 +8,6 @@ using System.IO;
using System.Runtime.InteropServices;
using MessagePack;
using Microsoft.AspNetCore.Internal;
-using Microsoft.AspNetCore.SignalR.Internal;
using Microsoft.AspNetCore.SignalR.Protocol;
namespace Microsoft.AspNetCore.SignalR.Redis.Internal
diff --git a/src/Microsoft.AspNetCore.SignalR.StackExchangeRedis/Internal/AckHandler.cs b/src/Microsoft.AspNetCore.SignalR.StackExchangeRedis/Internal/AckHandler.cs
new file mode 100644
index 0000000000..863fcdcb53
--- /dev/null
+++ b/src/Microsoft.AspNetCore.SignalR.StackExchangeRedis/Internal/AckHandler.cs
@@ -0,0 +1,117 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Concurrent;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis.Internal
+{
+ internal class AckHandler : IDisposable
+ {
+ private readonly ConcurrentDictionary _acks = new ConcurrentDictionary();
+ private readonly Timer _timer;
+ private readonly TimeSpan _ackThreshold = TimeSpan.FromSeconds(30);
+ private readonly TimeSpan _ackInterval = TimeSpan.FromSeconds(5);
+ private readonly object _lock = new object();
+ private bool _disposed;
+
+ public AckHandler()
+ {
+ // Don't capture the current ExecutionContext and its AsyncLocals onto the timer
+ bool restoreFlow = false;
+ try
+ {
+ if (!ExecutionContext.IsFlowSuppressed())
+ {
+ ExecutionContext.SuppressFlow();
+ restoreFlow = true;
+ }
+
+ _timer = new Timer(state => ((AckHandler)state).CheckAcks(), state: this, dueTime: _ackInterval, period: _ackInterval);
+ }
+ finally
+ {
+ // Restore the current ExecutionContext
+ if (restoreFlow)
+ {
+ ExecutionContext.RestoreFlow();
+ }
+ }
+ }
+
+ public Task CreateAck(int id)
+ {
+ lock (_lock)
+ {
+ if (_disposed)
+ {
+ return Task.CompletedTask;
+ }
+
+ return _acks.GetOrAdd(id, _ => new AckInfo()).Tcs.Task;
+ }
+ }
+
+ public void TriggerAck(int id)
+ {
+ if (_acks.TryRemove(id, out var ack))
+ {
+ ack.Tcs.TrySetResult(null);
+ }
+ }
+
+ private void CheckAcks()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ var utcNow = DateTime.UtcNow;
+
+ foreach (var pair in _acks)
+ {
+ var elapsed = utcNow - pair.Value.Created;
+ if (elapsed > _ackThreshold)
+ {
+ if (_acks.TryRemove(pair.Key, out var ack))
+ {
+ ack.Tcs.TrySetCanceled();
+ }
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ lock (_lock)
+ {
+ _disposed = true;
+
+ _timer.Dispose();
+
+ foreach (var pair in _acks)
+ {
+ if (_acks.TryRemove(pair.Key, out var ack))
+ {
+ ack.Tcs.TrySetCanceled();
+ }
+ }
+ }
+ }
+
+ private class AckInfo
+ {
+ public TaskCompletionSource