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

alex-plekhanov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new c676f7ac326 IGNITE-28870 Restore snapshot temporary files cleanup 
logic (#13358)
c676f7ac326 is described below

commit c676f7ac326576774b4647aba26b411a97fc5451
Author: oleg-vlsk <[email protected]>
AuthorDate: Tue Jul 28 19:22:57 2026 +1000

    IGNITE-28870 Restore snapshot temporary files cleanup logic (#13358)
---
 .../cache/persistence/filename/FileTreeUtils.java  |  25 ++--
 .../snapshot/IgniteSnapshotManagerSelfTest.java    | 134 +++++++++++++++++++++
 2 files changed, 148 insertions(+), 11 deletions(-)

diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/FileTreeUtils.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/FileTreeUtils.java
index 7d1dd7d7f05..7214180479f 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/FileTreeUtils.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/FileTreeUtils.java
@@ -19,6 +19,7 @@ package 
org.apache.ignite.internal.processors.cache.persistence.filename;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.NoSuchFileException;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
@@ -63,10 +64,12 @@ public class FileTreeUtils {
     public static void removeTmpSnapshotFiles(SnapshotFileTree sft, boolean 
err, IgniteLogger log) {
         NodeFileTree tmpFt = sft.tempFileTree();
 
-        removeTmpDir(tmpFt.root(), err, log);
+        U.delete(tmpFt.nodeStorage());
 
         for (File tmpDrStorage : tmpFt.extraStorages().values())
-            removeTmpDir(tmpDrStorage.getParentFile(), err, log);
+            U.delete(tmpDrStorage);
+
+        deleteSnapshotTempRoot(tmpFt.root(), err, log);
     }
 
     /**
@@ -104,20 +107,20 @@ public class FileTreeUtils {
     }
 
     /**
-     * @param dir Directory to remove
-     * @param err If {@code true} then operation ends with error.
+     * @param root Snapshot temporary root.
+     * @param err {@code True} if snapshot processing finished with an error.
      * @param log Logger.
      */
-    private static void removeTmpDir(File dir, boolean err, IgniteLogger log) {
-        U.delete(dir);
-
-        // Delete snapshot directory if no other files exists.
+    private static void deleteSnapshotTempRoot(File root, boolean err, 
IgniteLogger log) {
         try {
-            if (U.fileCount(dir.toPath()) == 0 || err)
-                U.delete(dir.toPath());
+            if (err || U.fileCount(root.toPath()) == 0)
+                U.delete(root.toPath());
+        }
+        catch (NoSuchFileException ignored) {
+            // Snapshot temporary root has already been removed.
         }
         catch (IOException e) {
-            log.error("Snapshot directory doesn't exist [snpName=" + 
dir.getName() + ", dir=" + dir.getParentFile() + ']');
+            log.error("Failed to clean up snapshot temporary root [dir=" + 
root + ']', e);
         }
     }
 
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManagerSelfTest.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManagerSelfTest.java
index f3edd6df687..353ba0bb5f7 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManagerSelfTest.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManagerSelfTest.java
@@ -22,6 +22,7 @@ import java.io.IOException;
 import java.lang.management.ManagementFactory;
 import java.lang.management.ThreadMXBean;
 import java.nio.ByteBuffer;
+import java.nio.file.Files;
 import java.nio.file.OpenOption;
 import java.util.Collections;
 import java.util.HashMap;
@@ -59,6 +60,8 @@ import 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStor
 import 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager;
 import 
org.apache.ignite.internal.processors.cache.persistence.file.FileVersionCheckingFactory;
 import 
org.apache.ignite.internal.processors.cache.persistence.file.RandomAccessFileIOFactory;
+import 
org.apache.ignite.internal.processors.cache.persistence.filename.FileTreeUtils;
+import 
org.apache.ignite.internal.processors.cache.persistence.filename.NodeFileTree;
 import 
org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree;
 import 
org.apache.ignite.internal.processors.cache.persistence.partstate.GroupPartitionId;
 import org.apache.ignite.internal.util.GridCloseableIteratorAdapter;
@@ -94,6 +97,9 @@ public class IgniteSnapshotManagerSelfTest extends 
AbstractSnapshotSelfTest {
     /** Number of threads being used to perform snapshot operation. */
     private Integer snapshotThreadPoolSize;
 
+    /** Extra storage root configured for the current test. */
+    private File extraStorageRoot;
+
     /** {@inheritDoc} */
     @Override protected IgniteConfiguration getConfiguration(String 
igniteInstanceName) throws Exception {
         IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
@@ -102,6 +108,9 @@ public class IgniteSnapshotManagerSelfTest extends 
AbstractSnapshotSelfTest {
         // listener registration and calling snpFutTask.start().
         
cfg.getDataStorageConfiguration().setCheckpointFrequency(TimeUnit.DAYS.toMillis(365));
 
+        if (extraStorageRoot != null)
+            
cfg.getDataStorageConfiguration().setExtraStoragePaths(extraStorageRoot.getAbsolutePath());
+
         if (listenLog != null)
             cfg.setGridLogger(listenLog);
 
@@ -111,6 +120,18 @@ public class IgniteSnapshotManagerSelfTest extends 
AbstractSnapshotSelfTest {
         return cfg;
     }
 
+    /** {@inheritDoc} */
+    @Override public void afterTestSnapshot() throws Exception {
+        try {
+            super.afterTestSnapshot();
+        }
+        finally {
+            U.delete(extraStorageRoot);
+
+            extraStorageRoot = null;
+        }
+    }
+
     /**
      * Test that all partitions are copied successfully even after multiple 
checkpoints occur during
      * the long copy of cache partition files.
@@ -647,6 +668,119 @@ public class IgniteSnapshotManagerSelfTest extends 
AbstractSnapshotSelfTest {
         assertFalse(noMatchParams.check());
     }
 
+    /**
+     * Tests that snapshot temporary root is not removed on successful cleanup 
if it still contains files after
+     * node-specific storage has been removed.
+     */
+    @Test
+    public void testSnapshotTmpRootIsNotRemovedIfNotEmptyOnSuccess() throws 
Exception {
+        IgniteEx ignite = startGridWithCache(dfltCacheCfg, CACHE_KEYS_RANGE);
+
+        SnapshotFileTree sft = snapshotFileTree(ignite, SNAPSHOT_NAME);
+
+        NodeFileTree tmpFt = sft.tempFileTree();
+
+        File root = tmpFt.root();
+        File nodeStorage = tmpFt.nodeStorage();
+
+        assertTrue(nodeStorage.mkdirs());
+
+        File extraFile = new File(root, "unexpected-tmp-file");
+
+        assertTrue(extraFile.createNewFile());
+
+        try {
+            FileTreeUtils.removeTmpSnapshotFiles(sft, false, log);
+
+            assertFalse("Node-specific temporary storage must be removed: " + 
nodeStorage, nodeStorage.exists());
+
+            assertTrue("Snapshot temporary root must not be removed if it is 
not empty: " + root, root.exists());
+
+            assertTrue("Unexpected temporary file must not be removed on 
successful cleanup: " + extraFile,
+                extraFile.exists());
+        }
+        finally {
+            U.delete(root);
+        }
+    }
+
+    /**
+     * Tests that snapshot temporary root is removed on cleanup after an error 
even if it still contains files after
+     * node-specific storage has been removed.
+     */
+    @Test
+    public void testSnapshotTmpRootIsRemovedIfNotEmptyOnError() throws 
Exception {
+        IgniteEx ignite = startGridWithCache(dfltCacheCfg, CACHE_KEYS_RANGE);
+
+        SnapshotFileTree sft = snapshotFileTree(ignite, SNAPSHOT_NAME);
+
+        NodeFileTree tmpFt = sft.tempFileTree();
+
+        File root = tmpFt.root();
+        File nodeStorage = tmpFt.nodeStorage();
+
+        assertTrue(nodeStorage.mkdirs());
+
+        File extraFile = new File(root, "unexpected-tmp-file");
+
+        assertTrue(extraFile.createNewFile());
+
+        FileTreeUtils.removeTmpSnapshotFiles(sft, true, log);
+
+        assertFalse("Node-specific temporary storage must be removed: " + 
nodeStorage, nodeStorage.exists());
+
+        assertFalse("Snapshot temporary root must be removed on error: " + 
root, root.exists());
+    }
+
+    /**
+     * Tests cleanup of temporary snapshot files located in an extra storage.
+     * <p>
+     * Unlike the default storage, where the node-specific storage is nested 
inside the temporary root of the
+     * current snapshot, an extra storage entry already points to the 
snapshot-specific temporary directory:
+     *
+     * <pre>
+     *     &lt;extra-storage&gt;/snp/                     - common snapshot 
temporary root
+     *     &lt;extra-storage&gt;/snp/&lt;snapshot-name&gt;      - 
snapshot-specific extra storage
+     * </pre>
+     *
+     * Therefore, cleanup must remove the snapshot-specific extra storage 
directory but preserve its common
+     * temporary root.
+     */
+    @Test
+    public void testSnapshotTmpExtraStorageCleanupPreservesTempRoot() throws 
Exception {
+        IgniteEx ignite = startGridWithExtraStorage();
+
+        SnapshotFileTree sft = snapshotFileTree(ignite, SNAPSHOT_NAME);
+
+        NodeFileTree tmpFt = sft.tempFileTree();
+
+        assertEquals(1, tmpFt.extraStorages().size());
+
+        File tmpExtraStorage = 
tmpFt.extraStorages().values().iterator().next();
+        File tmpExtraRoot = tmpExtraStorage.getParentFile();
+
+        assertTrue(tmpExtraStorage.mkdirs() || tmpExtraStorage.isDirectory());
+
+        FileTreeUtils.removeTmpSnapshotFiles(sft, false, log);
+
+        assertFalse("Snapshot-specific temporary extra storage must be 
removed: " + tmpExtraStorage,
+            tmpExtraStorage.exists());
+
+        assertTrue("Common snapshot temporary root must be preserved: " + 
tmpExtraRoot,
+            tmpExtraRoot.isDirectory());
+    }
+
+    /** */
+    private IgniteEx startGridWithExtraStorage() throws Exception {
+        extraStorageRoot = 
Files.createTempDirectory("ignite-snapshot-extra-storage-").toFile();
+
+        CacheConfiguration<Integer, Object> ccfg = new 
CacheConfiguration<>(dfltCacheCfg);
+
+        ccfg.setStoragePaths(extraStorageRoot.getAbsolutePath());
+
+        return startGridWithCache(ccfg, CACHE_KEYS_RANGE);
+    }
+
     /**
      * @param ignite Ignite instance to set factory.
      * @param factory New factory to use.

Reply via email to