Skip to content

Commit 0c0eb6c

Browse files
authored
JAVA-1944: Surface Read and WriteFailureException to RetryPolicy (#1084)
1 parent 192b11b commit 0c0eb6c

File tree

8 files changed

+134
-3
lines changed

8 files changed

+134
-3
lines changed

changelog/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
- [improvement] JAVA-1771: Send driver name and version in STARTUP message.
2020
- [improvement] JAVA-1388: Add dynamic port discovery for system.peers\_v2.
2121
- [documentation] JAVA-1810: Note which setters are not propagated to PreparedStatement.
22+
- [bug] JAVA-1944: Surface Read and WriteFailureException to RetryPolicy.
2223

2324
Merged from 3.5.x:
2425

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@
2525
import com.datastax.driver.core.exceptions.NoHostAvailableException;
2626
import com.datastax.driver.core.exceptions.OperationTimedOutException;
2727
import com.datastax.driver.core.exceptions.OverloadedException;
28+
import com.datastax.driver.core.exceptions.ReadFailureException;
2829
import com.datastax.driver.core.exceptions.ReadTimeoutException;
2930
import com.datastax.driver.core.exceptions.ServerError;
3031
import com.datastax.driver.core.exceptions.UnavailableException;
32+
import com.datastax.driver.core.exceptions.WriteFailureException;
3133
import com.datastax.driver.core.exceptions.WriteTimeoutException;
3234
import com.datastax.driver.core.policies.RetryPolicy;
3335
import com.datastax.driver.core.policies.RetryPolicy.RetryDecision.Type;
@@ -761,6 +763,23 @@ public void onSet(
761763
write(connection, prepareAndRetry(toPrepare.getQueryString()));
762764
// we're done for now, the prepareAndRetry callback will handle the rest
763765
return;
766+
case READ_FAILURE:
767+
assert exceptionToReport instanceof ReadFailureException;
768+
connection.release();
769+
retry =
770+
computeRetryDecisionOnRequestError((ReadFailureException) exceptionToReport);
771+
break;
772+
case WRITE_FAILURE:
773+
assert exceptionToReport instanceof WriteFailureException;
774+
connection.release();
775+
if (statement.isIdempotentWithDefault(
776+
manager.cluster.getConfiguration().getQueryOptions())) {
777+
retry =
778+
computeRetryDecisionOnRequestError((WriteFailureException) exceptionToReport);
779+
} else {
780+
retry = RetryPolicy.RetryDecision.rethrow();
781+
}
782+
break;
764783
default:
765784
connection.release();
766785
if (metricsEnabled()) metrics().getErrorMetrics().getOthers().inc();

driver-core/src/main/java/com/datastax/driver/core/policies/DefaultRetryPolicy.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import com.datastax.driver.core.Statement;
2121
import com.datastax.driver.core.WriteType;
2222
import com.datastax.driver.core.exceptions.DriverException;
23+
import com.datastax.driver.core.exceptions.ReadFailureException;
24+
import com.datastax.driver.core.exceptions.WriteFailureException;
2325

2426
/**
2527
* The default retry policy.
@@ -32,7 +34,8 @@
3234
* <li>On a write timeout, retries once on the same host if we timeout while writing the
3335
* distributed log used by batch statements.
3436
* <li>On an unavailable exception, retries once on the next host.
35-
* <li>On a request error, such as a client timeout, the query is retried on the next host.
37+
* <li>On a request error, such as a client timeout, the query is retried on the next host. Do not
38+
* retry on read or write failures.
3639
* </ul>
3740
*
3841
* <p>This retry policy is conservative in that it will never retry with a different consistency
@@ -137,6 +140,11 @@ public RetryDecision onUnavailable(
137140
@Override
138141
public RetryDecision onRequestError(
139142
Statement statement, ConsistencyLevel cl, DriverException e, int nbRetry) {
143+
// do not retry these by default as they generally indicate a data problem or
144+
// other issue that is unlikely to be resolved by a retry.
145+
if (e instanceof WriteFailureException || e instanceof ReadFailureException) {
146+
return RetryDecision.rethrow();
147+
}
140148
return RetryDecision.tryNextHost(cl);
141149
}
142150

driver-core/src/main/java/com/datastax/driver/core/policies/DowngradingConsistencyRetryPolicy.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import com.datastax.driver.core.Statement;
2121
import com.datastax.driver.core.WriteType;
2222
import com.datastax.driver.core.exceptions.DriverException;
23+
import com.datastax.driver.core.exceptions.ReadFailureException;
24+
import com.datastax.driver.core.exceptions.WriteFailureException;
2325

2426
/**
2527
* A retry policy that sometimes retries with a lower consistency level than the one initially
@@ -215,6 +217,11 @@ public RetryDecision onUnavailable(
215217
@Override
216218
public RetryDecision onRequestError(
217219
Statement statement, ConsistencyLevel cl, DriverException e, int nbRetry) {
220+
// do not retry these by default as they generally indicate a data problem or
221+
// other issue that is unlikely to be resolved by a retry.
222+
if (e instanceof WriteFailureException || e instanceof ReadFailureException) {
223+
return RetryDecision.rethrow();
224+
}
218225
return RetryDecision.tryNextHost(cl);
219226
}
220227

driver-core/src/main/java/com/datastax/driver/core/policies/RetryPolicy.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,8 @@ RetryDecision onUnavailable(
267267
* <li>On a client timeout, while waiting for the server response (see {@link
268268
* SocketOptions#getReadTimeoutMillis()});
269269
* <li>On a connection error (socket closed, etc.);
270-
* <li>When the contacted host replies with an {@code OVERLOADED} error or a {@code
271-
* SERVER_ERROR}.
270+
* <li>When the contacted host replies with an {@code OVERLOADED} error, {@code SERVER_ERROR},
271+
* {@code READ_FAILURE} or {@code WRITE_FAILURE}.
272272
* </ol>
273273
*
274274
* <p>Note that when such an error occurs, there is no guarantee that the mutation has been

driver-core/src/test/java/com/datastax/driver/core/policies/DefaultRetryPolicyIntegrationTest.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
import static org.scassandra.http.client.Consistency.LOCAL_SERIAL;
2121
import static org.scassandra.http.client.PrimingRequest.then;
2222
import static org.scassandra.http.client.Result.closed_connection;
23+
import static org.scassandra.http.client.Result.read_failure;
2324
import static org.scassandra.http.client.Result.read_request_timeout;
2425
import static org.scassandra.http.client.Result.unavailable;
26+
import static org.scassandra.http.client.Result.write_failure;
2527
import static org.scassandra.http.client.Result.write_request_timeout;
2628

2729
import com.datastax.driver.core.Cluster;
@@ -32,9 +34,11 @@
3234
import com.datastax.driver.core.exceptions.DriverException;
3335
import com.datastax.driver.core.exceptions.NoHostAvailableException;
3436
import com.datastax.driver.core.exceptions.OperationTimedOutException;
37+
import com.datastax.driver.core.exceptions.ReadFailureException;
3538
import com.datastax.driver.core.exceptions.ReadTimeoutException;
3639
import com.datastax.driver.core.exceptions.TransportException;
3740
import com.datastax.driver.core.exceptions.UnavailableException;
41+
import com.datastax.driver.core.exceptions.WriteFailureException;
3842
import com.datastax.driver.core.exceptions.WriteTimeoutException;
3943
import java.util.Collections;
4044
import org.assertj.core.api.Fail;
@@ -261,6 +265,46 @@ public void should_try_next_host_on_server_side_error(
261265
assertQueried(3, 1);
262266
}
263267

268+
@Test(groups = "short")
269+
public void should_rethrow_on_read_failure() {
270+
simulateError(1, read_failure);
271+
272+
try {
273+
query();
274+
fail("expected a ReadFailureException");
275+
} catch (DriverException e) {
276+
assertThat(e).isInstanceOf(ReadFailureException.class);
277+
}
278+
279+
assertOnRequestErrorWasCalled(1, ReadFailureException.class);
280+
assertThat(errors.getOthers().getCount()).isEqualTo(1);
281+
assertThat(errors.getRetries().getCount()).isEqualTo(0);
282+
assertThat(errors.getRetriesOnOtherErrors().getCount()).isEqualTo(0);
283+
assertQueried(1, 1);
284+
assertQueried(2, 0);
285+
assertQueried(3, 0);
286+
}
287+
288+
@Test(groups = "short")
289+
public void should_rethrow_on_write_failure() {
290+
simulateError(1, write_failure);
291+
292+
try {
293+
query();
294+
fail("expected a WriteFailureException");
295+
} catch (DriverException e) {
296+
assertThat(e).isInstanceOf(WriteFailureException.class);
297+
}
298+
299+
assertOnRequestErrorWasCalled(1, WriteFailureException.class);
300+
assertThat(errors.getOthers().getCount()).isEqualTo(1);
301+
assertThat(errors.getRetries().getCount()).isEqualTo(0);
302+
assertThat(errors.getRetriesOnOtherErrors().getCount()).isEqualTo(0);
303+
assertQueried(1, 1);
304+
assertQueried(2, 0);
305+
assertQueried(3, 0);
306+
}
307+
264308
@Test(groups = "short", dataProvider = "connectionErrors")
265309
public void should_try_next_host_on_connection_error(ClosedConnectionConfig.CloseType closeType) {
266310
simulateError(1, closed_connection, new ClosedConnectionConfig(closeType));

driver-core/src/test/java/com/datastax/driver/core/policies/DowngradingConsistencyRetryPolicyIntegrationTest.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
import static org.scassandra.http.client.Consistency.SERIAL;
2020
import static org.scassandra.http.client.PrimingRequest.then;
2121
import static org.scassandra.http.client.Result.closed_connection;
22+
import static org.scassandra.http.client.Result.read_failure;
2223
import static org.scassandra.http.client.Result.read_request_timeout;
2324
import static org.scassandra.http.client.Result.unavailable;
25+
import static org.scassandra.http.client.Result.write_failure;
2426
import static org.scassandra.http.client.Result.write_request_timeout;
2527
import static org.scassandra.http.client.WriteTypePrime.UNLOGGED_BATCH;
2628
import static org.testng.Assert.fail;
@@ -31,9 +33,11 @@
3133
import com.datastax.driver.core.exceptions.DriverException;
3234
import com.datastax.driver.core.exceptions.NoHostAvailableException;
3335
import com.datastax.driver.core.exceptions.OperationTimedOutException;
36+
import com.datastax.driver.core.exceptions.ReadFailureException;
3437
import com.datastax.driver.core.exceptions.ReadTimeoutException;
3538
import com.datastax.driver.core.exceptions.TransportException;
3639
import com.datastax.driver.core.exceptions.UnavailableException;
40+
import com.datastax.driver.core.exceptions.WriteFailureException;
3741
import com.datastax.driver.core.exceptions.WriteTimeoutException;
3842
import org.assertj.core.api.Assertions;
3943
import org.assertj.core.api.Fail;
@@ -519,4 +523,44 @@ public void should_rethrow_on_unavailable_if_CAS() {
519523
assertQueried(2, 1);
520524
assertQueried(3, 0);
521525
}
526+
527+
@Test(groups = "short")
528+
public void should_rethrow_on_read_failure() {
529+
simulateError(1, read_failure);
530+
531+
try {
532+
query();
533+
fail("expected a ReadFailureException");
534+
} catch (DriverException e) {
535+
assertThat(e).isInstanceOf(ReadFailureException.class);
536+
}
537+
538+
assertOnRequestErrorWasCalled(1, ReadFailureException.class);
539+
assertThat(errors.getOthers().getCount()).isEqualTo(1);
540+
assertThat(errors.getRetries().getCount()).isEqualTo(0);
541+
assertThat(errors.getRetriesOnOtherErrors().getCount()).isEqualTo(0);
542+
assertQueried(1, 1);
543+
assertQueried(2, 0);
544+
assertQueried(3, 0);
545+
}
546+
547+
@Test(groups = "short")
548+
public void should_rethrow_on_write_failure() {
549+
simulateError(1, write_failure);
550+
551+
try {
552+
query();
553+
fail("expected a WriteFailureException");
554+
} catch (DriverException e) {
555+
assertThat(e).isInstanceOf(WriteFailureException.class);
556+
}
557+
558+
assertOnRequestErrorWasCalled(1, WriteFailureException.class);
559+
assertThat(errors.getOthers().getCount()).isEqualTo(1);
560+
assertThat(errors.getRetries().getCount()).isEqualTo(0);
561+
assertThat(errors.getRetriesOnOtherErrors().getCount()).isEqualTo(0);
562+
assertQueried(1, 1);
563+
assertQueried(2, 0);
564+
assertQueried(3, 0);
565+
}
522566
}

upgrade_guide/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ versions of the Java driver.
1010
ConsistencyLevel (which does not include Serial CLs), but as a matter of
1111
correctness this was updated.
1212

13+
2. `ReadFailureException` and `WriteFailureException` are now surfaced to
14+
`RetryPolicy.onRequestError`. Consider updating custom `RetryPolicy`
15+
implementations to account for this. In the general case, we recommend
16+
using `RetryDecision.rethrow()`, see [JAVA-1944].
17+
18+
[JAVA-1944]: https://datastax-oss.atlassian.net/browse/JAVA-1944
19+
20+
1321
### 3.5.0
1422

1523
1. The `DowngradingConsistencyRetryPolicy` is now deprecated, see [JAVA-1752].

0 commit comments

Comments
 (0)