Merge branch 'cassandra-3.0' into cassandra-3.11

This commit is contained in:
Jon Meredith 2019-08-15 14:21:23 -06:00
commit 3df63ed054
41 changed files with 861 additions and 274 deletions

View File

@ -37,6 +37,7 @@ Merged from 2.2:
* Refactor Circle CI configuration (CASSANDRA-14806) * Refactor Circle CI configuration (CASSANDRA-14806)
* Fixing invalid CQL in security documentation (CASSANDRA-15020) * Fixing invalid CQL in security documentation (CASSANDRA-15020)
* Multi-version in-JVM dtests (CASSANDRA-14937) * Multi-version in-JVM dtests (CASSANDRA-14937)
* Allow instance class loaders to be garbage collected for inJVM dtest (CASSANDRA-15170)
3.11.4 3.11.4

View File

@ -1852,10 +1852,24 @@
</jar> </jar>
</target> </target>
<target name="test-jvm-dtest" depends="build-test" description="Execute unit tests"> <target name="test-jvm-dtest" depends="build-test" description="Execute in-jvm dtests">
<testmacro inputdir="${test.distributed.src}" timeout="${test.distributed.timeout}" forkmode="once" showoutput="true" filter="**/test/*Test.java"> <testmacro inputdir="${test.distributed.src}" timeout="${test.distributed.timeout}" forkmode="once" showoutput="true" filter="**/test/*Test.java">
<jvmarg value="-Dlogback.configurationFile=test/conf/logback-dtest.xml"/> <jvmarg value="-Dlogback.configurationFile=test/conf/logback-dtest.xml"/>
<jvmarg value="-Dcassandra.ring_delay_ms=1000"/> <jvmarg value="-Dcassandra.ring_delay_ms=10000"/>
<jvmarg value="-Dcassandra.tolerate_sstable_size=true"/>
<jvmarg value="-Djava.io.tmpdir=${tmp.dir}"/>
<jvmarg value="-Dcassandra.skip_sync=true" />
</testmacro>
</target>
<!-- Use this with an FQDN for test class, and a csv list of methods like this:
ant test-jvm-dtest-some -Dtest.name=org.apache.cassandra.distributed.test.ResourceLeakTest -Dtest.methods=looperTest
-->
<target name="test-jvm-dtest-some" depends="build-test" description="Execute some in-jvm dtests">
<testmacro inputdir="${test.distributed.src}" timeout="${test.distributed.timeout}" forkmode="once" showoutput="true">
<test name="${test.name}" methods="${test.methods}" outfile="build/test/output/TEST-${test.name}-${test.methods}"/>
<jvmarg value="-Dlogback.configurationFile=test/conf/logback-dtest.xml"/>
<jvmarg value="-Dcassandra.ring_delay_ms=10000"/>
<jvmarg value="-Dcassandra.tolerate_sstable_size=true"/> <jvmarg value="-Dcassandra.tolerate_sstable_size=true"/>
<jvmarg value="-Djava.io.tmpdir=${tmp.dir}"/> <jvmarg value="-Djava.io.tmpdir=${tmp.dir}"/>
<jvmarg value="-Dcassandra.skip_sync=true" /> <jvmarg value="-Dcassandra.skip_sync=true" />

View File

@ -35,6 +35,7 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ArrayListMultimap;
@ -71,6 +72,7 @@ import org.apache.cassandra.net.MessageOut;
import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.WriteResponseHandler; import org.apache.cassandra.service.WriteResponseHandler;
import org.apache.cassandra.utils.ExecutorUtils;
import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.MBeanWrapper; import org.apache.cassandra.utils.MBeanWrapper;
import org.apache.cassandra.utils.UUIDGen; import org.apache.cassandra.utils.UUIDGen;
@ -112,10 +114,9 @@ public class BatchlogManager implements BatchlogManagerMBean
TimeUnit.MILLISECONDS); TimeUnit.MILLISECONDS);
} }
public void shutdown() throws InterruptedException public void shutdownAndWait(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
{ {
batchlogTasks.shutdown(); ExecutorUtils.shutdownAndWait(timeout, unit, batchlogTasks);
batchlogTasks.awaitTermination(60, TimeUnit.SECONDS);
} }
public static void remove(UUID id) public static void remove(UUID id)

View File

@ -70,7 +70,7 @@ public class InfiniteLoopExecutor
return this; return this;
} }
public void shutdown() public void shutdownNow()
{ {
isShutdown = true; isShutdown = true;
thread.interrupt(); thread.interrupt();

View File

@ -19,9 +19,12 @@ package org.apache.cassandra.concurrent;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.utils.ExecutorUtils;
/** /**
* Centralized location for shared executors * Centralized location for shared executors
*/ */
@ -48,12 +51,8 @@ public class ScheduledExecutors
public static final DebuggableScheduledThreadPoolExecutor optionalTasks = new DebuggableScheduledThreadPoolExecutor("OptionalTasks"); public static final DebuggableScheduledThreadPoolExecutor optionalTasks = new DebuggableScheduledThreadPoolExecutor("OptionalTasks");
@VisibleForTesting @VisibleForTesting
public static void shutdownAndWait() throws InterruptedException public static void shutdownAndWait(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
{ {
ExecutorService[] executors = new ExecutorService[] { scheduledFastTasks, scheduledTasks, nonPeriodicTasks, optionalTasks }; ExecutorUtils.shutdownNowAndWait(timeout, unit, scheduledFastTasks, scheduledTasks, nonPeriodicTasks, optionalTasks);
for (ExecutorService executor : executors)
executor.shutdownNow();
for (ExecutorService executor : executors)
executor.awaitTermination(60, TimeUnit.SECONDS);
} }
} }

View File

@ -115,7 +115,7 @@ public class SharedExecutorPool
return executor; return executor;
} }
public synchronized void shutdownAndWait() throws InterruptedException public synchronized void shutdownAndWait(long timeout, TimeUnit unit) throws InterruptedException
{ {
shuttingDown = true; shuttingDown = true;
List<SEPExecutor> executors = new ArrayList<>(this.executors); List<SEPExecutor> executors = new ArrayList<>(this.executors);
@ -124,7 +124,7 @@ public class SharedExecutorPool
terminateWorkers(); terminateWorkers();
long until = System.nanoTime() + TimeUnit.MINUTES.toNanos(1L); long until = System.nanoTime() + unit.toNanos(timeout);
for (SEPExecutor executor : executors) for (SEPExecutor executor : executors)
executor.shutdown.await(until - System.nanoTime(), TimeUnit.NANOSECONDS); executor.shutdown.await(until - System.nanoTime(), TimeUnit.NANOSECONDS);
} }

View File

@ -25,6 +25,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.utils.ExecutorUtils;
import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FBUtilities;
import static org.apache.cassandra.config.DatabaseDescriptor.*; import static org.apache.cassandra.config.DatabaseDescriptor.*;
@ -114,12 +115,9 @@ public class StageManager
} }
@VisibleForTesting @VisibleForTesting
public static void shutdownAndWait() throws InterruptedException public static void shutdownAndWait(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
{ {
for (Stage stage : Stage.values()) ExecutorUtils.shutdownNowAndWait(timeout, unit, StageManager.stages.values());
StageManager.stages.get(stage).shutdownNow();
for (Stage stage : Stage.values())
StageManager.stages.get(stage).awaitTermination(60, TimeUnit.SECONDS);
} }
/** /**

View File

@ -83,6 +83,8 @@ import org.apache.cassandra.utils.memory.MemtableAllocator;
import org.json.simple.JSONArray; import org.json.simple.JSONArray;
import org.json.simple.JSONObject; import org.json.simple.JSONObject;
import static org.apache.cassandra.utils.ExecutorUtils.awaitTermination;
import static org.apache.cassandra.utils.ExecutorUtils.shutdown;
import static org.apache.cassandra.utils.Throwables.maybeFail; import static org.apache.cassandra.utils.Throwables.maybeFail;
public class ColumnFamilyStore implements ColumnFamilyStoreMBean public class ColumnFamilyStore implements ColumnFamilyStoreMBean
@ -264,18 +266,12 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
postFlushExecutor.awaitTermination(60, TimeUnit.SECONDS); postFlushExecutor.awaitTermination(60, TimeUnit.SECONDS);
} }
public static void shutdownReclaimExecutor() throws InterruptedException public static void shutdownExecutorsAndWait(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
{ {
reclaimExecutor.shutdown(); List<ExecutorService> executors = new ArrayList<>(perDiskflushExecutors.length + 3);
reclaimExecutor.awaitTermination(60, TimeUnit.SECONDS); Collections.addAll(executors, reclaimExecutor, postFlushExecutor, flushExecutor);
} Collections.addAll(executors, perDiskflushExecutors);
ExecutorUtils.shutdownAndWait(timeout, unit, executors);
public static void shutdownPerDiskFlushExecutors() throws InterruptedException
{
for (ExecutorService executorService : perDiskflushExecutors)
executorService.shutdown();
for (ExecutorService executorService : perDiskflushExecutors)
executorService.awaitTermination(60, TimeUnit.SECONDS);
} }
public void reload() public void reload()

View File

@ -390,6 +390,7 @@ public class CommitLog implements CommitLogMBean
/** /**
* Shuts down the threads used by the commit log, blocking until completion. * Shuts down the threads used by the commit log, blocking until completion.
* TODO this should accept a timeout, and throw TimeoutException
*/ */
public void shutdownBlocking() throws InterruptedException public void shutdownBlocking() throws InterruptedException
{ {

View File

@ -35,6 +35,7 @@ import com.google.common.util.concurrent.ListenableFutureTask;
import com.google.common.util.concurrent.Uninterruptibles; import com.google.common.util.concurrent.Uninterruptibles;
import io.netty.util.concurrent.FastThreadLocal; import io.netty.util.concurrent.FastThreadLocal;
import org.apache.cassandra.utils.ExecutorUtils;
import org.apache.cassandra.utils.MBeanWrapper; import org.apache.cassandra.utils.MBeanWrapper;
import org.apache.cassandra.utils.NoSpamLogger; import org.apache.cassandra.utils.NoSpamLogger;
import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.Pair;
@ -56,6 +57,8 @@ import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.utils.CassandraVersion; import org.apache.cassandra.utils.CassandraVersion;
import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.JVMStabilityInspector; import org.apache.cassandra.utils.JVMStabilityInspector;
import static org.apache.cassandra.utils.ExecutorUtils.awaitTermination;
import static org.apache.cassandra.utils.ExecutorUtils.shutdown;
/** /**
* This module is responsible for Gossiping information for the local endpoint. This abstraction * This module is responsible for Gossiping information for the local endpoint. This abstraction
@ -1808,4 +1811,10 @@ public class Gossiper implements IFailureDetectionEventListener, GossiperMBean
logger.info("No gossip backlog; proceeding"); logger.info("No gossip backlog; proceeding");
} }
@VisibleForTesting
public void stopShutdownAndWait(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
{
stop();
ExecutorUtils.shutdownAndWait(timeout, unit, executor);
}
} }

View File

@ -20,6 +20,7 @@ package org.apache.cassandra.hints;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -64,10 +65,10 @@ final class HintsCatalog
*/ */
static HintsCatalog load(File hintsDirectory, ImmutableMap<String, Object> writerParams) static HintsCatalog load(File hintsDirectory, ImmutableMap<String, Object> writerParams)
{ {
try try(Stream<Path> list = Files.list(hintsDirectory.toPath()))
{ {
Map<UUID, List<HintsDescriptor>> stores = Map<UUID, List<HintsDescriptor>> stores =
Files.list(hintsDirectory.toPath()) list
.filter(HintsDescriptor::isHintFileName) .filter(HintsDescriptor::isHintFileName)
.map(HintsDescriptor::readFromFileQuietly) .map(HintsDescriptor::readFromFileQuietly)
.filter(Optional::isPresent) .filter(Optional::isPresent)

View File

@ -65,6 +65,9 @@ import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.concurrent.OpOrder; import org.apache.cassandra.utils.concurrent.OpOrder;
import org.apache.cassandra.utils.concurrent.Refs; import org.apache.cassandra.utils.concurrent.Refs;
import static org.apache.cassandra.utils.ExecutorUtils.awaitTermination;
import static org.apache.cassandra.utils.ExecutorUtils.shutdown;
/** /**
* Handles the core maintenance functionality associated with indexes: adding/removing them to or from * Handles the core maintenance functionality associated with indexes: adding/removing them to or from
* a table, (re)building during bootstrap or other streaming operations, flushing, reloading metadata * a table, (re)building during bootstrap or other streaming operations, flushing, reloading metadata
@ -1176,12 +1179,10 @@ public class SecondaryIndexManager implements IndexRegistry
} }
@VisibleForTesting @VisibleForTesting
public static void shutdownExecutors() throws InterruptedException public static void shutdownAndWait(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
{ {
ExecutorService[] executors = new ExecutorService[]{ asyncExecutor, blockingExecutor }; ExecutorService[] executors = new ExecutorService[]{ asyncExecutor, blockingExecutor };
for (ExecutorService executor : executors) shutdown(executors);
executor.shutdown(); awaitTermination(timeout, unit, executors);
for (ExecutorService executor : executors)
executor.awaitTermination(60, TimeUnit.SECONDS);
} }
} }

View File

@ -26,6 +26,7 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
@ -44,11 +45,15 @@ import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
import org.apache.cassandra.db.lifecycle.SSTableSet; import org.apache.cassandra.db.lifecycle.SSTableSet;
import org.apache.cassandra.db.lifecycle.View; import org.apache.cassandra.db.lifecycle.View;
import org.apache.cassandra.io.sstable.format.SSTableReader; import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.utils.ExecutorUtils;
import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.MBeanWrapper; import org.apache.cassandra.utils.MBeanWrapper;
import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.WrappedRunnable; import org.apache.cassandra.utils.WrappedRunnable;
import static org.apache.cassandra.utils.ExecutorUtils.awaitTermination;
import static org.apache.cassandra.utils.ExecutorUtils.shutdown;
/** /**
* Manages the fixed-size memory pool for index summaries, periodically resizing them * Manages the fixed-size memory pool for index summaries, periodically resizing them
* in order to give more memory to hot sstables and less memory to cold sstables. * in order to give more memory to hot sstables and less memory to cold sstables.
@ -258,4 +263,10 @@ public class IndexSummaryManager implements IndexSummaryManagerMBean
{ {
return CompactionManager.instance.runIndexSummaryRedistribution(redistribution); return CompactionManager.instance.runIndexSummaryRedistribution(redistribution);
} }
@VisibleForTesting
public void shutdownAndWait(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
{
ExecutorUtils.shutdownAndWait(timeout, unit, executor);
}
} }

View File

@ -2371,10 +2371,10 @@ public abstract class SSTableReader extends SSTable implements SelfRefCounted<SS
} }
public static void shutdownBlocking() throws InterruptedException public static void shutdownBlocking(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
{ {
syncExecutor.shutdownNow();
syncExecutor.awaitTermination(0, TimeUnit.SECONDS); ExecutorUtils.shutdownNowAndWait(timeout, unit, syncExecutor);
resetTidying(); resetTidying();
} }
} }

View File

@ -1383,6 +1383,8 @@ public final class MessagingService implements MessagingServiceMBean
{ {
case "Unknown error: 316": case "Unknown error: 316":
case "No such file or directory": case "No such file or directory":
case "Bad file descriptor":
case "Thread signal failed":
return; return;
} }
} }

View File

@ -426,7 +426,7 @@ public class OutboundTcpConnection extends FastThreadLocalThread
long start = System.nanoTime(); long start = System.nanoTime();
long timeout = TimeUnit.MILLISECONDS.toNanos(DatabaseDescriptor.getRpcTimeout()); long timeout = TimeUnit.MILLISECONDS.toNanos(DatabaseDescriptor.getRpcTimeout());
while (System.nanoTime() - start < timeout) while (System.nanoTime() - start < timeout && !isStopped)
{ {
targetVersion = MessagingService.instance().getVersion(poolReference.endPoint()); targetVersion = MessagingService.instance().getVersion(poolReference.endPoint());
try try

View File

@ -23,15 +23,21 @@ import org.apache.cassandra.concurrent.NamedThreadFactory;
import org.apache.cassandra.config.Schema; import org.apache.cassandra.config.Schema;
import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.locator.AbstractReplicationStrategy; import org.apache.cassandra.locator.AbstractReplicationStrategy;
import org.apache.cassandra.utils.ExecutorUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import static org.apache.cassandra.utils.ExecutorUtils.awaitTermination;
import static org.apache.cassandra.utils.ExecutorUtils.shutdownNow;
public class PendingRangeCalculatorService public class PendingRangeCalculatorService
{ {
public static final PendingRangeCalculatorService instance = new PendingRangeCalculatorService(); public static final PendingRangeCalculatorService instance = new PendingRangeCalculatorService();
@ -112,9 +118,8 @@ public class PendingRangeCalculatorService
} }
@VisibleForTesting @VisibleForTesting
public void shutdownExecutor() throws InterruptedException public void shutdownExecutor(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
{ {
executor.shutdown(); ExecutorUtils.shutdownNowAndWait(timeout, unit, executor);
executor.awaitTermination(60, TimeUnit.SECONDS);
} }
} }

View File

@ -18,7 +18,6 @@
package org.apache.cassandra.service; package org.apache.cassandra.service;
import java.io.*; import java.io.*;
import java.lang.management.ManagementFactory;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -98,6 +97,7 @@ import org.apache.cassandra.utils.progress.jmx.JMXProgressSupport;
import org.apache.cassandra.utils.progress.jmx.LegacyJMXProgressSupport; import org.apache.cassandra.utils.progress.jmx.LegacyJMXProgressSupport;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
import static org.apache.cassandra.index.SecondaryIndexManager.getIndexName; import static org.apache.cassandra.index.SecondaryIndexManager.getIndexName;
import static org.apache.cassandra.index.SecondaryIndexManager.isIndexColumnFamily; import static org.apache.cassandra.index.SecondaryIndexManager.isIndexColumnFamily;
@ -4560,7 +4560,16 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
{ {
setMode(Mode.DRAINING, "starting drain process", !isFinalShutdown); setMode(Mode.DRAINING, "starting drain process", !isFinalShutdown);
BatchlogManager.instance.shutdown(); try
{
/* not clear this is reasonable time, but propagated from prior embedded behaviour */
BatchlogManager.instance.shutdownAndWait(1L, MINUTES);
}
catch (TimeoutException t)
{
logger.error("Batchlog manager timed out shutting down", t);
}
HintsService.instance.pauseDispatch(); HintsService.instance.pauseDispatch();
if (daemon != null) if (daemon != null)
@ -4653,7 +4662,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
// wait for miscellaneous tasks like sstable and commitlog segment deletion // wait for miscellaneous tasks like sstable and commitlog segment deletion
ScheduledExecutors.nonPeriodicTasks.shutdown(); ScheduledExecutors.nonPeriodicTasks.shutdown();
if (!ScheduledExecutors.nonPeriodicTasks.awaitTermination(1, TimeUnit.MINUTES)) if (!ScheduledExecutors.nonPeriodicTasks.awaitTermination(1, MINUTES))
logger.warn("Failed to wait for non periodic tasks to shutdown"); logger.warn("Failed to wait for non periodic tasks to shutdown");
ColumnFamilyStore.shutdownPostFlushExecutor(); ColumnFamilyStore.shutdownPostFlushExecutor();
@ -5286,4 +5295,13 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
DatabaseDescriptor.setHintedHandoffThrottleInKB(throttleInKB); DatabaseDescriptor.setHintedHandoffThrottleInKB(throttleInKB);
logger.info("Updated hinted_handoff_throttle_in_kb to {}", throttleInKB); logger.info("Updated hinted_handoff_throttle_in_kb to {}", throttleInKB);
} }
@VisibleForTesting
public void shutdownServer()
{
if (drainOnShutdown != null)
{
Runtime.getRuntime().removeShutdownHook(drainOnShutdown);
}
}
} }

View File

@ -19,11 +19,17 @@ package org.apache.cassandra.streaming;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.*; import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.apache.cassandra.concurrent.DebuggableThreadPoolExecutor; import org.apache.cassandra.concurrent.DebuggableThreadPoolExecutor;
import org.apache.cassandra.utils.ExecutorUtils;
import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FBUtilities;
/** /**
@ -341,4 +347,11 @@ public class StreamCoordinator
return sessionInfos.values(); return sessionInfos.values();
} }
} }
@VisibleForTesting
public static void shutdownAndWait(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
{
ExecutorUtils.shutdownAndWait(timeout, unit, streamExecutor);
}
} }

View File

@ -48,6 +48,7 @@ import org.apache.cassandra.metrics.StreamingMetrics;
import org.apache.cassandra.service.ActiveRepairService; import org.apache.cassandra.service.ActiveRepairService;
import org.apache.cassandra.streaming.messages.*; import org.apache.cassandra.streaming.messages.*;
import org.apache.cassandra.utils.CassandraVersion; import org.apache.cassandra.utils.CassandraVersion;
import org.apache.cassandra.utils.ExecutorUtils;
import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.JVMStabilityInspector; import org.apache.cassandra.utils.JVMStabilityInspector;
import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.Pair;
@ -837,4 +838,12 @@ public class StreamSession implements IEndpointStateChangeSubscriber
} }
} }
} }
@VisibleForTesting
public static void shutdownAndWait(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
{
List<ExecutorService> executors = ImmutableList.of(keepAliveExecutor);
ExecutorUtils.shutdownNow(executors);
ExecutorUtils.awaitTermination(timeout, unit, executors);
}
} }

View File

@ -0,0 +1,151 @@
/*
* 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.utils;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.cassandra.concurrent.InfiniteLoopExecutor;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
public class ExecutorUtils
{
public static Runnable runWithThreadName(Runnable runnable, String threadName)
{
return () -> {
String oldThreadName = Thread.currentThread().getName();
try
{
Thread.currentThread().setName(threadName);
runnable.run();
}
finally
{
Thread.currentThread().setName(oldThreadName);
}
};
}
public static void shutdownNow(Iterable<?> executors)
{
shutdown(true, executors);
}
public static void shutdown(Iterable<?> executors)
{
shutdown(false, executors);
}
public static void shutdown(boolean interrupt, Iterable<?> executors)
{
for (Object executor : executors)
{
if (executor instanceof ExecutorService)
{
if (interrupt) ((ExecutorService) executor).shutdownNow();
else ((ExecutorService) executor).shutdown();
}
else if (executor instanceof InfiniteLoopExecutor)
((InfiniteLoopExecutor) executor).shutdownNow();
else if (executor instanceof Thread)
((Thread) executor).interrupt();
else if (executor != null)
throw new IllegalArgumentException(executor.toString());
}
}
public static void shutdown(ExecutorService ... executors)
{
shutdown(Arrays.asList(executors));
}
public static void shutdownNow(ExecutorService ... executors)
{
shutdownNow(Arrays.asList(executors));
}
public static void awaitTermination(long timeout, TimeUnit unit, ExecutorService ... executors) throws InterruptedException, TimeoutException
{
awaitTermination(timeout, unit, Arrays.asList(executors));
}
public static void awaitTermination(long timeout, TimeUnit unit, Collection<?> executors) throws InterruptedException, TimeoutException
{
long deadline = System.nanoTime() + unit.toNanos(timeout);
awaitTerminationUntil(deadline, executors);
}
public static void awaitTerminationUntil(long deadline, Collection<?> executors) throws InterruptedException, TimeoutException
{
for (Object executor : executors)
{
long wait = deadline - System.nanoTime();
if (executor instanceof ExecutorService)
{
if (wait <= 0 || !((ExecutorService)executor).awaitTermination(wait, NANOSECONDS))
throw new TimeoutException(executor + " did not terminate on time");
}
else if (executor instanceof InfiniteLoopExecutor)
{
if (wait <= 0 || !((InfiniteLoopExecutor)executor).awaitTermination(wait, NANOSECONDS))
throw new TimeoutException(executor + " did not terminate on time");
}
else if (executor instanceof Thread)
{
Thread t = (Thread) executor;
if (wait <= 0)
throw new TimeoutException(executor + " did not terminate on time");
t.join((wait + 999999) / 1000000L, (int) (wait % 1000000L));
if (t.isAlive())
throw new TimeoutException(executor + " did not terminate on time");
}
else if (executor != null)
{
throw new IllegalArgumentException(executor.toString());
}
}
}
public static void shutdownAndWait(long timeout, TimeUnit unit, Collection<?> executors) throws TimeoutException, InterruptedException
{
shutdown(executors);
awaitTermination(timeout, unit, executors);
}
public static void shutdownNowAndWait(long timeout, TimeUnit unit, Collection<?> executors) throws TimeoutException, InterruptedException
{
shutdownNow(executors);
awaitTermination(timeout, unit, executors);
}
public static void shutdownAndWait(long timeout, TimeUnit unit, Object ... executors) throws TimeoutException, InterruptedException
{
shutdownAndWait(timeout, unit, Arrays.asList(executors));
}
public static void shutdownNowAndWait(long timeout, TimeUnit unit, Object ... executors) throws TimeoutException, InterruptedException
{
shutdownNowAndWait(timeout, unit, Arrays.asList(executors));
}
}

View File

@ -45,12 +45,16 @@ import org.apache.cassandra.db.lifecycle.View;
import org.apache.cassandra.io.sstable.format.SSTableReader; import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.util.Memory; import org.apache.cassandra.io.util.Memory;
import org.apache.cassandra.io.util.SafeMemory; import org.apache.cassandra.io.util.SafeMemory;
import org.apache.cassandra.utils.ExecutorUtils;
import org.apache.cassandra.utils.NoSpamLogger; import org.apache.cassandra.utils.NoSpamLogger;
import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.Pair;
import org.cliffc.high_scale_lib.NonBlockingHashMap; import org.cliffc.high_scale_lib.NonBlockingHashMap;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import org.apache.cassandra.concurrent.InfiniteLoopExecutor.InterruptibleRunnable;
import static org.apache.cassandra.utils.ExecutorUtils.awaitTermination;
import static org.apache.cassandra.utils.ExecutorUtils.shutdownNow;
import static org.apache.cassandra.utils.Throwables.maybeFail; import static org.apache.cassandra.utils.Throwables.maybeFail;
import static org.apache.cassandra.utils.Throwables.merge; import static org.apache.cassandra.utils.Throwables.merge;
@ -705,14 +709,8 @@ public final class Ref<T> implements RefCounted<T>
} }
@VisibleForTesting @VisibleForTesting
public static void shutdownReferenceReaper() throws InterruptedException public static void shutdownReferenceReaper(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
{ {
EXEC.shutdown(); ExecutorUtils.shutdownNowAndWait(timeout, unit, EXEC, STRONG_LEAK_DETECTOR);
EXEC.awaitTermination(60, TimeUnit.SECONDS);
if (STRONG_LEAK_DETECTOR != null)
{
STRONG_LEAK_DETECTOR.shutdownNow();
STRONG_LEAK_DETECTOR.awaitTermination(60, TimeUnit.SECONDS);
}
} }
} }

View File

@ -21,6 +21,7 @@ package org.apache.cassandra.utils.memory;
import java.lang.ref.PhantomReference; import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue; import java.lang.ref.ReferenceQueue;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
@ -40,6 +41,9 @@ import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.NoSpamLogger; import org.apache.cassandra.utils.NoSpamLogger;
import org.apache.cassandra.utils.concurrent.Ref; import org.apache.cassandra.utils.concurrent.Ref;
import static org.apache.cassandra.utils.ExecutorUtils.awaitTermination;
import static org.apache.cassandra.utils.ExecutorUtils.shutdownNow;
/** /**
* A pool of ByteBuffers that can be recycled. * A pool of ByteBuffers that can be recycled.
*/ */
@ -856,9 +860,9 @@ public class BufferPool
} }
@VisibleForTesting @VisibleForTesting
public static void shutdownLocalCleaner() throws InterruptedException public static void shutdownLocalCleaner(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
{ {
EXEC.shutdown(); shutdownNow(Arrays.asList(EXEC));
EXEC.awaitTermination(60, TimeUnit.SECONDS); awaitTermination(timeout, unit, Arrays.asList(EXEC));
} }
} }

View File

@ -19,6 +19,7 @@
package org.apache.cassandra.utils.memory; package org.apache.cassandra.utils.memory;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLongFieldUpdater; import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
@ -27,6 +28,7 @@ import com.codahale.metrics.Timer;
import org.apache.cassandra.metrics.CassandraMetricsRegistry; import org.apache.cassandra.metrics.CassandraMetricsRegistry;
import org.apache.cassandra.metrics.DefaultNameFactory; import org.apache.cassandra.metrics.DefaultNameFactory;
import org.apache.cassandra.utils.concurrent.WaitQueue; import org.apache.cassandra.utils.concurrent.WaitQueue;
import org.apache.cassandra.utils.ExecutorUtils;
/** /**
@ -67,12 +69,12 @@ public abstract class MemtablePool
} }
@VisibleForTesting @VisibleForTesting
public void shutdown() throws InterruptedException public void shutdownAndWait(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
{ {
cleaner.shutdown(); ExecutorUtils.shutdownNowAndWait(timeout, unit, cleaner);
cleaner.awaitTermination(60, TimeUnit.SECONDS);
} }
public abstract MemtableAllocator newAllocator(); public abstract MemtableAllocator newAllocator();
/** /**

View File

@ -23,7 +23,7 @@
<!-- Shutdown hook ensures that async appender flushes --> <!-- Shutdown hook ensures that async appender flushes -->
<shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/> <shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <appender name="INSTANCEFILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>./build/test/logs/${cassandra.testtag}/TEST-${suitename}.log</file> <file>./build/test/logs/${cassandra.testtag}/TEST-${suitename}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"> <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
@ -42,14 +42,14 @@
<immediateFlush>false</immediateFlush> <immediateFlush>false</immediateFlush>
</appender> </appender>
<appender name="ASYNCFILE" class="ch.qos.logback.classic.AsyncAppender"> <appender name="INSTANCEASYNCFILE" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold> <discardingThreshold>0</discardingThreshold>
<maxFlushTime>0</maxFlushTime> <maxFlushTime>0</maxFlushTime>
<queueSize>1024</queueSize> <queueSize>1024</queueSize>
<appender-ref ref="FILE"/> <appender-ref ref="INSTANCEFILE"/>
</appender> </appender>
<appender name="STDERR" target="System.err" class="ch.qos.logback.core.ConsoleAppender"> <appender name="INSTANCESTDERR" target="System.err" class="ch.qos.logback.core.ConsoleAppender">
<encoder> <encoder>
<pattern>%-5level %date{HH:mm:ss,SSS} %msg%n</pattern> <pattern>%-5level %date{HH:mm:ss,SSS} %msg%n</pattern>
</encoder> </encoder>
@ -58,7 +58,7 @@
</filter> </filter>
</appender> </appender>
<appender name="STDOUT" target="System.out" class="ch.qos.logback.core.ConsoleAppender"> <appender name="INSTANCESTDOUT" target="System.out" class="ch.qos.logback.core.ConsoleAppender">
<encoder> <encoder>
<pattern>%-5level %date{HH:mm:ss,SSS} %msg%n</pattern> <pattern>%-5level %date{HH:mm:ss,SSS} %msg%n</pattern>
</encoder> </encoder>
@ -67,7 +67,7 @@
</filter> </filter>
</appender> </appender>
<appender name="STDOUT" target="System.out" class="ch.qos.logback.core.ConsoleAppender"> <appender name="INSTANCESTDOUT" target="System.out" class="ch.qos.logback.core.ConsoleAppender">
<encoder> <encoder>
<pattern>%-5level [%thread] ${instance_id} %date{ISO8601} %F:%L - %msg%n</pattern> <pattern>%-5level [%thread] ${instance_id} %date{ISO8601} %F:%L - %msg%n</pattern>
</encoder> </encoder>
@ -79,8 +79,8 @@
<logger name="org.apache.hadoop" level="WARN"/> <logger name="org.apache.hadoop" level="WARN"/>
<root level="DEBUG"> <root level="DEBUG">
<appender-ref ref="ASYNCFILE" /> <appender-ref ref="INSTANCEASYNCFILE" />
<appender-ref ref="STDERR" /> <appender-ref ref="INSTANCESTDERR" />
<appender-ref ref="STDOUT" /> <appender-ref ref="INSTANCESTDOUT" />
</root> </root>
</configuration> </configuration>

View File

@ -19,10 +19,10 @@
package org.apache.cassandra.distributed; package org.apache.cassandra.distributed;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.function.Consumer;
import org.apache.cassandra.distributed.api.Feature;
import org.apache.cassandra.distributed.api.ICluster; import org.apache.cassandra.distributed.api.ICluster;
import org.apache.cassandra.distributed.impl.AbstractCluster; import org.apache.cassandra.distributed.impl.AbstractCluster;
import org.apache.cassandra.distributed.impl.IInvokableInstance; import org.apache.cassandra.distributed.impl.IInvokableInstance;
@ -35,31 +35,29 @@ import org.apache.cassandra.distributed.impl.Versions;
*/ */
public class Cluster extends AbstractCluster<IInvokableInstance> implements ICluster, AutoCloseable public class Cluster extends AbstractCluster<IInvokableInstance> implements ICluster, AutoCloseable
{ {
private Cluster(File root, Versions.Version version, List<InstanceConfig> configs, Set<Feature> features, ClassLoader sharedClassLoader) private Cluster(File root, Versions.Version version, List<InstanceConfig> configs, ClassLoader sharedClassLoader)
{ {
super(root, version, configs, features, sharedClassLoader); super(root, version, configs, sharedClassLoader);
} }
protected IInvokableInstance newInstanceWrapper(Versions.Version version, InstanceConfig config) protected IInvokableInstance newInstanceWrapper(int generation, Versions.Version version, InstanceConfig config)
{ {
return new Wrapper(version, config); return new Wrapper(generation, version, config);
}
public static Builder<IInvokableInstance, Cluster> build(int nodeCount)
{
return new Builder<>(nodeCount, Cluster::new);
}
public static Cluster create(int nodeCount, Consumer<InstanceConfig> configUpdater) throws IOException
{
return build(nodeCount).withConfig(configUpdater).start();
} }
public static Cluster create(int nodeCount) throws Throwable public static Cluster create(int nodeCount) throws Throwable
{ {
return create(nodeCount, Cluster::new); return build(nodeCount).start();
}
public static Cluster create(int nodeCount, Set<Feature> with) throws Throwable
{
return create(nodeCount, with, Cluster::new);
}
public static Cluster create(int nodeCount, File root)
{
return create(nodeCount, root, Cluster::new);
}
public static Cluster create(int nodeCount, File root, Set<Feature> with)
{
return create(nodeCount, Versions.CURRENT, root, with, Cluster::new);
} }
} }

View File

@ -19,12 +19,8 @@
package org.apache.cassandra.distributed; package org.apache.cassandra.distributed;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List; import java.util.List;
import java.util.Set;
import org.apache.cassandra.distributed.api.Feature;
import org.apache.cassandra.distributed.api.ICluster; import org.apache.cassandra.distributed.api.ICluster;
import org.apache.cassandra.distributed.impl.AbstractCluster; import org.apache.cassandra.distributed.impl.AbstractCluster;
import org.apache.cassandra.distributed.impl.IUpgradeableInstance; import org.apache.cassandra.distributed.impl.IUpgradeableInstance;
@ -40,35 +36,29 @@ import org.apache.cassandra.distributed.impl.Versions;
*/ */
public class UpgradeableCluster extends AbstractCluster<IUpgradeableInstance> implements ICluster, AutoCloseable public class UpgradeableCluster extends AbstractCluster<IUpgradeableInstance> implements ICluster, AutoCloseable
{ {
private UpgradeableCluster(File root, Versions.Version version, List<InstanceConfig> configs, Set<Feature> features, ClassLoader sharedClassLoader) private UpgradeableCluster(File root, Versions.Version version, List<InstanceConfig> configs, ClassLoader sharedClassLoader)
{ {
super(root, version, configs, features, sharedClassLoader); super(root, version, configs, sharedClassLoader);
} }
protected IUpgradeableInstance newInstanceWrapper(Versions.Version version, InstanceConfig config) protected IUpgradeableInstance newInstanceWrapper(int generation, Versions.Version version, InstanceConfig config)
{ {
return new Wrapper(version, config); return new Wrapper(generation, version, config);
}
public static Builder<IUpgradeableInstance, UpgradeableCluster> build(int nodeCount)
{
return new Builder<>(nodeCount, UpgradeableCluster::new);
} }
public static UpgradeableCluster create(int nodeCount) throws Throwable public static UpgradeableCluster create(int nodeCount) throws Throwable
{ {
return create(nodeCount, UpgradeableCluster::new); return build(nodeCount).start();
} }
public static UpgradeableCluster create(int nodeCount, File root) public static UpgradeableCluster create(int nodeCount, Versions.Version version) throws Throwable
{ {
return create(nodeCount, root, UpgradeableCluster::new); return build(nodeCount).withVersion(version).start();
} }
public static UpgradeableCluster create(int nodeCount, Versions.Version version) throws IOException
{
return create(nodeCount, version, UpgradeableCluster::new);
}
public static UpgradeableCluster create(int nodeCount, Versions.Version version, File root)
{
return create(nodeCount, version, root, UpgradeableCluster::new);
}
} }

View File

@ -20,7 +20,6 @@ package org.apache.cassandra.distributed.api;
import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.InetAddressAndPort;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Future; import java.util.concurrent.Future;
@ -43,7 +42,7 @@ public interface IInstance extends IIsolatedExecutor
Future<Void> shutdown(boolean graceful); Future<Void> shutdown(boolean graceful);
// these methods are not for external use, but for simplicity we leave them public and on the normal IInstance interface // these methods are not for external use, but for simplicity we leave them public and on the normal IInstance interface
void startup(ICluster cluster, Set<Feature> with); void startup(ICluster cluster);
void receiveMessage(IMessage message); void receiveMessage(IMessage message);
int getMessagingVersion(); int getMessagingVersion();

View File

@ -38,4 +38,5 @@ public interface IInstanceConfig
Object get(String fieldName); Object get(String fieldName);
String getString(String fieldName); String getString(String fieldName);
int getInt(String fieldName); int getInt(String fieldName);
boolean has(Feature featureFlag);
} }

View File

@ -18,11 +18,8 @@
package org.apache.cassandra.distributed.api; package org.apache.cassandra.distributed.api;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.net.MessagingService;
import java.util.function.BiConsumer;
public interface IMessageFilters public interface IMessageFilters
{ {
public interface Filter public interface Filter
@ -44,5 +41,6 @@ public interface IMessageFilters
void reset(); void reset();
// internal // internal
BiConsumer<InetAddressAndPort, IMessage> filter(BiConsumer<InetAddressAndPort, IMessage> applyIfNotFiltered); boolean permit(IInstance from, IInstance to, int verb);
} }

View File

@ -32,6 +32,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -44,7 +45,6 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.distributed.api.Feature;
import org.apache.cassandra.distributed.api.ICoordinator; import org.apache.cassandra.distributed.api.ICoordinator;
import org.apache.cassandra.distributed.api.IInstance; import org.apache.cassandra.distributed.api.IInstance;
import org.apache.cassandra.distributed.api.IInstanceConfig; import org.apache.cassandra.distributed.api.IInstanceConfig;
@ -89,10 +89,10 @@ public abstract class AbstractCluster<I extends IInstance> implements ICluster,
// to ensure we have instantiated the main classloader's LoggerFactory (and any LogbackStatusListener) // to ensure we have instantiated the main classloader's LoggerFactory (and any LogbackStatusListener)
// before we instantiate any for a new instance // before we instantiate any for a new instance
private static final Logger logger = LoggerFactory.getLogger(AbstractCluster.class); private static final Logger logger = LoggerFactory.getLogger(AbstractCluster.class);
private static final AtomicInteger generation = new AtomicInteger();
private final File root; private final File root;
private final ClassLoader sharedClassLoader; private final ClassLoader sharedClassLoader;
private final Set<Feature> features;
// mutated by starting/stopping a node // mutated by starting/stopping a node
private final List<I> instances; private final List<I> instances;
@ -103,6 +103,7 @@ public abstract class AbstractCluster<I extends IInstance> implements ICluster,
protected class Wrapper extends DelegatingInvokableInstance implements IUpgradeableInstance protected class Wrapper extends DelegatingInvokableInstance implements IUpgradeableInstance
{ {
private final int generation;
private final InstanceConfig config; private final InstanceConfig config;
private volatile IInvokableInstance delegate; private volatile IInvokableInstance delegate;
private volatile Versions.Version version; private volatile Versions.Version version;
@ -111,21 +112,22 @@ public abstract class AbstractCluster<I extends IInstance> implements ICluster,
protected IInvokableInstance delegate() protected IInvokableInstance delegate()
{ {
if (delegate == null) if (delegate == null)
delegate = newInstance(); delegate = newInstance(generation);
return delegate; return delegate;
} }
public Wrapper(Versions.Version version, InstanceConfig config) public Wrapper(int generation, Versions.Version version, InstanceConfig config)
{ {
this.generation = generation;
this.config = config; this.config = config;
this.version = version; this.version = version;
// we ensure there is always a non-null delegate, so that the executor may be used while the node is offline // we ensure there is always a non-null delegate, so that the executor may be used while the node is offline
this.delegate = newInstance(); this.delegate = newInstance(generation);
} }
private IInvokableInstance newInstance() private IInvokableInstance newInstance(int generation)
{ {
ClassLoader classLoader = new InstanceClassLoader(config.num(), version.classpath, sharedClassLoader); ClassLoader classLoader = new InstanceClassLoader(generation, version.classpath, sharedClassLoader);
return Instance.transferAdhoc((SerializableBiFunction<IInstanceConfig, ClassLoader, Instance>)Instance::new, classLoader) return Instance.transferAdhoc((SerializableBiFunction<IInstanceConfig, ClassLoader, Instance>)Instance::new, classLoader)
.apply(config, classLoader); .apply(config, classLoader);
} }
@ -145,11 +147,17 @@ public abstract class AbstractCluster<I extends IInstance> implements ICluster,
{ {
if (!isShutdown) if (!isShutdown)
throw new IllegalStateException(); throw new IllegalStateException();
delegate().startup(AbstractCluster.this, features); delegate().startup(AbstractCluster.this);
isShutdown = false; isShutdown = false;
updateMessagingVersions(); updateMessagingVersions();
} }
@Override
public synchronized Future<Void> shutdown()
{
return shutdown(true);
}
@Override @Override
public synchronized Future<Void> shutdown(boolean graceful) public synchronized Future<Void> shutdown(boolean graceful)
{ {
@ -185,26 +193,26 @@ public abstract class AbstractCluster<I extends IInstance> implements ICluster,
} }
} }
protected AbstractCluster(File root, Versions.Version version, List<InstanceConfig> configs, Set<Feature> features, ClassLoader sharedClassLoader) protected AbstractCluster(File root, Versions.Version version, List<InstanceConfig> configs, ClassLoader sharedClassLoader)
{ {
this.root = root; this.root = root;
this.features = features;
this.sharedClassLoader = sharedClassLoader; this.sharedClassLoader = sharedClassLoader;
this.instances = new ArrayList<>(); this.instances = new ArrayList<>();
this.instanceMap = new HashMap<>(); this.instanceMap = new HashMap<>();
int generation = AbstractCluster.generation.incrementAndGet();
for (InstanceConfig config : configs) for (InstanceConfig config : configs)
{ {
I instance = newInstanceWrapper(version, config); I instance = newInstanceWrapper(generation, version, config);
instances.add(instance); instances.add(instance);
// we use the config().broadcastAddressAndPort() here because we have not initialised the Instance // we use the config().broadcastAddressAndPort() here because we have not initialised the Instance
I prev = instanceMap.put(instance.broadcastAddressAndPort(), instance); I prev = instanceMap.put(instance.broadcastAddressAndPort(), instance);
if (null != prev) if (null != prev)
throw new IllegalStateException("Cluster cannot have multiple nodes with same InetAddressAndPort: " + instance.broadcastAddressAndPort() + " vs " + prev.broadcastAddressAndPort()); throw new IllegalStateException("Cluster cannot have multiple nodes with same InetAddressAndPort: " + instance.broadcastAddressAndPort() + " vs " + prev.broadcastAddressAndPort());
} }
this.filters = new MessageFilters(this); this.filters = new MessageFilters();
} }
protected abstract I newInstanceWrapper(Versions.Version version, InstanceConfig config); protected abstract I newInstanceWrapper(int generation, Versions.Version version, InstanceConfig config);
/** /**
* WARNING: we index from 1 here, for consistency with inet address! * WARNING: we index from 1 here, for consistency with inet address!
@ -262,9 +270,12 @@ public abstract class AbstractCluster<I extends IInstance> implements ICluster,
{ {
for (IInstance reportTo: instances) for (IInstance reportTo: instances)
{ {
if (reportTo.isShutdown())
continue;
for (IInstance reportFrom: instances) for (IInstance reportFrom: instances)
{ {
if (reportFrom == reportTo) if (reportFrom == reportTo || reportFrom.isShutdown())
continue; continue;
int minVersion = Math.min(reportFrom.getMessagingVersion(), reportTo.getMessagingVersion()); int minVersion = Math.min(reportFrom.getMessagingVersion(), reportTo.getMessagingVersion());
@ -337,71 +348,86 @@ public abstract class AbstractCluster<I extends IInstance> implements ICluster,
protected interface Factory<I extends IInstance, C extends AbstractCluster<I>> protected interface Factory<I extends IInstance, C extends AbstractCluster<I>>
{ {
C newCluster(File root, Versions.Version version, List<InstanceConfig> configs, Set<Feature> features, ClassLoader sharedClassLoader); C newCluster(File root, Versions.Version version, List<InstanceConfig> configs, ClassLoader sharedClassLoader);
} }
protected static <I extends IInstance, C extends AbstractCluster<I>> C public static class Builder<I extends IInstance, C extends AbstractCluster<I>>
create(int nodeCount, Factory<I, C> factory) throws Throwable
{ {
return create(nodeCount, Collections.emptySet(), factory); private final int nodeCount;
} private final Factory<I, C> factory;
private int subnet;
protected static <I extends IInstance, C extends AbstractCluster<I>> C private File root;
create(int nodeCount, Set<Feature> features, Factory<I, C> factory) throws Throwable private Versions.Version version;
{ private Consumer<InstanceConfig> configUpdater;
return create(nodeCount, Files.createTempDirectory("dtests").toFile(), features, factory); public Builder(int nodeCount, Factory<I, C> factory)
}
protected static <I extends IInstance, C extends AbstractCluster<I>> C
create(int nodeCount, File root, Factory<I, C> factory)
{
return create(nodeCount, root, Collections.emptySet(), factory);
}
protected static <I extends IInstance, C extends AbstractCluster<I>> C
create(int nodeCount, File root, Set<Feature> features, Factory<I, C> factory)
{
return create(nodeCount, Versions.CURRENT, root, features, factory);
}
protected static <I extends IInstance, C extends AbstractCluster<I>> C
create(int nodeCount, Versions.Version version, Factory<I, C> factory) throws IOException
{
return create(nodeCount, version, Collections.emptySet(), factory);
}
protected static <I extends IInstance, C extends AbstractCluster<I>> C
create(int nodeCount, Versions.Version version, Set<Feature> features, Factory<I, C> factory) throws IOException
{
return create(nodeCount, version, Files.createTempDirectory("dtests").toFile(), features, factory);
}
protected static <I extends IInstance, C extends AbstractCluster<I>> C
create(int nodeCount, Versions.Version version, File root, Factory<I, C> factory)
{
return create(nodeCount, version, root, Collections.emptySet(), factory);
}
protected static <I extends IInstance, C extends AbstractCluster<I>> C
create(int nodeCount, Versions.Version version, File root, Set<Feature> features, Factory<I, C> factory)
{
root.mkdirs();
setupLogging(root);
ClassLoader sharedClassLoader = Thread.currentThread().getContextClassLoader();
List<InstanceConfig> configs = new ArrayList<>();
long token = Long.MIN_VALUE + 1, increment = 2 * (Long.MAX_VALUE / nodeCount);
for (int i = 0 ; i < nodeCount ; ++i)
{ {
InstanceConfig config = InstanceConfig.generate(i + 1, root, String.valueOf(token)); this.nodeCount = nodeCount;
configs.add(config); this.factory = factory;
token += increment;
} }
C cluster = factory.newCluster(root, version, configs, features, sharedClassLoader); public Builder<I, C> withSubnet(int subnet)
return cluster; {
this.subnet = subnet;
return this;
}
public Builder<I, C> withRoot(File root)
{
this.root = root;
return this;
}
public Builder<I, C> withVersion(Versions.Version version)
{
this.version = version;
return this;
}
public Builder<I, C> withConfig(Consumer<InstanceConfig> updater)
{
this.configUpdater = updater;
return this;
}
public C createWithoutStarting() throws IOException
{
File root = this.root;
Versions.Version version = this.version;
if (root == null)
root = Files.createTempDirectory("dtests").toFile();
if (version == null)
version = Versions.CURRENT;
root.mkdirs();
setupLogging(root);
ClassLoader sharedClassLoader = Thread.currentThread().getContextClassLoader();
List<InstanceConfig> configs = new ArrayList<>();
long token = Long.MIN_VALUE + 1, increment = 2 * (Long.MAX_VALUE / nodeCount);
for (int i = 0; i < nodeCount; ++i)
{
InstanceConfig config = InstanceConfig.generate(i + 1, subnet, root, String.valueOf(token));
if (configUpdater != null)
configUpdater.accept(config);
configs.add(config);
token += increment;
}
C cluster = factory.newCluster(root, version, configs, sharedClassLoader);
return cluster;
}
public C start() throws IOException
{
C cluster = createWithoutStarting();
cluster.startup();
return cluster;
}
} }
private static void setupLogging(File root) private static void setupLogging(File root)
{ {
try try

View File

@ -27,7 +27,6 @@ import java.util.function.BiFunction;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import org.apache.cassandra.distributed.api.Feature;
import org.apache.cassandra.distributed.api.ICluster; import org.apache.cassandra.distributed.api.ICluster;
import org.apache.cassandra.distributed.api.ICoordinator; import org.apache.cassandra.distributed.api.ICoordinator;
import org.apache.cassandra.distributed.api.IInstanceConfig; import org.apache.cassandra.distributed.api.IInstanceConfig;
@ -112,9 +111,9 @@ public abstract class DelegatingInvokableInstance implements IInvokableInstance
} }
@Override @Override
public void startup(ICluster cluster, Set<Feature> with) public void startup(ICluster cluster)
{ {
delegate().startup(cluster, with); delegate().startup(cluster);
} }
@Override @Override

View File

@ -34,9 +34,6 @@ import java.util.concurrent.Future;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Function; import java.util.function.Function;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import org.apache.cassandra.batchlog.BatchlogManager; import org.apache.cassandra.batchlog.BatchlogManager;
import org.apache.cassandra.concurrent.ScheduledExecutors; import org.apache.cassandra.concurrent.ScheduledExecutors;
import org.apache.cassandra.concurrent.SharedExecutorPool; import org.apache.cassandra.concurrent.SharedExecutorPool;
@ -55,11 +52,12 @@ import org.apache.cassandra.db.Memtable;
import org.apache.cassandra.db.SystemKeyspace; import org.apache.cassandra.db.SystemKeyspace;
import org.apache.cassandra.db.commitlog.CommitLog; import org.apache.cassandra.db.commitlog.CommitLog;
import org.apache.cassandra.db.compaction.CompactionManager; import org.apache.cassandra.db.compaction.CompactionManager;
import org.apache.cassandra.db.monitoring.ApproximateTime;
import org.apache.cassandra.dht.IPartitioner; import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.dht.Token; import org.apache.cassandra.dht.Token;
import org.apache.cassandra.distributed.api.Feature;
import org.apache.cassandra.distributed.api.ICluster; import org.apache.cassandra.distributed.api.ICluster;
import org.apache.cassandra.distributed.api.ICoordinator; import org.apache.cassandra.distributed.api.ICoordinator;
import org.apache.cassandra.distributed.api.IInstance;
import org.apache.cassandra.distributed.api.IInstanceConfig; import org.apache.cassandra.distributed.api.IInstanceConfig;
import org.apache.cassandra.distributed.api.IListen; import org.apache.cassandra.distributed.api.IListen;
import org.apache.cassandra.distributed.api.IMessage; import org.apache.cassandra.distributed.api.IMessage;
@ -68,6 +66,7 @@ import org.apache.cassandra.gms.Gossiper;
import org.apache.cassandra.gms.VersionedValue; import org.apache.cassandra.gms.VersionedValue;
import org.apache.cassandra.hints.HintsService; import org.apache.cassandra.hints.HintsService;
import org.apache.cassandra.index.SecondaryIndexManager; import org.apache.cassandra.index.SecondaryIndexManager;
import org.apache.cassandra.io.sstable.IndexSummaryManager;
import org.apache.cassandra.io.sstable.format.SSTableReader; import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.util.DataInputBuffer; import org.apache.cassandra.io.util.DataInputBuffer;
import org.apache.cassandra.io.util.DataOutputBuffer; import org.apache.cassandra.io.util.DataOutputBuffer;
@ -82,12 +81,19 @@ import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.PendingRangeCalculatorService; import org.apache.cassandra.service.PendingRangeCalculatorService;
import org.apache.cassandra.service.QueryState; import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.streaming.StreamCoordinator;
import org.apache.cassandra.streaming.StreamSession;
import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.transport.messages.ResultMessage;
import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.NanoTimeToCurrentTimeMillis;
import org.apache.cassandra.utils.Throwables; import org.apache.cassandra.utils.Throwables;
import org.apache.cassandra.utils.concurrent.Ref; import org.apache.cassandra.utils.concurrent.Ref;
import org.apache.cassandra.utils.memory.BufferPool; import org.apache.cassandra.utils.memory.BufferPool;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.apache.cassandra.distributed.api.Feature.GOSSIP;
import static org.apache.cassandra.distributed.api.Feature.NETWORK;
public class Instance extends IsolatedExecutor implements IInvokableInstance public class Instance extends IsolatedExecutor implements IInvokableInstance
{ {
public final IInstanceConfig config; public final IInstanceConfig config;
@ -100,10 +106,10 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance
this.config = config; this.config = config;
InstanceIDDefiner.setInstanceId(config.num()); InstanceIDDefiner.setInstanceId(config.num());
FBUtilities.setBroadcastInetAddress(config.broadcastAddressAndPort().address); FBUtilities.setBroadcastInetAddress(config.broadcastAddressAndPort().address);
acceptsOnInstance((IInstanceConfig override) -> { // Set the config at instance creation, possibly before startup() has run on all other instances.
Config.setOverrideLoadConfig(() -> loadConfig(override)); // setMessagingVersions below will call runOnInstance which will instantiate
DatabaseDescriptor.daemonInitialization(); // the MessagingService and dependencies preventing later changes to network parameters.
}).accept(config); Config.setOverrideLoadConfig(() -> loadConfig(config));
} }
public IInstanceConfig config() public IInstanceConfig config()
@ -182,7 +188,10 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance
private void registerMockMessaging(ICluster cluster) private void registerMockMessaging(ICluster cluster)
{ {
BiConsumer<InetAddressAndPort, IMessage> deliverToInstance = (to, message) -> cluster.get(to).receiveMessage(message); BiConsumer<InetAddressAndPort, IMessage> deliverToInstance = (to, message) -> cluster.get(to).receiveMessage(message);
BiConsumer<InetAddressAndPort, IMessage> deliverToInstanceIfNotFiltered = cluster.filters().filter(deliverToInstance); BiConsumer<InetAddressAndPort, IMessage> deliverToInstanceIfNotFiltered = (to, message) -> {
if (cluster.filters().permit(this, cluster.get(to), message.verb()))
deliverToInstance.accept(to, message);
};
Map<InetAddress, InetAddressAndPort> addressAndPortMap = new HashMap<>(); Map<InetAddress, InetAddressAndPort> addressAndPortMap = new HashMap<>();
cluster.stream().forEach(instance -> { cluster.stream().forEach(instance -> {
@ -198,6 +207,26 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance
new MessageDeliverySink(deliverToInstanceIfNotFiltered, addressAndPortMap::get)); new MessageDeliverySink(deliverToInstanceIfNotFiltered, addressAndPortMap::get));
} }
// unnecessary if registerMockMessaging used
private void registerFilter(ICluster cluster)
{
IInstance instance = this;
MessagingService.instance().addMessageSink(new IMessageSink()
{
public boolean allowOutgoingMessage(MessageOut message, int id, InetAddress toAddress)
{
// Port is not passed in, so take a best guess at the destination port from this instance
IInstance to = cluster.get(InetAddressAndPort.getByAddressOverrideDefaults(toAddress, instance.config().broadcastAddressAndPort().port));
return cluster.filters().permit(instance, to, message.verb.ordinal());
}
public boolean allowIncomingMessage(MessageIn message, int id)
{
return true;
}
});
}
private class MessageDeliverySink implements IMessageSink private class MessageDeliverySink implements IMessageSink
{ {
private final BiConsumer<InetAddressAndPort, IMessage> deliver; private final BiConsumer<InetAddressAndPort, IMessage> deliver;
@ -216,6 +245,11 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance
assert from.equals(lookupAddressAndPort.apply(messageOut.from)); assert from.equals(lookupAddressAndPort.apply(messageOut.from));
InetAddressAndPort toFull = lookupAddressAndPort.apply(to); InetAddressAndPort toFull = lookupAddressAndPort.apply(to);
int version = MessagingService.instance().getVersion(to); int version = MessagingService.instance().getVersion(to);
out.writeInt(MessagingService.PROTOCOL_MAGIC);
out.writeInt(id);
long timestamp = System.currentTimeMillis();
out.writeInt((int) timestamp);
messageOut.serialize(out, version); messageOut.serialize(out, version);
deliver.accept(toFull, new Message(messageOut.verb.ordinal(), out.toByteArray(), id, version, from)); deliver.accept(toFull, new Message(messageOut.verb.ordinal(), out.toByteArray(), id, version, from));
} }
@ -233,14 +267,32 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance
} }
} }
public void receiveMessage(IMessage message) public void receiveMessage(IMessage imessage)
{ {
sync(() -> { sync(() -> {
try (DataInputBuffer in = new DataInputBuffer(message.bytes())) // Based on org.apache.cassandra.net.IncomingTcpConnection.receiveMessage
try (DataInputBuffer input = new DataInputBuffer(imessage.bytes()))
{ {
MessageIn<?> messageIn = MessageIn.read(in, message.version(), message.id()); int version = imessage.version();
Runnable deliver = new MessageDeliveryTask(messageIn, message.id());
deliver.run(); MessagingService.validateMagic(input.readInt());
int id;
if (version < MessagingService.VERSION_20)
id = Integer.parseInt(input.readUTF());
else
id = input.readInt();
long currentTime = ApproximateTime.currentTimeMillis();
MessageIn message = MessageIn.read(input, version, id, MessageIn.readConstructionTime(imessage.from().address, input, currentTime));
if (message == null)
{
// callback expired; nothing to do
return;
}
if (version <= MessagingService.current_version)
{
MessagingService.instance().receive(message, id);
}
// else ignore message
} }
catch (Throwable t) catch (Throwable t)
{ {
@ -260,12 +312,14 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance
} }
@Override @Override
public void startup(ICluster cluster, Set<Feature> with) public void startup(ICluster cluster)
{ {
sync(() -> { sync(() -> {
try try
{ {
mkdirs(); mkdirs();
DatabaseDescriptor.daemonInitialization();
DatabaseDescriptor.createAllDirectories(); DatabaseDescriptor.createAllDirectories();
// We need to persist this as soon as possible after startup checks. // We need to persist this as soon as possible after startup checks.
@ -294,17 +348,27 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance
{ {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
if (config.has(NETWORK))
// TODO: support each separately
if (with.contains(Feature.GOSSIP) || with.contains(Feature.NETWORK))
{ {
StorageService.instance.prepareToJoin(); registerFilter(cluster);
StorageService.instance.joinTokenRing(5000); MessagingService.instance().listen();
}
else
{
// Even though we don't use MessagingService, access the static SocketFactory
// instance here so that we start the static event loop state
// -- not sure what that means? SocketFactory.instance.getClass();
registerMockMessaging(cluster);
}
// TODO: this is more than just gossip
if (config.has(GOSSIP))
{
StorageService.instance.initServer();
} }
else else
{ {
initializeRing(cluster); initializeRing(cluster);
registerMockMessaging(cluster);
} }
SystemKeyspace.finishStartup(); SystemKeyspace.finishStartup();
@ -404,36 +468,40 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance
Future<?> future = async((ExecutorService executor) -> { Future<?> future = async((ExecutorService executor) -> {
Throwable error = null; Throwable error = null;
if (config.has(GOSSIP) || config.has(NETWORK))
{
StorageService.instance.shutdownServer();
}
error = parallelRun(error, executor, error = parallelRun(error, executor,
Gossiper.instance::stop, () -> Gossiper.instance.stopShutdownAndWait(1L, MINUTES),
CompactionManager.instance::forceShutdown, CompactionManager.instance::forceShutdown,
BatchlogManager.instance::shutdown, () -> BatchlogManager.instance.shutdownAndWait(1L, MINUTES),
HintsService.instance::shutdownBlocking, HintsService.instance::shutdownBlocking,
SecondaryIndexManager::shutdownExecutors, () -> StreamCoordinator.shutdownAndWait(1L, MINUTES),
ColumnFamilyStore::shutdownFlushExecutor, () -> StreamSession.shutdownAndWait(1L, MINUTES),
ColumnFamilyStore::shutdownPostFlushExecutor, () -> SecondaryIndexManager.shutdownAndWait(1L, MINUTES),
ColumnFamilyStore::shutdownReclaimExecutor, () -> IndexSummaryManager.instance.shutdownAndWait(1L, MINUTES),
ColumnFamilyStore::shutdownPerDiskFlushExecutors, () -> ColumnFamilyStore.shutdownExecutorsAndWait(1L, MINUTES),
PendingRangeCalculatorService.instance::shutdownExecutor, () -> PendingRangeCalculatorService.instance.shutdownExecutor(1L, MINUTES),
BufferPool::shutdownLocalCleaner, () -> BufferPool.shutdownLocalCleaner(1L, MINUTES),
Ref::shutdownReferenceReaper, () -> Ref.shutdownReferenceReaper(1L, MINUTES),
Memtable.MEMORY_POOL::shutdown, () -> Memtable.MEMORY_POOL.shutdownAndWait(1L, MINUTES),
ScheduledExecutors::shutdownAndWait, () -> SSTableReader.shutdownBlocking(1L, MINUTES)
SSTableReader::shutdownBlocking
); );
error = parallelRun(error, executor, error = parallelRun(error, executor,
() -> ScheduledExecutors.shutdownAndWait(1L, MINUTES),
MessagingService.instance()::shutdown MessagingService.instance()::shutdown
); );
error = parallelRun(error, executor, error = parallelRun(error, executor,
StageManager::shutdownAndWait, () -> StageManager.shutdownAndWait(1L, MINUTES),
SharedExecutorPool.SHARED::shutdownAndWait () -> SharedExecutorPool.SHARED.shutdownAndWait(1L, MINUTES)
); );
error = parallelRun(error, executor, error = parallelRun(error, executor,
CommitLog.instance::shutdownBlocking CommitLog.instance::shutdownBlocking
); );
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.stop();
Throwables.maybeFail(error); Throwables.maybeFail(error);
}).apply(isolatedExecutor); }).apply(isolatedExecutor);

View File

@ -46,6 +46,7 @@ public class InstanceClassLoader extends URLClassLoader
name.startsWith("org.apache.cassandra.distributed.api.") name.startsWith("org.apache.cassandra.distributed.api.")
|| name.startsWith("sun.") || name.startsWith("sun.")
|| name.startsWith("oracle.") || name.startsWith("oracle.")
|| name.startsWith("com.intellij.")
|| name.startsWith("com.sun.") || name.startsWith("com.sun.")
|| name.startsWith("com.sun.") || name.startsWith("com.sun.")
|| name.startsWith("java.") || name.startsWith("java.")
@ -61,16 +62,16 @@ public class InstanceClassLoader extends URLClassLoader
InstanceClassLoader create(int id, URL[] urls, ClassLoader sharedClassLoader); InstanceClassLoader create(int id, URL[] urls, ClassLoader sharedClassLoader);
} }
private final int id;
private final URL[] urls; private final URL[] urls;
private final int generation; // used to help debug class loader leaks, by helping determine which classloaders should have been collected
private final ClassLoader sharedClassLoader; private final ClassLoader sharedClassLoader;
InstanceClassLoader(int id, URL[] urls, ClassLoader sharedClassLoader) InstanceClassLoader(int generation, URL[] urls, ClassLoader sharedClassLoader)
{ {
super(urls, null); super(urls, null);
this.id = id;
this.urls = urls; this.urls = urls;
this.sharedClassLoader = sharedClassLoader; this.sharedClassLoader = sharedClassLoader;
this.generation = generation;
} }
@Override @Override
@ -107,7 +108,7 @@ public class InstanceClassLoader extends URLClassLoader
public String toString() public String toString()
{ {
return "InstanceClassLoader{" + return "InstanceClassLoader{" +
"id=" + id + "generation=" + generation +
", urls=" + Arrays.toString(urls) + ", urls=" + Arrays.toString(urls) +
'}'; '}';
} }

View File

@ -20,6 +20,7 @@ package org.apache.cassandra.distributed.impl;
import org.apache.cassandra.config.Config; import org.apache.cassandra.config.Config;
import org.apache.cassandra.config.ParameterizedClass; import org.apache.cassandra.config.ParameterizedClass;
import org.apache.cassandra.distributed.api.Feature;
import org.apache.cassandra.distributed.api.IInstanceConfig; import org.apache.cassandra.distributed.api.IInstanceConfig;
import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.SimpleSeedProvider; import org.apache.cassandra.locator.SimpleSeedProvider;
@ -30,6 +31,7 @@ import java.lang.reflect.Field;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.UUID; import java.util.UUID;
@ -45,6 +47,8 @@ public class InstanceConfig implements IInstanceConfig
public UUID hostId() { return hostId; } public UUID hostId() { return hostId; }
private final Map<String, Object> params = new TreeMap<>(); private final Map<String, Object> params = new TreeMap<>();
private EnumSet featureFlags;
private volatile InetAddressAndPort broadcastAddressAndPort; private volatile InetAddressAndPort broadcastAddressAndPort;
@Override @Override
@ -103,6 +107,7 @@ public class InstanceConfig implements IInstanceConfig
Collections.singletonMap("seeds", "127.0.0.1"))) Collections.singletonMap("seeds", "127.0.0.1")))
// legacy parameters // legacy parameters
.forceSet("commitlog_sync_batch_window_in_ms", 1.0); .forceSet("commitlog_sync_batch_window_in_ms", 1.0);
this.featureFlags = EnumSet.noneOf(Feature.class);
} }
private InstanceConfig(InstanceConfig copy) private InstanceConfig(InstanceConfig copy)
@ -110,6 +115,18 @@ public class InstanceConfig implements IInstanceConfig
this.num = copy.num; this.num = copy.num;
this.params.putAll(copy.params); this.params.putAll(copy.params);
this.hostId = copy.hostId; this.hostId = copy.hostId;
this.featureFlags = copy.featureFlags;
}
public InstanceConfig with(Feature featureFlag)
{
featureFlags.add(featureFlag);
return this;
}
public boolean has(Feature featureFlag)
{
return featureFlags.contains(featureFlag);
} }
public InstanceConfig set(String fieldName, Object value) public InstanceConfig set(String fieldName, Object value)
@ -200,13 +217,14 @@ public class InstanceConfig implements IInstanceConfig
return (String)params.get(name); return (String)params.get(name);
} }
public static InstanceConfig generate(int nodeNum, File root, String token) public static InstanceConfig generate(int nodeNum, int subnet, File root, String token)
{ {
String ipPrefix = "127.0." + subnet + ".";
return new InstanceConfig(nodeNum, return new InstanceConfig(nodeNum,
"127.0.0." + nodeNum, ipPrefix + nodeNum,
"127.0.0." + nodeNum, ipPrefix + nodeNum,
"127.0.0." + nodeNum, ipPrefix + nodeNum,
"127.0.0." + nodeNum, ipPrefix + nodeNum,
String.format("%s/node%d/saved_caches", root, nodeNum), String.format("%s/node%d/saved_caches", root, nodeNum),
new String[] { String.format("%s/node%d/data", root, nodeNum) }, new String[] { String.format("%s/node%d/data", root, nodeNum) },
String.format("%s/node%d/commitlog", root, nodeNum), String.format("%s/node%d/commitlog", root, nodeNum),

View File

@ -27,28 +27,36 @@ import java.io.Serializable;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import org.apache.cassandra.concurrent.NamedThreadFactory; import org.apache.cassandra.concurrent.NamedThreadFactory;
import org.apache.cassandra.distributed.api.IIsolatedExecutor; import org.apache.cassandra.distributed.api.IIsolatedExecutor;
import org.apache.cassandra.utils.ExecutorUtils;
public class IsolatedExecutor implements IIsolatedExecutor public class IsolatedExecutor implements IIsolatedExecutor
{ {
final ExecutorService isolatedExecutor; final ExecutorService isolatedExecutor;
private final String name;
private final ClassLoader classLoader; private final ClassLoader classLoader;
private final Method deserializeOnInstance; private final Method deserializeOnInstance;
IsolatedExecutor(String name, ClassLoader classLoader) IsolatedExecutor(String name, ClassLoader classLoader)
{ {
this.name = name;
this.isolatedExecutor = Executors.newCachedThreadPool(new NamedThreadFactory("isolatedExecutor", Thread.NORM_PRIORITY, classLoader, new ThreadGroup(name))); this.isolatedExecutor = Executors.newCachedThreadPool(new NamedThreadFactory("isolatedExecutor", Thread.NORM_PRIORITY, classLoader, new ThreadGroup(name)));
this.classLoader = classLoader; this.classLoader = classLoader;
this.deserializeOnInstance = lookupDeserializeOneObject(classLoader); this.deserializeOnInstance = lookupDeserializeOneObject(classLoader);
@ -57,9 +65,40 @@ public class IsolatedExecutor implements IIsolatedExecutor
public Future<Void> shutdown() public Future<Void> shutdown()
{ {
isolatedExecutor.shutdown(); isolatedExecutor.shutdown();
ThrowingRunnable.toRunnable(((URLClassLoader) classLoader)::close).run();
return CompletableFuture.runAsync(ThrowingRunnable.toRunnable(() -> isolatedExecutor.awaitTermination(60, TimeUnit.SECONDS)), /* Use a thread pool with a core pool size of zero to terminate the thread as soon as possible
Executors.newSingleThreadExecutor()); ** so the instance class loader can be garbage collected. Uses a custom thread factory
** rather than NamedThreadFactory to avoid calling FastThreadLocal.removeAll() in 3.0 and up
** as it was observed crashing during test failures and made it harder to find the real cause.
*/
ThreadFactory threadFactory = (Runnable r) -> {
Thread t = new Thread(r, name + "_shutdown");
t.setDaemon(true);
return t;
};
ExecutorService shutdownExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(), threadFactory);
return shutdownExecutor.submit(() -> {
try
{
ExecutorUtils.awaitTermination(60, TimeUnit.SECONDS, isolatedExecutor);
// Shutdown logging last - this is not ideal as the logging subsystem is initialized
// outsize of this class, however doing it this way provides access to the full
// logging system while termination is taking place.
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.stop();
// Close the instance class loader after shutting down the isolatedExecutor and logging
// in case error handling triggers loading additional classes
((URLClassLoader) classLoader).close();
}
finally
{
shutdownExecutor.shutdownNow();
}
return null;
});
} }
public <O> CallableNoExcept<Future<O>> async(CallableNoExcept<O> call) { return () -> isolatedExecutor.submit(call); } public <O> CallableNoExcept<Future<O>> async(CallableNoExcept<O> call) { return () -> isolatedExecutor.submit(call); }

View File

@ -32,33 +32,20 @@ import org.apache.cassandra.net.MessagingService;
public class MessageFilters implements IMessageFilters public class MessageFilters implements IMessageFilters
{ {
private final ICluster cluster;
private final Set<Filter> filters = new CopyOnWriteArraySet<>(); private final Set<Filter> filters = new CopyOnWriteArraySet<>();
public MessageFilters(AbstractCluster cluster) public boolean permit(IInstance from, IInstance to, int verb)
{ {
this.cluster = cluster; if (from == null || to == null)
} return false; // cannot deliver
int fromNum = from.config().num();
int toNum = to.config().num();
public BiConsumer<InetAddressAndPort, IMessage> filter(BiConsumer<InetAddressAndPort, IMessage> applyIfNotFiltered) for (Filter filter : filters)
{ if (filter.matches(fromNum, toNum, verb))
return (toAddress, message) -> return false;
{
IInstance from = cluster.get(message.from());
IInstance to = cluster.get(toAddress);
if (from == null || to == null)
return; // cannot deliver
int fromNum = from.config().num();
int toNum = to.config().num();
int verb = message.verb();
for (Filter filter : filters)
{
if (filter.matches(fromNum, toNum, verb))
return;
}
applyIfNotFiltered.accept(toAddress, message); return true;
};
} }
public class Filter implements IMessageFilters.Filter public class Filter implements IMessageFilters.Filter

View File

@ -29,6 +29,7 @@ import org.junit.Assert;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.apache.cassandra.distributed.impl.AbstractCluster; import org.apache.cassandra.distributed.impl.AbstractCluster;
import org.apache.cassandra.distributed.impl.IsolatedExecutor;
public class DistributedTestBase public class DistributedTestBase
{ {
@ -41,15 +42,38 @@ public class DistributedTestBase
public static String KEYSPACE = "distributed_test_keyspace"; public static String KEYSPACE = "distributed_test_keyspace";
public static void nativeLibraryWorkaround()
{
// Disable the Netty tcnative library otherwise the io.netty.internal.tcnative.CertificateCallbackTask,
// CertificateVerifierTask, SSLPrivateKeyMethodDecryptTask, SSLPrivateKeyMethodSignTask,
// SSLPrivateKeyMethodTask, and SSLTask hold a gcroot against the InstanceClassLoader.
System.setProperty("cassandra.disable_tcactive_openssl", "true");
System.setProperty("io.netty.transport.noNative", "true");
}
public static void processReaperWorkaround()
{
// Make sure the 'process reaper' thread is initially created under the main classloader,
// otherwise it gets created with the contextClassLoader pointing to an InstanceClassLoader
// which prevents it from being garbage collected.
IsolatedExecutor.ThrowingRunnable.toRunnable(() -> new ProcessBuilder().command("true").start().waitFor()).run();
}
@BeforeClass @BeforeClass
public static void setup() public static void setup()
{ {
System.setProperty("org.apache.cassandra.disable_mbean_registration", "true"); System.setProperty("org.apache.cassandra.disable_mbean_registration", "true");
nativeLibraryWorkaround();
processReaperWorkaround();
}
static String withKeyspace(String replaceIn)
{
return String.format(replaceIn, KEYSPACE);
} }
protected static <C extends AbstractCluster<?>> C init(C cluster) protected static <C extends AbstractCluster<?>> C init(C cluster)
{ {
cluster.startup();
cluster.schemaChange("CREATE KEYSPACE " + KEYSPACE + " WITH replication = {'class': 'SimpleStrategy', 'replication_factor': " + cluster.size() + "};"); cluster.schemaChange("CREATE KEYSPACE " + KEYSPACE + " WITH replication = {'class': 'SimpleStrategy', 'replication_factor': " + cluster.size() + "};");
return cluster; return cluster;
} }

View File

@ -20,8 +20,6 @@ package org.apache.cassandra.distributed.test;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -30,17 +28,17 @@ import com.google.common.collect.Iterables;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.dht.Token; import org.apache.cassandra.dht.Token;
import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.Feature;
import org.apache.cassandra.gms.ApplicationState; import org.apache.cassandra.gms.ApplicationState;
import org.apache.cassandra.gms.EndpointState; import org.apache.cassandra.gms.EndpointState;
import org.apache.cassandra.gms.Gossiper; import org.apache.cassandra.gms.Gossiper;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FBUtilities;
import static org.apache.cassandra.distributed.api.Feature.GOSSIP;
import static org.apache.cassandra.distributed.api.Feature.NETWORK;
public class GossipTest extends DistributedTestBase public class GossipTest extends DistributedTestBase
{ {
@ -48,8 +46,12 @@ public class GossipTest extends DistributedTestBase
public void nodeDownDuringMove() throws Throwable public void nodeDownDuringMove() throws Throwable
{ {
int liveCount = 1; int liveCount = 1;
System.setProperty("cassandra.ring_delay_ms", "5000"); // down from 30s default
System.setProperty("cassandra.consistent.rangemovement", "false"); System.setProperty("cassandra.consistent.rangemovement", "false");
try (Cluster cluster = Cluster.create(2 + liveCount, EnumSet.of(Feature.GOSSIP))) System.setProperty("cassandra.consistent.simultaneousmoves.allow", "true");
try (Cluster cluster = Cluster.build(2 + liveCount)
.withConfig(config -> config.with(NETWORK).with(GOSSIP))
.createWithoutStarting())
{ {
int fail = liveCount + 1; int fail = liveCount + 1;
int late = fail + 1; int late = fail + 1;

View File

@ -0,0 +1,202 @@
/*
* 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.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.List;
import java.util.function.Consumer;
import javax.management.MBeanServer;
import org.junit.Ignore;
import org.junit.Test;
import com.sun.management.HotSpotDiagnosticMXBean;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.impl.InstanceConfig;
import org.apache.cassandra.gms.Gossiper;
import org.apache.cassandra.service.CassandraDaemon;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.SigarLibrary;
import static org.apache.cassandra.distributed.api.Feature.GOSSIP;
import static org.apache.cassandra.distributed.api.Feature.NETWORK;
/* Resource Leak Test - useful when tracking down issues with in-JVM framework cleanup.
* All objects referencing the InstanceClassLoader need to be garbage collected or
* the JVM runs out of metaspace. This test also calls out to lsof to check which
* file handles are still opened.
*
* This is intended to be a burn type test where it is run outside of the test suites
* when a problem is detected (like OutOfMetaspace exceptions).
*
* Currently this test demonstrates that the InstanceClassLoader is cleaned up (load up
* the final hprof and check that the class loaders are not reachable from a GC root),
* but it shows that the file handles for Data/Index files are being leaked.
*/
@Ignore
public class ResourceLeakTest extends DistributedTestBase
{
// Parameters to adjust while hunting for leaks
final int numTestLoops = 1; // Set this value high to crash on leaks, or low when tracking down an issue.
final boolean dumpEveryLoop = false; // Dump heap & possibly files every loop
final boolean dumpFileHandles = false; // Call lsof whenever dumping resources
final boolean forceCollection = false; // Whether to explicitly force finalization/gc for smaller heap dumps
final long finalWaitMillis = 0l; // Number of millis to wait before final resource dump to give gc a chance
static final SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
static final String when = format.format(Date.from(Instant.now()));
static String outputFilename(String base, String description, String extension)
{
Path p = FileSystems.getDefault().getPath("build", "test",
String.join("-", when, base, description) + extension);
return p.toString();
}
/**
* Retrieves the process ID or <code>null</code> if the process ID cannot be retrieved.
* @return the process ID or <code>null</code> if the process ID cannot be retrieved.
*
* (Duplicated from HeapUtils to avoid refactoring older releases where this test is useful).
*/
private static Long getProcessId()
{
// Once Java 9 is ready the process API should provide a better way to get the process ID.
long pid = SigarLibrary.instance.getPid();
if (pid >= 0)
return Long.valueOf(pid);
return getProcessIdFromJvmName();
}
/**
* Retrieves the process ID from the JVM name.
* @return the process ID or <code>null</code> if the process ID cannot be retrieved.
*/
private static Long getProcessIdFromJvmName()
{
// the JVM name in Oracle JVMs is: '<pid>@<hostname>' but this might not be the case on all JVMs
String jvmName = ManagementFactory.getRuntimeMXBean().getName();
try
{
return Long.parseLong(jvmName.split("@")[0]);
}
catch (NumberFormatException e)
{
// ignore
}
return null;
}
static void dumpHeap(String description, boolean live) throws IOException
{
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
HotSpotDiagnosticMXBean mxBean = ManagementFactory.newPlatformMXBeanProxy(
server, "com.sun.management:type=HotSpotDiagnostic", HotSpotDiagnosticMXBean.class);
mxBean.dumpHeap(outputFilename("heap", description, ".hprof"), live);
}
static void dumpOpenFiles(String description) throws IOException, InterruptedException
{
long pid = getProcessId();
ProcessBuilder map = new ProcessBuilder("/usr/sbin/lsof", "-p", Long.toString(pid));
File output = new File(outputFilename("lsof", description, ".txt"));
map.redirectOutput(output);
map.redirectErrorStream(true);
map.start().waitFor();
}
void dumpResources(String description) throws IOException, InterruptedException
{
dumpHeap(description, false);
if (dumpFileHandles)
{
dumpOpenFiles(description);
}
}
void doTest(int numClusterNodes, Consumer<InstanceConfig> updater) throws Throwable
{
for (int loop = 0; loop < numTestLoops; loop++)
{
try (Cluster cluster = Cluster.build(numClusterNodes).withConfig(updater).start())
{
if (cluster.get(1).config().has(GOSSIP)) // Wait for gossip to settle on the seed node
cluster.get(1).runOnInstance(() -> Gossiper.waitToSettle());
init(cluster);
String tableName = "tbl" + loop;
cluster.schemaChange("CREATE TABLE " + KEYSPACE + "." + tableName + " (pk int, ck int, v int, PRIMARY KEY (pk, ck))");
cluster.coordinator(1).execute("INSERT INTO " + KEYSPACE + "." + tableName + "(pk,ck,v) VALUES (0,0,0)", ConsistencyLevel.ALL);
cluster.get(1).callOnInstance(() -> FBUtilities.waitOnFutures(Keyspace.open(KEYSPACE).flush()));
if (dumpEveryLoop)
{
dumpResources(String.format("loop%03d", loop));
}
}
catch (Throwable tr)
{
System.out.println("Dumping resources for exception: " + tr.getMessage());
tr.printStackTrace();
dumpResources("exception");
}
if (forceCollection)
{
System.runFinalization();
System.gc();
}
}
}
@Test
public void looperTest() throws Throwable
{
doTest(1, config -> {});
if (forceCollection)
{
System.runFinalization();
System.gc();
Thread.sleep(finalWaitMillis);
}
dumpResources("final");
}
@Test
public void looperGossipNetworkTest() throws Throwable
{
doTest(2, config -> config.with(GOSSIP).with(NETWORK));
if (forceCollection)
{
System.runFinalization();
System.gc();
Thread.sleep(finalWaitMillis);
}
dumpResources("final-gossip-network");
}
}

View File

@ -22,6 +22,7 @@ import java.io.OutputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@ -56,7 +57,7 @@ public class SEPExecutorTest
} }
// shutdown does not guarantee that threads are actually dead once it exits, only that they will stop promptly afterwards // shutdown does not guarantee that threads are actually dead once it exits, only that they will stop promptly afterwards
sharedPool.shutdownAndWait(); sharedPool.shutdownAndWait(1L, TimeUnit.MINUTES);
for (Thread thread : Thread.getAllStackTraces().keySet()) for (Thread thread : Thread.getAllStackTraces().keySet())
{ {
if (thread.getName().contains(MAGIC)) if (thread.getName().contains(MAGIC))