Skip to content

Commit e4b3b59

Browse files
committed
Never leave stale delete tombstones in version map (#29619)
Today the VersionMap does not clean up a stale delete tombstone if it does not require safe access. However, in a very rare situation due to concurrent refreshes, the safe-access flag may be flipped over then an engine accidentally consult that stale delete tombstone. This commit ensures to never leave stale delete tombstones in a version map by always pruning delete tombstones when putting a new index entry regardless of the value of the safe-access flag.
1 parent c87bd30 commit e4b3b59

File tree

3 files changed

+57
-0
lines changed

3 files changed

+57
-0
lines changed

server/src/main/java/org/elasticsearch/index/engine/LiveVersionMap.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,10 @@ void maybePutIndexUnderLock(BytesRef uid, IndexVersionValue version) {
312312
if (maps.isSafeAccessMode()) {
313313
putIndexUnderLock(uid, version);
314314
} else {
315+
// Even though we don't store a record of the indexing operation (and mark as unsafe),
316+
// we should still remove any previous delete for this uuid (avoid accidental accesses).
317+
// Not this should not hurt performance because the tombstone is small (or empty) when unsafe is relevant.
318+
removeTombstoneUnderLock(uid);
315319
maps.current.markAsUnsafe();
316320
assert putAssertionMap(uid, version);
317321
}

server/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1595,6 +1595,23 @@ public void testInternalVersioningOnPrimary() throws IOException {
15951595
assertOpsOnPrimary(ops, Versions.NOT_FOUND, true, engine);
15961596
}
15971597

1598+
public void testVersionOnPrimaryWithConcurrentRefresh() throws Exception {
1599+
List<Engine.Operation> ops = generateSingleDocHistory(false, VersionType.INTERNAL, false, 2, 10, 100);
1600+
CountDownLatch latch = new CountDownLatch(1);
1601+
AtomicBoolean running = new AtomicBoolean(true);
1602+
Thread refreshThread = new Thread(() -> {
1603+
latch.countDown();
1604+
while (running.get()) {
1605+
engine.refresh("test");
1606+
}
1607+
});
1608+
refreshThread.start();
1609+
latch.await();
1610+
assertOpsOnPrimary(ops, Versions.NOT_FOUND, true, engine);
1611+
running.set(false);
1612+
refreshThread.join();
1613+
}
1614+
15981615
private int assertOpsOnPrimary(List<Engine.Operation> ops, long currentOpVersion, boolean docDeleted, InternalEngine engine)
15991616
throws IOException {
16001617
String lastFieldValue = null;

server/src/test/java/org/elasticsearch/index/engine/LiveVersionMapTests.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
import java.util.concurrent.atomic.AtomicLong;
4141

4242
import static org.hamcrest.Matchers.empty;
43+
import static org.hamcrest.Matchers.equalTo;
44+
import static org.hamcrest.Matchers.nullValue;
4345

4446
public class LiveVersionMapTests extends ESTestCase {
4547

@@ -396,6 +398,40 @@ public void testPruneTombstonesWhileLocked() throws InterruptedException, IOExce
396398
assertEquals(0, map.getAllTombstones().size());
397399
}
398400

401+
public void testRandomlyIndexDeleteAndRefresh() throws Exception {
402+
final LiveVersionMap versionMap = new LiveVersionMap();
403+
final BytesRef uid = uid("1");
404+
final long versions = between(10, 1000);
405+
VersionValue latestVersion = null;
406+
for (long i = 0; i < versions; i++) {
407+
if (randomBoolean()) {
408+
versionMap.beforeRefresh();
409+
versionMap.afterRefresh(randomBoolean());
410+
}
411+
if (randomBoolean()) {
412+
versionMap.enforceSafeAccess();
413+
}
414+
try (Releasable ignore = versionMap.acquireLock(uid)) {
415+
if (randomBoolean()) {
416+
latestVersion = new DeleteVersionValue(randomNonNegativeLong(), randomLong(), randomLong(), randomLong());
417+
versionMap.putDeleteUnderLock(uid, (DeleteVersionValue) latestVersion);
418+
assertThat(versionMap.getUnderLock(uid), equalTo(latestVersion));
419+
} else if (randomBoolean()) {
420+
latestVersion = new IndexVersionValue(randomTranslogLocation(), randomNonNegativeLong(), randomLong(), randomLong());
421+
versionMap.maybePutIndexUnderLock(uid, (IndexVersionValue) latestVersion);
422+
if (versionMap.isSafeAccessRequired()) {
423+
assertThat(versionMap.getUnderLock(uid), equalTo(latestVersion));
424+
} else {
425+
assertThat(versionMap.getUnderLock(uid), nullValue());
426+
}
427+
}
428+
if (versionMap.getUnderLock(uid) != null) {
429+
assertThat(versionMap.getUnderLock(uid), equalTo(latestVersion));
430+
}
431+
}
432+
}
433+
}
434+
399435
IndexVersionValue randomIndexVersionValue() {
400436
return new IndexVersionValue(randomTranslogLocation(), randomNonNegativeLong(), randomNonNegativeLong(), randomNonNegativeLong());
401437
}

0 commit comments

Comments
 (0)