Skip to content

Commit 9d68015

Browse files
committed
Compare fault counts in the icheck -P gate, not the capped fault list
The pruneAndClear gate excluded marked-version faults by comparing _faults.size() with the marked-fault count, but both stop moving once the fault list reaches MAX_FAULTS: on a volume with more than MAX_FAULTS marked-version records a genuine fault found afterwards (the garbage chain is checked after the trees) is dropped from the list and the arithmetic still converges, so -P cleared the TransactionIndex despite real corruption. Count every fault found - recorded in the list or not - and gate on the counts instead.
1 parent b703ccb commit 9d68015

2 files changed

Lines changed: 77 additions & 13 deletions

File tree

persistit/core/src/main/java/com/persistit/IntegrityCheck.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ public class IntegrityCheck extends Task {
8080
private boolean _csv;
8181

8282
private final ArrayList<Fault> _faults = new ArrayList<Fault>();
83+
private int _faultCount;
8384
private int _markedVersionFaultCount;
8485
private final ArrayList<CleanupIndexHole> _holes = new ArrayList<CleanupIndexHole>();
8586

@@ -201,10 +202,9 @@ protected void visitDataRecord(final Key key, final int foundAt, final int tail,
201202
final int marked = MVV.countMarkedVersions(bytes, offset, length);
202203
if (marked > 0) {
203204
_counters._markedVersionCount += marked;
204-
if (addFault("MVV has " + plural(marked, "version") + " with a leftover mark bit", _currentPage, 0,
205-
foundAt)) {
206-
_markedVersionFaultCount++;
207-
}
205+
_markedVersionFaultCount++;
206+
addFault("MVV has " + plural(marked, "version") + " with a leftover mark bit", _currentPage, 0,
207+
foundAt);
208208
}
209209
if (_versionVisitor._count > 0) {
210210
_counters._mvvCount++;
@@ -314,9 +314,13 @@ protected void runTask() {
314314
* affected by a pre-#286 interrupted prune but do not block
315315
* clearing the TransactionIndex: the prune pass above has
316316
* already rewritten those marks (issue #292). Only other
317-
* faults and incomplete pruning count as failures here.
317+
* faults and incomplete pruning count as failures here. The
318+
* comparison uses fault counts, not the _faults list: the
319+
* list is capped at MAX_FAULTS, so a genuine fault found
320+
* after marked-version faults fill the cap would be missing
321+
* from the list but must still block the clear.
318322
*/
319-
if (_faults.size() == _markedVersionFaultCount && _counters._mvvPageCount == _counters._prunedPageCount
323+
if (_faultCount == _markedVersionFaultCount && _counters._mvvPageCount == _counters._prunedPageCount
320324
&& _counters._pruningErrorCount == 0) {
321325
final int count = _persistit.getTransactionIndex().resetMVVCounts(startTimestamp);
322326
postMessage(String.format("%,d aborted transactions were cleared by pruning", count), LOG_NORMAL);
@@ -356,17 +360,17 @@ private String plural(final int n, final String m) {
356360
}
357361
}
358362

359-
private boolean addFault(final String description, final long page, final int level, final int position) {
363+
private void addFault(final String description, final long page, final int level, final int position) {
360364
final Fault fault = new Fault(resourceName(), this, description, page, _treeDepth, level, position);
361-
final boolean recorded = _faults.size() < MAX_FAULTS;
362-
if (recorded)
365+
_faultCount++;
366+
if (_faults.size() < MAX_FAULTS)
363367
_faults.add(fault);
364368
postMessage(fault.toString(), LOG_VERBOSE);
365-
return recorded;
366369
}
367370

368371
private void addGarbageFault(final String description, final long page, final int level, final int position) {
369372
final Fault fault = new Fault(resourceName(), this, description, page, 3, level, position);
373+
_faultCount++;
370374
if (_faults.size() < MAX_FAULTS)
371375
_faults.add(fault);
372376
postMessage(fault.toString(), LOG_VERBOSE);

persistit/core/src/test/java/com/persistit/IntegrityCheckTest.java

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ public void testBrokenMVVs() throws Exception {
101101
final IntegrityCheck icheck = icheck();
102102
icheck.checkTree(ex.getTree());
103103
assertTrue(icheck.getFaults().length > 0);
104+
/*
105+
* A structurally corrupt MVV must not inflate the marked-version
106+
* counter: the leftover-mark scan runs only after visitAllVersions
107+
* has validated the record.
108+
*/
109+
assertEquals(0, icheck.getMarkedVersionCount());
104110
}
105111

106112
/**
@@ -185,6 +191,34 @@ public void testPruneAndClearWithLeftoverMarkBits() throws Exception {
185191
assertFalse(output.toString(), output.toString().contains("PruneAndClear failed"));
186192
}
187193

194+
/**
195+
* The -P gate must compare fault counts, not the MAX_FAULTS-capped fault
196+
* list: with more than MAX_FAULTS marked-version faults the list holds
197+
* only them, and a genuine fault found later (here a corrupt garbage
198+
* chain, checked after the trees) is not recorded in the list - it must
199+
* still block clearing the TransactionIndex.
200+
*/
201+
@Test
202+
public void testPruneAndClearBlockedByFaultBeyondFaultCap() throws Exception {
203+
final Exchange ex = _persistit.getExchange(_volumeName, "mvv", true);
204+
disableBackgroundCleanup();
205+
transactionalStore(ex);
206+
_persistit.getTransactionIndex().updateActiveTransactionCache();
207+
208+
assertTrue(corrupt5(ex, 0, IntegrityCheck.MAX_FAULTS + 1)[0] > IntegrityCheck.MAX_FAULTS);
209+
corrupt4(ex);
210+
211+
final IntegrityCheck icheck = (IntegrityCheck) CLI.parseTask(_persistit, "icheck trees=* -u -P");
212+
final StringWriter output = new StringWriter();
213+
icheck.setMessageWriter(new PrintWriter(output));
214+
icheck.setup(1, "icheck", "cli", 0, 5);
215+
icheck.run();
216+
217+
assertTrue(icheck.getMarkedVersionCount() > 0);
218+
assertTrue(output.toString(), output.toString().contains("PruneAndClear failed"));
219+
assertFalse(output.toString(), output.toString().contains("aborted transactions were cleared by pruning"));
220+
}
221+
188222
@Test
189223
public void testIndexFixHoles() throws Exception {
190224
final Exchange ex = _persistit.getExchange(_volumeName, "mvv", true);
@@ -376,6 +410,7 @@ private interface MVVCorruption {
376410

377411
/**
378412
* Applies a corruption to the raw bytes of up to ten stored MVV values
413+
* following key 500
379414
*
380415
* @param ex
381416
* @param corruption
@@ -384,12 +419,29 @@ private interface MVVCorruption {
384419
* @throws PersistitException
385420
*/
386421
private int[] corruptMVVs(final Exchange ex, final MVVCorruption corruption) throws PersistitException {
422+
return corruptMVVs(ex, 500, 10, corruption);
423+
}
424+
425+
/**
426+
* Applies a corruption to the raw bytes of up to <code>limit</code>
427+
* stored MVV values following <code>startKey</code>
428+
*
429+
* @param ex
430+
* @param startKey
431+
* @param limit
432+
* @param corruption
433+
* @return the number of values corrupted and the summed corruption
434+
* contributions, as a two-element array
435+
* @throws PersistitException
436+
*/
437+
private int[] corruptMVVs(final Exchange ex, final int startKey, final int limit, final MVVCorruption corruption)
438+
throws PersistitException {
387439
ex.ignoreMVCCFetch(true);
388440
try {
389-
ex.clear().to(key(500));
441+
ex.clear().to(key(startKey));
390442
int corrupted = 0;
391443
int total = 0;
392-
while (corrupted < 10 && ex.next()) {
444+
while (corrupted < limit && ex.next()) {
393445
final byte[] bytes = ex.getValue().getEncodedBytes();
394446
final int length = ex.getValue().getEncodedSize();
395447
if (MVV.isArrayMVV(bytes, 0, length)) {
@@ -460,7 +512,15 @@ private void corrupt4(final Exchange ex) throws PersistitException {
460512
* @throws PersistitException
461513
*/
462514
private int[] corrupt5(final Exchange ex) throws PersistitException {
463-
return corruptMVVs(ex, (bytes, length) -> {
515+
return corrupt5(ex, 500, 10);
516+
}
517+
518+
/**
519+
* Same as {@link #corrupt5(Exchange)} for up to <code>limit</code> values
520+
* following <code>startKey</code>
521+
*/
522+
private int[] corrupt5(final Exchange ex, final int startKey, final int limit) throws PersistitException {
523+
return corruptMVVs(ex, startKey, limit, (bytes, length) -> {
464524
int marked = 0;
465525
int from = 1;
466526
while (from + MVV.LENGTH_PER_VERSION <= length) {

0 commit comments

Comments
 (0)