This is an automated email from the ASF dual-hosted git repository.

adoroszlai pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new 07c71f1ca9b HDDS-16954. NoSuchElementException in SnapshotChainManager 
if snapshot is purged during iteration (#11500)
07c71f1ca9b is described below

commit 07c71f1ca9b4d234e19de47ff47e3447577ceb56
Author: Siyao Meng <[email protected]>
AuthorDate: Tue Oct 13 15:15:37 2026 -0700

    HDDS-16954. NoSuchElementException in SnapshotChainManager if snapshot is 
purged during iteration (#11500)
    
    Co-authored-by: Claude Sonnet 5.5 <[email protected]>
---
 .../hadoop/ozone/om/SnapshotChainManager.java      | 30 ++++++++++++++--------
 .../ozone/om/snapshot/TestSnapshotChain.java       | 29 +++++++++++++++++++++
 2 files changed, 48 insertions(+), 11 deletions(-)

diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotChainManager.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotChainManager.java
index 360f1f86b05..5dc6d1dbfb2 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotChainManager.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/SnapshotChainManager.java
@@ -454,18 +454,26 @@ public boolean hasNext() {
 
       @Override
       public UUID next() {
-        try {
-          UUID prevSnapshotId = currentSnapshotId;
-          if (reverse && hasPreviousGlobalSnapshot(currentSnapshotId) ||
-              !reverse && hasNextGlobalSnapshot(currentSnapshotId)) {
-            currentSnapshotId =
-                reverse ? previousGlobalSnapshot(currentSnapshotId) : 
nextGlobalSnapshot(currentSnapshotId);
-          } else {
-            currentSnapshotId = null;
+        // Hold the same lock as deleteSnapshot so that the chain cannot 
change while advancing.
+        synchronized (SnapshotChainManager.this) {
+          try {
+            UUID prevSnapshotId = currentSnapshotId;
+            if (prevSnapshotId == null) {
+              throw new NoSuchElementException("No more snapshots in the 
snapshot chain.");
+            }
+            // The snapshot may have been purged since the iterator moved to 
it, in which case the walk ends here.
+            if (globalSnapshotChain.containsKey(prevSnapshotId) &&
+                (reverse && hasPreviousGlobalSnapshot(prevSnapshotId) ||
+                !reverse && hasNextGlobalSnapshot(prevSnapshotId))) {
+              currentSnapshotId =
+                  reverse ? previousGlobalSnapshot(prevSnapshotId) : 
nextGlobalSnapshot(prevSnapshotId);
+            } else {
+              currentSnapshotId = null;
+            }
+            return prevSnapshotId;
+          } catch (IOException e) {
+            throw new UncheckedIOException("Error while getting next snapshot 
for " + currentSnapshotId, e);
           }
-          return prevSnapshotId;
-        } catch (IOException e) {
-          throw new UncheckedIOException("Error while getting next snapshot 
for " + currentSnapshotId, e);
         }
       }
     };
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotChain.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotChain.java
index 4534e4b7d3a..b73706d01ce 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotChain.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestSnapshotChain.java
@@ -30,6 +30,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
@@ -380,6 +381,34 @@ public void testSnapshotChainIterator(int 
numberOfSnapshots) throws IOException
 
   }
 
+  @ParameterizedTest
+  @ValueSource(booleans = {true, false})
+  public void testSnapshotChainIteratorWhenNextSnapshotIsPurged(boolean 
reverse) throws IOException {
+    Table<String, SnapshotInfo> snapshotInfo = 
omMetadataManager.getSnapshotInfoTable();
+    List<SnapshotInfo> snapshotInfoList = new ArrayList<>();
+    UUID prevSnapshotID = null;
+    long time = System.currentTimeMillis();
+    for (int i = 0; i < 3; i++) {
+      UUID snapshotID = UUID.randomUUID();
+      SnapshotInfo snapInfo = createSnapshotInfo(snapshotID, prevSnapshotID, 
prevSnapshotID, time++);
+      snapshotInfo.put(snapshotID.toString(), snapInfo);
+      prevSnapshotID = snapshotID;
+      snapshotInfoList.add(snapInfo);
+    }
+    chainManager = new SnapshotChainManager(omMetadataManager);
+    Iterator<UUID> iterator = chainManager.iterator(reverse);
+    assertEquals(snapshotInfoList.get(reverse ? 2 : 0).getSnapshotId(), 
iterator.next());
+
+    // Purge the snapshot the iterator moved to, as a concurrent snapshot 
purge would.
+    SnapshotInfo purged = snapshotInfoList.get(1);
+    assertTrue(chainManager.deleteSnapshot(purged));
+
+    assertTrue(iterator.hasNext());
+    assertEquals(purged.getSnapshotId(), iterator.next());
+    assertFalse(iterator.hasNext());
+    assertThrows(NoSuchElementException.class, iterator::next);
+  }
+
   private static Stream<? extends Arguments> invalidSnapshotChain() {
     List<UUID> nodes = IntStream.range(0, 5)
         .mapToObj(i -> UUID.randomUUID())


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to