|
76 | 76 | import static org.hamcrest.Matchers.containsString; |
77 | 77 | import static org.hamcrest.Matchers.equalTo; |
78 | 78 | import static org.hamcrest.Matchers.instanceOf; |
| 79 | +import static org.hamcrest.Matchers.matchesPattern; |
79 | 80 | import static org.hamcrest.Matchers.nullValue; |
80 | 81 |
|
81 | 82 | public class RepositoryAnalysisFailureIT extends AbstractSnapshotIntegTestCase { |
@@ -385,6 +386,55 @@ public BytesReference onContendedCompareAndExchange(BytesRegister register, Byte |
385 | 386 | assertAnalysisFailureMessage(analyseRepositoryExpectFailure(request).getMessage()); |
386 | 387 | } |
387 | 388 |
|
| 389 | + public void testFailsOnLostIncrement() { |
| 390 | + final RepositoryAnalyzeAction.Request request = new RepositoryAnalyzeAction.Request("test-repo"); |
| 391 | + final AtomicBoolean registerWasCorrupted = new AtomicBoolean(); |
| 392 | + |
| 393 | + blobStore.setDisruption(new Disruption() { |
| 394 | + @Override |
| 395 | + public BytesReference onContendedCompareAndExchange(BytesRegister register, BytesReference expected, BytesReference updated) { |
| 396 | + if (expected.equals(updated) == false // not the initial read |
| 397 | + && updated.length() == Long.BYTES // not the final write |
| 398 | + && randomBoolean() |
| 399 | + && register.get().equals(expected) // would have succeeded |
| 400 | + && registerWasCorrupted.compareAndSet(false, true)) { |
| 401 | + |
| 402 | + // indicate success without actually applying the update |
| 403 | + return expected; |
| 404 | + } |
| 405 | + |
| 406 | + return register.compareAndExchange(expected, updated); |
| 407 | + } |
| 408 | + }); |
| 409 | + |
| 410 | + safeAwait((ActionListener<RepositoryAnalyzeAction.Response> l) -> analyseRepository(request, l.delegateResponse((ll, e) -> { |
| 411 | + if (ExceptionsHelper.unwrapCause(e) instanceof RepositoryVerificationException repositoryVerificationException) { |
| 412 | + assertAnalysisFailureMessage(repositoryVerificationException.getMessage()); |
| 413 | + assertTrue( |
| 414 | + "did not lose increment, so why did the verification fail?", |
| 415 | + // clear flag for final assertion |
| 416 | + registerWasCorrupted.compareAndSet(true, false) |
| 417 | + ); |
| 418 | + assertThat( |
| 419 | + asInstanceOf( |
| 420 | + RepositoryVerificationException.class, |
| 421 | + ExceptionsHelper.unwrapCause(repositoryVerificationException.getCause()) |
| 422 | + ).getMessage(), |
| 423 | + matchesPattern(""" |
| 424 | + \\[test-repo] Successfully completed all \\[.*] atomic increments of register \\[test-register-contended-.*] \ |
| 425 | + so its expected value is \\[OptionalBytesReference\\[.*]], but reading its value with \\[.*] unexpectedly \ |
| 426 | + yielded \\[OptionalBytesReference\\[.*]]\\. This anomaly may indicate an atomicity failure amongst concurrent \ |
| 427 | + compare-and-exchange operations on registers in this repository\\.""") |
| 428 | + ); |
| 429 | + ll.onResponse(null); |
| 430 | + } else { |
| 431 | + ll.onFailure(e); |
| 432 | + } |
| 433 | + }))); |
| 434 | + |
| 435 | + assertFalse(registerWasCorrupted.get()); |
| 436 | + } |
| 437 | + |
388 | 438 | public void testFailsIfRegisterHoldsSpuriousValue() { |
389 | 439 | final RepositoryAnalyzeAction.Request request = new RepositoryAnalyzeAction.Request("test-repo"); |
390 | 440 |
|
|
0 commit comments