You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by dc...@apache.org on 2020/09/16 20:42:51 UTC

[cassandra] branch trunk updated: NPE thrown while updating speculative execution time if keyspace is removed during task execution

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

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


The following commit(s) were added to refs/heads/trunk by this push:
     new f670db4  NPE thrown while updating speculative execution time if keyspace is removed during task execution
f670db4 is described below

commit f670db4a0ec6c2d76b52fa1510f585c49b4f731e
Author: Caleb Rackliffe <ca...@gmail.com>
AuthorDate: Wed Sep 16 09:52:42 2020 -0700

    NPE thrown while updating speculative execution time if keyspace is removed during task execution
    
    patch by Caleb Rackliffe; reviewed by Aleksey Yeschenko, David Capwell for CASSANDRA-15949
---
 CHANGES.txt                                        |  1 +
 src/java/org/apache/cassandra/db/Keyspace.java     | 76 ++++++++++-------
 src/java/org/apache/cassandra/db/ReadCommand.java  |  6 +-
 src/java/org/apache/cassandra/schema/Schema.java   | 11 ++-
 ...leMetadataProvider.java => SchemaProvider.java} | 27 ++++++-
 .../apache/cassandra/service/CassandraDaemon.java  | 25 ++++--
 .../unit/org/apache/cassandra/db/KeyspaceTest.java | 22 ++++-
 .../net/MessageSerializationPropertyTest.java      |  6 +-
 .../cassandra/service/OptionalTasksTest.java       | 94 ++++++++++++++++++++++
 9 files changed, 220 insertions(+), 48 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 7761d87..90f5995 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -10,6 +10,7 @@
  * When compaction gets interrupted, the exception should include the compactionId (CASSANDRA-15954)
  * Make Table/Keyspace Metric Names Consistent With Each Other (CASSANDRA-15909)
  * Mutating sstable component may race with entire-sstable-streaming(ZCS) causing checksum validation failure (CASSANDRA-15861)
+ * NPE thrown while updating speculative execution time if keyspace is removed during task execution (CASSANDRA-15949)
 Merged from 3.11:
  * Use IF NOT EXISTS for index and UDT create statements in snapshot schema files (CASSANDRA-13935)
  * Make sure LCS handles duplicate sstable added/removed notifications correctly (CASSANDRA-14103)
diff --git a/src/java/org/apache/cassandra/db/Keyspace.java b/src/java/org/apache/cassandra/db/Keyspace.java
index fc4f56f..1b05a36 100644
--- a/src/java/org/apache/cassandra/db/Keyspace.java
+++ b/src/java/org/apache/cassandra/db/Keyspace.java
@@ -19,18 +19,29 @@ package org.apache.cassandra.db;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.*;
-import java.util.concurrent.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Future;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.locks.Lock;
+import java.util.stream.Stream;
 
-import com.google.common.base.Function;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Iterables;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.cassandra.concurrent.Stage;
-import org.apache.cassandra.config.*;
+import org.apache.cassandra.config.DatabaseDescriptor;
 import org.apache.cassandra.db.compaction.CompactionManager;
 import org.apache.cassandra.db.lifecycle.SSTableSet;
 import org.apache.cassandra.db.partitions.PartitionUpdate;
@@ -48,14 +59,18 @@ import org.apache.cassandra.schema.KeyspaceMetadata;
 import org.apache.cassandra.schema.ReplicationParams;
 import org.apache.cassandra.schema.Schema;
 import org.apache.cassandra.schema.SchemaConstants;
+import org.apache.cassandra.schema.SchemaProvider;
 import org.apache.cassandra.schema.TableId;
 import org.apache.cassandra.schema.TableMetadata;
 import org.apache.cassandra.schema.TableMetadataRef;
 import org.apache.cassandra.tracing.Tracing;
-import org.apache.cassandra.utils.*;
+import org.apache.cassandra.utils.ByteBufferUtil;
+import org.apache.cassandra.utils.FBUtilities;
+import org.apache.cassandra.utils.JVMStabilityInspector;
 import org.apache.cassandra.utils.concurrent.OpOrder;
 
-import static java.util.concurrent.TimeUnit.*;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
 import static org.apache.cassandra.utils.MonotonicClock.approxTime;
 
 /**
@@ -93,14 +108,7 @@ public class Keyspace
     private final KeyspaceWriteHandler writeHandler;
     private volatile ReplicationParams replicationParams;
     private final KeyspaceRepairManager repairManager;
-
-    public static final Function<String,Keyspace> keyspaceTransformer = new Function<String, Keyspace>()
-    {
-        public Keyspace apply(String keyspaceName)
-        {
-            return Keyspace.open(keyspaceName);
-        }
-    };
+    private final SchemaProvider schema;
 
     private static volatile boolean initialized = false;
 
@@ -121,21 +129,22 @@ public class Keyspace
         return open(keyspaceName, Schema.instance, false);
     }
 
-    private static Keyspace open(String keyspaceName, Schema schema, boolean loadSSTables)
+    @VisibleForTesting
+    static Keyspace open(String keyspaceName, SchemaProvider schema, boolean loadSSTables)
     {
         Keyspace keyspaceInstance = schema.getKeyspaceInstance(keyspaceName);
 
         if (keyspaceInstance == null)
         {
-            // instantiate the Keyspace.  we could use putIfAbsent but it's important to making sure it is only done once
-            // per keyspace, so we synchronize and re-check before doing it.
-            synchronized (Keyspace.class)
+            // Instantiate the Keyspace while holding the Schema lock. This both ensures we only do it once per
+            // keyspace, and also ensures that Keyspace construction sees a consistent view of the schema.
+            synchronized (schema)
             {
                 keyspaceInstance = schema.getKeyspaceInstance(keyspaceName);
                 if (keyspaceInstance == null)
                 {
                     // open and store the keyspace
-                    keyspaceInstance = new Keyspace(keyspaceName, loadSSTables);
+                    keyspaceInstance = new Keyspace(keyspaceName, schema, loadSSTables);
                     schema.storeKeyspaceInstance(keyspaceInstance);
                 }
             }
@@ -150,7 +159,7 @@ public class Keyspace
 
     public static Keyspace clear(String keyspaceName, Schema schema)
     {
-        synchronized (Keyspace.class)
+        synchronized (schema)
         {
             Keyspace t = schema.removeKeyspaceInstance(keyspaceName);
             if (t != null)
@@ -207,7 +216,7 @@ public class Keyspace
 
     public ColumnFamilyStore getColumnFamilyStore(String cfName)
     {
-        TableMetadata table = Schema.instance.getTableMetadata(getName(), cfName);
+        TableMetadata table = schema.getTableMetadata(getName(), cfName);
         if (table == null)
             throw new IllegalArgumentException(String.format("Unknown keyspace/cf pair (%s.%s)", getName(), cfName));
         return getColumnFamilyStore(table.id);
@@ -324,10 +333,12 @@ public class Keyspace
         return list;
     }
 
-    private Keyspace(String keyspaceName, boolean loadSSTables)
+    private Keyspace(String keyspaceName, SchemaProvider schema, boolean loadSSTables)
     {
-        metadata = Schema.instance.getKeyspaceMetadata(keyspaceName);
+        this.schema = schema;
+        metadata = schema.getKeyspaceMetadata(keyspaceName);
         assert metadata != null : "Unknown keyspace " + keyspaceName;
+        
         if (metadata.isVirtual())
             throw new IllegalStateException("Cannot initialize Keyspace with virtual metadata " + keyspaceName);
         createReplicationStrategy(metadata);
@@ -337,7 +348,7 @@ public class Keyspace
         for (TableMetadata cfm : metadata.tablesAndViews())
         {
             logger.trace("Initializing {}.{}", getName(), cfm.name);
-            initCf(Schema.instance.getTableMetadataRef(cfm.id), loadSSTables);
+            initCf(schema.getTableMetadataRef(cfm.id), loadSSTables);
         }
         this.viewManager.reload(false);
 
@@ -347,6 +358,7 @@ public class Keyspace
 
     private Keyspace(KeyspaceMetadata metadata)
     {
+        this.schema = Schema.instance;
         this.metadata = metadata;
         createReplicationStrategy(metadata);
         this.metric = new KeyspaceMetrics(this);
@@ -743,22 +755,30 @@ public class Keyspace
 
     public static Iterable<Keyspace> all()
     {
-        return Iterables.transform(Schema.instance.getKeyspaces(), keyspaceTransformer);
+        return Iterables.transform(Schema.instance.getKeyspaces(), Keyspace::open);
+    }
+
+    /**
+     * @return a {@link Stream} of all existing/open {@link Keyspace} instances
+     */
+    public static Stream<Keyspace> allExisting()
+    {
+        return Schema.instance.getKeyspaces().stream().map(Schema.instance::getKeyspaceInstance).filter(Objects::nonNull);
     }
 
     public static Iterable<Keyspace> nonSystem()
     {
-        return Iterables.transform(Schema.instance.getNonSystemKeyspaces(), keyspaceTransformer);
+        return Iterables.transform(Schema.instance.getNonSystemKeyspaces(), Keyspace::open);
     }
 
     public static Iterable<Keyspace> nonLocalStrategy()
     {
-        return Iterables.transform(Schema.instance.getNonLocalStrategyKeyspaces(), keyspaceTransformer);
+        return Iterables.transform(Schema.instance.getNonLocalStrategyKeyspaces(), Keyspace::open);
     }
 
     public static Iterable<Keyspace> system()
     {
-        return Iterables.transform(SchemaConstants.LOCAL_SYSTEM_KEYSPACE_NAMES, keyspaceTransformer);
+        return Iterables.transform(SchemaConstants.LOCAL_SYSTEM_KEYSPACE_NAMES, Keyspace::open);
     }
 
     @Override
diff --git a/src/java/org/apache/cassandra/db/ReadCommand.java b/src/java/org/apache/cassandra/db/ReadCommand.java
index 6b19ed9..884dbe0 100644
--- a/src/java/org/apache/cassandra/db/ReadCommand.java
+++ b/src/java/org/apache/cassandra/db/ReadCommand.java
@@ -61,7 +61,7 @@ import org.apache.cassandra.schema.Schema;
 import org.apache.cassandra.schema.SchemaConstants;
 import org.apache.cassandra.schema.TableId;
 import org.apache.cassandra.schema.TableMetadata;
-import org.apache.cassandra.schema.TableMetadataProvider;
+import org.apache.cassandra.schema.SchemaProvider;
 import org.apache.cassandra.service.ActiveRepairService;
 import org.apache.cassandra.service.ClientWarn;
 import org.apache.cassandra.tracing.Tracing;
@@ -909,7 +909,7 @@ public abstract class ReadCommand extends AbstractReadQuery
     @VisibleForTesting
     public static class Serializer implements IVersionedSerializer<ReadCommand>
     {
-        private final TableMetadataProvider schema;
+        private final SchemaProvider schema;
 
         public Serializer()
         {
@@ -917,7 +917,7 @@ public abstract class ReadCommand extends AbstractReadQuery
         }
 
         @VisibleForTesting
-        public Serializer(TableMetadataProvider schema)
+        public Serializer(SchemaProvider schema)
         {
             this.schema = Objects.requireNonNull(schema, "schema");
         }
diff --git a/src/java/org/apache/cassandra/schema/Schema.java b/src/java/org/apache/cassandra/schema/Schema.java
index 0498993..4b70f67 100644
--- a/src/java/org/apache/cassandra/schema/Schema.java
+++ b/src/java/org/apache/cassandra/schema/Schema.java
@@ -20,7 +20,6 @@ package org.apache.cassandra.schema;
 import java.util.*;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.stream.Collectors;
-import javax.annotation.Nullable;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.MapDifference;
@@ -36,7 +35,6 @@ import org.apache.cassandra.db.marshal.UserType;
 import org.apache.cassandra.db.virtual.VirtualKeyspaceRegistry;
 import org.apache.cassandra.exceptions.ConfigurationException;
 import org.apache.cassandra.exceptions.InvalidRequestException;
-import org.apache.cassandra.exceptions.UnknownTableException;
 import org.apache.cassandra.gms.ApplicationState;
 import org.apache.cassandra.gms.Gossiper;
 import org.apache.cassandra.io.sstable.Descriptor;
@@ -51,7 +49,7 @@ import static java.lang.String.format;
 
 import static com.google.common.collect.Iterables.size;
 
-public final class Schema implements TableMetadataProvider
+public final class Schema implements SchemaProvider
 {
     public static final Schema instance = new Schema();
 
@@ -192,6 +190,7 @@ public final class Schema implements TableMetadataProvider
      *
      * @return Keyspace object or null if keyspace was not found
      */
+    @Override
     public Keyspace getKeyspaceInstance(String keyspaceName)
     {
         return keyspaceInstances.get(keyspaceName);
@@ -219,6 +218,7 @@ public final class Schema implements TableMetadataProvider
      *
      * @throws IllegalArgumentException if Keyspace is already stored
      */
+    @Override
     public void storeKeyspaceInstance(Keyspace keyspace)
     {
         if (keyspaceInstances.containsKey(keyspace.getName()))
@@ -283,6 +283,7 @@ public final class Schema implements TableMetadataProvider
      *
      * @return The keyspace metadata or null if it wasn't found
      */
+    @Override
     public KeyspaceMetadata getKeyspaceMetadata(String keyspaceName)
     {
         assert keyspaceName != null;
@@ -356,6 +357,7 @@ public final class Schema implements TableMetadataProvider
      *
      * @return TableMetadataRef object or null if it wasn't found
      */
+    @Override
     public TableMetadataRef getTableMetadataRef(String keyspace, String table)
     {
         TableMetadata tm = getTableMetadata(keyspace, table);
@@ -381,11 +383,13 @@ public final class Schema implements TableMetadataProvider
      *
      * @return metadata about Table or View
      */
+    @Override
     public TableMetadataRef getTableMetadataRef(TableId id)
     {
         return metadataRefs.get(id);
     }
 
+    @Override
     public TableMetadataRef getTableMetadataRef(Descriptor descriptor)
     {
         return getTableMetadataRef(descriptor.ksname, descriptor.cfname);
@@ -418,7 +422,6 @@ public final class Schema implements TableMetadataProvider
     }
 
     @Override
-    @Nullable
     public TableMetadata getTableMetadata(TableId id)
     {
         TableMetadata table = keyspaces.getTableOrViewNullable(id);
diff --git a/src/java/org/apache/cassandra/schema/TableMetadataProvider.java b/src/java/org/apache/cassandra/schema/SchemaProvider.java
similarity index 50%
rename from src/java/org/apache/cassandra/schema/TableMetadataProvider.java
rename to src/java/org/apache/cassandra/schema/SchemaProvider.java
index 7c5ae8a..51c4e6b 100644
--- a/src/java/org/apache/cassandra/schema/TableMetadataProvider.java
+++ b/src/java/org/apache/cassandra/schema/SchemaProvider.java
@@ -2,13 +2,26 @@ package org.apache.cassandra.schema;
 
 import javax.annotation.Nullable;
 
+import org.apache.cassandra.db.Keyspace;
 import org.apache.cassandra.exceptions.UnknownTableException;
+import org.apache.cassandra.io.sstable.Descriptor;
 
-public interface TableMetadataProvider
+public interface SchemaProvider
 {
     @Nullable
+    Keyspace getKeyspaceInstance(String keyspaceName);
+
+    void storeKeyspaceInstance(Keyspace keyspace);
+
+    @Nullable
+    KeyspaceMetadata getKeyspaceMetadata(String keyspaceName);
+
+    @Nullable
     TableMetadata getTableMetadata(TableId id);
 
+    @Nullable
+    TableMetadata getTableMetadata(String keyspace, String table);
+
     default TableMetadata getExistingTableMetadata(TableId id) throws UnknownTableException
     {
         TableMetadata metadata = getTableMetadata(id);
@@ -21,4 +34,16 @@ public interface TableMetadataProvider
                           id);
         throw new UnknownTableException(message, id);
     }
+
+    @Nullable
+    TableMetadataRef getTableMetadataRef(String keyspace, String table);
+
+    @Nullable
+    TableMetadataRef getTableMetadataRef(TableId id);
+
+    @Nullable
+    default TableMetadataRef getTableMetadataRef(Descriptor descriptor)
+    {
+        return getTableMetadataRef(descriptor.ksname, descriptor.cfname);
+    }
 }
diff --git a/src/java/org/apache/cassandra/service/CassandraDaemon.java b/src/java/org/apache/cassandra/service/CassandraDaemon.java
index e1a254a..4b92d69 100644
--- a/src/java/org/apache/cassandra/service/CassandraDaemon.java
+++ b/src/java/org/apache/cassandra/service/CassandraDaemon.java
@@ -167,6 +167,21 @@ public class CassandraDaemon
         }
     }
 
+    @VisibleForTesting
+    public static Runnable SPECULATION_THRESHOLD_UPDATER = 
+        () -> 
+        {
+            try
+            {
+                Keyspace.allExisting().forEach(k -> k.getColumnFamilyStores().forEach(ColumnFamilyStore::updateSpeculationThreshold));
+            }
+            catch (Throwable t)
+            {
+                logger.warn("Failed to update speculative retry thresholds.", t);
+                JVMStabilityInspector.inspectThrowable(t);
+            }
+        };
+    
     static final CassandraDaemon instance = new CassandraDaemon();
 
     private NativeTransportService nativeTransportService;
@@ -431,12 +446,10 @@ public class CassandraDaemon
         ScheduledExecutors.optionalTasks.scheduleWithFixedDelay(ColumnFamilyStore.getBackgroundCompactionTaskSubmitter(), 5, 1, TimeUnit.MINUTES);
 
         // schedule periodic recomputation of speculative retry thresholds
-        ScheduledExecutors.optionalTasks.scheduleWithFixedDelay(
-            () -> Keyspace.all().forEach(k -> k.getColumnFamilyStores().forEach(ColumnFamilyStore::updateSpeculationThreshold)),
-            DatabaseDescriptor.getReadRpcTimeout(NANOSECONDS),
-            DatabaseDescriptor.getReadRpcTimeout(NANOSECONDS),
-            NANOSECONDS
-        );
+        ScheduledExecutors.optionalTasks.scheduleWithFixedDelay(SPECULATION_THRESHOLD_UPDATER, 
+                                                                DatabaseDescriptor.getReadRpcTimeout(NANOSECONDS),
+                                                                DatabaseDescriptor.getReadRpcTimeout(NANOSECONDS),
+                                                                NANOSECONDS);
 
         initializeNativeTransport();
 
diff --git a/test/unit/org/apache/cassandra/db/KeyspaceTest.java b/test/unit/org/apache/cassandra/db/KeyspaceTest.java
index c549b85..fd15366 100644
--- a/test/unit/org/apache/cassandra/db/KeyspaceTest.java
+++ b/test/unit/org/apache/cassandra/db/KeyspaceTest.java
@@ -1,4 +1,4 @@
-/**
+/*
  * 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
@@ -21,6 +21,10 @@ package org.apache.cassandra.db;
 import java.nio.ByteBuffer;
 import java.util.*;
 
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+import org.mockito.Mockito;
+
 import org.apache.cassandra.Util;
 import org.apache.cassandra.cql3.CQLTester;
 import org.apache.cassandra.cql3.ColumnIdentifier;
@@ -33,13 +37,12 @@ import org.apache.cassandra.db.filter.*;
 import org.apache.cassandra.db.partitions.PartitionIterator;
 import org.apache.cassandra.io.sstable.format.SSTableReader;
 import org.apache.cassandra.metrics.ClearableHistogram;
+import org.apache.cassandra.schema.SchemaProvider;
 import org.apache.cassandra.utils.ByteBufferUtil;
 import org.apache.cassandra.utils.FBUtilities;
-import org.junit.Test;
 
 import static org.junit.Assert.*;
 
-
 public class KeyspaceTest extends CQLTester
 {
     // Test needs synchronous table drop to avoid flushes causing flaky failures of testLimitSSTables
@@ -492,4 +495,17 @@ public class KeyspaceTest extends CQLTester
                 cfs.metadata(), FBUtilities.nowInSeconds(), ColumnFilter.all(cfs.metadata()), RowFilter.NONE, DataLimits.cqlLimits(3), Util.dk("0"), filter);
         assertRowsInResult(cfs, command);
     }
+
+    @Test
+    public void shouldThrowOnMissingKeyspace()
+    {
+        SchemaProvider schema = Mockito.mock(SchemaProvider.class);
+        String ksName = "MissingKeyspace";
+        
+        Mockito.when(schema.getKeyspaceMetadata(ksName)).thenReturn(null);
+
+        Assertions.assertThatThrownBy(() -> Keyspace.open(ksName, schema, false))
+                  .isInstanceOf(AssertionError.class)
+                  .hasMessage("Unknown keyspace " + ksName);
+    }
 }
diff --git a/test/unit/org/apache/cassandra/net/MessageSerializationPropertyTest.java b/test/unit/org/apache/cassandra/net/MessageSerializationPropertyTest.java
index 7750f15..3cde6f9 100644
--- a/test/unit/org/apache/cassandra/net/MessageSerializationPropertyTest.java
+++ b/test/unit/org/apache/cassandra/net/MessageSerializationPropertyTest.java
@@ -32,7 +32,7 @@ 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.schema.TableMetadata;
-import org.apache.cassandra.schema.TableMetadataProvider;
+import org.apache.cassandra.schema.SchemaProvider;
 import org.apache.cassandra.utils.ByteBufferUtil;
 import org.apache.cassandra.utils.CassandraGenerators;
 import org.apache.cassandra.utils.FBUtilities;
@@ -88,7 +88,7 @@ public class MessageSerializationPropertyTest implements Serializable
     @Test
     public void testMessageSerialization() throws Exception
     {
-        TableMetadataProvider schema = Mockito.mock(TableMetadataProvider.class, Mockito.CALLS_REAL_METHODS);
+        SchemaProvider schema = Mockito.mock(SchemaProvider.class, Mockito.CALLS_REAL_METHODS);
         ReadCommand.Serializer readCommandSerializer = new ReadCommand.Serializer(schema);
         Supplier<? extends IVersionedAsymmetricSerializer<?, ?>> original = Verb.READ_REQ.unsafeSetSerializer(() -> readCommandSerializer);
         try (DataOutputBuffer first = new DataOutputBuffer(1024);
@@ -127,7 +127,7 @@ public class MessageSerializationPropertyTest implements Serializable
         }
     }
 
-    private static void withTable(TableMetadataProvider schema, Message<?> message, Consumer<TableMetadata> fn)
+    private static void withTable(SchemaProvider schema, Message<?> message, Consumer<TableMetadata> fn)
     {
         TableMetadata metadata = null;
         if (message.payload instanceof ReadQuery)
diff --git a/test/unit/org/apache/cassandra/service/OptionalTasksTest.java b/test/unit/org/apache/cassandra/service/OptionalTasksTest.java
new file mode 100644
index 0000000..5d141a0
--- /dev/null
+++ b/test/unit/org/apache/cassandra/service/OptionalTasksTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.service;
+
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.apache.cassandra.SchemaLoader;
+import org.apache.cassandra.db.ColumnFamilyStore;
+import org.apache.cassandra.db.Keyspace;
+import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.schema.KeyspaceParams;
+import org.apache.cassandra.schema.Schema;
+import org.apache.cassandra.schema.TableMetadata;
+
+import static org.apache.cassandra.SchemaLoader.standardCFMD;
+import static org.apache.cassandra.service.CassandraDaemon.SPECULATION_THRESHOLD_UPDATER;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+public class OptionalTasksTest
+{
+    private static final String KEYSPACE = "OpitonalTasksTest";
+    private static final String TABLE = "SpeculationThreshold";
+
+    @BeforeClass
+    public static void defineSchema() throws ConfigurationException
+    {
+        SchemaLoader.prepareServer();
+        SchemaLoader.createKeyspace(KEYSPACE, KeyspaceParams.simple(1), standardCFMD(KEYSPACE, TABLE));
+    }
+    
+    @Test
+    public void shouldIgnoreDroppedKeyspace()
+    {
+        // Set the initial sampling state...
+        TableMetadata metadata = Schema.instance.getTableMetadata(KEYSPACE, TABLE);
+        ColumnFamilyStore cfs = Schema.instance.getColumnFamilyStoreInstance(Objects.requireNonNull(metadata).id);
+        Objects.requireNonNull(cfs).metric.coordinatorReadLatency.update(100, TimeUnit.NANOSECONDS);
+        
+        // Remove the Keyspace name to make it invisible to the updater...
+        Keyspace removed = Schema.instance.removeKeyspaceInstance(KEYSPACE);
+
+        try
+        {
+            long originalValue = cfs.sampleReadLatencyNanos;
+
+            // ...and ensure that the speculation threshold updater doesn't run.
+            SPECULATION_THRESHOLD_UPDATER.run();
+
+            assertEquals(originalValue, cfs.sampleReadLatencyNanos);
+        }
+        finally
+        {
+            // Restore the removed Keyspace to put things back the way we found them.
+            Schema.instance.storeKeyspaceInstance(removed);
+        }
+    }
+
+    @Test
+    public void shouldUpdateSpeculationThreshold()
+    {
+        // Set the initial sampling state...
+        TableMetadata metadata = Schema.instance.getTableMetadata(KEYSPACE, TABLE);
+        ColumnFamilyStore cfs = Schema.instance.getColumnFamilyStoreInstance(Objects.requireNonNull(metadata).id);
+        Objects.requireNonNull(cfs).metric.coordinatorReadLatency.update(100, TimeUnit.NANOSECONDS);
+
+        long originalValue = cfs.sampleReadLatencyNanos;
+        
+        // ...and ensure that the speculation threshold updater runs.
+        SPECULATION_THRESHOLD_UPDATER.run();
+        
+        assertNotEquals(originalValue, cfs.sampleReadLatencyNanos);
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org