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

dcapwell pushed a commit to branch cassandra-3.0
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/cassandra-3.0 by this push:
     new db03460  stop_paranoid disk failure policy is ignored on 
CorruptSSTableException after node is up
db03460 is described below

commit db034609554a3185c0808cc67e9f0c148cc912c4
Author: Stefan Miklosovic <[email protected]>
AuthorDate: Wed Jul 29 14:47:48 2020 -0700

    stop_paranoid disk failure policy is ignored on CorruptSSTableException 
after node is up
    
    patch by Stefan Miklosovic; reviewed by David Capwell, Brandon Williams for 
CASSANDRA-15191
---
 CHANGES.txt                                        |   1 +
 .../AbstractLocalAwareExecutorService.java         |  10 +-
 .../org/apache/cassandra/db/ColumnFamilyStore.java |   8 +-
 src/java/org/apache/cassandra/db/Directories.java  |   3 +-
 .../db/commitlog/CommitLogSegmentManager.java      |   1 -
 .../org/apache/cassandra/db/lifecycle/Tracker.java |   8 +
 .../cassandra/io/sstable/format/SSTableReader.java |   6 +-
 .../org/apache/cassandra/io/util/FileUtils.java    |   2 +-
 .../apache/cassandra/service/CassandraDaemon.java  |  21 +-
 .../cassandra/service/DefaultFSErrorHandler.java   |   6 +-
 .../cassandra/utils/JVMStabilityInspector.java     |  30 +-
 .../cassandra/distributed/impl/Instance.java       |   4 +
 ...bilityInspectorCorruptSSTableExceptionTest.java | 205 ++++++
 .../io/sstable/format/ForwardingSSTableReader.java | 766 +++++++++++++++++++++
 .../org/apache/cassandra/db/DirectoriesTest.java   |   3 +-
 .../cassandra/utils/JVMStabilityInspectorTest.java |  14 +
 16 files changed, 1045 insertions(+), 43 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index d22ba43..a755b7e 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 3.0.22:
+ * stop_paranoid disk failure policy is ignored on CorruptSSTableException 
after node is up (CASSANDRA-15191)
  * 3.x fails to start if commit log has range tombstones from a column which 
is also deleted (CASSANDRA-15970)
  * Forbid altering UDTs used in partition keys (CASSANDRA-15933)
  * Fix empty/null json string representation (CASSANDRA-15896)
diff --git 
a/src/java/org/apache/cassandra/concurrent/AbstractLocalAwareExecutorService.java
 
b/src/java/org/apache/cassandra/concurrent/AbstractLocalAwareExecutorService.java
index 4b1fe05..d666a36 100644
--- 
a/src/java/org/apache/cassandra/concurrent/AbstractLocalAwareExecutorService.java
+++ 
b/src/java/org/apache/cassandra/concurrent/AbstractLocalAwareExecutorService.java
@@ -29,9 +29,6 @@ import java.util.concurrent.TimeoutException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import org.apache.cassandra.io.FSError;
-import org.apache.cassandra.io.sstable.CorruptSSTableException;
-import org.apache.cassandra.io.util.FileUtils;
 import org.apache.cassandra.utils.concurrent.SimpleCondition;
 import org.apache.cassandra.utils.JVMStabilityInspector;
 
@@ -169,12 +166,7 @@ public abstract class AbstractLocalAwareExecutorService 
implements LocalAwareExe
                 logger.error("Uncaught exception on thread {}", 
Thread.currentThread(), t);
                 result = t;
                 failure = true;
-                if (t instanceof CorruptSSTableException)
-                    FileUtils.handleCorruptSSTable((CorruptSSTableException) 
t);
-                else if (t instanceof FSError)
-                    FileUtils.handleFSError((FSError) t);
-                else
-                    JVMStabilityInspector.inspectThrowable(t);
+                JVMStabilityInspector.inspectThrowable(t);
             }
             finally
             {
diff --git a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java 
b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java
index 70c14c0..7de68f9 100644
--- a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java
+++ b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java
@@ -699,7 +699,7 @@ public class ColumnFamilyStore implements 
ColumnFamilyStoreMBean
             }
             catch (IOException e)
             {
-                FileUtils.handleCorruptSSTable(new CorruptSSTableException(e, 
entry.getKey().filenameFor(Component.STATS)));
+                JVMStabilityInspector.inspectThrowable(new 
CorruptSSTableException(e, entry.getKey().filenameFor(Component.STATS)));
                 logger.error("Cannot read sstable {}; other IO error, skipping 
table", entry, e);
                 continue;
             }
@@ -729,19 +729,19 @@ public class ColumnFamilyStore implements 
ColumnFamilyStoreMBean
             }
             catch (CorruptSSTableException ex)
             {
-                FileUtils.handleCorruptSSTable(ex);
+                JVMStabilityInspector.inspectThrowable(ex);
                 logger.error("Corrupt sstable {}; skipping table", entry, ex);
                 continue;
             }
             catch (FSError ex)
             {
-                FileUtils.handleFSError(ex);
+                JVMStabilityInspector.inspectThrowable(ex);
                 logger.error("Cannot read sstable {}; file system error, 
skipping table", entry, ex);
                 continue;
             }
             catch (IOException ex)
             {
-                FileUtils.handleCorruptSSTable(new CorruptSSTableException(ex, 
entry.getKey().filenameFor(Component.DATA)));
+                JVMStabilityInspector.inspectThrowable(new 
CorruptSSTableException(ex, entry.getKey().filenameFor(Component.DATA)));
                 logger.error("Cannot read sstable {}; other IO error, skipping 
table", entry, ex);
                 continue;
             }
diff --git a/src/java/org/apache/cassandra/db/Directories.java 
b/src/java/org/apache/cassandra/db/Directories.java
index 0f3b2b6..d169428 100644
--- a/src/java/org/apache/cassandra/db/Directories.java
+++ b/src/java/org/apache/cassandra/db/Directories.java
@@ -55,6 +55,7 @@ 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.JVMStabilityInspector;
 import org.apache.cassandra.utils.Pair;
 
 /**
@@ -245,7 +246,7 @@ public class Directories
             {
                 // don't just let the default exception handler do this, we 
need the create loop to continue
                 logger.error("Failed to create {} directory", dir);
-                FileUtils.handleFSError(e);
+                JVMStabilityInspector.inspectThrowable(e);
             }
         }
 
diff --git 
a/src/java/org/apache/cassandra/db/commitlog/CommitLogSegmentManager.java 
b/src/java/org/apache/cassandra/db/commitlog/CommitLogSegmentManager.java
index 7651d1c..c24aa12 100644
--- a/src/java/org/apache/cassandra/db/commitlog/CommitLogSegmentManager.java
+++ b/src/java/org/apache/cassandra/db/commitlog/CommitLogSegmentManager.java
@@ -161,7 +161,6 @@ public class CommitLogSegmentManager
                     }
                     catch (Throwable t)
                     {
-                        JVMStabilityInspector.inspectThrowable(t);
                         if (!CommitLog.handleCommitError("Failed managing 
commit log segments", t))
                             return;
                         // sleep some arbitrary period to avoid spamming CL
diff --git a/src/java/org/apache/cassandra/db/lifecycle/Tracker.java 
b/src/java/org/apache/cassandra/db/lifecycle/Tracker.java
index 9feaa3e..58c491c 100644
--- a/src/java/org/apache/cassandra/db/lifecycle/Tracker.java
+++ b/src/java/org/apache/cassandra/db/lifecycle/Tracker.java
@@ -486,4 +486,12 @@ public class Tracker
     {
         return view.get();
     }
+
+    @VisibleForTesting
+    public void removeUnsafe(Set<SSTableReader> toRemove)
+    {
+        Pair<View, View> result = apply(view -> {
+            return updateLiveSet(toRemove, emptySet()).apply(view);
+        });
+    }
 }
diff --git a/src/java/org/apache/cassandra/io/sstable/format/SSTableReader.java 
b/src/java/org/apache/cassandra/io/sstable/format/SSTableReader.java
index 0ca1f3a..e7f9613 100644
--- a/src/java/org/apache/cassandra/io/sstable/format/SSTableReader.java
+++ b/src/java/org/apache/cassandra/io/sstable/format/SSTableReader.java
@@ -545,19 +545,19 @@ public abstract class SSTableReader extends SSTable 
implements SelfRefCounted<SS
                     }
                     catch (CorruptSSTableException ex)
                     {
-                        FileUtils.handleCorruptSSTable(ex);
+                        JVMStabilityInspector.inspectThrowable(ex);
                         logger.error("Corrupt sstable {}; skipping table", 
entry, ex);
                         return;
                     }
                     catch (FSError ex)
                     {
-                        FileUtils.handleFSError(ex);
+                        JVMStabilityInspector.inspectThrowable(ex);
                         logger.error("Cannot read sstable {}; file system 
error, skipping table", entry, ex);
                         return;
                     }
                     catch (IOException ex)
                     {
-                        FileUtils.handleCorruptSSTable(new 
CorruptSSTableException(ex, entry.getKey().filenameFor(Component.DATA)));
+                        JVMStabilityInspector.inspectThrowable(new 
CorruptSSTableException(ex, entry.getKey().filenameFor(Component.DATA)));
                         logger.error("Cannot read sstable {}; other IO error, 
skipping table", entry, ex);
                         return;
                     }
diff --git a/src/java/org/apache/cassandra/io/util/FileUtils.java 
b/src/java/org/apache/cassandra/io/util/FileUtils.java
index f9406e5..191e965 100644
--- a/src/java/org/apache/cassandra/io/util/FileUtils.java
+++ b/src/java/org/apache/cassandra/io/util/FileUtils.java
@@ -509,7 +509,7 @@ public final class FileUtils
      */
     public static void handleFSErrorAndPropagate(FSError e)
     {
-        handleFSError(e);
+        JVMStabilityInspector.inspectThrowable(e);
         throw propagate(e);
     }
 
diff --git a/src/java/org/apache/cassandra/service/CassandraDaemon.java 
b/src/java/org/apache/cassandra/service/CassandraDaemon.java
index 271f763..14d8aab 100644
--- a/src/java/org/apache/cassandra/service/CassandraDaemon.java
+++ b/src/java/org/apache/cassandra/service/CassandraDaemon.java
@@ -224,26 +224,15 @@ public class CassandraDaemon
             public void uncaughtException(Thread t, Throwable e)
             {
                 StorageMetrics.exceptions.inc();
-                logger.error("Exception in thread " + t, e);
+                logger.error("Exception in thread {}", t, e);
                 Tracing.trace("Exception in thread {}", t, e);
                 for (Throwable e2 = e; e2 != null; e2 = e2.getCause())
                 {
-                    JVMStabilityInspector.inspectThrowable(e2);
-
-                    if (e2 instanceof FSError)
-                    {
-                        if (e2 != e) // make sure FSError gets logged exactly 
once.
-                            logger.error("Exception in thread " + t, e2);
-                        FileUtils.handleFSError((FSError) e2);
-                    }
-
-                    if (e2 instanceof CorruptSSTableException)
-                    {
-                        if (e2 != e)
-                            logger.error("Exception in thread " + t, e2);
-                        
FileUtils.handleCorruptSSTable((CorruptSSTableException) e2);
-                    }
+                    // make sure error gets logged exactly once.
+                    if (e2 != e && (e2 instanceof FSError || e2 instanceof 
CorruptSSTableException))
+                        logger.error("Exception in thread {}", t, e2);
                 }
+                JVMStabilityInspector.inspectThrowable(e);
             }
         });
 
diff --git a/src/java/org/apache/cassandra/service/DefaultFSErrorHandler.java 
b/src/java/org/apache/cassandra/service/DefaultFSErrorHandler.java
index 0413e0d..1c81f65 100644
--- a/src/java/org/apache/cassandra/service/DefaultFSErrorHandler.java
+++ b/src/java/org/apache/cassandra/service/DefaultFSErrorHandler.java
@@ -42,10 +42,11 @@ public class DefaultFSErrorHandler implements FSErrorHandler
         if (!StorageService.instance.isSetupCompleted())
             handleStartupFSError(e);
 
-        JVMStabilityInspector.inspectThrowable(e);
         switch (DatabaseDescriptor.getDiskFailurePolicy())
         {
             case stop_paranoid:
+                // exception not logged here on purpose as it is already logged
+                logger.error("Stopping transports as disk_failure_policy is " 
+ DatabaseDescriptor.getDiskFailurePolicy());
                 StorageService.instance.stopTransports();
                 break;
         }
@@ -57,11 +58,12 @@ public class DefaultFSErrorHandler implements FSErrorHandler
         if (!StorageService.instance.isSetupCompleted())
             handleStartupFSError(e);
 
-        JVMStabilityInspector.inspectThrowable(e);
         switch (DatabaseDescriptor.getDiskFailurePolicy())
         {
             case stop_paranoid:
             case stop:
+                // exception not logged here on purpose as it is already logged
+                logger.error("Stopping transports as disk_failure_policy is " 
+ DatabaseDescriptor.getDiskFailurePolicy());
                 StorageService.instance.stopTransports();
                 break;
             case best_effort:
diff --git a/src/java/org/apache/cassandra/utils/JVMStabilityInspector.java 
b/src/java/org/apache/cassandra/utils/JVMStabilityInspector.java
index 89ef129..b018e04 100644
--- a/src/java/org/apache/cassandra/utils/JVMStabilityInspector.java
+++ b/src/java/org/apache/cassandra/utils/JVMStabilityInspector.java
@@ -21,6 +21,7 @@ import java.io.FileNotFoundException;
 import java.net.SocketException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
 
 import com.google.common.annotations.VisibleForTesting;
 
@@ -32,6 +33,7 @@ import org.apache.cassandra.config.Config;
 import org.apache.cassandra.config.DatabaseDescriptor;
 import org.apache.cassandra.io.FSError;
 import org.apache.cassandra.io.sstable.CorruptSSTableException;
+import org.apache.cassandra.io.util.FileUtils;
 import org.apache.cassandra.service.StorageService;
 
 /**
@@ -55,6 +57,24 @@ public final class JVMStabilityInspector
      */
     public static void inspectThrowable(Throwable t)
     {
+        inspectThrowable(t, JVMStabilityInspector::inspectDiskError);
+    }
+
+    public static void inspectCommitLogThrowable(Throwable t)
+    {
+        inspectThrowable(t, JVMStabilityInspector::inspectCommitLogError);
+    }
+
+    private static void inspectDiskError(Throwable t)
+    {
+        if (t instanceof CorruptSSTableException)
+            FileUtils.handleCorruptSSTable((CorruptSSTableException) t);
+        else if (t instanceof FSError)
+            FileUtils.handleFSError((FSError) t);
+    }
+
+    public static void inspectThrowable(Throwable t, Consumer<Throwable> fn) 
throws OutOfMemoryError
+    {
         boolean isUnstable = false;
         if (t instanceof OutOfMemoryError)
         {
@@ -81,7 +101,9 @@ public final class JVMStabilityInspector
 
         if (DatabaseDescriptor.getDiskFailurePolicy() == 
Config.DiskFailurePolicy.die)
             if (t instanceof FSError || t instanceof CorruptSSTableException)
-            isUnstable = true;
+                isUnstable = true;
+
+        fn.accept(t);
 
         // Check for file handle exhaustion
         if (t instanceof FileNotFoundException || t instanceof SocketException)
@@ -92,10 +114,10 @@ public final class JVMStabilityInspector
             killer.killCurrentJVM(t);
 
         if (t.getCause() != null)
-            inspectThrowable(t.getCause());
+            inspectThrowable(t.getCause(), fn);
     }
 
-    public static void inspectCommitLogThrowable(Throwable t)
+    private static void inspectCommitLogError(Throwable t)
     {
         if (!StorageService.instance.isSetupCompleted())
         {
@@ -103,8 +125,6 @@ public final class JVMStabilityInspector
             killer.killCurrentJVM(t, true);
         } else if (DatabaseDescriptor.getCommitFailurePolicy() == 
Config.CommitFailurePolicy.die)
             killer.killCurrentJVM(t);
-        else
-            inspectThrowable(t);
     }
 
     public static void killCurrentJVM(Throwable t, boolean quiet)
diff --git 
a/test/distributed/org/apache/cassandra/distributed/impl/Instance.java 
b/test/distributed/org/apache/cassandra/distributed/impl/Instance.java
index 90e6787..8fa5966 100644
--- a/test/distributed/org/apache/cassandra/distributed/impl/Instance.java
+++ b/test/distributed/org/apache/cassandra/distributed/impl/Instance.java
@@ -83,6 +83,7 @@ import org.apache.cassandra.io.sstable.format.SSTableReader;
 import org.apache.cassandra.io.util.DataInputBuffer;
 import org.apache.cassandra.io.util.DataOutputBuffer;
 import org.apache.cassandra.io.util.DataOutputPlus;
+import org.apache.cassandra.io.util.FileUtils;
 import org.apache.cassandra.net.CompactEndpointSerializationHelper;
 import org.apache.cassandra.net.IMessageSink;
 import org.apache.cassandra.net.MessageIn;
@@ -91,6 +92,7 @@ import org.apache.cassandra.net.MessagingService;
 import org.apache.cassandra.schema.LegacySchemaMigrator;
 import org.apache.cassandra.service.CassandraDaemon;
 import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.service.DefaultFSErrorHandler;
 import org.apache.cassandra.service.PendingRangeCalculatorService;
 import org.apache.cassandra.service.QueryState;
 import org.apache.cassandra.service.StorageService;
@@ -504,6 +506,8 @@ public class Instance extends IsolatedExecutor implements 
IInvokableInstance
         sync(() -> {
             try
             {
+                FileUtils.setFSErrorHandler(new DefaultFSErrorHandler());
+
                 mkdirs();
 
                 assert 
config.networkTopology().contains(config.broadcastAddress());
diff --git 
a/test/distributed/org/apache/cassandra/distributed/test/JVMStabilityInspectorCorruptSSTableExceptionTest.java
 
b/test/distributed/org/apache/cassandra/distributed/test/JVMStabilityInspectorCorruptSSTableExceptionTest.java
new file mode 100644
index 0000000..98ca496
--- /dev/null
+++ 
b/test/distributed/org/apache/cassandra/distributed/test/JVMStabilityInspectorCorruptSSTableExceptionTest.java
@@ -0,0 +1,205 @@
+/*
+ * 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.distributed.test;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.cassandra.config.Config.DiskFailurePolicy;
+import org.apache.cassandra.db.ColumnFamilyStore;
+import org.apache.cassandra.db.DecoratedKey;
+import org.apache.cassandra.db.Keyspace;
+import org.apache.cassandra.db.RowIndexEntry;
+import org.apache.cassandra.db.filter.ColumnFilter;
+import org.apache.cassandra.db.rows.SliceableUnfilteredRowIterator;
+import org.apache.cassandra.distributed.Cluster;
+import org.apache.cassandra.distributed.api.ConsistencyLevel;
+import org.apache.cassandra.distributed.api.IInvokableInstance;
+import 
org.apache.cassandra.distributed.api.IIsolatedExecutor.SerializableCallable;
+import org.apache.cassandra.distributed.shared.AbstractBuilder;
+import org.apache.cassandra.distributed.shared.NetworkTopology;
+import org.apache.cassandra.gms.Gossiper;
+import org.apache.cassandra.io.sstable.CorruptSSTableException;
+import org.apache.cassandra.io.sstable.format.ForwardingSSTableReader;
+import org.apache.cassandra.io.sstable.format.SSTableReader;
+import org.apache.cassandra.io.sstable.format.SSTableReadsListener;
+import org.apache.cassandra.io.util.FileDataInput;
+import org.apache.cassandra.service.CassandraDaemon;
+import org.apache.cassandra.service.StorageService;
+
+import static org.apache.cassandra.distributed.api.Feature.GOSSIP;
+import static org.apache.cassandra.distributed.api.Feature.NATIVE_PROTOCOL;
+import static org.apache.cassandra.distributed.api.Feature.NETWORK;
+
+public class JVMStabilityInspectorCorruptSSTableExceptionTest extends 
TestBaseImpl
+{
+    @Test
+    public void 
testAbstractLocalAwareExecutorServiceOnIgnoredDiskFailurePolicy() throws 
Exception
+    {
+        test(DiskFailurePolicy.ignore, true, true);
+    }
+
+    @Test
+    public void 
testAbstractLocalAwareExecutorServiceOnStopParanoidDiskFailurePolicy() throws 
Exception
+    {
+        test(DiskFailurePolicy.stop_paranoid, false, false);
+    }
+
+    private static void test(DiskFailurePolicy policy, boolean 
expectNativeTransportRunning, boolean expectGossiperEnabled) throws Exception
+    {
+        String table = policy.name();
+        try (final Cluster cluster = init(getCluster(policy).start()))
+        {
+            IInvokableInstance node = cluster.get(1);
+            boolean[] setup = node.callOnInstance(() -> {
+                CassandraDaemon instanceForTesting = 
CassandraDaemon.getInstanceForTesting();
+                instanceForTesting.completeSetup();
+                StorageService.instance.registerDaemon(instanceForTesting);
+                return new boolean[]{ 
StorageService.instance.isNativeTransportRunning(), 
Gossiper.instance.isEnabled() };
+            });
+
+            // make sure environment is setup propertly
+            Assert.assertTrue("Native support is not running, test is not 
ready!", setup[0]);
+            Assert.assertTrue("Gossiper is not running, test is not ready!", 
setup[1]);
+
+            cluster.schemaChange("CREATE TABLE " + KEYSPACE + "." + table + " 
(id bigint PRIMARY KEY)");
+            node.executeInternal("INSERT INTO " + KEYSPACE + "." + table + " 
(id) VALUES (?)", 0L);
+            corruptTable(node, KEYSPACE, table);
+
+            try
+            {
+                cluster.coordinator(1).execute("SELECT * FROM " + KEYSPACE + 
'.' + table + " WHERE id=?", ConsistencyLevel.ONE, 0L);
+                Assert.fail("Select should fail as we corrupted SSTable on 
purpose.");
+            }
+            catch (final Exception ex)
+            {
+                // we expect that above query fails as we corrupted an sstable
+            }
+
+            waitForStop(!expectGossiperEnabled, node, new 
SerializableCallable<Boolean>()
+            {
+                public Boolean call()
+                {
+                    return Gossiper.instance.isEnabled();
+                }
+            });
+
+            waitForStop(!expectNativeTransportRunning, node, new 
SerializableCallable<Boolean>()
+            {
+                public Boolean call()
+                {
+                    return StorageService.instance.isNativeTransportRunning();
+                }
+            });
+        }
+    }
+
+    private static void waitForStop(boolean shouldWaitForStop,
+                                    IInvokableInstance node,
+                                    SerializableCallable<Boolean> 
serializableCallable) throws Exception
+    {
+        int attempts = 3;
+        boolean running = true;
+
+        while (attempts > 0 && running)
+        {
+            try
+            {
+                running = node.callOnInstance(serializableCallable);
+                attempts--;
+            }
+            catch (final NoClassDefFoundError ex)
+            {
+                // gossiper throws this
+                Assert.assertEquals("Could not initialize class 
org.apache.cassandra.service.StorageService", ex.getMessage());
+                running = false;
+            }
+            catch (final ExceptionInInitializerError ex)
+            {
+                // native thows this, ignore on purpose, this means that 
native transport is closed.
+                running = false;
+            }
+
+            Thread.sleep(5000);
+        }
+
+        if (shouldWaitForStop && running)
+        {
+            Assert.fail("we did want a service to stop, but it did not.");
+        }
+
+        if (!shouldWaitForStop && !running)
+        {
+            Assert.fail("we did not want a service to stop, but it did.");
+        }
+    }
+
+    private static void corruptTable(IInvokableInstance node, String keyspace, 
String table)
+    {
+        node.runOnInstance(() -> {
+            ColumnFamilyStore cf = 
Keyspace.open(keyspace).getColumnFamilyStore(table);
+            cf.forceBlockingFlush();
+
+            Set<SSTableReader> remove = cf.getLiveSSTables();
+            Set<SSTableReader> replace = new HashSet<>();
+            for (SSTableReader r : remove)
+                replace.add(new CorruptedSSTableReader(r));
+
+            cf.getTracker().removeUnsafe(remove);
+            cf.addSSTables(replace);
+        });
+    }
+
+    private static AbstractBuilder<IInvokableInstance, Cluster, 
Cluster.Builder> getCluster(DiskFailurePolicy diskFailurePolicy)
+    {
+        return Cluster.build()
+                      
.withNodeIdTopology(NetworkTopology.singleDcNetworkTopology(1, "dc0", "rack0"))
+                      .withConfig(config -> config.with(NETWORK, GOSSIP, 
NATIVE_PROTOCOL)
+                                                  .set("disk_failure_policy", 
diskFailurePolicy.name()));
+    }
+
+    private static final class CorruptedSSTableReader extends 
ForwardingSSTableReader
+    {
+        public CorruptedSSTableReader(SSTableReader delegate)
+        {
+            super(delegate);
+        }
+
+        @Override
+        public SliceableUnfilteredRowIterator iterator(DecoratedKey key, 
ColumnFilter selectedColumns, boolean reversed, boolean isForThrift, 
SSTableReadsListener listener)
+        {
+            throw throwCorrupted();
+        }
+
+        @Override
+        public SliceableUnfilteredRowIterator iterator(FileDataInput file, 
DecoratedKey key, RowIndexEntry indexEntry, ColumnFilter selectedColumns, 
boolean reversed, boolean isForThrift)
+        {
+            throw throwCorrupted();
+        }
+
+        private CorruptSSTableException throwCorrupted()
+        {
+            throw new CorruptSSTableException(new IOException("failed to get 
position"), descriptor.baseFilename());
+        }
+    }
+}
\ No newline at end of file
diff --git 
a/test/distributed/org/apache/cassandra/io/sstable/format/ForwardingSSTableReader.java
 
b/test/distributed/org/apache/cassandra/io/sstable/format/ForwardingSSTableReader.java
new file mode 100644
index 0000000..bd92ca0
--- /dev/null
+++ 
b/test/distributed/org/apache/cassandra/io/sstable/format/ForwardingSSTableReader.java
@@ -0,0 +1,766 @@
+/*
+ * 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.io.sstable.format;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import com.google.common.util.concurrent.RateLimiter;
+
+import org.apache.cassandra.cache.InstrumentingCache;
+import org.apache.cassandra.cache.KeyCacheKey;
+import org.apache.cassandra.db.ColumnFamilyStore;
+import org.apache.cassandra.db.DataRange;
+import org.apache.cassandra.db.DecoratedKey;
+import org.apache.cassandra.db.PartitionPosition;
+import org.apache.cassandra.db.RowIndexEntry;
+import org.apache.cassandra.db.filter.ColumnFilter;
+import org.apache.cassandra.db.rows.SliceableUnfilteredRowIterator;
+import org.apache.cassandra.dht.AbstractBounds;
+import org.apache.cassandra.dht.IPartitioner;
+import org.apache.cassandra.dht.Range;
+import org.apache.cassandra.dht.Token;
+import org.apache.cassandra.io.compress.CompressionMetadata;
+import org.apache.cassandra.io.sstable.Component;
+import org.apache.cassandra.io.sstable.ISSTableScanner;
+import org.apache.cassandra.io.sstable.SSTable;
+import org.apache.cassandra.io.sstable.metadata.StatsMetadata;
+import org.apache.cassandra.io.util.ChannelProxy;
+import org.apache.cassandra.io.util.FileDataInput;
+import org.apache.cassandra.io.util.RandomAccessReader;
+import org.apache.cassandra.io.util.SegmentedFile;
+import org.apache.cassandra.metrics.RestorableMeter;
+import org.apache.cassandra.utils.EstimatedHistogram;
+import org.apache.cassandra.utils.IFilter;
+import org.apache.cassandra.utils.Pair;
+import org.apache.cassandra.utils.concurrent.Ref;
+
+public abstract class ForwardingSSTableReader extends SSTableReader
+{
+    // This method is only accessiable via extension and not for calling 
directly;
+    // to work around this, rely on reflection if the method gets called
+    private static final Method ESTIMATE_ROWS_FROM_INDEX;
+    static {
+        try
+        {
+            Method m = 
SSTable.class.getDeclaredMethod("estimateRowsFromIndex", 
RandomAccessReader.class);
+            m.setAccessible(true);
+            ESTIMATE_ROWS_FROM_INDEX = m;
+        }
+        catch (NoSuchMethodException e)
+        {
+            throw new AssertionError(e);
+        }
+    }
+
+    private final SSTableReader delegate;
+
+    public ForwardingSSTableReader(SSTableReader delegate)
+    {
+        super(delegate.descriptor, SSTable.componentsFor(delegate.descriptor),
+              delegate.metadata, delegate.maxDataAge, 
delegate.getSSTableMetadata(),
+              delegate.openReason, delegate.header);
+        this.delegate = delegate;
+        this.first = delegate.first;
+        this.last = delegate.last;
+    }
+
+    @Override
+    public boolean equals(Object that)
+    {
+        return delegate.equals(that);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return delegate.hashCode();
+    }
+
+    @Override
+    public String getFilename()
+    {
+        return delegate.getFilename();
+    }
+
+    @Override
+    public void setupOnline()
+    {
+        delegate.setupOnline();
+    }
+
+    @Override
+    public boolean isKeyCacheSetup()
+    {
+        return delegate.isKeyCacheSetup();
+    }
+
+    @Override
+    public boolean loadSummary(SegmentedFile.Builder ibuilder, 
SegmentedFile.Builder dbuilder)
+    {
+        return delegate.loadSummary(ibuilder, dbuilder);
+    }
+
+    @Override
+    public void saveSummary(SegmentedFile.Builder ibuilder, 
SegmentedFile.Builder dbuilder)
+    {
+        delegate.saveSummary(ibuilder, dbuilder);
+    }
+
+    @Override
+    public void saveBloomFilter()
+    {
+        delegate.saveBloomFilter();
+    }
+
+    @Override
+    public void setReplaced()
+    {
+        delegate.setReplaced();
+    }
+
+    @Override
+    public boolean isReplaced()
+    {
+        return delegate.isReplaced();
+    }
+
+    @Override
+    public void runOnClose(Runnable runOnClose)
+    {
+        delegate.runOnClose(runOnClose);
+    }
+
+    @Override
+    public SSTableReader cloneWithRestoredStart(DecoratedKey restoredStart)
+    {
+        return delegate.cloneWithRestoredStart(restoredStart);
+    }
+
+    @Override
+    public SSTableReader cloneWithNewStart(DecoratedKey newStart, Runnable 
runOnClose)
+    {
+        return delegate.cloneWithNewStart(newStart, runOnClose);
+    }
+
+    @Override
+    public SSTableReader cloneWithNewSummarySamplingLevel(ColumnFamilyStore 
parent, int samplingLevel) throws IOException
+    {
+        return delegate.cloneWithNewSummarySamplingLevel(parent, 
samplingLevel);
+    }
+
+    @Override
+    public RestorableMeter getReadMeter()
+    {
+        return delegate.getReadMeter();
+    }
+
+    @Override
+    public int getIndexSummarySamplingLevel()
+    {
+        return delegate.getIndexSummarySamplingLevel();
+    }
+
+    @Override
+    public long getIndexSummaryOffHeapSize()
+    {
+        return delegate.getIndexSummaryOffHeapSize();
+    }
+
+    @Override
+    public int getMinIndexInterval()
+    {
+        return delegate.getMinIndexInterval();
+    }
+
+    @Override
+    public double getEffectiveIndexInterval()
+    {
+        return delegate.getEffectiveIndexInterval();
+    }
+
+    @Override
+    public void releaseSummary()
+    {
+        delegate.releaseSummary();
+    }
+
+    @Override
+    public long getIndexScanPosition(PartitionPosition key)
+    {
+        return delegate.getIndexScanPosition(key);
+    }
+
+    @Override
+    public CompressionMetadata getCompressionMetadata()
+    {
+        return delegate.getCompressionMetadata();
+    }
+
+    @Override
+    public long getCompressionMetadataOffHeapSize()
+    {
+        return delegate.getCompressionMetadataOffHeapSize();
+    }
+
+    @Override
+    public void forceFilterFailures()
+    {
+        delegate.forceFilterFailures();
+    }
+
+    @Override
+    public IFilter getBloomFilter()
+    {
+        return delegate.getBloomFilter();
+    }
+
+    @Override
+    public long getBloomFilterSerializedSize()
+    {
+        return delegate.getBloomFilterSerializedSize();
+    }
+
+    @Override
+    public long getBloomFilterOffHeapSize()
+    {
+        return delegate.getBloomFilterOffHeapSize();
+    }
+
+    @Override
+    public long estimatedKeys()
+    {
+        return delegate.estimatedKeys();
+    }
+
+    @Override
+    public long estimatedKeysForRanges(Collection<Range<Token>> ranges)
+    {
+        return delegate.estimatedKeysForRanges(ranges);
+    }
+
+    @Override
+    public int getIndexSummarySize()
+    {
+        return delegate.getIndexSummarySize();
+    }
+
+    @Override
+    public int getMaxIndexSummarySize()
+    {
+        return delegate.getMaxIndexSummarySize();
+    }
+
+    @Override
+    public byte[] getIndexSummaryKey(int index)
+    {
+        return delegate.getIndexSummaryKey(index);
+    }
+
+    @Override
+    public Iterable<DecoratedKey> getKeySamples(Range<Token> range)
+    {
+        return delegate.getKeySamples(range);
+    }
+
+    @Override
+    public List<Pair<Long, Long>> 
getPositionsForRanges(Collection<Range<Token>> ranges)
+    {
+        return delegate.getPositionsForRanges(ranges);
+    }
+
+    @Override
+    public KeyCacheKey getCacheKey(DecoratedKey key)
+    {
+        return delegate.getCacheKey(key);
+    }
+
+    @Override
+    public void cacheKey(DecoratedKey key, RowIndexEntry info)
+    {
+        delegate.cacheKey(key, info);
+    }
+
+    @Override
+    public RowIndexEntry getCachedPosition(DecoratedKey key, boolean 
updateStats)
+    {
+        return delegate.getCachedPosition(key, updateStats);
+    }
+
+    @Override
+    protected RowIndexEntry getCachedPosition(KeyCacheKey unifiedKey, boolean 
updateStats)
+    {
+        return delegate.getCachedPosition(unifiedKey, updateStats);
+    }
+
+    @Override
+    protected RowIndexEntry getPosition(PartitionPosition key, Operator op, 
boolean updateCacheAndStats, boolean permitMatchPastLast, SSTableReadsListener 
listener)
+    {
+        return delegate.getPosition(key, op, updateCacheAndStats, 
permitMatchPastLast, listener);
+    }
+
+    @Override
+    public SliceableUnfilteredRowIterator iterator(DecoratedKey key, 
ColumnFilter selectedColumns, boolean reversed, boolean isForThrift, 
SSTableReadsListener listener)
+    {
+        return delegate.iterator(key, selectedColumns, reversed, isForThrift, 
listener);
+    }
+
+    @Override
+    public SliceableUnfilteredRowIterator iterator(FileDataInput file, 
DecoratedKey key, RowIndexEntry indexEntry, ColumnFilter selectedColumns, 
boolean reversed, boolean isForThrift)
+    {
+        return delegate.iterator(file, key, indexEntry, selectedColumns, 
reversed, isForThrift);
+    }
+
+    @Override
+    public DecoratedKey firstKeyBeyond(PartitionPosition token)
+    {
+        return delegate.firstKeyBeyond(token);
+    }
+
+    @Override
+    public long uncompressedLength()
+    {
+        return delegate.uncompressedLength();
+    }
+
+    @Override
+    public long onDiskLength()
+    {
+        return delegate.onDiskLength();
+    }
+
+    @Override
+    public double getCrcCheckChance()
+    {
+        return delegate.getCrcCheckChance();
+    }
+
+    @Override
+    public void setCrcCheckChance(double crcCheckChance)
+    {
+        delegate.setCrcCheckChance(crcCheckChance);
+    }
+
+    @Override
+    public void markObsolete(Runnable tidier)
+    {
+        delegate.markObsolete(tidier);
+    }
+
+    @Override
+    public boolean isMarkedCompacted()
+    {
+        return delegate.isMarkedCompacted();
+    }
+
+    @Override
+    public void markSuspect()
+    {
+        delegate.markSuspect();
+    }
+
+    @Override
+    public boolean isMarkedSuspect()
+    {
+        return delegate.isMarkedSuspect();
+    }
+
+    @Override
+    public ISSTableScanner getScanner()
+    {
+        return delegate.getScanner();
+    }
+
+    @Override
+    public ISSTableScanner getScanner(ColumnFilter columns, DataRange 
dataRange, boolean isForThrift, SSTableReadsListener listener)
+    {
+        return delegate.getScanner(columns, dataRange, isForThrift, listener);
+    }
+
+    @Override
+    public ISSTableScanner getScanner(Range<Token> range, RateLimiter limiter)
+    {
+        return delegate.getScanner(range, limiter);
+    }
+
+    @Override
+    public ISSTableScanner getScanner(RateLimiter limiter)
+    {
+        return delegate.getScanner(limiter);
+    }
+
+    @Override
+    public ISSTableScanner getScanner(Collection<Range<Token>> ranges, 
RateLimiter limiter)
+    {
+        return delegate.getScanner(ranges, limiter);
+    }
+
+    @Override
+    public ISSTableScanner 
getScanner(Iterator<AbstractBounds<PartitionPosition>> rangeIterator)
+    {
+        return delegate.getScanner(rangeIterator);
+    }
+
+    @Override
+    public ISSTableScanner getScanner(ColumnFilter columns, DataRange 
dataRange, RateLimiter limiter, boolean isForThrift, SSTableReadsListener 
listener)
+    {
+        return delegate.getScanner(columns, dataRange, limiter, isForThrift, 
listener);
+    }
+
+    @Override
+    public FileDataInput getFileDataInput(long position)
+    {
+        return delegate.getFileDataInput(position);
+    }
+
+    @Override
+    public boolean newSince(long age)
+    {
+        return delegate.newSince(age);
+    }
+
+    @Override
+    public void createLinks(String snapshotDirectoryPath)
+    {
+        delegate.createLinks(snapshotDirectoryPath);
+    }
+
+    @Override
+    public boolean isRepaired()
+    {
+        return delegate.isRepaired();
+    }
+
+    @Override
+    public long getBloomFilterFalsePositiveCount()
+    {
+        return delegate.getBloomFilterFalsePositiveCount();
+    }
+
+    @Override
+    public long getRecentBloomFilterFalsePositiveCount()
+    {
+        return delegate.getRecentBloomFilterFalsePositiveCount();
+    }
+
+    @Override
+    public long getBloomFilterTruePositiveCount()
+    {
+        return delegate.getBloomFilterTruePositiveCount();
+    }
+
+    @Override
+    public long getRecentBloomFilterTruePositiveCount()
+    {
+        return delegate.getRecentBloomFilterTruePositiveCount();
+    }
+
+    @Override
+    public InstrumentingCache<KeyCacheKey, RowIndexEntry> getKeyCache()
+    {
+        return delegate.getKeyCache();
+    }
+
+    @Override
+    public EstimatedHistogram getEstimatedPartitionSize()
+    {
+        return delegate.getEstimatedPartitionSize();
+    }
+
+    @Override
+    public EstimatedHistogram getEstimatedColumnCount()
+    {
+        return delegate.getEstimatedColumnCount();
+    }
+
+    @Override
+    public double getEstimatedDroppableTombstoneRatio(int gcBefore)
+    {
+        return delegate.getEstimatedDroppableTombstoneRatio(gcBefore);
+    }
+
+    @Override
+    public double getDroppableTombstonesBefore(int gcBefore)
+    {
+        return delegate.getDroppableTombstonesBefore(gcBefore);
+    }
+
+    @Override
+    public double getCompressionRatio()
+    {
+        return delegate.getCompressionRatio();
+    }
+
+    @Override
+    public long getMinTimestamp()
+    {
+        return delegate.getMinTimestamp();
+    }
+
+    @Override
+    public long getMaxTimestamp()
+    {
+        return delegate.getMaxTimestamp();
+    }
+
+    @Override
+    public int getMinLocalDeletionTime()
+    {
+        return delegate.getMinLocalDeletionTime();
+    }
+
+    @Override
+    public int getMaxLocalDeletionTime()
+    {
+        return delegate.getMaxLocalDeletionTime();
+    }
+
+    @Override
+    public boolean hasTombstones()
+    {
+        return delegate.hasTombstones();
+    }
+
+    @Override
+    public int getMinTTL()
+    {
+        return delegate.getMinTTL();
+    }
+
+    @Override
+    public int getMaxTTL()
+    {
+        return delegate.getMaxTTL();
+    }
+
+    @Override
+    public long getTotalColumnsSet()
+    {
+        return delegate.getTotalColumnsSet();
+    }
+
+    @Override
+    public long getTotalRows()
+    {
+        return delegate.getTotalRows();
+    }
+
+    @Override
+    public int getAvgColumnSetPerRow()
+    {
+        return delegate.getAvgColumnSetPerRow();
+    }
+
+    @Override
+    public int getSSTableLevel()
+    {
+        return delegate.getSSTableLevel();
+    }
+
+    @Override
+    public void reloadSSTableMetadata() throws IOException
+    {
+        delegate.reloadSSTableMetadata();
+    }
+
+    @Override
+    public StatsMetadata getSSTableMetadata()
+    {
+        return delegate.getSSTableMetadata();
+    }
+
+    @Override
+    public RandomAccessReader openDataReader(RateLimiter limiter)
+    {
+        return delegate.openDataReader(limiter);
+    }
+
+    @Override
+    public RandomAccessReader openDataReader()
+    {
+        return delegate.openDataReader();
+    }
+
+    @Override
+    public RandomAccessReader openIndexReader()
+    {
+        return delegate.openIndexReader();
+    }
+
+    @Override
+    public ChannelProxy getDataChannel()
+    {
+        return delegate.getDataChannel();
+    }
+
+    @Override
+    public ChannelProxy getIndexChannel()
+    {
+        return delegate.getIndexChannel();
+    }
+
+    @Override
+    public long getCreationTimeFor(Component component)
+    {
+        return delegate.getCreationTimeFor(component);
+    }
+
+    @Override
+    public long getKeyCacheHit()
+    {
+        return delegate.getKeyCacheHit();
+    }
+
+    @Override
+    public long getKeyCacheRequest()
+    {
+        return delegate.getKeyCacheRequest();
+    }
+
+    @Override
+    public void incrementReadCount()
+    {
+        delegate.incrementReadCount();
+    }
+
+    @Override
+    public boolean mayOverlapsWith(SSTableReader other)
+    {
+        return delegate.mayOverlapsWith(other);
+    }
+
+    @Override
+    public Ref<SSTableReader> tryRef()
+    {
+        return delegate.tryRef();
+    }
+
+    @Override
+    public Ref<SSTableReader> selfRef()
+    {
+        return delegate.selfRef();
+    }
+
+    @Override
+    public Ref<SSTableReader> ref()
+    {
+        return delegate.ref();
+    }
+
+    @Override
+    void setup(boolean trackHotness)
+    {
+        delegate.setup(trackHotness);
+    }
+
+    @Override
+    public void overrideReadMeter(RestorableMeter readMeter)
+    {
+        delegate.overrideReadMeter(readMeter);
+    }
+
+    @Override
+    public void addTo(Ref.IdentityCollection identities)
+    {
+        delegate.addTo(identities);
+    }
+
+    @Override
+    public IPartitioner getPartitioner()
+    {
+        return delegate.getPartitioner();
+    }
+
+    @Override
+    public DecoratedKey decorateKey(ByteBuffer key)
+    {
+        return delegate.decorateKey(key);
+    }
+
+    @Override
+    public String getIndexFilename()
+    {
+        return delegate.getIndexFilename();
+    }
+
+    @Override
+    public String getColumnFamilyName()
+    {
+        return delegate.getColumnFamilyName();
+    }
+
+    @Override
+    public String getKeyspaceName()
+    {
+        return delegate.getKeyspaceName();
+    }
+
+    @Override
+    public List<String> getAllFilePaths()
+    {
+        return delegate.getAllFilePaths();
+    }
+
+    @Override
+    protected long estimateRowsFromIndex(RandomAccessReader ifile) throws 
IOException
+    {
+        try
+        {
+            return (Long) ESTIMATE_ROWS_FROM_INDEX.invoke(delegate, ifile);
+        }
+        catch (IllegalAccessException e)
+        {
+            throw new AssertionError(e);
+        }
+        catch (InvocationTargetException e)
+        {
+            Throwable cause = e.getCause();
+            if (cause instanceof IOException)
+                throw (IOException) cause;
+            if (cause instanceof Error)
+                throw (Error) cause;
+            if (cause instanceof RuntimeException)
+                throw (RuntimeException) cause;
+            throw new RuntimeException(cause);
+        }
+    }
+
+    @Override
+    public long bytesOnDisk()
+    {
+        return delegate.bytesOnDisk();
+    }
+
+    @Override
+    public String toString()
+    {
+        return delegate.toString();
+    }
+
+    @Override
+    public synchronized void addComponents(Collection<Component> newComponents)
+    {
+        delegate.addComponents(newComponents);
+    }
+}
\ No newline at end of file
diff --git a/test/unit/org/apache/cassandra/db/DirectoriesTest.java 
b/test/unit/org/apache/cassandra/db/DirectoriesTest.java
index 5ef001a..5cdfa0f 100644
--- a/test/unit/org/apache/cassandra/db/DirectoriesTest.java
+++ b/test/unit/org/apache/cassandra/db/DirectoriesTest.java
@@ -48,6 +48,7 @@ import org.apache.cassandra.io.util.FileUtils;
 import org.apache.cassandra.schema.IndexMetadata;
 import org.apache.cassandra.service.DefaultFSErrorHandler;
 import org.apache.cassandra.utils.ByteBufferUtil;
+import org.apache.cassandra.utils.JVMStabilityInspector;
 import org.apache.cassandra.utils.Pair;
 
 import static org.junit.Assert.assertEquals;
@@ -332,7 +333,7 @@ public class DirectoriesTest
             {
                 String[] path = new String[] {KS, "bad"};
                 File dir = new File(Directories.dataDirectories[0].location, 
StringUtils.join(path, File.separator));
-                FileUtils.handleFSError(new FSWriteError(new 
IOException("Unable to create directory " + dir), dir));
+                JVMStabilityInspector.inspectThrowable(new FSWriteError(new 
IOException("Unable to create directory " + dir), dir));
             }
 
             for (DataDirectory dd : Directories.dataDirectories)
diff --git 
a/test/unit/org/apache/cassandra/utils/JVMStabilityInspectorTest.java 
b/test/unit/org/apache/cassandra/utils/JVMStabilityInspectorTest.java
index 00447da..ecb2955 100644
--- a/test/unit/org/apache/cassandra/utils/JVMStabilityInspectorTest.java
+++ b/test/unit/org/apache/cassandra/utils/JVMStabilityInspectorTest.java
@@ -26,6 +26,8 @@ import org.junit.Test;
 import org.apache.cassandra.config.Config;
 import org.apache.cassandra.config.DatabaseDescriptor;
 import org.apache.cassandra.io.FSReadError;
+import org.apache.cassandra.io.FSWriteError;
+import org.apache.cassandra.io.sstable.CorruptSSTableException;
 
 import static java.util.Arrays.asList;
 import static org.junit.Assert.assertFalse;
@@ -53,6 +55,18 @@ public class JVMStabilityInspectorTest
             JVMStabilityInspector.inspectThrowable(new FSReadError(new 
IOException(), "blah"));
             assertTrue(killerForTests.wasKilled());
 
+            killerForTests.reset();
+            JVMStabilityInspector.inspectThrowable(new FSWriteError(new 
IOException(), "blah"));
+            assertTrue(killerForTests.wasKilled());
+
+            killerForTests.reset();
+            JVMStabilityInspector.inspectThrowable(new 
CorruptSSTableException(new IOException(), "blah"));
+            assertTrue(killerForTests.wasKilled());
+
+            killerForTests.reset();
+            JVMStabilityInspector.inspectThrowable(new RuntimeException(new 
CorruptSSTableException(new IOException(), "blah")));
+            assertTrue(killerForTests.wasKilled());
+
             
DatabaseDescriptor.setCommitFailurePolicy(Config.CommitFailurePolicy.die);
             killerForTests.reset();
             JVMStabilityInspector.inspectCommitLogThrowable(new Throwable());


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

Reply via email to