Skip to content

Commit cd6a2ac

Browse files
committed
Fixes JAVA-5949 prevent connection churn on backpressure errors when establishing connections
1 parent dbf72aa commit cd6a2ac

File tree

4 files changed

+92
-3
lines changed

4 files changed

+92
-3
lines changed

driver-core/src/main/com/mongodb/MongoException.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,22 @@ public class MongoException extends RuntimeException {
5050
*/
5151
public static final String UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL = "UnknownTransactionCommitResult";
5252

53+
/**
54+
* An error label indicating that the server is overloaded.
55+
*
56+
* @see #hasErrorLabel(String)
57+
* @since 5.7
58+
*/
59+
public static final String SYSTEM_OVERLOADED_ERROR_LABEL = "SystemOverloadedError";
60+
61+
/**
62+
* An error label indicating that the operation is safely retryable.
63+
*
64+
* @see #hasErrorLabel(String)
65+
* @since 5.7
66+
*/
67+
public static final String RETRYABLE_ERROR_LABEL = "RetryableError";
68+
5369
private static final long serialVersionUID = -4415279469780082174L;
5470

5571
private final int code;

driver-core/src/main/com/mongodb/internal/connection/DefaultSdamServerDescriptionManager.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.mongodb.internal.connection;
1818

19+
import com.mongodb.MongoException;
1920
import com.mongodb.annotations.ThreadSafe;
2021
import com.mongodb.connection.ClusterConnectionMode;
2122
import com.mongodb.connection.ServerDescription;
@@ -137,9 +138,28 @@ private void handleException(final SdamIssue sdamIssue, final boolean beforeHand
137138
serverMonitor.connect();
138139
} else if (sdamIssue.relatedToNetworkNotTimeout()
139140
|| (beforeHandshake && (sdamIssue.relatedToNetworkTimeout() || sdamIssue.relatedToAuth()))) {
140-
updateDescription(sdamIssue.serverDescription());
141-
connectionPool.invalidate(sdamIssue.exception().orElse(null));
142-
serverMonitor.cancelCurrentCheck();
141+
// Backpressure spec: Don't clear pool or mark server unknown for connection establishment failures
142+
// (network errors or timeouts during handshake). Authentication errors after handshake should still
143+
// clear the pool as they're not related to overload.
144+
// TLS configuration errors (certificate validation, protocol mismatches) should also clear the pool
145+
// as they indicate configuration issues, not server overload.
146+
if (beforeHandshake && (sdamIssue.relatedToNetworkNotTimeout() || sdamIssue.relatedToNetworkTimeout())
147+
&& !sdamIssue.relatedToAuth() && !sdamIssue.relatedToTlsConfigurationError()) {
148+
// Don't update server description to Unknown
149+
// Don't invalidate the connection pool
150+
// Apply error labels for backpressure
151+
sdamIssue.exception().ifPresent(exception -> {
152+
if (exception instanceof MongoException) {
153+
MongoException mongoException = (MongoException) exception;
154+
mongoException.addLabel(MongoException.SYSTEM_OVERLOADED_ERROR_LABEL);
155+
mongoException.addLabel(MongoException.RETRYABLE_ERROR_LABEL);
156+
}
157+
});
158+
} else {
159+
updateDescription(sdamIssue.serverDescription());
160+
connectionPool.invalidate(sdamIssue.exception().orElse(null));
161+
serverMonitor.cancelCurrentCheck();
162+
}
143163
} else if (sdamIssue.relatedToWriteConcern() || sdamIssue.relatedToStalePrimary()) {
144164
updateDescription(sdamIssue.serverDescription());
145165
serverMonitor.connect();

driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnectionInitializer.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ private BsonDocument createHelloCommand(final Authenticator authenticator, final
198198
helloCommandDocument.append("speculativeAuthenticate", speculativeAuthenticateDocument);
199199
}
200200
}
201+
// Add backpressure support indication
202+
helloCommandDocument.append("backpressure", BsonBoolean.TRUE);
201203
return helloCommandDocument;
202204
}
203205

driver-core/src/main/com/mongodb/internal/connection/SdamServerDescriptionManager.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
import com.mongodb.connection.TopologyVersion;
3131
import com.mongodb.lang.Nullable;
3232

33+
import javax.net.ssl.SSLHandshakeException;
34+
import javax.net.ssl.SSLPeerUnverifiedException;
35+
import java.security.cert.CertPathValidatorException;
36+
import java.security.cert.CertificateException;
3337
import java.util.Optional;
3438

3539
import static com.mongodb.assertions.Assertions.assertNotNull;
@@ -162,6 +166,53 @@ boolean relatedToWriteConcern() {
162166
return exception instanceof MongoWriteConcernWithResponseException;
163167
}
164168

169+
/**
170+
* Checks if the exception is related to TLS configuration errors that are NOT due to server overload.
171+
* These include certificate validation failures, protocol mismatches, etc.
172+
*
173+
* @return true if this is a TLS configuration error (not network-related)
174+
*/
175+
boolean relatedToTlsConfigurationError() {
176+
if (!(exception instanceof MongoSocketException)) {
177+
return false;
178+
}
179+
Throwable cause = exception.getCause();
180+
while (cause != null) {
181+
// Check for various certificate validation and TLS configuration errors
182+
if (cause instanceof CertificateException
183+
|| cause instanceof CertPathValidatorException
184+
|| cause instanceof SSLPeerUnverifiedException) {
185+
return true; // Certificate/peer validation failure
186+
}
187+
188+
// Check for SunCertPathBuilderException by class name to avoid compile-time dependency on internal classes
189+
String className = cause.getClass().getName();
190+
if (className.equals("sun.security.provider.certpath.SunCertPathBuilderException")) {
191+
return true; // Certificate path building failure
192+
}
193+
194+
// SSLHandshakeException can be either network or config, so we check the message
195+
if (cause instanceof SSLHandshakeException) {
196+
String message = cause.getMessage();
197+
if (message != null) {
198+
String lowerMessage = message.toLowerCase();
199+
// These indicate configuration issues, not network issues
200+
if (lowerMessage.contains("certificate")
201+
|| lowerMessage.contains("verify")
202+
|| lowerMessage.contains("trust")
203+
|| lowerMessage.contains("hostname")
204+
|| lowerMessage.contains("protocol")
205+
|| lowerMessage.contains("cipher")
206+
|| lowerMessage.contains("handshake_failure")) {
207+
return true;
208+
}
209+
}
210+
}
211+
cause = cause.getCause();
212+
}
213+
return false;
214+
}
215+
165216
private static boolean stale(@Nullable final Throwable t, final ServerDescription currentServerDescription) {
166217
return TopologyVersionHelper.topologyVersion(t)
167218
.map(candidateTopologyVersion -> TopologyVersionHelper.newerOrEqual(

0 commit comments

Comments
 (0)