diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md
index 893ec2de0..6fe54fcfe 100644
--- a/docs/ReleaseNotes.md
+++ b/docs/ReleaseNotes.md
@@ -8,7 +8,7 @@ Current package versions:
## Unreleased
-- (none)
+- Add overrideable `AfterDisconnectAsync()` callback on `DefaultOptionsProvider` ([#2952 by philon-msft](https://github.com/StackExchange/StackExchange.Redis/pull/2952))
## 2.9.17
diff --git a/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs b/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs
index 703adbcac..261a99e1e 100644
--- a/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs
+++ b/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs
@@ -307,6 +307,12 @@ protected virtual string GetDefaultClientName() =>
/// The logger for the connection, to emit to the connection output log.
public virtual Task AfterConnectAsync(ConnectionMultiplexer multiplexer, Action log) => Task.CompletedTask;
+ ///
+ /// The action to perform, if any, immediately after a connection is closed.
+ ///
+ /// The multiplexer that just disconnected.
+ public virtual Task AfterDisconnectAsync(ConnectionMultiplexer multiplexer) => Task.CompletedTask;
+
///
/// Gets the default SSL "enabled or not" based on a set of endpoints.
/// Note: this setting then applies for *all* endpoints.
diff --git a/src/StackExchange.Redis/ConfigurationOptions.cs b/src/StackExchange.Redis/ConfigurationOptions.cs
index c0021f024..731b83289 100644
--- a/src/StackExchange.Redis/ConfigurationOptions.cs
+++ b/src/StackExchange.Redis/ConfigurationOptions.cs
@@ -210,6 +210,8 @@ public DefaultOptionsProvider Defaults
internal Func, Task> AfterConnectAsync => Defaults.AfterConnectAsync;
+ internal Func AfterDisconnectAsync => Defaults.AfterDisconnectAsync;
+
///
/// Gets or sets whether connect/configuration timeouts should be explicitly notified via a TimeoutException.
///
@@ -305,8 +307,8 @@ public bool HighIntegrity
///
/// Supply a user certificate from a PEM file pair and enable TLS.
///
- /// The path for the the user certificate (commonly a .crt file).
- /// The path for the the user key (commonly a .key file).
+ /// The path for the user certificate (commonly a .crt file).
+ /// The path for the user key (commonly a .key file).
public void SetUserPemCertificate(string userCertificatePath, string? userKeyPath = null)
{
CertificateSelectionCallback = CreatePemUserCertificateCallback(userCertificatePath, userKeyPath);
@@ -317,7 +319,7 @@ public void SetUserPemCertificate(string userCertificatePath, string? userKeyPat
///
/// Supply a user certificate from a PFX file and optional password and enable TLS.
///
- /// The path for the the user certificate (commonly a .pfx file).
+ /// The path for the user certificate (commonly a .pfx file).
/// The password for the certificate file.
public void SetUserPfxCertificate(string userCertificatePath, string? password = null)
{
@@ -383,7 +385,7 @@ private static bool CheckTrustedIssuer(X509Certificate2 certificateToValidate, X
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
chain.ChainPolicy.VerificationTime = chainToValidate?.ChainPolicy?.VerificationTime ?? DateTime.Now;
chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 0);
- // Ensure entended key usage checks are run and that we're observing a server TLS certificate
+ // Ensure intended key usage checks are run and that we're observing a server TLS certificate
chain.ChainPolicy.ApplicationPolicy.Add(_serverAuthOid);
chain.ChainPolicy.ExtraStore.Add(authority);
diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.cs b/src/StackExchange.Redis/ConnectionMultiplexer.cs
index bf6b66674..56c1bbc0b 100644
--- a/src/StackExchange.Redis/ConnectionMultiplexer.cs
+++ b/src/StackExchange.Redis/ConnectionMultiplexer.cs
@@ -2294,9 +2294,11 @@ public void Close(bool allowCommandsToComplete = true)
var quits = QuitAllServers();
WaitAllIgnoreErrors(quits);
}
+
DisposeAndClearServers();
OnCloseReaderWriter();
OnClosing(true);
+ RawConfig.AfterDisconnectAsync?.Invoke(this).Wait(SyncConnectTimeout(true));
Interlocked.Increment(ref _connectionCloseCount);
}
@@ -2306,7 +2308,11 @@ public void Close(bool allowCommandsToComplete = true)
/// Whether to allow all in-queue commands to complete first.
public async Task CloseAsync(bool allowCommandsToComplete = true)
{
+ if (_isDisposed) return;
+
+ OnClosing(false);
_isDisposed = true;
+ _profilingSessionProvider = null;
using (var tmp = pulse)
{
pulse = null;
@@ -2319,6 +2325,10 @@ public async Task CloseAsync(bool allowCommandsToComplete = true)
}
DisposeAndClearServers();
+ OnCloseReaderWriter();
+ OnClosing(true);
+ await RawConfig.AfterDisconnectAsync(this).ForAwait();
+ Interlocked.Increment(ref _connectionCloseCount);
}
private void DisposeAndClearServers()
diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
index 10044dc9b..43413ecd2 100644
--- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
+++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
@@ -1848,6 +1848,7 @@ static StackExchange.Redis.StreamPosition.Beginning.get -> StackExchange.Redis.R
static StackExchange.Redis.StreamPosition.NewMessages.get -> StackExchange.Redis.RedisValue
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.AbortOnConnectFail.get -> bool
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.AfterConnectAsync(StackExchange.Redis.ConnectionMultiplexer! multiplexer, System.Action! log) -> System.Threading.Tasks.Task!
+virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.AfterDisconnectAsync(StackExchange.Redis.ConnectionMultiplexer! multiplexer) -> System.Threading.Tasks.Task!
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.AllowAdmin.get -> bool
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.BacklogPolicy.get -> StackExchange.Redis.BacklogPolicy!
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.CheckCertificateRevocation.get -> bool
diff --git a/tests/StackExchange.Redis.Tests/DefaultOptionsTests.cs b/tests/StackExchange.Redis.Tests/DefaultOptionsTests.cs
index be80fd9c5..81223adbd 100644
--- a/tests/StackExchange.Redis.Tests/DefaultOptionsTests.cs
+++ b/tests/StackExchange.Redis.Tests/DefaultOptionsTests.cs
@@ -151,6 +151,31 @@ public async Task AfterConnectAsyncHandler()
Assert.Equal(1, provider.Calls);
}
+ public class TestAfterDisconnectOptionsProvider : DefaultOptionsProvider
+ {
+ public int Calls;
+
+ public override Task AfterDisconnectAsync(ConnectionMultiplexer muxer)
+ {
+ Interlocked.Increment(ref Calls);
+ return Task.CompletedTask;
+ }
+ }
+
+ [Fact]
+ public async Task AfterDisconnectAsyncHandler()
+ {
+ var options = ConfigurationOptions.Parse(GetConfiguration());
+ var provider = new TestAfterDisconnectOptionsProvider();
+ options.Defaults = provider;
+
+ await using var conn = await ConnectionMultiplexer.ConnectAsync(options, Writer);
+ await conn.CloseAsync();
+
+ Assert.False(conn.IsConnected);
+ Assert.Equal(1, provider.Calls);
+ }
+
public class TestClientNameOptionsProvider : DefaultOptionsProvider
{
protected override string GetDefaultClientName() => "Hey there";