Skip to content

Commit 5dd9d67

Browse files
committed
Add DefaultOptionsProvider.AfterDisconnectAsync()
1 parent fec2cae commit 5dd9d67

File tree

6 files changed

+49
-5
lines changed

6 files changed

+49
-5
lines changed

docs/ReleaseNotes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Current package versions:
88

99
## Unreleased
1010

11-
- (none)
11+
- Add overrideable `AfterDisconnectAsync()` callback on `DefaultOptionsProvider` ([#2952 by philon-msft](https://github.com/StackExchange/StackExchange.Redis/pull/2952))
1212

1313
## 2.9.17
1414

src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,12 @@ protected virtual string GetDefaultClientName() =>
307307
/// <param name="log">The logger for the connection, to emit to the connection output log.</param>
308308
public virtual Task AfterConnectAsync(ConnectionMultiplexer multiplexer, Action<string> log) => Task.CompletedTask;
309309

310+
/// <summary>
311+
/// The action to perform, if any, immediately after a connection is closed.
312+
/// </summary>
313+
/// <param name="multiplexer">The multiplexer that just disconnected.</param>
314+
public virtual Task AfterDisconnectAsync(ConnectionMultiplexer multiplexer) => Task.CompletedTask;
315+
310316
/// <summary>
311317
/// Gets the default SSL "enabled or not" based on a set of endpoints.
312318
/// Note: this setting then applies for *all* endpoints.

src/StackExchange.Redis/ConfigurationOptions.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,8 @@ public DefaultOptionsProvider Defaults
210210

211211
internal Func<ConnectionMultiplexer, Action<string>, Task> AfterConnectAsync => Defaults.AfterConnectAsync;
212212

213+
internal Func<ConnectionMultiplexer, Task> AfterDisconnectAsync => Defaults.AfterDisconnectAsync;
214+
213215
/// <summary>
214216
/// Gets or sets whether connect/configuration timeouts should be explicitly notified via a TimeoutException.
215217
/// </summary>
@@ -305,8 +307,8 @@ public bool HighIntegrity
305307
/// <summary>
306308
/// Supply a user certificate from a PEM file pair and enable TLS.
307309
/// </summary>
308-
/// <param name="userCertificatePath">The path for the the user certificate (commonly a .crt file).</param>
309-
/// <param name="userKeyPath">The path for the the user key (commonly a .key file).</param>
310+
/// <param name="userCertificatePath">The path for the user certificate (commonly a .crt file).</param>
311+
/// <param name="userKeyPath">The path for the user key (commonly a .key file).</param>
310312
public void SetUserPemCertificate(string userCertificatePath, string? userKeyPath = null)
311313
{
312314
CertificateSelectionCallback = CreatePemUserCertificateCallback(userCertificatePath, userKeyPath);
@@ -317,7 +319,7 @@ public void SetUserPemCertificate(string userCertificatePath, string? userKeyPat
317319
/// <summary>
318320
/// Supply a user certificate from a PFX file and optional password and enable TLS.
319321
/// </summary>
320-
/// <param name="userCertificatePath">The path for the the user certificate (commonly a .pfx file).</param>
322+
/// <param name="userCertificatePath">The path for the user certificate (commonly a .pfx file).</param>
321323
/// <param name="password">The password for the certificate file.</param>
322324
public void SetUserPfxCertificate(string userCertificatePath, string? password = null)
323325
{
@@ -383,7 +385,7 @@ private static bool CheckTrustedIssuer(X509Certificate2 certificateToValidate, X
383385
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
384386
chain.ChainPolicy.VerificationTime = chainToValidate?.ChainPolicy?.VerificationTime ?? DateTime.Now;
385387
chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 0);
386-
// Ensure entended key usage checks are run and that we're observing a server TLS certificate
388+
// Ensure intended key usage checks are run and that we're observing a server TLS certificate
387389
chain.ChainPolicy.ApplicationPolicy.Add(_serverAuthOid);
388390

389391
chain.ChainPolicy.ExtraStore.Add(authority);

src/StackExchange.Redis/ConnectionMultiplexer.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2294,9 +2294,11 @@ public void Close(bool allowCommandsToComplete = true)
22942294
var quits = QuitAllServers();
22952295
WaitAllIgnoreErrors(quits);
22962296
}
2297+
22972298
DisposeAndClearServers();
22982299
OnCloseReaderWriter();
22992300
OnClosing(true);
2301+
RawConfig.AfterDisconnectAsync?.Invoke(this).Wait(SyncConnectTimeout(true));
23002302
Interlocked.Increment(ref _connectionCloseCount);
23012303
}
23022304

@@ -2306,7 +2308,11 @@ public void Close(bool allowCommandsToComplete = true)
23062308
/// <param name="allowCommandsToComplete">Whether to allow all in-queue commands to complete first.</param>
23072309
public async Task CloseAsync(bool allowCommandsToComplete = true)
23082310
{
2311+
if (_isDisposed) return;
2312+
2313+
OnClosing(false);
23092314
_isDisposed = true;
2315+
_profilingSessionProvider = null;
23102316
using (var tmp = pulse)
23112317
{
23122318
pulse = null;
@@ -2319,6 +2325,10 @@ public async Task CloseAsync(bool allowCommandsToComplete = true)
23192325
}
23202326

23212327
DisposeAndClearServers();
2328+
OnCloseReaderWriter();
2329+
OnClosing(true);
2330+
await RawConfig.AfterDisconnectAsync(this).ForAwait();
2331+
Interlocked.Increment(ref _connectionCloseCount);
23222332
}
23232333

23242334
private void DisposeAndClearServers()

src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1848,6 +1848,7 @@ static StackExchange.Redis.StreamPosition.Beginning.get -> StackExchange.Redis.R
18481848
static StackExchange.Redis.StreamPosition.NewMessages.get -> StackExchange.Redis.RedisValue
18491849
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.AbortOnConnectFail.get -> bool
18501850
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.AfterConnectAsync(StackExchange.Redis.ConnectionMultiplexer! multiplexer, System.Action<string!>! log) -> System.Threading.Tasks.Task!
1851+
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.AfterDisconnectAsync(StackExchange.Redis.ConnectionMultiplexer! multiplexer) -> System.Threading.Tasks.Task!
18511852
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.AllowAdmin.get -> bool
18521853
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.BacklogPolicy.get -> StackExchange.Redis.BacklogPolicy!
18531854
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.CheckCertificateRevocation.get -> bool

tests/StackExchange.Redis.Tests/DefaultOptionsTests.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,31 @@ public async Task AfterConnectAsyncHandler()
151151
Assert.Equal(1, provider.Calls);
152152
}
153153

154+
public class TestAfterDisconnectOptionsProvider : DefaultOptionsProvider
155+
{
156+
public int Calls;
157+
158+
public override Task AfterDisconnectAsync(ConnectionMultiplexer muxer)
159+
{
160+
Interlocked.Increment(ref Calls);
161+
return Task.CompletedTask;
162+
}
163+
}
164+
165+
[Fact]
166+
public async Task AfterDisconnectAsyncHandler()
167+
{
168+
var options = ConfigurationOptions.Parse(GetConfiguration());
169+
var provider = new TestAfterDisconnectOptionsProvider();
170+
options.Defaults = provider;
171+
172+
await using var conn = await ConnectionMultiplexer.ConnectAsync(options, Writer);
173+
await conn.CloseAsync();
174+
175+
Assert.False(conn.IsConnected);
176+
Assert.Equal(1, provider.Calls);
177+
}
178+
154179
public class TestClientNameOptionsProvider : DefaultOptionsProvider
155180
{
156181
protected override string GetDefaultClientName() => "Hey there";

0 commit comments

Comments
 (0)