Skip to content

Commit b383b47

Browse files
committed
Initial copy of Microsoft.AspNetCore.SignalR.StackExchangeRedis
0 parents  commit b383b47

14 files changed

+1523
-0
lines changed

IntelliTect.SignalR.SqlServer.sln

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.31424.327
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "demo", "demo", "{62241700-24B6-4D13-A183-2502D628ED43}"
7+
EndProject
8+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{21874FF3-BE97-4F3B-AD63-EC347FF757FF}"
9+
EndProject
10+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntelliTect.SignalR.SqlServer", "src\IntelliTect.SignalR.SqlServer\IntelliTect.SignalR.SqlServer.csproj", "{62E576F6-E0F9-43AC-848E-97E7E93E4EF0}"
11+
EndProject
12+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemoServer", "demo\DemoServer\DemoServer.csproj", "{FACCC87C-37E3-4095-9997-75EE3C54CD49}"
13+
EndProject
14+
Global
15+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
16+
Debug|Any CPU = Debug|Any CPU
17+
Release|Any CPU = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
20+
{62E576F6-E0F9-43AC-848E-97E7E93E4EF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21+
{62E576F6-E0F9-43AC-848E-97E7E93E4EF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
22+
{62E576F6-E0F9-43AC-848E-97E7E93E4EF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
23+
{62E576F6-E0F9-43AC-848E-97E7E93E4EF0}.Release|Any CPU.Build.0 = Release|Any CPU
24+
{FACCC87C-37E3-4095-9997-75EE3C54CD49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25+
{FACCC87C-37E3-4095-9997-75EE3C54CD49}.Debug|Any CPU.Build.0 = Debug|Any CPU
26+
{FACCC87C-37E3-4095-9997-75EE3C54CD49}.Release|Any CPU.ActiveCfg = Release|Any CPU
27+
{FACCC87C-37E3-4095-9997-75EE3C54CD49}.Release|Any CPU.Build.0 = Release|Any CPU
28+
EndGlobalSection
29+
GlobalSection(SolutionProperties) = preSolution
30+
HideSolutionNode = FALSE
31+
EndGlobalSection
32+
GlobalSection(NestedProjects) = preSolution
33+
{62E576F6-E0F9-43AC-848E-97E7E93E4EF0} = {21874FF3-BE97-4F3B-AD63-EC347FF757FF}
34+
{FACCC87C-37E3-4095-9997-75EE3C54CD49} = {62241700-24B6-4D13-A183-2502D628ED43}
35+
EndGlobalSection
36+
GlobalSection(ExtensibilityGlobals) = postSolution
37+
SolutionGuid = {41DEFC27-E139-4A35-989F-DA872E4B9F0A}
38+
EndGlobalSection
39+
EndGlobal
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net5.0</TargetFramework>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<PackageReference Include="Microsoft.Data.SqlClient" Version="3.0.0" />
9+
<PackageReference Include="Microsoft.Orleans.Clustering.AdoNet" Version="3.5.0" />
10+
<PackageReference Include="Microsoft.Orleans.OrleansRuntime" Version="3.5.0" />
11+
<PackageReference Include="Microsoft.Orleans.Persistence.AdoNet" Version="3.5.0" />
12+
<PackageReference Include="SignalR.Orleans" Version="2.0.1" />
13+
</ItemGroup>
14+
15+
</Project>
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Concurrent;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Microsoft.Extensions.Internal;
9+
10+
namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis.Internal
11+
{
12+
internal class AckHandler : IDisposable
13+
{
14+
private readonly ConcurrentDictionary<int, AckInfo> _acks = new ConcurrentDictionary<int, AckInfo>();
15+
private readonly Timer _timer;
16+
private readonly TimeSpan _ackThreshold = TimeSpan.FromSeconds(30);
17+
private readonly TimeSpan _ackInterval = TimeSpan.FromSeconds(5);
18+
private readonly object _lock = new object();
19+
private bool _disposed;
20+
21+
public AckHandler()
22+
{
23+
_timer = NonCapturingTimer.Create(state => ((AckHandler)state!).CheckAcks(), state: this, dueTime: _ackInterval, period: _ackInterval);
24+
}
25+
26+
public Task CreateAck(int id)
27+
{
28+
lock (_lock)
29+
{
30+
if (_disposed)
31+
{
32+
return Task.CompletedTask;
33+
}
34+
35+
return _acks.GetOrAdd(id, _ => new AckInfo()).Tcs.Task;
36+
}
37+
}
38+
39+
public void TriggerAck(int id)
40+
{
41+
if (_acks.TryRemove(id, out var ack))
42+
{
43+
ack.Tcs.TrySetResult();
44+
}
45+
}
46+
47+
private void CheckAcks()
48+
{
49+
if (_disposed)
50+
{
51+
return;
52+
}
53+
54+
var utcNow = DateTime.UtcNow;
55+
56+
foreach (var pair in _acks)
57+
{
58+
var elapsed = utcNow - pair.Value.Created;
59+
if (elapsed > _ackThreshold)
60+
{
61+
if (_acks.TryRemove(pair.Key, out var ack))
62+
{
63+
ack.Tcs.TrySetCanceled();
64+
}
65+
}
66+
}
67+
}
68+
69+
public void Dispose()
70+
{
71+
lock (_lock)
72+
{
73+
_disposed = true;
74+
75+
_timer.Dispose();
76+
77+
foreach (var pair in _acks)
78+
{
79+
if (_acks.TryRemove(pair.Key, out var ack))
80+
{
81+
ack.Tcs.TrySetCanceled();
82+
}
83+
}
84+
}
85+
}
86+
87+
private class AckInfo
88+
{
89+
public TaskCompletionSource Tcs { get; private set; }
90+
public DateTime Created { get; private set; }
91+
92+
public AckInfo()
93+
{
94+
Created = DateTime.UtcNow;
95+
Tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
96+
}
97+
}
98+
}
99+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using Microsoft.AspNetCore.SignalR.Protocol;
8+
9+
namespace Microsoft.AspNetCore.SignalR.Internal
10+
{
11+
internal class DefaultHubMessageSerializer
12+
{
13+
private readonly List<IHubProtocol> _hubProtocols;
14+
15+
public DefaultHubMessageSerializer(IHubProtocolResolver hubProtocolResolver, IList<string>? globalSupportedProtocols, IList<string>? hubSupportedProtocols)
16+
{
17+
var supportedProtocols = hubSupportedProtocols ?? globalSupportedProtocols ?? Array.Empty<string>();
18+
_hubProtocols = new List<IHubProtocol>(supportedProtocols.Count);
19+
foreach (var protocolName in supportedProtocols)
20+
{
21+
var protocol = hubProtocolResolver.GetProtocol(protocolName, (supportedProtocols as IReadOnlyList<string>) ?? supportedProtocols.ToList());
22+
if (protocol != null)
23+
{
24+
_hubProtocols.Add(protocol);
25+
}
26+
}
27+
}
28+
29+
public IReadOnlyList<SerializedMessage> SerializeMessage(HubMessage message)
30+
{
31+
var list = new List<SerializedMessage>(_hubProtocols.Count);
32+
foreach (var protocol in _hubProtocols)
33+
{
34+
list.Add(new SerializedMessage(protocol.Name, protocol.GetMessageBytes(message)));
35+
}
36+
37+
return list;
38+
}
39+
}
40+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis.Internal
5+
{
6+
// The size of the enum is defined by the protocol. Do not change it. If you need more than 255 items,
7+
// add an additional enum.
8+
internal enum GroupAction : byte
9+
{
10+
// These numbers are used by the protocol, do not change them and always use explicit assignment
11+
// when adding new items to this enum. 0 is intentionally omitted
12+
Add = 1,
13+
Remove = 2,
14+
}
15+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Runtime.CompilerServices;
5+
6+
namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis.Internal
7+
{
8+
internal class RedisChannels
9+
{
10+
private readonly string _prefix;
11+
12+
/// <summary>
13+
/// Gets the name of the channel for sending to all connections.
14+
/// </summary>
15+
/// <remarks>
16+
/// The payload on this channel is <see cref="RedisInvocation"/> objects containing
17+
/// invocations to be sent to all connections
18+
/// </remarks>
19+
public string All { get; }
20+
21+
/// <summary>
22+
/// Gets the name of the internal channel for group management messages.
23+
/// </summary>
24+
public string GroupManagement { get; }
25+
26+
public RedisChannels(string prefix)
27+
{
28+
_prefix = prefix;
29+
30+
All = prefix + ":all";
31+
GroupManagement = prefix + ":internal:groups";
32+
}
33+
34+
/// <summary>
35+
/// Gets the name of the channel for sending a message to a specific connection.
36+
/// </summary>
37+
/// <param name="connectionId">The ID of the connection to get the channel for.</param>
38+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
39+
public string Connection(string connectionId)
40+
{
41+
return _prefix + ":connection:" + connectionId;
42+
}
43+
44+
/// <summary>
45+
/// Gets the name of the channel for sending a message to a named group of connections.
46+
/// </summary>
47+
/// <param name="groupName">The name of the group to get the channel for.</param>
48+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
49+
public string Group(string groupName)
50+
{
51+
return _prefix + ":group:" + groupName;
52+
}
53+
54+
/// <summary>
55+
/// Gets the name of the channel for sending a message to all collections associated with a user.
56+
/// </summary>
57+
/// <param name="userId">The ID of the user to get the channel for.</param>
58+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
59+
public string User(string userId)
60+
{
61+
return _prefix + ":user:" + userId;
62+
}
63+
64+
/// <summary>
65+
/// Gets the name of the acknowledgement channel for the specified server.
66+
/// </summary>
67+
/// <param name="serverName">The name of the server to get the acknowledgement channel for.</param>
68+
/// <returns></returns>
69+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
70+
public string Ack(string serverName)
71+
{
72+
return _prefix + ":internal:ack:" + serverName;
73+
}
74+
}
75+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis.Internal
5+
{
6+
internal readonly struct RedisGroupCommand
7+
{
8+
/// <summary>
9+
/// Gets the ID of the group command.
10+
/// </summary>
11+
public int Id { get; }
12+
13+
/// <summary>
14+
/// Gets the name of the server that sent the command.
15+
/// </summary>
16+
public string ServerName { get; }
17+
18+
/// <summary>
19+
/// Gets the action to be performed on the group.
20+
/// </summary>
21+
public GroupAction Action { get; }
22+
23+
/// <summary>
24+
/// Gets the group on which the action is performed.
25+
/// </summary>
26+
public string GroupName { get; }
27+
28+
/// <summary>
29+
/// Gets the ID of the connection to be added or removed from the group.
30+
/// </summary>
31+
public string ConnectionId { get; }
32+
33+
public RedisGroupCommand(int id, string serverName, GroupAction action, string groupName, string connectionId)
34+
{
35+
Id = id;
36+
ServerName = serverName;
37+
Action = action;
38+
GroupName = groupName;
39+
ConnectionId = connectionId;
40+
}
41+
}
42+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using Microsoft.AspNetCore.SignalR.Protocol;
6+
7+
namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis.Internal
8+
{
9+
internal readonly struct RedisInvocation
10+
{
11+
/// <summary>
12+
/// Gets a list of connections that should be excluded from this invocation.
13+
/// May be null to indicate that no connections are to be excluded.
14+
/// </summary>
15+
public IReadOnlyList<string>? ExcludedConnectionIds { get; }
16+
17+
/// <summary>
18+
/// Gets the message serialization cache containing serialized payloads for the message.
19+
/// </summary>
20+
public SerializedHubMessage Message { get; }
21+
22+
public RedisInvocation(SerializedHubMessage message, IReadOnlyList<string>? excludedConnectionIds)
23+
{
24+
Message = message;
25+
ExcludedConnectionIds = excludedConnectionIds;
26+
}
27+
}
28+
}

0 commit comments

Comments
 (0)