Expose per-attempt connection outcomes with the selected TLS client certificate
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 45/100
Research direction
The issue is about modifying the StackExchange.Redis client's connection logic to expose per-attempt outcomes. Start by examining the PhysicalBridge class and its OnConnectionFailed method to understand the current suppression logic. Look for where LocalCertificateSelectionCallback is invoked and how to wrap it to capture the selected certificate. The goal is to design and implement a new callback, ConnectionAttemptCompleted, with the proposed EventArgs, ensuring it fires for all physical connection attempts.
Written by the indexing model from the issue text.
Description
Summary
Please expose a supported, structured callback for every physical connection attempt, including:
- initial connection attempts;
- successful reconnects;
- failed reconnects that do not raise the existing
ConnectionFailedevent; - the client certificate selected for that specific attempt.
The callback should be invoked for both success and failure. This would allow certificate integrations to correlate a connection outcome with the exact certificate used.
The existing ConnectionFailed and ConnectionRestored behavior can remain unchanged for compatibility.
Motivation
We maintain a client-certificate management integration for StackExchange.Redis. It selects a client certificate for each TLS handshake and reports whether that certificate succeeded or failed, allowing fallback to a previously known-good certificate.
In short, I want to provide a client certificate and know if that certificate works.
Two limitations in the current public API prevent reliable integration.
1. Reconnect failures can be suppressed
In StackExchange.Redis, PhysicalBridge.OnConnectionFailed raises ConnectionFailed only while reportNextFailure is true.
After the first failure, the flag remains false until that bridge establishes a connection successfully. Subsequent failed reconnect attempts are therefore not reported through ConnectionFailed.
This creates the following sequence:
- Redis is connected successfully.
- The established connection closes with
SocketClosed. ConnectionFailedis raised, consuming the bridge's notification.- A newly rotated client certificate is selected for reconnect.
- The Redis server rejects that certificate.
- The reconnect fails, but no new
ConnectionFailedevent is raised because connectivity was never restored. - The integration cannot report the rejected certificate or activate its known-good fallback.
- Subsequent reconnects continue using the rejected certificate.
A custom failure classifier cannot solve this because it is never invoked when the event itself is suppressed.
2. The selected certificate cannot be correlated with the attempt
LocalCertificateSelectionCallback reports which certificate was selected, while ConnectionFailed and ConnectionRestored report outcomes independently.
The connection outcome does not identify the selected certificate. Integrations must therefore maintain a queue of selected certificates and assume connection outcomes arrive in the same order.
That assumption can fail when interactive and subscription bridges reconnect concurrently or when failures are suppressed.
For example:
- Certificate A is selected.
- The attempt fails because Redis AUTH credentials are invalid.
- Certificate A must be removed from the outstanding-attempt queue without being marked unhealthy.
- Multiple bridges and reconnect attempts can interleave selections and outcomes.
- Certificate B is later selected and rejected.
- Without an outcome carrying the selected certificate, the rejection can be incorrectly attributed to A.
This can produce incorrect fallback decisions, incorrect telemetry, and retained certificate references.
Correlation versus causation
Including the selected certificate would provide deterministic correlation:
Certificate B was selected for the connection attempt that produced exception X.
It would not by itself prove causation:
Certificate B caused exception X.
The same attempt may fail because of:
- client-certificate rejection;
- server-certificate validation;
- Redis authentication;
- socket connection failure;
- TLS protocol negotiation;
- Redis handshake failure.
Including the stage at which the attempt failed would allow integrations to classify the failure without assuming every TLS exception was caused by the client certificate.
Proposed API
Add a configuration-time callback that is invoked for every completed physical connection attempt:
public Action<ConnectionAttemptCompletedEventArgs>?
ConnectionAttemptCompleted { get; set; }
A configuration-time callback is preferable to an event that can only be attached after Connect returns because it also observes initial connection attempts.
Suggested event arguments:
public sealed class ConnectionAttemptCompletedEventArgs : EventArgs
{
public EndPoint EndPoint { get; }
public ConnectionType ConnectionType { get; }
public string? PhysicalName { get; }
public X509Certificate? ClientCertificate { get; }
public bool Succeeded { get; }
public ConnectionFailureType? FailureType { get; }
public Exception? Exception { get; }
public bool? ServerCertificateAccepted { get; }
public SslPolicyErrors? ServerCertificatePolicyErrors { get; }
}
Capturing the selected certificate
StackExchange.Redis would need to wrap the configured local-certificate selection callback and record the returned certificate on the current physical connection attempt.
This should cover certificate selection configured through both:
ConfigurationOptions.CertificateSelection;SslClientAuthenticationOptions.LocalCertificateSelectionCallback.
The captured certificate would then be included in the completion callback for the same attempt.
Optional failure stage information
If practical, the result could also expose the stage at which the attempt failed:
public ConnectionAttemptStage Stage { get; }
public enum ConnectionAttemptStage
{
SocketConnect,
TlsAuthentication,
RedisAuthentication,
RedisHandshake,
Established,
}
Example usage
configuration.ConnectionAttemptCompleted = args =>
{
if (args.ClientCertificateThumbprint is null)
{
return;
}
if (args.Succeeded)
{
// Log that the connection was successful, mark the client certificate as the LKG.
return;
}
if (IsClientCertificateRejection(args))
{
// Log that the certificate is not valid, trigger a fallback to LKG if available.
}
};
Existing alternatives
Currently we utilize a custom ILoggerFactory to detect these errors as it currently receives errors for individual physical failures before ConnectionFailed suppression. However, logging is not an appropriate application-control-flow contract, and correlation with the certificate and stage of authentication (to distinguish between TLS server and client certificate failures) is entirely missing.
Compatibility
The existing ConnectionFailed and ConnectionRestored events should retain their current suppression and notification behavior.
The proposed callback is additive and intended for integrations that require per-attempt outcomes.
- Dominant language
- C#
- Stars
- 6.2k
- Forks
- 1.6k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 43
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from StackExchange/StackExchange.Redis
-
Difficulty 1/5 Under an hour Newbie friendliness 78/100
StackExchange/StackExchange.Redis#3249 ·
-
HotKeysClusterTests.CanUseClusterFilter skips intermittently, hiding the assertions it exists for Open
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
StackExchange/StackExchange.Redis#3239 · 1 comment ·
-
Difficulty 4/5 3-5 days Newbie friendliness 45/100
StackExchange/StackExchange.Redis#3240 ·
-
Difficulty 5/5 Over a week Newbie friendliness 35/100
StackExchange/StackExchange.Redis#3233 · 1 comment ·
-
⚙️ area:failover
StackExchange/StackExchange.Redis#3147 · 1 assignee ·
All issues in StackExchange/StackExchange.Redis
Similar issues
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
nightscout/nocturne#1425 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
RayWangQvQ/BiliBiliToolPro#1137 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100