cassandra-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jmcken...@apache.org
Subject [1/2] cassandra git commit: Re-enable memory-mapped I/O on Windows
Date Thu, 02 Jul 2015 21:19:43 GMT
Repository: cassandra
Updated Branches:
  refs/heads/trunk d7d9a9af5 -> 036ddaf53


Re-enable memory-mapped I/O on Windows

Patch by jmckenzie; reviewed by tjake for CASSANDRA-9658


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/b7ae07e5
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/b7ae07e5
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/b7ae07e5

Branch: refs/heads/trunk
Commit: b7ae07e5c3e4e87c374ee580890c9a00c113f320
Parents: b73aa8e
Author: Joshua McKenzie <jmckenzie@apache.org>
Authored: Thu Jul 2 17:15:14 2015 -0400
Committer: Joshua McKenzie <jmckenzie@apache.org>
Committed: Thu Jul 2 17:15:14 2015 -0400

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 .../cassandra/config/DatabaseDescriptor.java    |  49 ++++----
 .../org/apache/cassandra/db/Directories.java    |  18 ++-
 .../db/WindowsFailedSnapshotTracker.java        | 115 +++++++++++++++++++
 .../org/apache/cassandra/io/util/FileUtils.java |  32 ++++++
 .../cassandra/repair/messages/RepairOption.java |  15 ++-
 .../cassandra/service/CassandraDaemon.java      |   4 +
 .../apache/cassandra/db/SystemKeyspaceTest.java |  44 ++++++-
 .../io/sstable/SSTableRewriterTest.java         |  22 ++++
 .../repair/messages/RepairOptionTest.java       |  10 +-
 .../service/StorageServiceServerTest.java       |  76 +++++++++++-
 11 files changed, 356 insertions(+), 30 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7ae07e5/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 745cacb..a160b09 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 2.2.0-rc2
+ * Re-enable memory-mapped I/O on Windows (CASSANDRA-9658)
  * Warn when an extra-large partition is compacted (CASSANDRA-9643)
  * (cqlsh) Allow setting the initial connection timeout (CASSANDRA-9601)
  * BulkLoader has --transport-factory option but does not use it (CASSANDRA-9675)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7ae07e5/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
index 632bf0a..5589bc2 100644
--- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
+++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
@@ -291,34 +291,23 @@ public class DatabaseDescriptor
         if (conf.commitlog_total_space_in_mb == null)
             conf.commitlog_total_space_in_mb = 8192;
 
-        // Always force standard mode access on Windows - CASSANDRA-6993. Windows won't allow
deletion of hard-links to files that
-        // are memory-mapped which causes trouble with snapshots.
-        if (FBUtilities.isWindows())
+        /* evaluate the DiskAccessMode Config directive, which also affects indexAccessMode
selection */
+        if (conf.disk_access_mode == Config.DiskAccessMode.auto)
         {
-            conf.disk_access_mode = Config.DiskAccessMode.standard;
+            conf.disk_access_mode = hasLargeAddressSpace() ? Config.DiskAccessMode.mmap :
Config.DiskAccessMode.standard;
             indexAccessMode = conf.disk_access_mode;
-            logger.info("Windows environment detected.  DiskAccessMode set to {}, indexAccessMode
{}", conf.disk_access_mode, indexAccessMode);
+            logger.info("DiskAccessMode 'auto' determined to be {}, indexAccessMode is {}",
conf.disk_access_mode, indexAccessMode);
+        }
+        else if (conf.disk_access_mode == Config.DiskAccessMode.mmap_index_only)
+        {
+            conf.disk_access_mode = Config.DiskAccessMode.standard;
+            indexAccessMode = Config.DiskAccessMode.mmap;
+            logger.info("DiskAccessMode is {}, indexAccessMode is {}", conf.disk_access_mode,
indexAccessMode);
         }
         else
         {
-            /* evaluate the DiskAccessMode Config directive, which also affects indexAccessMode
selection */
-            if (conf.disk_access_mode == Config.DiskAccessMode.auto)
-            {
-                conf.disk_access_mode = hasLargeAddressSpace() ? Config.DiskAccessMode.mmap
: Config.DiskAccessMode.standard;
-                indexAccessMode = conf.disk_access_mode;
-                logger.info("DiskAccessMode 'auto' determined to be {}, indexAccessMode is
{}", conf.disk_access_mode, indexAccessMode);
-            }
-            else if (conf.disk_access_mode == Config.DiskAccessMode.mmap_index_only)
-            {
-                conf.disk_access_mode = Config.DiskAccessMode.standard;
-                indexAccessMode = Config.DiskAccessMode.mmap;
-                logger.info("DiskAccessMode is {}, indexAccessMode is {}", conf.disk_access_mode,
indexAccessMode);
-            }
-            else
-            {
-                indexAccessMode = conf.disk_access_mode;
-                logger.info("DiskAccessMode is {}, indexAccessMode is {}", conf.disk_access_mode,
indexAccessMode);
-            }
+            indexAccessMode = conf.disk_access_mode;
+            logger.info("DiskAccessMode is {}, indexAccessMode is {}", conf.disk_access_mode,
indexAccessMode);
         }
 
         /* Authentication, authorization and role management backend, implementing IAuthenticator,
IAuthorizer & IRoleMapper*/
@@ -1303,11 +1292,25 @@ public class DatabaseDescriptor
         return conf.disk_access_mode;
     }
 
+    // Do not use outside unit tests.
+    @VisibleForTesting
+    public static void setDiskAccessMode(Config.DiskAccessMode mode)
+    {
+        conf.disk_access_mode = mode;
+    }
+
     public static Config.DiskAccessMode getIndexAccessMode()
     {
         return indexAccessMode;
     }
 
+    // Do not use outside unit tests.
+    @VisibleForTesting
+    public static void setIndexAccessMode(Config.DiskAccessMode mode)
+    {
+        indexAccessMode = mode;
+    }
+
     public static void setDiskFailurePolicy(Config.DiskFailurePolicy policy)
     {
         conf.disk_failure_policy = policy;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7ae07e5/src/java/org/apache/cassandra/db/Directories.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/Directories.java b/src/java/org/apache/cassandra/db/Directories.java
index ee8ecde..4982407 100644
--- a/src/java/org/apache/cassandra/db/Directories.java
+++ b/src/java/org/apache/cassandra/db/Directories.java
@@ -49,6 +49,7 @@ import org.apache.cassandra.io.FSWriteError;
 import org.apache.cassandra.io.util.FileUtils;
 import org.apache.cassandra.io.sstable.*;
 import org.apache.cassandra.utils.ByteBufferUtil;
+import org.apache.cassandra.utils.FBUtilities;
 import org.apache.cassandra.utils.Pair;
 
 /**
@@ -697,7 +698,22 @@ public class Directories
             if (snapshotDir.exists())
             {
                 logger.debug("Removing snapshot directory {}", snapshotDir);
-                FileUtils.deleteRecursive(snapshotDir);
+                try
+                {
+                    FileUtils.deleteRecursive(snapshotDir);
+                }
+                catch (FSWriteError e)
+                {
+                    if (FBUtilities.isWindows())
+                    {
+                        logger.warn("Failed to delete snapshot directory [{}]. Folder will
be deleted on JVM shutdown or next node restart on crash. You can safely attempt to delete
this folder but it will fail so long as readers are open on the files.", snapshotDir);
+                        WindowsFailedSnapshotTracker.handleFailedSnapshot(snapshotDir);
+                    }
+                    else
+                    {
+                        throw e;
+                    }
+                }
             }
         }
     }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7ae07e5/src/java/org/apache/cassandra/db/WindowsFailedSnapshotTracker.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/WindowsFailedSnapshotTracker.java b/src/java/org/apache/cassandra/db/WindowsFailedSnapshotTracker.java
new file mode 100644
index 0000000..ce89823
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/WindowsFailedSnapshotTracker.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.db;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.io.util.FileUtils;
+
+
+public class WindowsFailedSnapshotTracker
+{
+    private static final Logger logger = LoggerFactory.getLogger(WindowsFailedSnapshotTracker.class);
+    private static PrintWriter _failedSnapshotFile;
+
+    @VisibleForTesting
+    // Need to handle null for unit tests
+    public static final String TODELETEFILE = System.getenv("CASSANDRA_HOME") == null
+                 ? ".toDelete"
+                 : System.getenv("CASSANDRA_HOME") + File.separator + ".toDelete";
+
+    public static void deleteOldSnapshots()
+    {
+        if (new File(TODELETEFILE).exists())
+        {
+            try
+            {
+                BufferedReader reader = new BufferedReader(new FileReader(TODELETEFILE));
+                String snapshotDirectory;
+                while ((snapshotDirectory = reader.readLine()) != null)
+                {
+                    File f = new File(snapshotDirectory);
+
+                    // Skip folders that aren't a subset of temp or a data folder. We don't
want people to accidentally
+                    // delete something important by virtue of adding something invalid to
the .toDelete file.
+                    boolean validFolder = FileUtils.isSubDirectory(new File(System.getenv("TEMP")),
f);
+                    for (String s : DatabaseDescriptor.getAllDataFileLocations())
+                        validFolder |= FileUtils.isSubDirectory(new File(s), f);
+
+                    if (!validFolder)
+                    {
+                        logger.warn("Skipping invalid directory found in .toDelete: {}. Only
%TEMP% or data file subdirectories are valid.", f);
+                        continue;
+                    }
+
+                    // Could be a non-existent directory if deletion worked on previous JVM
shutdown.
+                    if (f.exists())
+                    {
+                        logger.warn("Discovered obsolete snapshot. Deleting directory [{}]",
snapshotDirectory);
+                        FileUtils.deleteRecursive(new File(snapshotDirectory));
+                    }
+                }
+                reader.close();
+
+                // Only delete the old .toDelete file if we succeed in deleting all our known
bad snapshots.
+                Files.delete(Paths.get(TODELETEFILE));
+            }
+            catch (IOException e)
+            {
+                logger.warn("Failed to open {}. Obsolete snapshots from previous runs will
not be deleted.", TODELETEFILE);
+                logger.warn("Exception: " + e);
+            }
+        }
+
+        try
+        {
+            _failedSnapshotFile = new PrintWriter(new FileWriter(TODELETEFILE, true));
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(String.format("Failed to create failed snapshot tracking
file [%s]. Aborting", TODELETEFILE));
+        }
+    }
+
+    public static synchronized void handleFailedSnapshot(File dir)
+    {
+        assert(_failedSnapshotFile != null);
+        FileUtils.deleteRecursiveOnExit(dir);
+        _failedSnapshotFile.println(dir.toString());
+        _failedSnapshotFile.flush();
+    }
+
+    @VisibleForTesting
+    public static void resetForTests()
+    {
+        _failedSnapshotFile.close();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7ae07e5/src/java/org/apache/cassandra/io/util/FileUtils.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/io/util/FileUtils.java b/src/java/org/apache/cassandra/io/util/FileUtils.java
index 2566952..3552ca9 100644
--- a/src/java/org/apache/cassandra/io/util/FileUtils.java
+++ b/src/java/org/apache/cassandra/io/util/FileUtils.java
@@ -379,6 +379,23 @@ public class FileUtils
         deleteWithConfirm(dir);
     }
 
+    /**
+     * Schedules deletion of all file and subdirectories under "dir" on JVM shutdown.
+     * @param dir Directory to be deleted
+     */
+    public static void deleteRecursiveOnExit(File dir)
+    {
+        if (dir.isDirectory())
+        {
+            String[] children = dir.list();
+            for (String child : children)
+                deleteRecursiveOnExit(new File(dir, child));
+        }
+
+        logger.debug("Scheduling deferred deletion of file: " + dir);
+        dir.deleteOnExit();
+    }
+
     public static void skipBytesFully(DataInput in, int bytes) throws IOException
     {
         int n = 0;
@@ -467,4 +484,19 @@ public class FileUtils
             out.write(buffer, 0, left);
         }
     }
+
+    public static boolean isSubDirectory(File parent, File child) throws IOException
+    {
+        parent = parent.getCanonicalFile();
+        child = child.getCanonicalFile();
+
+        File toCheck = child;
+        while (toCheck != null)
+        {
+            if (parent.equals(toCheck))
+                return true;
+            toCheck = toCheck.getParentFile();
+        }
+        return false;
+    }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7ae07e5/src/java/org/apache/cassandra/repair/messages/RepairOption.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/repair/messages/RepairOption.java b/src/java/org/apache/cassandra/repair/messages/RepairOption.java
index a13d0fe..7b9a9af 100644
--- a/src/java/org/apache/cassandra/repair/messages/RepairOption.java
+++ b/src/java/org/apache/cassandra/repair/messages/RepairOption.java
@@ -22,10 +22,14 @@ import java.util.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import org.apache.cassandra.config.Config;
+import org.apache.cassandra.config.DatabaseDescriptor;
 import org.apache.cassandra.dht.IPartitioner;
 import org.apache.cassandra.dht.Range;
 import org.apache.cassandra.dht.Token;
 import org.apache.cassandra.repair.RepairParallelism;
+import org.apache.cassandra.tools.nodetool.Repair;
+import org.apache.cassandra.utils.FBUtilities;
 
 /**
  * Repair options.
@@ -221,7 +225,16 @@ public class RepairOption
 
     public RepairOption(RepairParallelism parallelism, boolean primaryRange, boolean incremental,
boolean trace, int jobThreads, Collection<Range<Token>> ranges)
     {
-        this.parallelism = parallelism;
+        if (FBUtilities.isWindows() &&
+            (DatabaseDescriptor.getDiskAccessMode() != Config.DiskAccessMode.standard ||
DatabaseDescriptor.getIndexAccessMode() != Config.DiskAccessMode.standard) &&
+            parallelism == RepairParallelism.SEQUENTIAL)
+        {
+            logger.warn("Sequential repair disabled when memory-mapped I/O is configured
on Windows. Reverting to parallel.");
+            this.parallelism = RepairParallelism.PARALLEL;
+        }
+        else
+            this.parallelism = parallelism;
+
         this.primaryRange = primaryRange;
         this.incremental = incremental;
         this.trace = trace;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7ae07e5/src/java/org/apache/cassandra/service/CassandraDaemon.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/service/CassandraDaemon.java b/src/java/org/apache/cassandra/service/CassandraDaemon.java
index 49e0c58..167d6b9 100644
--- a/src/java/org/apache/cassandra/service/CassandraDaemon.java
+++ b/src/java/org/apache/cassandra/service/CassandraDaemon.java
@@ -144,6 +144,10 @@ public class CassandraDaemon
      */
     protected void setup()
     {
+        // Delete any failed snapshot deletions on Windows - see CASSANDRA-9658
+        if (FBUtilities.isWindows())
+            WindowsFailedSnapshotTracker.deleteOldSnapshots();
+
         logSystemInfo();
 
         CLibrary.tryMlockall();

http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7ae07e5/test/unit/org/apache/cassandra/db/SystemKeyspaceTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/db/SystemKeyspaceTest.java b/test/unit/org/apache/cassandra/db/SystemKeyspaceTest.java
index 093f359..b8aa161 100644
--- a/test/unit/org/apache/cassandra/db/SystemKeyspaceTest.java
+++ b/test/unit/org/apache/cassandra/db/SystemKeyspaceTest.java
@@ -22,6 +22,7 @@ import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.*;
 
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 import org.apache.cassandra.config.DatabaseDescriptor;
@@ -38,6 +39,13 @@ import static org.junit.Assert.assertTrue;
 
 public class SystemKeyspaceTest
 {
+    @BeforeClass
+    public static void prepSnapshotTracker()
+    {
+        if (FBUtilities.isWindows())
+            WindowsFailedSnapshotTracker.deleteOldSnapshots();
+    }
+
     @Test
     public void testLocalTokens()
     {
@@ -78,6 +86,28 @@ public class SystemKeyspaceTest
         assert firstId.equals(secondId) : String.format("%s != %s%n", firstId.toString(),
secondId.toString());
     }
 
+    private void assertDeletedOrDeferred(int expectedCount)
+    {
+        if (FBUtilities.isWindows())
+            assertEquals(expectedCount, getDeferredDeletionCount());
+        else
+            assertTrue(getSystemSnapshotFiles().isEmpty());
+    }
+
+    private int getDeferredDeletionCount()
+    {
+        try
+        {
+            Class c = Class.forName("java.io.DeleteOnExitHook");
+            LinkedHashSet<String> files = (LinkedHashSet<String>)FBUtilities.getProtectedField(c,
"files").get(c);
+            return files.size();
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
     @Test
     public void snapshotSystemKeyspaceIfUpgrading() throws IOException
     {
@@ -86,13 +116,15 @@ public class SystemKeyspaceTest
             cfs.clearUnsafe();
         Keyspace.clearSnapshot(null, SystemKeyspace.NAME);
 
+        int baseline = getDeferredDeletionCount();
+
         SystemKeyspace.snapshotOnVersionChange();
-        assertTrue(getSystemSnapshotFiles().isEmpty());
+        assertDeletedOrDeferred(baseline);
 
         // now setup system.local as if we're upgrading from a previous version
         setupReleaseVersion(getOlderVersionString());
         Keyspace.clearSnapshot(null, SystemKeyspace.NAME);
-        assertTrue(getSystemSnapshotFiles().isEmpty());
+        assertDeletedOrDeferred(baseline);
 
         // Compare versions again & verify that snapshots were created for all tables
in the system ks
         SystemKeyspace.snapshotOnVersionChange();
@@ -104,7 +136,13 @@ public class SystemKeyspaceTest
         setupReleaseVersion(FBUtilities.getReleaseVersionString());
 
         SystemKeyspace.snapshotOnVersionChange();
-        assertTrue(getSystemSnapshotFiles().isEmpty());
+
+        // snapshotOnVersionChange for upgrade case will open a SSTR when the CFS is flushed.
On Windows, we won't be
+        // able to delete hard-links to that file while segments are memory-mapped, so they'll
be marked for deferred deletion.
+        // 10 files expected.
+        assertDeletedOrDeferred(baseline + 10);
+
+        Keyspace.clearSnapshot(null, SystemKeyspace.NAME);
     }
 
     private String getOlderVersionString()

http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7ae07e5/test/unit/org/apache/cassandra/io/sstable/SSTableRewriterTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/io/sstable/SSTableRewriterTest.java b/test/unit/org/apache/cassandra/io/sstable/SSTableRewriterTest.java
index 5b4374e..dbf95c1 100644
--- a/test/unit/org/apache/cassandra/io/sstable/SSTableRewriterTest.java
+++ b/test/unit/org/apache/cassandra/io/sstable/SSTableRewriterTest.java
@@ -27,12 +27,15 @@ import java.util.concurrent.TimeUnit;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import org.junit.After;
+import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import com.google.common.util.concurrent.Uninterruptibles;
 import org.junit.Test;
 
 import org.apache.cassandra.SchemaLoader;
 import org.apache.cassandra.Util;
+import org.apache.cassandra.config.Config;
+import org.apache.cassandra.config.DatabaseDescriptor;
 import org.apache.cassandra.config.KSMetaData;
 import org.apache.cassandra.db.*;
 import org.apache.cassandra.db.compaction.AbstractCompactedRow;
@@ -62,9 +65,21 @@ public class SSTableRewriterTest extends SchemaLoader
     private static final String KEYSPACE = "SSTableRewriterTest";
     private static final String CF = "Standard1";
 
+    private static Config.DiskAccessMode standardMode;
+    private static Config.DiskAccessMode indexMode;
+
     @BeforeClass
     public static void defineSchema() throws ConfigurationException
     {
+        if (FBUtilities.isWindows())
+        {
+            standardMode = DatabaseDescriptor.getDiskAccessMode();
+            indexMode = DatabaseDescriptor.getIndexAccessMode();
+
+            DatabaseDescriptor.setDiskAccessMode(Config.DiskAccessMode.standard);
+            DatabaseDescriptor.setIndexAccessMode(Config.DiskAccessMode.standard);
+        }
+
         SchemaLoader.prepareServer();
         SchemaLoader.createKeyspace(KEYSPACE,
                 SimpleStrategy.class,
@@ -72,6 +87,13 @@ public class SSTableRewriterTest extends SchemaLoader
                 SchemaLoader.standardCFMD(KEYSPACE, CF));
     }
 
+    @AfterClass
+    public static void revertDiskAccess()
+    {
+        DatabaseDescriptor.setDiskAccessMode(standardMode);
+        DatabaseDescriptor.setIndexAccessMode(indexMode);
+    }
+
     @After
     public void truncateCF()
     {

http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7ae07e5/test/unit/org/apache/cassandra/repair/messages/RepairOptionTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/repair/messages/RepairOptionTest.java b/test/unit/org/apache/cassandra/repair/messages/RepairOptionTest.java
index 557d0b1..11ae69f 100644
--- a/test/unit/org/apache/cassandra/repair/messages/RepairOptionTest.java
+++ b/test/unit/org/apache/cassandra/repair/messages/RepairOptionTest.java
@@ -24,11 +24,14 @@ import java.util.Set;
 
 import org.junit.Test;
 
+import org.apache.cassandra.config.Config;
+import org.apache.cassandra.config.DatabaseDescriptor;
 import org.apache.cassandra.dht.IPartitioner;
 import org.apache.cassandra.dht.Murmur3Partitioner;
 import org.apache.cassandra.dht.Range;
 import org.apache.cassandra.dht.Token;
 import org.apache.cassandra.repair.RepairParallelism;
+import org.apache.cassandra.utils.FBUtilities;
 
 import static org.junit.Assert.*;
 
@@ -42,7 +45,12 @@ public class RepairOptionTest
 
         // parse with empty options
         RepairOption option = RepairOption.parse(new HashMap<String, String>(), partitioner);
-        assertTrue(option.getParallelism() == RepairParallelism.SEQUENTIAL);
+
+        if (FBUtilities.isWindows() && (DatabaseDescriptor.getDiskAccessMode() !=
Config.DiskAccessMode.standard || DatabaseDescriptor.getIndexAccessMode() != Config.DiskAccessMode.standard))
+            assertTrue(option.getParallelism() == RepairParallelism.PARALLEL);
+        else
+            assertTrue(option.getParallelism() == RepairParallelism.SEQUENTIAL);
+
         assertFalse(option.isPrimaryRange());
         assertFalse(option.isIncremental());
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7ae07e5/test/unit/org/apache/cassandra/service/StorageServiceServerTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/service/StorageServiceServerTest.java b/test/unit/org/apache/cassandra/service/StorageServiceServerTest.java
index a20fa6c..4481501 100644
--- a/test/unit/org/apache/cassandra/service/StorageServiceServerTest.java
+++ b/test/unit/org/apache/cassandra/service/StorageServiceServerTest.java
@@ -20,7 +20,9 @@
 package org.apache.cassandra.service;
 
 import java.io.File;
+import java.io.FileWriter;
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.net.InetAddress;
 import java.util.*;
 
@@ -37,7 +39,7 @@ import org.apache.cassandra.config.DatabaseDescriptor;
 import org.apache.cassandra.config.KSMetaData;
 import org.apache.cassandra.config.Schema;
 import org.apache.cassandra.db.Keyspace;
-import org.apache.cassandra.schema.LegacySchemaTables;
+import org.apache.cassandra.db.WindowsFailedSnapshotTracker;
 import org.apache.cassandra.db.SystemKeyspace;
 import org.apache.cassandra.dht.Range;
 import org.apache.cassandra.dht.Token;
@@ -48,9 +50,12 @@ import org.apache.cassandra.exceptions.ConfigurationException;
 import org.apache.cassandra.locator.IEndpointSnitch;
 import org.apache.cassandra.locator.PropertyFileSnitch;
 import org.apache.cassandra.locator.TokenMetadata;
+import org.apache.cassandra.schema.LegacySchemaTables;
+import org.apache.cassandra.utils.FBUtilities;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 @RunWith(OrderedJUnit4ClassRunner.class)
 public class StorageServiceServerTest
@@ -95,6 +100,75 @@ public class StorageServiceServerTest
         StorageService.instance.takeSnapshot("snapshot");
     }
 
+    private void checkTempFilePresence(File f, boolean exist)
+    {
+        for (int i = 0; i < 5; i++)
+        {
+            File subdir = new File(f, Integer.toString(i));
+            subdir.mkdir();
+            for (int j = 0; j < 5; j++)
+            {
+                File subF = new File(subdir, Integer.toString(j));
+                assert(exist ? subF.exists() : !subF.exists());
+            }
+        }
+    }
+
+    @Test
+    public void testSnapshotFailureHandler() throws IOException
+    {
+        assumeTrue(FBUtilities.isWindows());
+
+        // Initial "run" of Cassandra, nothing in failed snapshot file
+        WindowsFailedSnapshotTracker.deleteOldSnapshots();
+
+        File f = new File(System.getenv("TEMP") + File.separator + Integer.toString(new Random().nextInt()));
+        f.mkdir();
+        f.deleteOnExit();
+        for (int i = 0; i < 5; i++)
+        {
+            File subdir = new File(f, Integer.toString(i));
+            subdir.mkdir();
+            for (int j = 0; j < 5; j++)
+                new File(subdir, Integer.toString(j)).createNewFile();
+        }
+
+        checkTempFilePresence(f, true);
+
+        // Confirm deletion is recursive
+        for (int i = 0; i < 5; i++)
+            WindowsFailedSnapshotTracker.handleFailedSnapshot(new File(f, Integer.toString(i)));
+
+        assert new File(WindowsFailedSnapshotTracker.TODELETEFILE).exists();
+
+        // Simulate shutdown and restart of C* node, closing out the list of failed snapshots.
+        WindowsFailedSnapshotTracker.resetForTests();
+
+        // Perform new run, mimicking behavior of C* at startup
+        WindowsFailedSnapshotTracker.deleteOldSnapshots();
+        checkTempFilePresence(f, false);
+
+        // Check to make sure we don't delete non-temp, non-datafile locations
+        WindowsFailedSnapshotTracker.resetForTests();
+        PrintWriter tempPrinter = new PrintWriter(new FileWriter(WindowsFailedSnapshotTracker.TODELETEFILE,
true));
+        tempPrinter.println(".safeDir");
+        tempPrinter.close();
+
+        File protectedDir = new File(".safeDir");
+        protectedDir.mkdir();
+        File protectedFile = new File(protectedDir, ".safeFile");
+        protectedFile.createNewFile();
+
+        WindowsFailedSnapshotTracker.handleFailedSnapshot(protectedDir);
+        WindowsFailedSnapshotTracker.deleteOldSnapshots();
+
+        assert protectedDir.exists();
+        assert protectedFile.exists();
+
+        protectedFile.delete();
+        protectedDir.delete();
+    }
+
     @Test
     public void testColumnFamilySnapshot() throws IOException
     {


Mime
View raw message