Skip to content

Commit 5e64ad3

Browse files
committed
3.x: fail fast on permanent keyspace setup failures
Keyspace setup during connection borrow used to treat every failed internal USE as a host or connection failure. That is correct for driver-side failures and transient server errors, but permanent validation errors such as a missing keyspace should fail the user request directly. Return synchronous USE write failures as failed futures so pool accounting is restored. Preserve QueryValidationException from internal USE without defuncting the connection, and have RequestHandler fail fast for those permanent errors. Keep internal driver failures and transient server-side errors on the normal next-host path before the user query is written. Preserve the synchronous setKeyspace busy-connection behavior after async failures are reported through futures: BusyConnectionException is still converted to a non-defuncting ConnectionException, while ConnectionException keeps the previous defunct behavior. Add focused coverage for defunct connections, pool accounting, permanent validation errors, transient server errors, retry-policy accounting, single-connection-pool next-host failover, and synchronous busy keyspace setup. Release test ByteBufs used to synthesize server errors.
1 parent b666e09 commit 5e64ad3

4 files changed

Lines changed: 471 additions & 11 deletions

File tree

driver-core/src/main/java/com/datastax/driver/core/Connection.java

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import com.datastax.driver.core.exceptions.DriverInternalError;
3535
import com.datastax.driver.core.exceptions.FrameTooLongException;
3636
import com.datastax.driver.core.exceptions.OperationTimedOutException;
37+
import com.datastax.driver.core.exceptions.QueryValidationException;
3738
import com.datastax.driver.core.exceptions.TransportException;
3839
import com.datastax.driver.core.exceptions.UnsupportedProtocolVersionException;
3940
import com.datastax.driver.core.utils.MoreFutures;
@@ -848,14 +849,14 @@ void setKeyspace(String keyspace) throws ConnectionException {
848849
} catch (ConnectionException e) {
849850
throw defunct(e);
850851
} catch (BusyConnectionException e) {
851-
logger.warn(
852-
"Tried to set the keyspace on busy {}. "
853-
+ "This should not happen but is not critical (it will be retried)",
854-
this);
855-
throw new ConnectionException(endPoint, "Tried to set the keyspace on busy connection");
852+
throw keyspaceBusyException();
856853
} catch (ExecutionException e) {
857854
Throwable cause = e.getCause();
858-
if (cause instanceof OperationTimedOutException) {
855+
if (cause instanceof BusyConnectionException) {
856+
throw keyspaceBusyException();
857+
} else if (cause instanceof ConnectionException) {
858+
throw defunct((ConnectionException) cause);
859+
} else if (cause instanceof OperationTimedOutException) {
859860
// Rethrow so that the caller doesn't try to use the connection, but do not defunct as we
860861
// don't want to mark down
861862
logger.warn(
@@ -869,6 +870,14 @@ void setKeyspace(String keyspace) throws ConnectionException {
869870
}
870871
}
871872

873+
private ConnectionException keyspaceBusyException() {
874+
logger.warn(
875+
"Tried to set the keyspace on busy {}. "
876+
+ "This should not happen but is not critical (it will be retried)",
877+
this);
878+
return new ConnectionException(endPoint, "Tried to set the keyspace on busy connection");
879+
}
880+
872881
ListenableFuture<Connection> setKeyspaceAsync(final String keyspace)
873882
throws ConnectionException, BusyConnectionException {
874883
SetKeyspaceAttempt existingAttempt = targetKeyspace.get();
@@ -900,7 +909,17 @@ ListenableFuture<Connection> setKeyspaceAsync(final String keyspace)
900909
logger.debug("{} Setting keyspace {}", this, keyspace);
901910
// Note: we quote the keyspace below, because the name is the one coming from Cassandra, so
902911
// it's in the right case already
903-
Future future = write(new Requests.Query("USE \"" + keyspace + '"'));
912+
Future future;
913+
try {
914+
future = write(new Requests.Query("USE \"" + keyspace + '"'));
915+
} catch (ConnectionException | BusyConnectionException e) {
916+
targetKeyspace.compareAndSet(attempt, defaultKeyspaceAttempt);
917+
ksFuture.setException(e);
918+
return ksFuture;
919+
} catch (RuntimeException e) {
920+
targetKeyspace.compareAndSet(attempt, defaultKeyspaceAttempt);
921+
throw e;
922+
}
904923
Futures.addCallback(
905924
future,
906925
new FutureCallback<Message.Response>() {
@@ -915,7 +934,12 @@ public void onSuccess(Message.Response response) {
915934
targetKeyspace.compareAndSet(attempt, defaultKeyspaceAttempt);
916935
if (response.type == ERROR) {
917936
Responses.Error error = (Responses.Error) response;
918-
ksFuture.setException(defunct(error.asException(endPoint)));
937+
DriverException exception = error.asException(endPoint);
938+
if (exception instanceof QueryValidationException) {
939+
ksFuture.setException(exception);
940+
} else {
941+
ksFuture.setException(defunct(exception));
942+
}
919943
} else {
920944
ksFuture.setException(
921945
defunct(

driver-core/src/main/java/com/datastax/driver/core/HostConnectionPool.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import static com.datastax.driver.core.Connection.State.TRASHED;
2828

2929
import com.datastax.driver.core.exceptions.AuthenticationException;
30+
import com.datastax.driver.core.exceptions.BusyConnectionException;
3031
import com.datastax.driver.core.exceptions.BusyPoolException;
3132
import com.datastax.driver.core.exceptions.ConnectionException;
3233
import com.datastax.driver.core.exceptions.UnsupportedProtocolVersionException;
@@ -611,7 +612,22 @@ ListenableFuture<Connection> borrowConnection(
611612
if (totalInFlightCount > currentCapacity) maybeSpawnNewConnection(shardId);
612613
}
613614

614-
return leastBusy.setKeyspaceAsync(manager.poolsState.keyspace);
615+
final Connection borrowedConnection = leastBusy;
616+
ListenableFuture<Connection> setKeyspaceFuture =
617+
borrowedConnection.setKeyspaceAsync(manager.poolsState.keyspace);
618+
Futures.addCallback(
619+
setKeyspaceFuture,
620+
new FutureCallback<Connection>() {
621+
@Override
622+
public void onSuccess(Connection connection) {}
623+
624+
@Override
625+
public void onFailure(Throwable t) {
626+
borrowedConnection.release(t instanceof BusyConnectionException);
627+
}
628+
},
629+
MoreExecutors.directExecutor());
630+
return setKeyspaceFuture;
615631
}
616632

617633
private ListenableFuture<Connection> enqueue(
@@ -698,8 +714,14 @@ private void dequeue(final Connection connection) {
698714
pendingBorrowCount.decrementAndGet();
699715
// Ensure that the keyspace set on the connection is the one set on the pool state, in the
700716
// general case it will be.
701-
ListenableFuture<Connection> setKeyspaceFuture =
702-
connection.setKeyspaceAsync(manager.poolsState.keyspace);
717+
ListenableFuture<Connection> setKeyspaceFuture;
718+
try {
719+
setKeyspaceFuture = connection.setKeyspaceAsync(manager.poolsState.keyspace);
720+
} catch (ConnectionException | BusyConnectionException e) {
721+
pendingBorrow.setException(e);
722+
connection.inFlight.decrementAndGet();
723+
continue;
724+
}
703725
// Slight optimization, if the keyspace was already correct the future will be complete, so
704726
// simply complete it here.
705727
if (setKeyspaceFuture.isDone()) {

driver-core/src/main/java/com/datastax/driver/core/RequestHandler.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.datastax.driver.core.exceptions.NoHostAvailableException;
3232
import com.datastax.driver.core.exceptions.OperationTimedOutException;
3333
import com.datastax.driver.core.exceptions.OverloadedException;
34+
import com.datastax.driver.core.exceptions.QueryValidationException;
3435
import com.datastax.driver.core.exceptions.ReadFailureException;
3536
import com.datastax.driver.core.exceptions.ReadTimeoutException;
3637
import com.datastax.driver.core.exceptions.ServerError;
@@ -468,6 +469,10 @@ public void onSuccess(Connection connection) {
468469

469470
@Override
470471
public void onFailure(Throwable t) {
472+
if (t instanceof QueryValidationException) {
473+
setFinalException(null, (QueryValidationException) t);
474+
return;
475+
}
471476
if (t instanceof BusyPoolException) {
472477
logError(host.getEndPoint(), t);
473478
} else {

0 commit comments

Comments
 (0)