Skip to content

Commit b157fd6

Browse files
committed
Handle synchronous request retry failures
1 parent b666e09 commit b157fd6

2 files changed

Lines changed: 196 additions & 12 deletions

File tree

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

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -418,15 +418,25 @@ private boolean query(final Host host) {
418418
statementTable = defs.getTable(0);
419419
}
420420

421-
ListenableFuture<Connection> connectionFuture =
422-
pool.borrowConnection(
423-
poolingOptions.getPoolTimeoutMillis(),
424-
TimeUnit.MILLISECONDS,
425-
poolingOptions.getMaxQueueSize(),
426-
statement.getPartitioner(),
427-
routingKey,
428-
statementKeyspace,
429-
statementTable);
421+
ListenableFuture<Connection> connectionFuture;
422+
try {
423+
connectionFuture =
424+
pool.borrowConnection(
425+
poolingOptions.getPoolTimeoutMillis(),
426+
TimeUnit.MILLISECONDS,
427+
poolingOptions.getMaxQueueSize(),
428+
statement.getPartitioner(),
429+
routingKey,
430+
statementKeyspace,
431+
statementTable);
432+
} catch (ConnectionException e) {
433+
if (metricsEnabled()) metrics().getErrorMetrics().getConnectionErrors().inc();
434+
logError(host.getEndPoint(), e);
435+
return false;
436+
} catch (BusyConnectionException e) {
437+
logError(host.getEndPoint(), e);
438+
return false;
439+
}
430440
Futures.addCallback(
431441
connectionFuture,
432442
new FutureCallback<Connection>() {
@@ -816,7 +826,18 @@ public void onSet(
816826
toPrepare.getQueryKeyspace(),
817827
connection.endPoint);
818828

819-
write(connection, prepareAndRetry(toPrepare.getQueryString()));
829+
try {
830+
write(connection, prepareAndRetry(toPrepare.getQueryString()));
831+
} catch (ConnectionException e) {
832+
if (metricsEnabled()) metrics().getErrorMetrics().getConnectionErrors().inc();
833+
connection.release();
834+
logError(connection.endPoint, e);
835+
retry(false, null);
836+
} catch (BusyConnectionException e) {
837+
connection.release(true);
838+
logError(connection.endPoint, e);
839+
retry(false, null);
840+
}
820841
// we're done for now, the prepareAndRetry callback will handle the rest
821842
return;
822843
case READ_FAILURE:

driver-core/src/test/java/com/datastax/driver/core/RequestHandlerTest.java

Lines changed: 165 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,134 @@
2222
package com.datastax.driver.core;
2323

2424
import static org.assertj.core.api.Assertions.assertThat;
25+
import static org.mockito.Matchers.any;
26+
import static org.mockito.Matchers.anyInt;
27+
import static org.mockito.Matchers.anyLong;
28+
import static org.mockito.Mockito.doThrow;
29+
import static org.mockito.Mockito.spy;
30+
import static org.scassandra.http.client.PrimingRequest.preparedStatementBuilder;
31+
import static org.scassandra.http.client.PrimingRequest.queryBuilder;
2532
import static org.scassandra.http.client.PrimingRequest.then;
33+
import static org.scassandra.http.client.Result.unprepared;
34+
import static org.testng.Assert.fail;
2635

36+
import com.datastax.driver.core.exceptions.ConnectionException;
37+
import com.datastax.driver.core.exceptions.NoHostAvailableException;
2738
import com.google.common.collect.ImmutableMap;
39+
import java.lang.reflect.Field;
40+
import java.nio.ByteBuffer;
2841
import java.util.Collections;
2942
import java.util.List;
3043
import java.util.Map;
3144
import java.util.concurrent.TimeUnit;
3245
import java.util.concurrent.TimeoutException;
46+
import java.util.concurrent.atomic.AtomicBoolean;
3347
import org.scassandra.Scassandra;
34-
import org.scassandra.http.client.PrimingRequest;
48+
import org.scassandra.http.client.UnpreparedConfig;
3549
import org.testng.annotations.Test;
3650

3751
public class RequestHandlerTest {
3852

53+
@Test(groups = "short")
54+
public void should_try_next_host_when_borrow_connection_fails_synchronously() throws Exception {
55+
ScassandraCluster scassandra = ScassandraCluster.builder().withNodes(2).build();
56+
Cluster cluster = null;
57+
58+
try {
59+
scassandra.init();
60+
scassandra
61+
.node(2)
62+
.primingClient()
63+
.prime(
64+
queryBuilder()
65+
.withQuery("mock query")
66+
.withThen(then().withRows(row("result", "result2")))
67+
.build());
68+
69+
cluster = newCluster(scassandra);
70+
Session session = cluster.connect();
71+
Host host1 = TestUtils.findHost(cluster, 1);
72+
Host host2 = TestUtils.findHost(cluster, 2);
73+
74+
failBorrowingFrom(session, host1, new ConnectionException(host1.getEndPoint(), "mock"));
75+
76+
ResultSet rs = session.execute("mock query");
77+
78+
assertThat(rs.one().getString("result")).isEqualTo("result2");
79+
assertThat(rs.getExecutionInfo().getQueriedHost()).isEqualTo(host2);
80+
} finally {
81+
if (cluster != null) cluster.close();
82+
scassandra.stop();
83+
}
84+
}
85+
86+
@Test(groups = "short")
87+
public void should_report_no_host_when_unprepared_prepare_write_fails_synchronously()
88+
throws Exception {
89+
final Scassandra scassandra = TestUtils.createScassandraServer();
90+
Cluster cluster = null;
91+
92+
try {
93+
scassandra.start();
94+
ScassandraCluster.primeSystemLocalRow(scassandra);
95+
String query = "SELECT v FROM mock_table";
96+
scassandra
97+
.primingClient()
98+
.prime(
99+
preparedStatementBuilder()
100+
.withQuery(query)
101+
.withThen(then().withRows(row("result", "result1")))
102+
.build());
103+
104+
cluster =
105+
Cluster.builder()
106+
.addContactPoint(TestUtils.ipOfNode(1))
107+
.withPort(scassandra.getBinaryPort())
108+
.withPoolingOptions(
109+
new PoolingOptions()
110+
.setCoreConnectionsPerHost(HostDistance.LOCAL, 1)
111+
.setMaxConnectionsPerHost(HostDistance.LOCAL, 1)
112+
.setHeartbeatIntervalSeconds(0))
113+
.build();
114+
Session session = cluster.connect();
115+
Host host = TestUtils.findHost(cluster, 1);
116+
PreparedStatement prepared = session.prepare(query);
117+
118+
scassandra.primingClient().clearPreparedPrimes();
119+
scassandra
120+
.primingClient()
121+
.prime(
122+
preparedStatementBuilder()
123+
.withQuery(query)
124+
.withThen(
125+
then()
126+
.withResult(unprepared)
127+
.withFixedDelay(200L)
128+
.withConfig(
129+
new UnpreparedConfig(
130+
prepared.getPreparedId().boundValuesMetadata.id.toString())))
131+
.build());
132+
133+
Connection connection = getSingleConnection(session);
134+
ResultSetFuture future = session.executeAsync(prepared.bind());
135+
waitForInFlight(connection);
136+
markDefunctWithoutClosing(connection);
137+
138+
try {
139+
future.getUninterruptibly(5, TimeUnit.SECONDS);
140+
fail("expected a NoHostAvailableException");
141+
} catch (NoHostAvailableException e) {
142+
assertThat(e.getErrors()).containsKey(host.getEndPoint());
143+
assertThat(e.getErrors().get(host.getEndPoint()))
144+
.isInstanceOf(ConnectionException.class)
145+
.hasMessageContaining("Write attempt on defunct connection");
146+
}
147+
} finally {
148+
if (cluster != null) cluster.close();
149+
scassandra.stop();
150+
}
151+
}
152+
39153
@Test(groups = "short")
40154
public void should_handle_race_between_response_and_cancellation() {
41155
final Scassandra scassandra = TestUtils.createScassandraServer();
@@ -50,7 +164,7 @@ public void should_handle_race_between_response_and_cancellation() {
50164
scassandra
51165
.primingClient()
52166
.prime(
53-
PrimingRequest.queryBuilder()
167+
queryBuilder()
54168
.withQuery("mock query")
55169
.withThen(then().withRows(rows).withFixedDelay(10L))
56170
.build());
@@ -100,4 +214,53 @@ private Connection getSingleConnection(Session session) {
100214
HostConnectionPool pool = ((SessionManager) session).pools.values().iterator().next();
101215
return pool.connections[0].get(0);
102216
}
217+
218+
private Cluster newCluster(ScassandraCluster scassandra) {
219+
return Cluster.builder()
220+
.addContactPoints(scassandra.address(1).getAddress())
221+
.withPort(scassandra.getBinaryPort())
222+
.withLoadBalancingPolicy(new SortingLoadBalancingPolicy())
223+
.withPoolingOptions(
224+
new PoolingOptions()
225+
.setCoreConnectionsPerHost(HostDistance.LOCAL, 1)
226+
.setMaxConnectionsPerHost(HostDistance.LOCAL, 1)
227+
.setHeartbeatIntervalSeconds(0))
228+
.build();
229+
}
230+
231+
private void failBorrowingFrom(Session session, Host host, ConnectionException failure)
232+
throws Exception {
233+
SessionManager manager = (SessionManager) session;
234+
HostConnectionPool pool = spy(manager.pools.get(host));
235+
manager.pools.put(host, pool);
236+
doThrow(failure)
237+
.when(pool)
238+
.borrowConnection(
239+
anyLong(),
240+
(TimeUnit) any(),
241+
anyInt(),
242+
(Token.Factory) any(),
243+
(ByteBuffer) any(),
244+
(String) any(),
245+
(String) any());
246+
}
247+
248+
private void waitForInFlight(Connection connection) throws InterruptedException {
249+
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5);
250+
while (connection.inFlight.get() == 0 && System.nanoTime() < deadline) {
251+
Thread.sleep(1);
252+
}
253+
assertThat(connection.inFlight.get()).isGreaterThan(0);
254+
}
255+
256+
private void markDefunctWithoutClosing(Connection connection) throws Exception {
257+
// Keep the pending EXECUTE alive; only the following PREPARE write should see defunct.
258+
Field isDefunct = Connection.class.getDeclaredField("isDefunct");
259+
isDefunct.setAccessible(true);
260+
((AtomicBoolean) isDefunct.get(connection)).set(true);
261+
}
262+
263+
private static List<Map<String, ?>> row(String key, String value) {
264+
return Collections.<Map<String, ?>>singletonList(ImmutableMap.of(key, value));
265+
}
103266
}

0 commit comments

Comments
 (0)