Skip to content

Commit f42c211

Browse files
committed
Add redis startup error handling
1 parent 968809f commit f42c211

File tree

3 files changed

+66
-3
lines changed

3 files changed

+66
-3
lines changed

src/SignalR/server/StackExchangeRedis/src/Internal/RedisLog.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ public static void ConnectingToEndpoints(ILogger logger, EndPointCollection endp
5252
[LoggerMessage(11, LogLevel.Warning, "Error processing message for internal server message.", EventName = "InternalMessageFailed")]
5353
public static partial void InternalMessageFailed(ILogger logger, Exception exception);
5454

55+
[LoggerMessage(12, LogLevel.Error, "Error connecting to Redis.", EventName = "ErrorConnecting")]
56+
public static partial void ErrorConnecting(ILogger logger, Exception ex);
57+
5558
// This isn't DefineMessage-based because it's just the simple TextWriter logging from ConnectionMultiplexer
5659
public static void ConnectionMultiplexerMessage(ILogger logger, string? message)
5760
{

src/SignalR/server/StackExchangeRedis/src/RedisHubLifetimeManager.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ public RedisHubLifetimeManager(ILogger<RedisHubLifetimeManager<THub>> logger,
8383
public override async Task OnConnectedAsync(HubConnectionContext connection)
8484
{
8585
await EnsureRedisServerConnection();
86+
8687
var feature = new RedisFeature();
8788
connection.Features.Set<IRedisFeature>(feature);
8889

@@ -105,11 +106,17 @@ public override Task OnDisconnectedAsync(HubConnectionContext connection)
105106
{
106107
_connections.Remove(connection);
107108

108-
var tasks = new List<Task>();
109+
// If the bus is null then the Redis connection failed to be established and none of the other connection setup ran
110+
if (_bus is null)
111+
{
112+
return Task.CompletedTask;
113+
}
109114

110115
var connectionChannel = _channels.Connection(connection.ConnectionId);
116+
var tasks = new List<Task>();
117+
111118
RedisLog.Unsubscribe(_logger, connectionChannel);
112-
tasks.Add(_bus!.UnsubscribeAsync(connectionChannel));
119+
tasks.Add(_bus.UnsubscribeAsync(connectionChannel));
113120

114121
var feature = connection.Features.GetRequiredFeature<IRedisFeature>();
115122
var groupNames = feature.Groups;
@@ -550,7 +557,15 @@ private async Task EnsureRedisServerConnection()
550557
if (_redisServerConnection == null)
551558
{
552559
var writer = new LoggerTextWriter(_logger);
553-
_redisServerConnection = await _options.ConnectAsync(writer);
560+
try
561+
{
562+
_redisServerConnection = await _options.ConnectAsync(writer);
563+
}
564+
catch (Exception ex)
565+
{
566+
RedisLog.ErrorConnecting(_logger, ex);
567+
throw;
568+
}
554569
_bus = _redisServerConnection.GetSubscriber();
555570

556571
_redisServerConnection.ConnectionRestored += (_, e) =>

src/SignalR/server/StackExchangeRedis/test/RedisHubLifetimeManagerTests.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@
77
using Microsoft.AspNetCore.SignalR.Specification.Tests;
88
using Microsoft.AspNetCore.SignalR.Tests;
99
using Microsoft.AspNetCore.Testing;
10+
using Microsoft.Extensions.Logging;
1011
using Microsoft.Extensions.Logging.Abstractions;
12+
using Microsoft.Extensions.Logging.Testing;
1113
using Microsoft.Extensions.Options;
14+
using Moq;
1215
using Newtonsoft.Json.Linq;
1316
using Newtonsoft.Json.Serialization;
1417
using Xunit;
@@ -83,6 +86,48 @@ public async Task CamelCasedJsonIsPreservedAcrossRedisBoundary()
8386
}
8487
}
8588

89+
[Fact]
90+
public async Task ErrorFromConnectionFactoryLogsAndAllowsDisconnect()
91+
{
92+
var server = new TestRedisServer();
93+
94+
var testSink = new TestSink();
95+
var logger = new TestLogger("", testSink, true);
96+
var mockLoggerFactory = new Mock<ILoggerFactory>();
97+
mockLoggerFactory
98+
.Setup(m => m.CreateLogger(It.IsAny<string>()))
99+
.Returns((string categoryName) => (ILogger)logger);
100+
var loggerT = mockLoggerFactory.Object.CreateLogger<RedisHubLifetimeManager<Hub>>();
101+
102+
var manager = new RedisHubLifetimeManager<Hub>(
103+
loggerT,
104+
Options.Create(new RedisOptions()
105+
{
106+
ConnectionFactory = _ => throw new Exception("throw from connect")
107+
}),
108+
new DefaultHubProtocolResolver(new IHubProtocol[]
109+
{
110+
}, NullLogger<DefaultHubProtocolResolver>.Instance));
111+
112+
using (var client = new TestClient())
113+
{
114+
var connection = HubConnectionContextUtils.Create(client.Connection);
115+
116+
var ex = await Assert.ThrowsAsync<Exception>(() => manager.OnConnectedAsync(connection)).DefaultTimeout();
117+
Assert.Equal("throw from connect", ex.Message);
118+
119+
await manager.OnDisconnectedAsync(connection).DefaultTimeout();
120+
}
121+
122+
var logs = testSink.Writes.ToArray();
123+
// Two of the same error logs, one for the pre-emptive attempt to connect to Redis in the ctor, and one during the OnConnectedAsync for the connection
124+
Assert.Equal(2, logs.Length);
125+
Assert.Equal("Error connecting to Redis.", logs[0].Message);
126+
Assert.Equal("throw from connect", logs[0].Exception.Message);
127+
Assert.Equal("Error connecting to Redis.", logs[1].Message);
128+
Assert.Equal("throw from connect", logs[1].Exception.Message);
129+
}
130+
86131
public override TestRedisServer CreateBackplane()
87132
{
88133
return new TestRedisServer();

0 commit comments

Comments
 (0)