Skip to content

Commit efe29bf

Browse files
committed
3.x: handle synchronous request retry failures
1 parent 7da1f87 commit efe29bf

3 files changed

Lines changed: 187 additions & 3 deletions

File tree

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,11 @@ boolean isDefunct() {
791791
return isDefunct.get();
792792
}
793793

794+
@VisibleForTesting
795+
void markDefunctForTest() {
796+
isDefunct.set(true);
797+
}
798+
794799
int maxAvailableStreams() {
795800
return dispatcher.streamIdHandler.maxAvailableStreams();
796801
}

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -812,7 +812,21 @@ public void onSet(
812812
toPrepare.getQueryKeyspace(),
813813
connection.endPoint);
814814

815-
write(connection, prepareAndRetry(toPrepare.getQueryString()));
815+
try {
816+
write(connection, prepareAndRetry(toPrepare.getQueryString()));
817+
} catch (ConnectionException e) {
818+
if (metricsEnabled()) metrics().getErrorMetrics().getConnectionErrors().inc();
819+
connection.release();
820+
logError(connection.endPoint, e);
821+
retry(false, null);
822+
} catch (BusyConnectionException e) {
823+
connection.release(true);
824+
logError(connection.endPoint, e);
825+
retry(false, null);
826+
} catch (RuntimeException e) {
827+
connection.release();
828+
throw e;
829+
}
816830
// we're done for now, the prepareAndRetry callback will handle the rest
817831
return;
818832
case READ_FAILURE:

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

Lines changed: 167 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,133 @@
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.doReturn;
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 com.google.common.util.concurrent.Futures;
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;
3346
import org.scassandra.Scassandra;
34-
import org.scassandra.http.client.PrimingRequest;
47+
import org.scassandra.http.client.UnpreparedConfig;
3548
import org.testng.annotations.Test;
3649

3750
public class RequestHandlerTest {
3851

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

0 commit comments

Comments
 (0)