From be1f050bc8c0cd695a42952e3fc84625ad48d83a Mon Sep 17 00:00:00 2001 From: Benedict Elliott Smith Date: Wed, 28 Jul 2021 19:47:45 +0100 Subject: [PATCH] [CASSANDRA-16925] CEP-10 Phase 1: Mockable Task Execution Co-authored-by: Benedict Elliott Smith Co-authored-by: Sam Tunnicliffe --- build.xml | 21 +- checkstyle.xml | 42 +- checkstyle_suppressions.xml | 32 +- .../org/apache/cassandra/auth/AuthCache.java | 47 +- .../cassandra/auth/AuthenticatedUser.java | 2 - .../cassandra/batchlog/BatchlogManager.java | 13 +- .../cassandra/cache/AutoSavingCache.java | 25 +- .../AbstractLocalAwareExecutorService.java | 229 -------- ...DebuggableScheduledThreadPoolExecutor.java | 127 ----- .../DebuggableThreadPoolExecutor.java | 426 --------------- .../concurrent/ExecutionFailure.java | 179 +++++++ .../cassandra/concurrent/ExecutorBuilder.java | 92 ++++ .../concurrent/ExecutorBuilderFactory.java | 81 +++ .../cassandra/concurrent/ExecutorFactory.java | 266 ++++++++++ .../cassandra/concurrent/ExecutorLocals.java | 85 +-- .../cassandra/concurrent/ExecutorPlus.java | 183 +++++++ .../cassandra/concurrent/FutureTask.java | 149 ++++++ .../concurrent/FutureTaskWithResources.java | 57 ++ .../concurrent/ImmediateExecutor.java | 113 +++- .../concurrent/InfiniteLoopExecutor.java | 97 +++- .../cassandra/concurrent/Interruptible.java | 49 ++ .../JMXEnabledSingleThreadExecutor.java | 82 --- .../JMXEnabledThreadPoolExecutor.java | 191 ------- .../concurrent/LocalAwareExecutorPlus.java | 26 + .../concurrent/LocalAwareExecutorService.java | 77 --- .../LocalAwareSequentialExecutorPlus.java | 26 + .../LocalAwareSingleThreadExecutorPlus.java | 26 + .../LocalAwareThreadPoolExecutorPlus.java | 26 + .../concurrent/NamedThreadFactory.java | 110 +++- .../concurrent/ResizableThreadPool.java | 39 +- ...an.java => ResizableThreadPoolMXBean.java} | 2 +- .../cassandra/concurrent/SEPExecutor.java | 76 ++- .../cassandra/concurrent/SEPWorker.java | 10 +- .../concurrent/ScheduledExecutorPlus.java | 25 + .../concurrent/ScheduledExecutors.java | 14 +- .../ScheduledThreadPoolExecutorPlus.java | 240 +++++++++ .../concurrent/SequentialExecutorPlus.java | 53 ++ .../concurrent/SharedExecutorPool.java | 19 +- .../{ExecutorLocal.java => Shutdownable.java} | 26 +- .../concurrent/SingleThreadExecutorPlus.java | 100 ++++ .../apache/cassandra/concurrent/Stage.java | 121 ++--- .../cassandra/concurrent/SyncFutureTask.java | 70 +++ .../cassandra/concurrent/TaskFactory.java | 178 +++++++ .../concurrent/ThreadPoolExecutorBase.java | 186 +++++++ .../concurrent/ThreadPoolExecutorBuilder.java | 204 ++++++++ .../ThreadPoolExecutorJMXAdapter.java | 246 +++++++++ .../concurrent/ThreadPoolExecutorPlus.java | 125 +++++ .../concurrent/WrappedExecutorPlus.java | 178 +++++++ .../cql3/functions/JavaBasedUDFunction.java | 2 +- .../cql3/functions/ScriptBasedUDFunction.java | 2 +- .../cql3/functions/UDFExecutorService.java | 40 +- .../cassandra/cql3/functions/UDFunction.java | 4 +- .../cassandra/db/ColumnFamilyStore.java | 115 ++-- .../org/apache/cassandra/db/Keyspace.java | 25 +- .../org/apache/cassandra/db/Mutation.java | 4 +- .../cassandra/db/MutationVerbHandler.java | 5 +- .../apache/cassandra/db/SystemKeyspace.java | 4 +- .../AbstractCommitLogSegmentManager.java | 138 ++--- .../commitlog/AbstractCommitLogService.java | 99 ++-- .../db/commitlog/CommitLogArchiver.java | 9 +- .../db/commitlog/CommitLogReplayer.java | 3 +- .../db/commitlog/CommitLogSegment.java | 2 +- .../commitlog/CommitLogSegmentManagerCDC.java | 8 +- .../db/compaction/CompactionLogger.java | 13 +- .../db/compaction/CompactionManager.java | 163 +++--- .../cassandra/db/compaction/Scrubber.java | 1 + .../cassandra/db/context/CounterContext.java | 4 +- .../db/monitoring/MonitoringTask.java | 1 - .../CassandraKeyspaceRepairManager.java | 14 +- .../db/repair/PendingAntiCompaction.java | 36 +- .../apache/cassandra/db/view/ViewBuilder.java | 47 +- .../cassandra/db/view/ViewBuilderTask.java | 1 - .../apache/cassandra/dht/BootStrapper.java | 5 +- .../org/apache/cassandra/gms/Gossiper.java | 24 +- .../cassandra/hadoop/cql3/CqlInputFormat.java | 3 +- src/java/org/apache/cassandra/hints/Hint.java | 7 +- .../cassandra/hints/HintVerbHandler.java | 2 +- .../hints/HintsDispatchExecutor.java | 24 +- .../apache/cassandra/hints/HintsService.java | 5 +- .../cassandra/hints/HintsWriteExecutor.java | 12 +- .../index/SecondaryIndexManager.java | 98 ++-- .../cassandra/index/sasi/TermIterator.java | 16 +- .../sasi/disk/PerSSTableIndexWriter.java | 28 +- .../io/sstable/IndexSummaryManager.java | 9 +- .../sstable/SSTableSimpleUnsortedWriter.java | 12 +- .../io/sstable/format/SSTableReader.java | 68 +-- .../cassandra/metrics/CompactionMetrics.java | 6 +- .../org/apache/cassandra/metrics/Sampler.java | 33 +- .../cassandra/metrics/ThreadPoolMetrics.java | 4 +- .../cassandra/net/AsyncChannelPromise.java | 2 +- .../apache/cassandra/net/FutureCombiner.java | 86 --- .../apache/cassandra/net/FutureDelegate.java | 46 +- .../apache/cassandra/net/FutureResult.java | 7 +- .../cassandra/net/InboundMessageHandler.java | 2 +- .../apache/cassandra/net/InboundSockets.java | 9 +- .../cassandra/net/MessagingService.java | 5 +- .../cassandra/net/OutboundConnection.java | 4 +- .../net/OutboundConnectionInitiator.java | 12 +- .../cassandra/net/OutboundConnections.java | 7 +- .../cassandra/net/RequestCallbacks.java | 6 +- .../apache/cassandra/net/SocketFactory.java | 4 +- .../repair/KeyspaceRepairManager.java | 14 +- .../apache/cassandra/repair/RepairJob.java | 110 ++-- .../cassandra/repair/RepairRunnable.java | 141 ++--- .../cassandra/repair/RepairSession.java | 20 +- .../repair/asymmetric/RangeDenormalizer.java | 1 - .../repair/consistent/CoordinatorSession.java | 99 ++-- .../repair/consistent/LocalSessions.java | 30 +- .../schema/MigrationCoordinator.java | 10 +- .../cassandra/schema/MigrationManager.java | 5 +- .../service/ActiveRepairService.java | 69 +-- .../cassandra/service/CacheService.java | 7 +- .../cassandra/service/CassandraDaemon.java | 34 +- .../apache/cassandra/service/ClientWarn.java | 21 +- .../PendingRangeCalculatorService.java | 106 ++-- ...dingRangeCalculatorServiceDiagnostics.java | 21 +- .../PendingRangeCalculatorServiceEvent.java | 14 +- .../cassandra/service/StorageService.java | 50 +- .../service/snapshot/SnapshotManager.java | 5 +- .../streaming/StreamReceiveTask.java | 5 +- .../streaming/StreamResultFuture.java | 4 +- .../cassandra/streaming/StreamSession.java | 12 +- .../streaming/StreamTransferTask.java | 5 +- .../async/NettyStreamingMessageSender.java | 16 +- .../cassandra/tools/GenerateTokens.java | 1 - .../cassandra/tracing/TraceStateImpl.java | 19 +- .../org/apache/cassandra/tracing/Tracing.java | 21 +- .../cassandra/transport/Dispatcher.java | 10 +- .../org/apache/cassandra/utils/Closeable.java | 24 + .../utils/DiagnosticSnapshotService.java | 4 +- .../apache/cassandra/utils/ExecutorUtils.java | 13 +- .../apache/cassandra/utils/FBUtilities.java | 103 +--- .../utils/JVMStabilityInspector.java | 16 + .../cassandra/utils/RecomputingSupplier.java | 19 +- .../apache/cassandra/utils/WithResources.java | 88 ++++ .../utils/binlog/ExternalArchiver.java | 4 +- .../utils/concurrent/AbstractFuture.java | 489 ++++++++++++++++++ .../utils/concurrent/AsyncFuture.java | 376 ++------------ .../utils/concurrent/AsyncPromise.java | 98 +++- .../cassandra/utils/concurrent/Awaitable.java | 2 - .../cassandra/utils/concurrent/Future.java | 58 ++- .../utils/concurrent/FutureCombiner.java | 262 ++++++++++ .../utils/concurrent/ImmediateFuture.java | 52 ++ .../utils/concurrent/IntrusiveStack.java | 156 ++++++ .../utils/concurrent/ListenerList.java | 360 +++++++++++++ .../cassandra/utils/concurrent/Promise.java | 12 +- .../cassandra/utils/concurrent/Ref.java | 9 +- .../utils/concurrent/RunnableFuture.java | 23 + .../utils/concurrent/SyncFuture.java | 160 ++++++ .../cassandra/utils/concurrent/WaitQueue.java | 2 + .../cassandra/utils/memory/BufferPool.java | 7 +- .../utils/memory/MemtableCleaner.java | 4 +- .../utils/memory/MemtableCleanerThread.java | 49 +- .../cassandra/utils/memory/MemtablePool.java | 1 - .../progress/jmx/JMXBroadcastExecutor.java | 5 +- .../cassandra/net/ConnectionBurnTest.java | 3 +- .../cassandra/distributed/impl/Instance.java | 15 +- .../distributed/test/IPMembershipTest.java | 1 - .../distributed/test/JVMDTestTest.java | 4 +- ...yInspectorCorruptSSTableExceptionTest.java | 4 +- .../distributed/test/SecondaryIndexTest.java | 108 ++++ .../test/metrics/HintsServiceMetricsTest.java | 3 +- .../apache/cassandra/cql3/ViewLongTest.java | 2 +- .../cassandra/cache/CacheProviderTest.java | 2 +- .../concurrent/AbstractExecutorPlusTest.java | 200 +++++++ ...ggableScheduledThreadPoolExecutorTest.java | 4 +- .../DebuggableThreadPoolExecutorTest.java | 93 ++-- .../concurrent/ExecutorPlusTest.java | 38 ++ .../concurrent/InfiniteLoopExecutorTest.java | 80 +++ .../LocalAwareExecutorPlusTest.java | 52 ++ .../cassandra/concurrent/SEPExecutorTest.java | 11 +- .../cassandra/concurrent/WaitQueueTest.java | 4 +- .../org/apache/cassandra/cql3/ViewTest.java | 2 +- .../miscellaneous/CrcCheckChanceTest.java | 2 +- .../org/apache/cassandra/db/CellSpecTest.java | 4 +- .../cassandra/db/ClusteringHeapSizeTest.java | 4 +- .../apache/cassandra/db/NativeCellTest.java | 3 +- .../cassandra/db/RecoveryManagerTest.java | 46 +- .../AbstractCommitLogServiceTest.java | 14 +- .../CommitLogInitWithExceptionTest.java | 2 +- .../CommitLogSegmentBackpressureTest.java | 4 +- .../db/compaction/CompactionExecutorTest.java | 25 +- .../CorruptedSSTablesCompactionsTest.java | 2 +- .../db/repair/PendingAntiCompactionTest.java | 20 +- .../streaming/CassandraStreamManagerTest.java | 2 +- .../cassandra/hints/HintsBufferTest.java | 2 +- .../io/sstable/IndexSummaryManagerTest.java | 2 +- .../io/sstable/SSTableRewriterTest.java | 2 +- .../apache/cassandra/metrics/SamplerTest.java | 2 +- .../metrics/ThreadPoolMetricsTest.java | 32 +- .../net/AsyncChannelPromiseTest.java | 57 +- .../apache/cassandra/net/HandshakeTest.java | 2 +- .../cassandra/repair/RepairJobTest.java | 13 +- .../consistent/CoordinatorMessagingTest.java | 25 +- .../consistent/CoordinatorSessionTest.java | 35 +- .../repair/consistent/LocalSessionTest.java | 43 +- .../schema/MigrationCoordinatorTest.java | 2 +- .../service/ActiveRepairServiceTest.java | 10 +- .../apache/cassandra/service/RemoveTest.java | 2 +- ...SSTableStreamingCorrectFilesCountTest.java | 4 +- .../streaming/StreamTransferTaskTest.java | 2 +- .../cassandra/tools/BulkLoaderTest.java | 8 +- .../cassandra/tools/GetVersionTest.java | 2 +- .../cassandra/tools/OfflineToolUtils.java | 32 +- .../tools/SSTableExpiredBlockersTest.java | 2 +- .../tools/SSTableExportSchemaLoadingTest.java | 2 +- .../cassandra/tools/SSTableExportTest.java | 2 +- .../tools/SSTableLevelResetterTest.java | 2 +- .../tools/SSTableMetadataViewerTest.java | 4 +- .../tools/SSTableOfflineRelevelTest.java | 2 +- .../tools/SSTableRepairedAtSetterTest.java | 8 +- .../tools/ToolsSchemaLoadingTest.java | 10 +- .../concurrent/AbstractTestAsyncPromise.java | 299 +++++++++-- .../utils/concurrent/AsyncPromiseTest.java | 206 +++++++- .../utils/concurrent/ImmediateFutureTest.java | 106 ++++ .../memory/MemtableCleanerThreadTest.java | 47 +- .../utils/memory/NativeAllocatorTest.java | 3 +- .../cassandra/fqltool/QueryReplayer.java | 5 +- .../cassandra/stress/CompactionStress.java | 6 +- 219 files changed, 7660 insertions(+), 3398 deletions(-) delete mode 100644 src/java/org/apache/cassandra/concurrent/AbstractLocalAwareExecutorService.java delete mode 100644 src/java/org/apache/cassandra/concurrent/DebuggableScheduledThreadPoolExecutor.java delete mode 100644 src/java/org/apache/cassandra/concurrent/DebuggableThreadPoolExecutor.java create mode 100644 src/java/org/apache/cassandra/concurrent/ExecutionFailure.java create mode 100644 src/java/org/apache/cassandra/concurrent/ExecutorBuilder.java create mode 100644 src/java/org/apache/cassandra/concurrent/ExecutorBuilderFactory.java create mode 100644 src/java/org/apache/cassandra/concurrent/ExecutorFactory.java create mode 100644 src/java/org/apache/cassandra/concurrent/ExecutorPlus.java create mode 100644 src/java/org/apache/cassandra/concurrent/FutureTask.java create mode 100644 src/java/org/apache/cassandra/concurrent/FutureTaskWithResources.java create mode 100644 src/java/org/apache/cassandra/concurrent/Interruptible.java delete mode 100644 src/java/org/apache/cassandra/concurrent/JMXEnabledSingleThreadExecutor.java delete mode 100644 src/java/org/apache/cassandra/concurrent/JMXEnabledThreadPoolExecutor.java create mode 100644 src/java/org/apache/cassandra/concurrent/LocalAwareExecutorPlus.java delete mode 100644 src/java/org/apache/cassandra/concurrent/LocalAwareExecutorService.java create mode 100644 src/java/org/apache/cassandra/concurrent/LocalAwareSequentialExecutorPlus.java create mode 100644 src/java/org/apache/cassandra/concurrent/LocalAwareSingleThreadExecutorPlus.java create mode 100644 src/java/org/apache/cassandra/concurrent/LocalAwareThreadPoolExecutorPlus.java rename src/java/org/apache/cassandra/concurrent/{JMXEnabledThreadPoolExecutorMBean.java => ResizableThreadPoolMXBean.java} (95%) create mode 100644 src/java/org/apache/cassandra/concurrent/ScheduledExecutorPlus.java create mode 100644 src/java/org/apache/cassandra/concurrent/ScheduledThreadPoolExecutorPlus.java create mode 100644 src/java/org/apache/cassandra/concurrent/SequentialExecutorPlus.java rename src/java/org/apache/cassandra/concurrent/{ExecutorLocal.java => Shutdownable.java} (52%) create mode 100644 src/java/org/apache/cassandra/concurrent/SingleThreadExecutorPlus.java create mode 100644 src/java/org/apache/cassandra/concurrent/SyncFutureTask.java create mode 100644 src/java/org/apache/cassandra/concurrent/TaskFactory.java create mode 100644 src/java/org/apache/cassandra/concurrent/ThreadPoolExecutorBase.java create mode 100644 src/java/org/apache/cassandra/concurrent/ThreadPoolExecutorBuilder.java create mode 100644 src/java/org/apache/cassandra/concurrent/ThreadPoolExecutorJMXAdapter.java create mode 100644 src/java/org/apache/cassandra/concurrent/ThreadPoolExecutorPlus.java create mode 100644 src/java/org/apache/cassandra/concurrent/WrappedExecutorPlus.java delete mode 100644 src/java/org/apache/cassandra/net/FutureCombiner.java create mode 100644 src/java/org/apache/cassandra/utils/Closeable.java create mode 100644 src/java/org/apache/cassandra/utils/WithResources.java create mode 100644 src/java/org/apache/cassandra/utils/concurrent/AbstractFuture.java create mode 100644 src/java/org/apache/cassandra/utils/concurrent/FutureCombiner.java create mode 100644 src/java/org/apache/cassandra/utils/concurrent/ImmediateFuture.java create mode 100644 src/java/org/apache/cassandra/utils/concurrent/IntrusiveStack.java create mode 100644 src/java/org/apache/cassandra/utils/concurrent/ListenerList.java create mode 100644 src/java/org/apache/cassandra/utils/concurrent/RunnableFuture.java create mode 100644 src/java/org/apache/cassandra/utils/concurrent/SyncFuture.java create mode 100644 test/distributed/org/apache/cassandra/distributed/test/SecondaryIndexTest.java create mode 100644 test/unit/org/apache/cassandra/concurrent/AbstractExecutorPlusTest.java create mode 100644 test/unit/org/apache/cassandra/concurrent/ExecutorPlusTest.java create mode 100644 test/unit/org/apache/cassandra/concurrent/InfiniteLoopExecutorTest.java create mode 100644 test/unit/org/apache/cassandra/concurrent/LocalAwareExecutorPlusTest.java create mode 100644 test/unit/org/apache/cassandra/utils/concurrent/ImmediateFutureTest.java diff --git a/build.xml b/build.xml index e16e9361cb..f5acaff30c 100644 --- a/build.xml +++ b/build.xml @@ -716,17 +716,18 @@ artifactId="cassandra-parent" version="${version}" relativePath="${final.name}-parent.pom"/> - - - + + + - - - - - - - + + + + + + + + diff --git a/checkstyle.xml b/checkstyle.xml index 8a90cc7a09..c745c2e976 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -1,21 +1,20 @@ + 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. +--> @@ -39,10 +38,17 @@ - - + + + + + + + + + diff --git a/checkstyle_suppressions.xml b/checkstyle_suppressions.xml index 3a2ae0c4a2..860efc50e7 100644 --- a/checkstyle_suppressions.xml +++ b/checkstyle_suppressions.xml @@ -1,21 +1,20 @@ + 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. +--> + diff --git a/src/java/org/apache/cassandra/auth/AuthCache.java b/src/java/org/apache/cassandra/auth/AuthCache.java index 32e9f0f943..e69cd6ce12 100644 --- a/src/java/org/apache/cassandra/auth/AuthCache.java +++ b/src/java/org/apache/cassandra/auth/AuthCache.java @@ -18,7 +18,10 @@ package org.apache.cassandra.auth; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.BooleanSupplier; import java.util.function.Function; import java.util.function.IntConsumer; @@ -29,22 +32,33 @@ import org.slf4j.LoggerFactory; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; -import org.apache.cassandra.concurrent.DebuggableThreadPoolExecutor; +import org.apache.cassandra.concurrent.ExecutorPlus; +import org.apache.cassandra.concurrent.Shutdownable; +import org.apache.cassandra.utils.ExecutorUtils; import org.apache.cassandra.utils.MBeanWrapper; import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; -public class AuthCache implements AuthCacheMBean +public class AuthCache implements AuthCacheMBean, Shutdownable { private static final Logger logger = LoggerFactory.getLogger(AuthCache.class); public static final String MBEAN_NAME_BASE = "org.apache.cassandra.auth:type="; + // Keep a handle on created instances so their executors can be terminated cleanly + private static final Set REGISTRY = new HashSet<>(4); + + public static void shutdownAllAndWait(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException + { + ExecutorUtils.shutdownNowAndWait(timeout, unit, REGISTRY); + } + /** * Underlying cache. LoadingCache will call underlying load function on {@link #get} if key is not present */ protected volatile LoadingCache cache; - private DebuggableThreadPoolExecutor cacheRefreshExecutor; + private ExecutorPlus cacheRefreshExecutor; private String name; private IntConsumer setValidityDelegate; @@ -94,9 +108,10 @@ public class AuthCache implements AuthCacheMBean */ protected void init() { - this.cacheRefreshExecutor = new DebuggableThreadPoolExecutor(name + "Refresh", Thread.NORM_PRIORITY); + this.cacheRefreshExecutor = executorFactory().sequential(name + "Refresh"); cache = initCache(null); MBeanWrapper.instance.registerMBean(this, getObjectName()); + REGISTRY.add(this); } protected void unregisterMBean() @@ -233,4 +248,28 @@ public class AuthCache implements AuthCacheMBean policy.setMaximum(getMaxEntries())); return cache; } + + @Override + public boolean isTerminated() + { + return cacheRefreshExecutor.isTerminated(); + } + + @Override + public void shutdown() + { + cacheRefreshExecutor.shutdown(); + } + + @Override + public Object shutdownNow() + { + return cacheRefreshExecutor.shutdownNow(); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit units) throws InterruptedException + { + return cacheRefreshExecutor.awaitTermination(timeout, units); + } } diff --git a/src/java/org/apache/cassandra/auth/AuthenticatedUser.java b/src/java/org/apache/cassandra/auth/AuthenticatedUser.java index c2d93cad7a..678295c7b5 100644 --- a/src/java/org/apache/cassandra/auth/AuthenticatedUser.java +++ b/src/java/org/apache/cassandra/auth/AuthenticatedUser.java @@ -18,7 +18,6 @@ package org.apache.cassandra.auth; import java.util.Set; - import com.google.common.base.Objects; import org.apache.cassandra.config.DatabaseDescriptor; @@ -164,5 +163,4 @@ public class AuthenticatedUser { return Objects.hashCode(name); } - } diff --git a/src/java/org/apache/cassandra/batchlog/BatchlogManager.java b/src/java/org/apache/cassandra/batchlog/BatchlogManager.java index 799acbc39e..432858c628 100644 --- a/src/java/org/apache/cassandra/batchlog/BatchlogManager.java +++ b/src/java/org/apache/cassandra/batchlog/BatchlogManager.java @@ -28,9 +28,6 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -38,10 +35,11 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Collections2; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.RateLimiter; +import org.apache.cassandra.concurrent.ScheduledExecutorPlus; +import org.apache.cassandra.utils.concurrent.Future; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.cassandra.concurrent.DebuggableScheduledThreadPoolExecutor; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.db.ColumnFamilyStore; @@ -80,6 +78,7 @@ import org.apache.cassandra.utils.UUIDGen; import static com.google.common.collect.Iterables.transform; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.cql3.QueryProcessor.executeInternal; import static org.apache.cassandra.cql3.QueryProcessor.executeInternalWithPaging; import static org.apache.cassandra.net.Verb.MUTATION_REQ; @@ -100,15 +99,13 @@ public class BatchlogManager implements BatchlogManagerMBean private volatile UUID lastReplayedUuid = UUIDGen.minTimeUUID(0); // Single-thread executor service for scheduling and serializing log replay. - private final ScheduledExecutorService batchlogTasks; + private final ScheduledExecutorPlus batchlogTasks; private final RateLimiter rateLimiter = RateLimiter.create(Double.MAX_VALUE); public BatchlogManager() { - ScheduledThreadPoolExecutor executor = new DebuggableScheduledThreadPoolExecutor("BatchlogTasks"); - executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); - batchlogTasks = executor; + batchlogTasks = executorFactory().scheduled(false, "BatchlogTasks"); } public void start() diff --git a/src/java/org/apache/cassandra/cache/AutoSavingCache.java b/src/java/org/apache/cassandra/cache/AutoSavingCache.java index c7a015c453..a9a09fe99e 100644 --- a/src/java/org/apache/cassandra/cache/AutoSavingCache.java +++ b/src/java/org/apache/cassandra/cache/AutoSavingCache.java @@ -17,10 +17,13 @@ */ package org.apache.cassandra.cache; -import java.io.*; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.*; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -28,10 +31,7 @@ import org.cliffc.high_scale_lib.NonBlockingHashSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListeningExecutorService; -import com.google.common.util.concurrent.MoreExecutors; - +import org.apache.cassandra.concurrent.ExecutorPlus; import org.apache.cassandra.concurrent.ScheduledExecutors; import org.apache.cassandra.schema.TableId; import org.apache.cassandra.schema.TableMetadata; @@ -50,6 +50,9 @@ import org.apache.cassandra.service.CacheService; import org.apache.cassandra.utils.JVMStabilityInspector; import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.UUIDGen; +import org.apache.cassandra.utils.concurrent.Future; + +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.utils.Clock.Global.nanoTime; @@ -155,12 +158,12 @@ public class AutoSavingCache extends InstrumentingCache loadSavedAsync() + public Future loadSavedAsync() { - final ListeningExecutorService es = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); + final ExecutorPlus es = executorFactory().sequential("loadSavedCache"); final long start = nanoTime(); - ListenableFuture cacheLoad = es.submit(this::loadSaved); + Future cacheLoad = es.submit(this::loadSaved); cacheLoad.addListener(() -> { if (size() > 0) logger.info("Completed loading ({} ms; {} keys) {} cache", @@ -168,7 +171,7 @@ public class AutoSavingCache extends InstrumentingCache futureTask); - protected abstract void onCompletion(); - - /** Task Submission / Creation / Objects **/ - - public FutureTask submit(Callable task) - { - return submit(newTaskFor(task)); - } - - public FutureTask submit(Runnable task) - { - return submit(newTaskFor(task, null)); - } - - public FutureTask submit(Runnable task, T result) - { - return submit(newTaskFor(task, result)); - } - - public List> invokeAll(Collection> tasks) - { - throw new UnsupportedOperationException(); - } - - public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException - { - throw new UnsupportedOperationException(); - } - - public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException - { - throw new UnsupportedOperationException(); - } - - public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException - { - throw new UnsupportedOperationException(); - } - - protected FutureTask newTaskFor(Runnable runnable, T result) - { - return newTaskFor(runnable, result, ExecutorLocals.create()); - } - - protected FutureTask newTaskFor(Runnable runnable, T result, ExecutorLocals locals) - { - if (locals != null) - { - if (runnable instanceof LocalSessionFutureTask) - return (LocalSessionFutureTask) runnable; - return new LocalSessionFutureTask(runnable, result, locals); - } - if (runnable instanceof FutureTask) - return (FutureTask) runnable; - return new FutureTask<>(runnable, result); - } - - protected FutureTask newTaskFor(Callable callable) - { - if (isTracing()) - { - if (callable instanceof LocalSessionFutureTask) - return (LocalSessionFutureTask) callable; - return new LocalSessionFutureTask(callable, ExecutorLocals.create()); - } - if (callable instanceof FutureTask) - return (FutureTask) callable; - return new FutureTask<>(callable); - } - - private class LocalSessionFutureTask extends FutureTask - { - private final ExecutorLocals locals; - - public LocalSessionFutureTask(Callable callable, ExecutorLocals locals) - { - super(callable); - this.locals = locals; - } - - public LocalSessionFutureTask(Runnable runnable, T result, ExecutorLocals locals) - { - super(runnable, result); - this.locals = locals; - } - - public void run() - { - ExecutorLocals old = ExecutorLocals.create(); - ExecutorLocals.set(locals); - try - { - super.run(); - } - finally - { - ExecutorLocals.set(old); - } - } - } - - class FutureTask extends Condition.Async implements Future, Runnable - { - private boolean failure; - private Object result = this; - private final Callable callable; - - public FutureTask(Callable callable) - { - this.callable = callable; - } - public FutureTask(Runnable runnable, T result) - { - this(Executors.callable(runnable, result)); - } - - public void run() - { - try - { - result = callable.call(); - } - catch (Throwable t) - { - logger.error("Uncaught exception on thread {}", Thread.currentThread(), t); - result = t; - failure = true; - JVMStabilityInspector.inspectThrowable(t); - } - finally - { - signalAll(); - onCompletion(); - } - } - - public boolean cancel(boolean mayInterruptIfRunning) - { - return false; - } - - public boolean isCancelled() - { - return false; - } - - public boolean isDone() - { - return isSignalled(); - } - - public T get() throws InterruptedException, ExecutionException - { - await(); - Object result = this.result; - if (failure) - throw new ExecutionException((Throwable) result); - return (T) result; - } - - public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException - { - if (!await(timeout, unit)) - throw new TimeoutException(); - Object result = this.result; - if (failure) - throw new ExecutionException((Throwable) result); - return (T) result; - } - } - - private FutureTask submit(FutureTask task) - { - addTask(task); - return task; - } - - public void execute(Runnable command) - { - addTask(newTaskFor(command, ExecutorLocals.create())); - } - - public void execute(Runnable command, ExecutorLocals locals) - { - addTask(newTaskFor(command, null, locals)); - } -} diff --git a/src/java/org/apache/cassandra/concurrent/DebuggableScheduledThreadPoolExecutor.java b/src/java/org/apache/cassandra/concurrent/DebuggableScheduledThreadPoolExecutor.java deleted file mode 100644 index 3b9d2ff927..0000000000 --- a/src/java/org/apache/cassandra/concurrent/DebuggableScheduledThreadPoolExecutor.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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.concurrent; - -import java.util.concurrent.*; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.apache.cassandra.service.StorageService; -import org.apache.cassandra.utils.JVMStabilityInspector; - -/** - * Like DebuggableThreadPoolExecutor, DebuggableScheduledThreadPoolExecutor always - * logs exceptions from the tasks it is given, even if Future.get is never called elsewhere. - * - * DebuggableScheduledThreadPoolExecutor also catches exceptions during Task execution - * so that they don't supress subsequent invocations of the task. - * - * Finally, there is a special rejected execution handler for tasks rejected during the shutdown hook. - * - * For fire and forget tasks (like ref tidy) we can safely ignore the exceptions. - * For any callers that care to know their task was rejected we cancel passed task. - */ -public class DebuggableScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor -{ - private static final Logger logger = LoggerFactory.getLogger(DebuggableScheduledThreadPoolExecutor.class); - - public static final RejectedExecutionHandler rejectedExecutionHandler = new RejectedExecutionHandler() - { - public void rejectedExecution(Runnable task, ThreadPoolExecutor executor) - { - if (executor.isShutdown()) - { - if (!StorageService.instance.isShutdown()) - throw new RejectedExecutionException("ScheduledThreadPoolExecutor has shut down."); - - //Give some notification to the caller the task isn't going to run - if (task instanceof Future) - ((Future) task).cancel(false); - - logger.debug("ScheduledThreadPoolExecutor has shut down as part of C* shutdown"); - } - else - { - throw new AssertionError("Unknown rejection of ScheduledThreadPoolExecutor task"); - } - } - }; - - public DebuggableScheduledThreadPoolExecutor(int corePoolSize, String threadPoolName, int priority) - { - super(corePoolSize, new NamedThreadFactory(threadPoolName, priority)); - setRejectedExecutionHandler(rejectedExecutionHandler); - } - - public DebuggableScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) - { - super(corePoolSize, threadFactory); - setRejectedExecutionHandler(rejectedExecutionHandler); - } - - public DebuggableScheduledThreadPoolExecutor(String threadPoolName) - { - this(1, threadPoolName, Thread.NORM_PRIORITY); - setRejectedExecutionHandler(rejectedExecutionHandler); - } - - // We need this as well as the wrapper for the benefit of non-repeating tasks - @Override - public void afterExecute(Runnable r, Throwable t) - { - super.afterExecute(r,t); - DebuggableThreadPoolExecutor.logExceptionsAfterExecute(r, t); - } - - // override scheduling to supress exceptions that would cancel future executions - @Override - public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) - { - return super.scheduleAtFixedRate(new UncomplainingRunnable(command), initialDelay, period, unit); - } - - @Override - public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) - { - return super.scheduleWithFixedDelay(new UncomplainingRunnable(command), initialDelay, delay, unit); - } - - private static class UncomplainingRunnable implements Runnable - { - private final Runnable runnable; - - public UncomplainingRunnable(Runnable runnable) - { - this.runnable = runnable; - } - - public void run() - { - try - { - runnable.run(); - } - catch (Throwable t) - { - JVMStabilityInspector.inspectThrowable(t); - DebuggableThreadPoolExecutor.handleOrLog(t); - } - } - } -} diff --git a/src/java/org/apache/cassandra/concurrent/DebuggableThreadPoolExecutor.java b/src/java/org/apache/cassandra/concurrent/DebuggableThreadPoolExecutor.java deleted file mode 100644 index a2de775dd7..0000000000 --- a/src/java/org/apache/cassandra/concurrent/DebuggableThreadPoolExecutor.java +++ /dev/null @@ -1,426 +0,0 @@ -/* - * 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.concurrent; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Callable; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.RejectedExecutionHandler; -import java.util.concurrent.RunnableFuture; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static org.apache.cassandra.tracing.Tracing.isTracing; - -/** - * This class encorporates some Executor best practices for Cassandra. Most of the executors in the system - * should use or extend this. There are two main improvements over a vanilla TPE: - * - * - If a task throws an exception, the default uncaught exception handler will be invoked; if there is - * no such handler, the exception will be logged. - * - MaximumPoolSize is not supported. Here is what that means (quoting TPE javadoc): - * - * If fewer than corePoolSize threads are running, the Executor always prefers adding a new thread rather than queuing. - * If corePoolSize or more threads are running, the Executor always prefers queuing a request rather than adding a new thread. - * If a request cannot be queued, a new thread is created unless this would exceed maximumPoolSize, in which case, the task will be rejected. - * - * We don't want this last stage of creating new threads if the queue is full; it makes it needlessly difficult to - * reason about the system's behavior. In other words, if DebuggableTPE has allocated our maximum number of (core) - * threads and the queue is full, we want the enqueuer to block. But to allow the number of threads to drop if a - * stage is less busy, core thread timeout is enabled. - */ -public class DebuggableThreadPoolExecutor extends ThreadPoolExecutor implements LocalAwareExecutorService -{ - protected static final Logger logger = LoggerFactory.getLogger(DebuggableThreadPoolExecutor.class); - public static final RejectedExecutionHandler blockingExecutionHandler = new RejectedExecutionHandler() - { - public void rejectedExecution(Runnable task, ThreadPoolExecutor executor) - { - ((DebuggableThreadPoolExecutor) executor).onInitialRejection(task); - BlockingQueue queue = executor.getQueue(); - while (true) - { - if (executor.isShutdown()) - { - ((DebuggableThreadPoolExecutor) executor).onFinalRejection(task); - throw new RejectedExecutionException("ThreadPoolExecutor has shut down"); - } - try - { - if (queue.offer(task, 1000, TimeUnit.MILLISECONDS)) - { - ((DebuggableThreadPoolExecutor) executor).onFinalAccept(task); - break; - } - } - catch (InterruptedException e) - { - throw new AssertionError(e); - } - } - } - }; - - public DebuggableThreadPoolExecutor(String threadPoolName, int priority) - { - this(1, Integer.MAX_VALUE, TimeUnit.SECONDS, new LinkedBlockingQueue(), new NamedThreadFactory(threadPoolName, priority)); - } - - public DebuggableThreadPoolExecutor(int corePoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue queue, ThreadFactory factory) - { - this(corePoolSize, corePoolSize, keepAliveTime, unit, queue, factory); - } - - public DebuggableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory) - { - super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory); - allowCoreThreadTimeOut(true); - - // block task submissions until queue has room. - // this is fighting TPE's design a bit because TPE rejects if queue.offer reports a full queue. - // we'll just override this with a handler that retries until it gets in. ugly, but effective. - // (there is an extensive analysis of the options here at - // http://today.java.net/pub/a/today/2008/10/23/creating-a-notifying-blocking-thread-pool-executor.html) - this.setRejectedExecutionHandler(blockingExecutionHandler); - } - - /** - * Creates a thread pool that creates new threads as needed, but - * will reuse previously constructed threads when they are - * available. - * @param threadPoolName the name of the threads created by this executor - * @return The new DebuggableThreadPoolExecutor - */ - public static DebuggableThreadPoolExecutor createCachedThreadpoolWithMaxSize(String threadPoolName) - { - return new DebuggableThreadPoolExecutor(0, Integer.MAX_VALUE, - 60L, TimeUnit.SECONDS, - new SynchronousQueue(), - new NamedThreadFactory(threadPoolName)); - } - - /** - * Returns a ThreadPoolExecutor with a fixed number of threads. - * When all threads are actively executing tasks, new tasks are queued. - * If (most) threads are expected to be idle most of the time, prefer createWithMaxSize() instead. - * @param threadPoolName the name of the threads created by this executor - * @param size the fixed number of threads for this executor - * @return the new DebuggableThreadPoolExecutor - */ - public static DebuggableThreadPoolExecutor createWithFixedPoolSize(String threadPoolName, int size) - { - return createWithMaximumPoolSize(threadPoolName, size, Integer.MAX_VALUE, TimeUnit.SECONDS); - } - - /** - * Returns a ThreadPoolExecutor with a fixed maximum number of threads, but whose - * threads are terminated when idle for too long. - * When all threads are actively executing tasks, new tasks are queued. - * @param threadPoolName the name of the threads created by this executor - * @param size the maximum number of threads for this executor - * @param keepAliveTime the time an idle thread is kept alive before being terminated - * @param unit tht time unit for {@code keepAliveTime} - * @return the new DebuggableThreadPoolExecutor - */ - public static DebuggableThreadPoolExecutor createWithMaximumPoolSize(String threadPoolName, int size, int keepAliveTime, TimeUnit unit) - { - return new DebuggableThreadPoolExecutor(size, Integer.MAX_VALUE, keepAliveTime, unit, new LinkedBlockingQueue(), new NamedThreadFactory(threadPoolName)); - } - - protected void onInitialRejection(Runnable task) {} - protected void onFinalAccept(Runnable task) {} - protected void onFinalRejection(Runnable task) {} - - public void execute(Runnable command, ExecutorLocals locals) - { - super.execute(locals == null || command instanceof LocalSessionWrapper - ? command - : LocalSessionWrapper.create(command, null, locals)); - } - - public void maybeExecuteImmediately(Runnable command) - { - execute(command); - } - - // execute does not call newTaskFor - @Override - public void execute(Runnable command) - { - super.execute(isTracing() && !(command instanceof LocalSessionWrapper) - ? LocalSessionWrapper.create(command) - : command); - } - - @Override - protected RunnableFuture newTaskFor(Runnable runnable, T result) - { - if (isTracing() && !(runnable instanceof LocalSessionWrapper)) - return LocalSessionWrapper.create(runnable, result); - if (runnable instanceof RunnableFuture) - return new ForwardingRunnableFuture<>((RunnableFuture) runnable, result); - return super.newTaskFor(runnable, result); - } - - @Override - protected RunnableFuture newTaskFor(Callable callable) - { - if (isTracing() && !(callable instanceof LocalSessionWrapper)) - return LocalSessionWrapper.create(callable); - return super.newTaskFor(callable); - } - - @Override - protected void afterExecute(Runnable r, Throwable t) - { - super.afterExecute(r, t); - - maybeResetTraceSessionWrapper(r); - logExceptionsAfterExecute(r, t); - } - - protected static void maybeResetTraceSessionWrapper(Runnable r) - { - if (r instanceof LocalSessionWrapper) - { - LocalSessionWrapper tsw = (LocalSessionWrapper) r; - // we have to reset trace state as its presence is what denotes the current thread is tracing - // and if left this thread might start tracing unrelated tasks - tsw.reset(); - } - } - - @Override - protected void beforeExecute(Thread t, Runnable r) - { - if (r instanceof LocalSessionWrapper) - ((LocalSessionWrapper) r).setupContext(); - - super.beforeExecute(t, r); - } - - @Override - public int getActiveTaskCount() - { - return getActiveCount(); - } - - @Override - public int getPendingTaskCount() - { - return getQueue().size(); - } - - /** - * Send @param t and any exception wrapped by @param r to the default uncaught exception handler, - * or log them if none such is set up - */ - public static void logExceptionsAfterExecute(Runnable r, Throwable t) - { - Throwable hiddenThrowable = extractThrowable(r); - if (hiddenThrowable != null) - handleOrLog(hiddenThrowable); - - // ThreadPoolExecutor will re-throw exceptions thrown by its Task (which will be seen by - // the default uncaught exception handler) so we only need to do anything if that handler - // isn't set up yet. - if (t != null && Thread.getDefaultUncaughtExceptionHandler() == null) - handleOrLog(t); - } - - /** - * Send @param t to the default uncaught exception handler, or log it if none such is set up - */ - public static void handleOrLog(Throwable t) - { - if (Thread.getDefaultUncaughtExceptionHandler() == null) - logger.error("Error in ThreadPoolExecutor", t); - else - Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), t); - } - - /** - * @return any exception wrapped by @param runnable, i.e., if it is a FutureTask - */ - public static Throwable extractThrowable(Runnable runnable) - { - // Check for exceptions wrapped by FutureTask or tasks which wrap FutureTask (HasDelegateFuture interface) - Throwable throwable = null; - if (runnable instanceof Future) - { - throwable = extractThrowable(((Future) runnable)); - } - if (throwable == null && runnable instanceof HasDelegateFuture) - { - throwable = extractThrowable(((HasDelegateFuture) runnable).getDelegate()); - } - - return throwable; - } - - private static Throwable extractThrowable(Future future) - { - // Check for exceptions wrapped by Future. We do this by calling get(), which will - // cause it to throw any saved exception. - // - // Complicating things, calling get() on a ScheduledFutureTask will block until the task - // is cancelled. Hence, the extra isDone check beforehand. - if (future.isDone()) - { - try - { - future.get(); - } - catch (InterruptedException e) - { - throw new AssertionError(e); - } - catch (CancellationException e) - { - logger.trace("Task cancelled", e); - } - catch (ExecutionException e) - { - return e.getCause(); - } - } - return null; - } - - /** - * If a task wraps a {@link Future} then it should implement this interface to expose the underlining future for - * {@link #extractThrowable(Runnable)} to handle. - */ - private interface HasDelegateFuture - { - Future getDelegate(); - } - - /** - * Used to wrap a Runnable or Callable passed to submit or execute so we can clone the ExecutorLocals and move - * them into the worker thread. - * - * The {@link DebuggableThreadPoolExecutor#afterExecute(java.lang.Runnable, java.lang.Throwable)} - * method is called after the runnable completes, which will then call {@link #extractThrowable(Runnable)} to - * attempt to get the "hidden" throwable from a task which implements {@link Future}. The problem is that {@link LocalSessionWrapper} - * expects that the {@link Callable} provided to it will throw; which is not true for {@link RunnableFuture} tasks; - * the expected semantic in this case is to have the LocalSessionWrapper future be successful and a new implementation - * {@link FutureLocalSessionWrapper} is created to expose the underline {@link Future} for {@link #extractThrowable(Runnable)}. - * - * If a task is a {@link Runnable} the create family of methods should be called rather than {@link Executors#callable(Runnable)} - * since they will handle the case where the task is also a future, and will make sure the {@link #extractThrowable(Runnable)} - * is able to detect the task's underline exception. - * - * @param - */ - private static class LocalSessionWrapper extends FutureTask - { - private final ExecutorLocals locals; - - private LocalSessionWrapper(Callable callable, ExecutorLocals locals) - { - super(callable); - this.locals = locals; - } - - static LocalSessionWrapper create(Runnable command) - { - return create(command, null, ExecutorLocals.create()); - } - - static LocalSessionWrapper create(Runnable command, T result) - { - return create(command, result, ExecutorLocals.create()); - } - - static LocalSessionWrapper create(Runnable command, T result, ExecutorLocals locals) - { - if (command instanceof RunnableFuture) - return new FutureLocalSessionWrapper<>((RunnableFuture) command, result, locals); - return new LocalSessionWrapper<>(Executors.callable(command, result), locals); - } - - static LocalSessionWrapper create(Callable command) - { - return new LocalSessionWrapper<>(command, ExecutorLocals.create()); - } - - private void setupContext() - { - ExecutorLocals.set(locals); - } - - private void reset() - { - ExecutorLocals.set(null); - } - } - - private static class FutureLocalSessionWrapper extends LocalSessionWrapper implements HasDelegateFuture - { - private final RunnableFuture delegate; - - private FutureLocalSessionWrapper(RunnableFuture command, T result, ExecutorLocals locals) - { - super(() -> { - command.run(); - return result; - }, locals); - this.delegate = command; - } - - public Future getDelegate() - { - return delegate; - } - } - - /** - * Similar to {@link FutureLocalSessionWrapper}, this class wraps a {@link Future} and will be success - * if the underline future is marked as failed; the main difference is that this class does not setup - * {@link ExecutorLocals}. - * - * @param - */ - private static class ForwardingRunnableFuture extends FutureTask implements HasDelegateFuture - { - private final RunnableFuture delegate; - - public ForwardingRunnableFuture(RunnableFuture delegate, T result) - { - super(delegate, result); - this.delegate = delegate; - } - - public Future getDelegate() - { - return delegate; - } - } -} diff --git a/src/java/org/apache/cassandra/concurrent/ExecutionFailure.java b/src/java/org/apache/cassandra/concurrent/ExecutionFailure.java new file mode 100644 index 0000000000..61bdae0578 --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/ExecutionFailure.java @@ -0,0 +1,179 @@ +/* + * 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.concurrent; + +import java.util.concurrent.Callable; +import java.util.concurrent.Future; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.cassandra.db.compaction.CompactionInterruptedException; +import org.apache.cassandra.utils.Closeable; +import org.apache.cassandra.utils.JVMStabilityInspector; +import org.apache.cassandra.utils.WithResources; + +/** + * Standardised handling of failures during execution - mostly this involves invoking a thread's + * {@link java.lang.Thread.UncaughtExceptionHandler} or + * {@link JVMStabilityInspector#uncaughtException(Thread, Throwable)}, + * with special handling for {@link CompactionInterruptedException}. + * This class also provides wrappers for {@link WithResources} with {@link Runnable} and {@link Callable}. + */ +public class ExecutionFailure +{ + private static final Logger logger = LoggerFactory.getLogger(ExecutionFailure.class); + + /** + * Invoke the relevant {@link java.lang.Thread.UncaughtExceptionHandler}, + * ignoring (except for logging) any {@link CompactionInterruptedException} + */ + public static void handle(Throwable t) + { + try + { + if (t instanceof CompactionInterruptedException) + { + // TODO: should we check to see there aren't nested CompactionInterruptedException? + logger.info(t.getMessage()); + if (t.getSuppressed() != null && t.getSuppressed().length > 0) + logger.warn("Interruption of compaction encountered exceptions:", t); + else + logger.trace("Full interruption stack trace:", t); + } + else + { + Thread thread = Thread.currentThread(); + Thread.UncaughtExceptionHandler handler = thread.getUncaughtExceptionHandler(); + if (handler == null || handler.equals(thread.getThreadGroup())) + handler = JVMStabilityInspector::uncaughtException; + handler.uncaughtException(thread, t); + } + } + catch (Throwable shouldNeverHappen) + { + logger.error("Unexpected error while handling unexpected error", shouldNeverHappen); + } + } + + /** + * See {@link #propagating(WithResources, Runnable)} + */ + static Runnable propagating(Runnable wrap) + { + return wrap instanceof FutureTask ? wrap : propagating(WithResources.none(), wrap); + } + + /** + * In the case of plain executions, we want to handle exceptions without the full {@link FutureTask} machinery + * while still propagating the exception to the encapsulating Future + */ + static Runnable propagating(WithResources withResources, Runnable wrap) + { + return enforceOptions(withResources, wrap, true); + } + + /** + * See {@link #suppressing(WithResources, Runnable)} + */ + static Runnable suppressing(Runnable wrap) + { + return wrap instanceof FutureTask ? wrap : suppressing(WithResources.none(), wrap); + } + + /** + * In the case of scheduled periodic tasks, we don't want exceptions propagating to cancel the recurring execution. + */ + static Runnable suppressing(WithResources withResources, Runnable wrap) + { + return enforceOptions(withResources, wrap, false); + } + + /** + * Encapsulate the execution, propagating or suppressing any exceptions as requested. + * + * note that if {@code wrap} is a {@link java.util.concurrent.Future} its exceptions may not be captured, + * however the codebase should be using our internal {@link Future} variants which handle exceptions in the + * desired way. + */ + private static Runnable enforceOptions(WithResources withResources, Runnable wrap, boolean propagate) + { + return new Runnable() + { + @Override + public void run() + { + try (Closeable close = withResources.get()) + { + wrap.run(); + } + catch (Throwable t) + { + handle(t); + if (propagate) + throw t; + } + } + + @Override + public String toString() + { + return wrap.toString(); + } + }; + } + + /** + * See {@link #enforceOptions(WithResources, Callable)} + */ + static Callable propagating(Callable wrap) + { + return enforceOptions(WithResources.none(), wrap); + } + + /** + * In the case of non-recurring scheduled tasks, we want to handle exceptions without the full {@link FutureTask} + * machinery, while still propagating the exception to the encapsulating Future + */ + static Callable enforceOptions(WithResources withResources, Callable wrap) + { + return new Callable() + { + @Override + public V call() throws Exception + { + try (Closeable close = withResources.get()) + { + return wrap.call(); + } + catch (Throwable t) + { + handle(t); + throw t; + } + } + + @Override + public String toString() + { + return wrap.toString(); + } + }; + } +} diff --git a/src/java/org/apache/cassandra/concurrent/ExecutorBuilder.java b/src/java/org/apache/cassandra/concurrent/ExecutorBuilder.java new file mode 100644 index 0000000000..89ca28af24 --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/ExecutorBuilder.java @@ -0,0 +1,92 @@ +/* + * 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.concurrent; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.TimeUnit; + +import com.google.common.annotations.VisibleForTesting; + +import org.apache.cassandra.utils.JVMStabilityInspector; + +/** + * Configure an executor before creating it. + * See {@link ThreadPoolExecutorBuilder} + */ +public interface ExecutorBuilder +{ + /** + * Threads for the executor built by this factory will timeout (terminate) after the specified period. + */ + ExecutorBuilder withKeepAlive(long keepAlive, TimeUnit keepAliveUnits); + + /** + * Core threads for the executor built by this factory will never timeout (default for single threaded builders). + * Note that there is ordinarily no difference between core and non-core threads; only when the queue limit is zero + * do we create non-core threads. + */ + ExecutorBuilder withKeepAlive(); + + /** + * Specify the priority of threads that service the executor built by this factory (default to {@link Thread#NORM_PRIORITY}) + */ + ExecutorBuilder withThreadPriority(int threadPriority); + + /** + * Threads for the executor built by this factory will all be (transitively) members of {@code threadGroup}, + * but may directly reside in a child thread group. + */ + ExecutorBuilder withThreadGroup(ThreadGroup threadGroup); + + /** + * Use the system default thread group for the threads we create. + * This is used only for testing, so that we do not hold onto a transitive global reference to all threads. + */ + @VisibleForTesting + ExecutorBuilder withDefaultThreadGroup(); + + /** + * The executor built by this factory will limit the number of queued tasks; default is unlimited. + * Once the queue limit is reached and all threads are executing tasks will be rejected + * (see {@link #withRejectedExecutionHandler(RejectedExecutionHandler)}) + */ + ExecutorBuilder withQueueLimit(int queueLimit); + + /** + * Set the {@link RejectedExecutionHandler} for the executor built by this factory. + * By default this is executor-specific, either: + *
  • {@link ThreadPoolExecutorBase#blockingExecutionHandler} + *
  • {@link ScheduledThreadPoolExecutorPlus#rejectedExecutionHandler} + *
  • and maybe wrapped by {@link ThreadPoolExecutorJMXAdapter#rejectedExecutionHandler} + */ + ExecutorBuilder withRejectedExecutionHandler(RejectedExecutionHandler rejectedExecutionHandler); + + /** + * Set the {@link UncaughtExceptionHandler} for threads that service executors built by this factory. + * By default {@link JVMStabilityInspector#uncaughtException(Thread, Throwable)} + */ + ExecutorBuilder withUncaughtExceptionHandler(UncaughtExceptionHandler uncaughtExceptionHandler); + + /** + * Build the configured executor + */ + E build(); +} diff --git a/src/java/org/apache/cassandra/concurrent/ExecutorBuilderFactory.java b/src/java/org/apache/cassandra/concurrent/ExecutorBuilderFactory.java new file mode 100644 index 0000000000..f96def8c28 --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/ExecutorBuilderFactory.java @@ -0,0 +1,81 @@ +/* + * 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.concurrent; + +/** + * Entry point for configuring and creating new executors. + * + * Supports quick and easy construction of default-configured executors via + *
  • {@link #sequential(String)} + *
  • {@link #pooled(String, int)} + * + * Supports custom configuration of executors via + *
  • {@link #configureSequential(String)} + *
  • {@link #configurePooled(String, int)} + */ +public interface ExecutorBuilderFactory +{ + /** + * Configure a sequential (single threaded) executor + */ + ExecutorBuilder configureSequential(String name); + + /** + * Configure a pooled executor with the requested number of threads + */ + ExecutorBuilder configurePooled(String name, int threads); + + /** + * Return a default configuration of sequential executor + */ + default S sequential(String name) { return configureSequential(name).build(); } + + /** + * Return a default configuration of pooled executor + */ + default E pooled(String name, int threads) { return configurePooled(name, threads).build(); } + + /** + * Entry point for configuring and creating new executors. + * + * Supports quick and easy construction of default-configured executors via + *
  • {@link #sequential(String)} + *
  • {@link #pooled(String, int)} + * + * Supports custom configuration of executors via + *
  • {@link #configureSequential(String)} + *
  • {@link #configurePooled(String, int)} + * + * Supports any of the above with added JMX registration via sub-factories + *
  • {@link #withJmx(String)} + *
  • {@link #withJmxInternal()} + */ + interface Jmxable extends ExecutorBuilderFactory + { + /** + * @return a factory that configures executors that register against JMX using the provided jmx path + */ + ExecutorBuilderFactory withJmx(String jmxPath); + + /** + * @return a factory that configures executors that register against JMX using the "internal" jmx path + */ + default ExecutorBuilderFactory withJmxInternal() { return withJmx("internal"); } + } +} diff --git a/src/java/org/apache/cassandra/concurrent/ExecutorFactory.java b/src/java/org/apache/cassandra/concurrent/ExecutorFactory.java new file mode 100644 index 0000000000..9c7a2cf659 --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/ExecutorFactory.java @@ -0,0 +1,266 @@ +/* + * 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.concurrent; + +import java.util.function.Consumer; + +import org.apache.cassandra.utils.JVMStabilityInspector; + +import static java.lang.Thread.*; +import static org.apache.cassandra.concurrent.NamedThreadFactory.createThread; +import static org.apache.cassandra.concurrent.NamedThreadFactory.setupThread; +import static org.apache.cassandra.concurrent.ThreadPoolExecutorBuilder.pooledJmx; +import static org.apache.cassandra.concurrent.ThreadPoolExecutorBuilder.sequentialJmx; + +/** + * Entry point for configuring and creating new executors. + * + * Supports quick and easy construction of default-configured executors via + *
  • {@link #sequential(String)} + *
  • {@link #pooled(String, int)} + *
  • {@link #scheduled(String)} + *
  • {@link #scheduled(boolean, String)} + *
  • {@link #scheduled(boolean, String, int)} + * + * Supports custom configuration of executors via + *
  • {@link #configureSequential(String)} + *
  • {@link #configurePooled(String, int)} + * + * Supports any of the above with added JMX registration via sub-factories + *
  • {@link #withJmx(String)} + *
  • {@link #withJmxInternal()} + * + * Supports any of the above with the resultant executor propagating {@link ExecutorLocals} via sub-factory + *
  • {@link #localAware()} + * + * Supports shared executors via sub-factory {@code localAware().withJMX()} + * using {@link LocalAwareSubFactoryWithJMX#shared(String, int, ExecutorPlus.MaximumPoolSizeListener)} + */ +public interface ExecutorFactory extends ExecutorBuilderFactory.Jmxable +{ + public interface LocalAwareSubFactoryWithJMX extends ExecutorBuilderFactory + { + LocalAwareExecutorPlus shared(String name, int threads, ExecutorPlus.MaximumPoolSizeListener onSetMaxSize); + } + + public interface LocalAwareSubFactory extends ExecutorBuilderFactory + { + LocalAwareSubFactoryWithJMX withJmx(String jmxPath); + default LocalAwareSubFactoryWithJMX withJmxInternal() { return withJmx("internal"); } + } + + /** + * @return a factory that configures executors that propagate {@link ExecutorLocals} to the executing thread + */ + LocalAwareSubFactory localAware(); + + /** + * @param name the name of the executor, the executor's thread group, and of any worker threads + * @return a default-configured {@link ScheduledExecutorPlus} + */ + default ScheduledExecutorPlus scheduled(String name) { return scheduled(true, name, NORM_PRIORITY); } + + /** + * @param executeOnShutdown if false, waiting tasks will be cancelled on shutdown + * @param name the name of the executor, the executor's thread group, and of any worker threads + * @return a {@link ScheduledExecutorPlus} with normal thread priority + */ + default ScheduledExecutorPlus scheduled(boolean executeOnShutdown, String name) { return scheduled(executeOnShutdown, name, NORM_PRIORITY); } + + /** + * @param executeOnShutdown if false, waiting tasks will be cancelled on shutdown + * @param name the name of the executor, the executor's thread group, and of any worker threads + * @param priority the thread priority of workers + * @return a {@link ScheduledExecutorPlus} + */ + ScheduledExecutorPlus scheduled(boolean executeOnShutdown, String name, int priority); + + /** + * Create and start a new thread to execute {@code runnable} + * @param name the name of the thread + * @param runnable the task to execute + * @return the new thread + */ + Thread startThread(String name, Runnable runnable); + + /** + * Create and start a new InfiniteLoopExecutor to repeatedly invoke {@code runnable}. + * On shutdown, the executing thread will be interrupted; to support clean shutdown + * {@code runnable} should propagate {@link InterruptedException} + * + * @param name the name of the thread used to invoke the task repeatedly + * @param task the task to execute repeatedly + * @return the new thread + */ + Interruptible infiniteLoop(String name, Interruptible.Task task, boolean simulatorSafe); + + /** + * Create and start a new InfiniteLoopExecutor to repeatedly invoke {@code runnable}. + * On shutdown, the executing thread will be interrupted; to support clean shutdown + * {@code runnable} should propagate {@link InterruptedException} + * + * @param name the name of the thread used to invoke the task repeatedly + * @param task the task to execute repeatedly + * @param interruptHandler perform specific processing of interrupts of the task execution thread + * @return the new thread + */ + Interruptible infiniteLoop(String name, Interruptible.Task task, boolean simulatorSafe, Consumer interruptHandler); + + /** + * Create and start a new InfiniteLoopExecutor to repeatedly invoke {@code runnable}. + * On shutdown, the executing thread will be interrupted; to support clean shutdown + * {@code runnable} should propagate {@link InterruptedException} + * + * @param name the name of the thread used to invoke the task repeatedly + * @param task the task to execute repeatedly + * @return the new thread + */ + default Interruptible infiniteLoop(String name, Interruptible.SimpleTask task, boolean simulatorSafe) + { + return infiniteLoop(name, Interruptible.Task.from(task), simulatorSafe); + } + + /** + * Create a new thread group for use with builders - this thread group will be situated within + * this factory's parent thread group, and may be supplied to multiple executor builders. + */ + ThreadGroup newThreadGroup(String name); + + public static final class Global + { + // deliberately not volatile to ensure zero overhead outside of testing; + // depend on other memory visibility primitives to ensure visibility + private static ExecutorFactory FACTORY = new ExecutorFactory.Default(null, null, JVMStabilityInspector::uncaughtException); + public static ExecutorFactory executorFactory() + { + return FACTORY; + } + + public static void unsafeSet(ExecutorFactory executorFactory) + { + FACTORY = executorFactory; + } + } + + public static final class Default extends NamedThreadFactory.MetaFactory implements ExecutorFactory + { + public Default(ClassLoader contextClassLoader, ThreadGroup threadGroup, UncaughtExceptionHandler uncaughtExceptionHandler) + { + super(contextClassLoader, threadGroup, uncaughtExceptionHandler); + } + + public LocalAwareSubFactory localAware() + { + return new LocalAwareSubFactory() + { + public ExecutorBuilder configureSequential(String name) + { + return ThreadPoolExecutorBuilder.sequential(LocalAwareSingleThreadExecutorPlus::new, contextClassLoader, threadGroup, uncaughtExceptionHandler, name); + } + + public ExecutorBuilder configurePooled(String name, int threads) + { + return ThreadPoolExecutorBuilder.pooled(LocalAwareThreadPoolExecutorPlus::new, contextClassLoader, threadGroup, uncaughtExceptionHandler, name, threads); + } + + public LocalAwareSubFactoryWithJMX withJmx(String jmxPath) + { + return new LocalAwareSubFactoryWithJMX() + { + public ExecutorBuilder configureSequential(String name) + { + return sequentialJmx(LocalAwareSingleThreadExecutorPlus::new, contextClassLoader, threadGroup, uncaughtExceptionHandler, name, jmxPath); + } + + public ExecutorBuilder configurePooled(String name, int threads) + { + return pooledJmx(LocalAwareThreadPoolExecutorPlus::new, contextClassLoader, threadGroup, uncaughtExceptionHandler, name, threads, jmxPath); + } + + public LocalAwareExecutorPlus shared(String name, int threads, ExecutorPlus.MaximumPoolSizeListener onSetMaxSize) + { + return SharedExecutorPool.SHARED.newExecutor(threads, onSetMaxSize, jmxPath, name); + } + }; + } + }; + } + + @Override + public ExecutorBuilderFactory withJmx(String jmxPath) + { + return new ExecutorBuilderFactory() + { + @Override + public ExecutorBuilder configureSequential(String name) + { + return ThreadPoolExecutorBuilder.sequentialJmx(SingleThreadExecutorPlus::new, contextClassLoader, threadGroup, uncaughtExceptionHandler, name, jmxPath); + } + + @Override + public ExecutorBuilder configurePooled(String name, int threads) + { + return ThreadPoolExecutorBuilder.pooledJmx(ThreadPoolExecutorPlus::new, contextClassLoader, threadGroup, uncaughtExceptionHandler, name, threads, jmxPath); + } + }; + } + + public ExecutorBuilder configureSequential(String name) + { + return ThreadPoolExecutorBuilder.sequential(SingleThreadExecutorPlus::new, contextClassLoader, threadGroup, uncaughtExceptionHandler, name); + } + + public ExecutorBuilder configurePooled(String name, int threads) + { + return ThreadPoolExecutorBuilder.pooled(ThreadPoolExecutorPlus::new, contextClassLoader, threadGroup, uncaughtExceptionHandler, name, threads); + } + + public ScheduledExecutorPlus scheduled(boolean executeOnShutdown, String name, int priority) + { + ScheduledThreadPoolExecutorPlus executor = new ScheduledThreadPoolExecutorPlus(newThreadFactory(name, priority)); + if (!executeOnShutdown) + executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + return executor; + } + + public Thread startThread(String name, Runnable runnable) + { + Thread thread = setupThread(createThread(threadGroup, runnable, name, true), Thread.NORM_PRIORITY, contextClassLoader, uncaughtExceptionHandler); + thread.start(); + return thread; + } + + public Interruptible infiniteLoop(String name, Interruptible.Task task, boolean simulatorSafe) + { + return new InfiniteLoopExecutor(this, name, task); + } + + @Override + public Interruptible infiniteLoop(String name, Interruptible.Task task, boolean simulatorSafe, Consumer interruptHandler) + { + return new InfiniteLoopExecutor(this, name, task, interruptHandler); + } + + @Override + public ThreadGroup newThreadGroup(String name) + { + return threadGroup == null ? null : new ThreadGroup(threadGroup, name); + } + } +} \ No newline at end of file diff --git a/src/java/org/apache/cassandra/concurrent/ExecutorLocals.java b/src/java/org/apache/cassandra/concurrent/ExecutorLocals.java index 8e6d6eae13..4eeb2e5945 100644 --- a/src/java/org/apache/cassandra/concurrent/ExecutorLocals.java +++ b/src/java/org/apache/cassandra/concurrent/ExecutorLocals.java @@ -18,11 +18,11 @@ package org.apache.cassandra.concurrent; -import java.util.Arrays; - +import io.netty.util.concurrent.FastThreadLocal; import org.apache.cassandra.service.ClientWarn; import org.apache.cassandra.tracing.TraceState; -import org.apache.cassandra.tracing.Tracing; +import org.apache.cassandra.utils.Closeable; +import org.apache.cassandra.utils.WithResources; /* * This class only knows about Tracing and ClientWarn, so if any different executor locals are added, it must be @@ -30,55 +30,80 @@ import org.apache.cassandra.tracing.Tracing; * * We don't enumerate the ExecutorLocal.all array each time because it would be much slower. */ -public class ExecutorLocals +public class ExecutorLocals implements WithResources, Closeable { - private static final ExecutorLocal tracing = Tracing.instance; - private static final ExecutorLocal clientWarn = ClientWarn.instance; + private static final ExecutorLocals none = new ExecutorLocals(null, null); + private static final FastThreadLocal locals = new FastThreadLocal() + { + @Override + protected ExecutorLocals initialValue() + { + return none; + } + }; + + public static class Impl + { + @SuppressWarnings("resource") + protected static void set(TraceState traceState, ClientWarn.State clientWarnState) + { + if (traceState == null && clientWarnState == null) locals.set(none); + else locals.set(new ExecutorLocals(traceState, clientWarnState)); + } + } public final TraceState traceState; public final ClientWarn.State clientWarnState; - private ExecutorLocals(TraceState traceState, ClientWarn.State clientWarnState) + protected ExecutorLocals(TraceState traceState, ClientWarn.State clientWarnState) { this.traceState = traceState; this.clientWarnState = clientWarnState; } - static + /** + * @return an ExecutorLocals object which has the current trace state and client warn state. + */ + public static ExecutorLocals current() { - assert Arrays.equals(ExecutorLocal.all, new ExecutorLocal[]{ tracing, clientWarn }) - : "ExecutorLocals has not been updated to reflect new ExecutorLocal.all"; + return locals.get(); } /** - * This creates a new ExecutorLocals object based on what is already set. - * - * @return an ExecutorLocals object which has the trace state and client warn state captured if either has been set, - * or null if both are unset. The null result short-circuits logic in - * {@link AbstractLocalAwareExecutorService#newTaskFor(Runnable, Object, ExecutorLocals)}, preventing - * unnecessarily calling {@link ExecutorLocals#set(ExecutorLocals)}. + * The {@link #current}Locals, if any; otherwise {@link WithResources#none()}. + * Used to propagate current to other executors as a {@link WithResources}. */ - public static ExecutorLocals create() + public static WithResources propagate() { - TraceState traceState = tracing.get(); - ClientWarn.State clientWarnState = clientWarn.get(); - if (traceState == null && clientWarnState == null) - return null; - else - return new ExecutorLocals(traceState, clientWarnState); + ExecutorLocals locals = current(); + return locals == none ? WithResources.none() : locals; } + @SuppressWarnings("resource") public static ExecutorLocals create(TraceState traceState) { - ClientWarn.State clientWarnState = clientWarn.get(); - return new ExecutorLocals(traceState, clientWarnState); + ExecutorLocals current = locals.get(); + return current.traceState == traceState ? current : new ExecutorLocals(traceState, current.clientWarnState); } - public static void set(ExecutorLocals locals) + public static void clear() { - TraceState traceState = locals == null ? null : locals.traceState; - ClientWarn.State clientWarnState = locals == null ? null : locals.clientWarnState; - tracing.set(traceState); - clientWarn.set(clientWarnState); + locals.set(none); + } + + /** + * Overwrite current locals, and return the previous ones + */ + public Closeable get() + { + ExecutorLocals old = current(); + if (old != this) + locals.set(this); + return old; + } + + public void close() + { + locals.set(this); } } diff --git a/src/java/org/apache/cassandra/concurrent/ExecutorPlus.java b/src/java/org/apache/cassandra/concurrent/ExecutorPlus.java new file mode 100644 index 0000000000..c42a475619 --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/ExecutorPlus.java @@ -0,0 +1,183 @@ +/* + * 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.concurrent; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.apache.cassandra.utils.WithResources; +import org.apache.cassandra.utils.concurrent.Future; + +/** + * Cassandra's extension of {@link ExecutorService}, using our own {@link Future}, supporting + * {@link #inExecutor()}, and execution with associated resources {@link #execute(WithResources, Runnable)} + * (which is primarily used for encapsulating {@link ExecutorLocals} without leaking implementing classes). + */ +public interface ExecutorPlus extends ExecutorService, ResizableThreadPool +{ + interface MaximumPoolSizeListener + { + /** + * Listener to follow changes to the maximum pool size + */ + void onUpdateMaximumPoolSize(int maximumPoolSize); + } + + /** + * MAY execute {@code task} immediately, if the calling thread is permitted to do so. + */ + default void maybeExecuteImmediately(Runnable task) + { + execute(task); + } + + /** + * Overrides {@link ExecutorService#submit(Callable)} to return a Cassandra {@link Future} + */ + @Override + Future submit(Callable task); + + /** + * Overrides {@link ExecutorService#submit(Runnable, Object)} to return a Cassandra {@link Future} + */ + @Override + Future submit(Runnable task, T result); + + /** + * Overrides {@link ExecutorService#submit(Runnable)} to return a Cassandra {@link Future} + */ + @Override + Future submit(Runnable task); + + /* + * ============================================== + * WithResources variants of submit and execute. + * + * (We need a way to inject a TraceState directly into the Executor context without going through + * the global Tracing sessions; see CASSANDRA-5668) + * ============================================== + */ + + /** + * Invoke {@code task}. The invoking thread will first instantiate the resources provided before + * invoking {@code task}, so that thread state may be modified and cleaned up. + * + * The invoking thread will execute something semantically equivlent to: + * + * + * try (Closeable close = withResources.get()) + * { + * task.run(); + * } + * + * + * @param withResources the resources to create and hold while executing {@code task} + * @param task the task to execute + */ + void execute(WithResources withResources, Runnable task); + + /** + * Invoke {@code task}, returning a future representing this computation. + * The invoking thread will first instantiate the resources provided before + * invoking {@code task}, so that thread state may be modified and cleaned up. + * + * The invoking thread will execute something semantically equivlent to: + * + * + * try (Closeable close = withResources.get()) + * { + * return task.call(); + * } + * + * + * @param withResources the resources to create and hold while executing {@code task} + * @param task the task to execute + */ + Future submit(WithResources withResources, Callable task); + + /** + * Invoke {@code task}, returning a future yielding {@code null} if successful, + * or the abnormal termination of {@code task} otherwise. + * + * The invoking thread will first instantiate the resources provided before + * invoking {@code task}, so that thread state may be modified and cleaned up + * + * + * try (Closeable close = withResources.get()) + * { + * task.run(); + * return null; + * } + * + * + * @param withResources the resources to create and hold while executing {@code task} + * @param task the task to execute + */ + Future submit(WithResources withResources, Runnable task); + + /** + * Invoke {@code task}, returning a future yielding {@code result} if successful, + * or the abnormal termination of {@code task} otherwise. + * + * The invoking thread will first instantiate the resources provided before + * invoking {@code task}, so that thread state may be modified and cleaned up. + * + * The invoking thread will execute something semantically equivlent to: + * + * + * try (Closeable close = withResources.get()) + * { + * task.run(); + * return result; + * } + * + * + * @param withResources the resources to create and hold while executing {@code task} + * @param task the task to execute + * @param result the result if successful + */ + Future submit(WithResources withResources, Runnable task, T result); + + /** + * @return true iff the caller is a worker thread actively serving this executor + */ + boolean inExecutor(); + + default List> invokeAll(Collection> tasks) throws InterruptedException + { + throw new UnsupportedOperationException(); + } + default List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException + { + throw new UnsupportedOperationException(); + } + default T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException + { + throw new UnsupportedOperationException(); + } + default T invokeAny(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException + { + throw new UnsupportedOperationException(); + } +} diff --git a/src/java/org/apache/cassandra/concurrent/FutureTask.java b/src/java/org/apache/cassandra/concurrent/FutureTask.java new file mode 100644 index 0000000000..e2fc5cf3dc --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/FutureTask.java @@ -0,0 +1,149 @@ +/* + * 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.concurrent; + +import java.util.concurrent.Callable; + +import org.apache.cassandra.utils.concurrent.RunnableFuture; + +import org.apache.cassandra.utils.concurrent.AsyncFuture; + +/** + * A FutureTask that utilises Cassandra's {@link AsyncFuture}, making it compatible with {@link ExecutorPlus}. + * Propagates exceptions to the uncaught exception handler. + */ +public class FutureTask extends AsyncFuture implements RunnableFuture +{ + private Callable call; + + public FutureTask(Callable call) + { + this.call = call; + } + + public FutureTask(Runnable run) + { + this.call = callable(run); + } + + V call() throws Exception + { + return call.call(); + } + + public void run() + { + try + { + if (!setUncancellable()) + return; + + trySuccess(call()); + } + catch (Throwable t) + { + tryFailure(t); + } + finally + { + call = null; + } + } + + protected boolean tryFailure(Throwable t) + { + ExecutionFailure.handle(t); + return super.tryFailure(t); + } + + public static Callable callable(Runnable run) + { + return new Callable() + { + public T call() + { + run.run(); + return null; + } + + public String toString() + { + return run.toString(); + } + }; + } + + public static Callable callable(Object id, Runnable run) + { + return new Callable() + { + public T call() + { + run.run(); + return null; + } + + public String toString() + { + return id.toString(); + } + }; + } + + public static Callable callable(Runnable run, T result) + { + return new Callable() + { + public T call() + { + run.run(); + return result; + } + + public String toString() + { + return run + "->" + result; + } + }; + } + + public static Callable callable(Object id, Runnable run, T result) + { + return new Callable() + { + public T call() + { + run.run(); + return result; + } + + public String toString() + { + return id.toString(); + } + }; + } + + @Override + protected String description() + { + Object desc = call; + return desc == null ? null : call.toString(); + } +} diff --git a/src/java/org/apache/cassandra/concurrent/FutureTaskWithResources.java b/src/java/org/apache/cassandra/concurrent/FutureTaskWithResources.java new file mode 100644 index 0000000000..78c4987240 --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/FutureTaskWithResources.java @@ -0,0 +1,57 @@ +/* + * 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.concurrent; + +import java.util.concurrent.Callable; + +import org.apache.cassandra.utils.Closeable; +import org.apache.cassandra.utils.WithResources; +import org.apache.cassandra.utils.concurrent.AsyncFuture; + +/** + * A FutureTask that utilises Cassandra's {@link AsyncFuture}, making it compatible with {@link ExecutorPlus}. + * Encapsulates a {@link WithResources}; the call will instantiate the resources before executing, + * and close them after executing but before completing the task. + * + * Propagates exceptions to the uncaught exception handler. + */ +public class FutureTaskWithResources extends FutureTask +{ + private final WithResources withResources; + + public FutureTaskWithResources(WithResources withResources, Callable call) + { + super(call); + this.withResources = withResources; + } + + public FutureTaskWithResources(WithResources withResources, Runnable task) + { + super(task); + this.withResources = withResources; + } + + V call() throws Exception + { + try (Closeable ignore = withResources.get()) + { + return super.call(); + } + } +} diff --git a/src/java/org/apache/cassandra/concurrent/ImmediateExecutor.java b/src/java/org/apache/cassandra/concurrent/ImmediateExecutor.java index 10c369c350..14777ff231 100644 --- a/src/java/org/apache/cassandra/concurrent/ImmediateExecutor.java +++ b/src/java/org/apache/cassandra/concurrent/ImmediateExecutor.java @@ -18,30 +18,127 @@ package org.apache.cassandra.concurrent; +import org.apache.cassandra.utils.Closeable; +import org.apache.cassandra.utils.WithResources; +import org.apache.cassandra.utils.concurrent.Future; +import org.apache.cassandra.utils.concurrent.ImmediateFuture; + import java.util.Collections; import java.util.List; -import java.util.concurrent.AbstractExecutorService; +import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; -public class ImmediateExecutor extends AbstractExecutorService implements LocalAwareExecutorService +public class ImmediateExecutor implements LocalAwareExecutorPlus { public static final ImmediateExecutor INSTANCE = new ImmediateExecutor(); private ImmediateExecutor() {} - public void execute(Runnable command, ExecutorLocals locals) + public Future submit(Callable task) { - command.run(); + try + { + return ImmediateFuture.success(task.call()); + } + catch (Throwable t) + { + ExecutionFailure.handle(t); + return ImmediateFuture.failure(t); + } } - public void maybeExecuteImmediately(Runnable command) + public Future submit(Runnable task, T result) { - command.run(); + try + { + task.run(); + return ImmediateFuture.success(result); + } + catch (Throwable t) + { + ExecutionFailure.handle(t); + return ImmediateFuture.failure(t); + } } - public void execute(Runnable command) + public Future submit(Runnable task) { - command.run(); + try + { + task.run(); + return ImmediateFuture.success(null); + } + catch (Throwable t) + { + ExecutionFailure.handle(t); + return ImmediateFuture.failure(t); + } + } + + @Override + public void execute(WithResources withResources, Runnable task) + { + try (Closeable ignored = withResources.get()) + { + task.run(); + } + catch (Throwable t) + { + ExecutionFailure.handle(t); + } + } + + @Override + public Future submit(WithResources withResources, Callable task) + { + try (Closeable ignored = withResources.get()) + { + return ImmediateFuture.success(task.call()); + } + catch (Throwable t) + { + ExecutionFailure.handle(t); + return ImmediateFuture.failure(t); + } + } + + @Override + public Future submit(WithResources withResources, Runnable task) + { + return submit(withResources, task, null); + } + + @Override + public Future submit(WithResources withResources, Runnable task, T result) + { + try (Closeable ignored = withResources.get()) + { + task.run(); + return ImmediateFuture.success(result); + } + catch (Throwable t) + { + ExecutionFailure.handle(t); + return ImmediateFuture.failure(t); + } + } + + @Override + public boolean inExecutor() + { + return true; + } + + public void execute(Runnable task) + { + try + { + task.run(); + } + catch (Throwable t) + { + ExecutionFailure.handle(t); + } } public int getActiveTaskCount() { return 0; } diff --git a/src/java/org/apache/cassandra/concurrent/InfiniteLoopExecutor.java b/src/java/org/apache/cassandra/concurrent/InfiniteLoopExecutor.java index 8e72d91083..4012970c78 100644 --- a/src/java/org/apache/cassandra/concurrent/InfiniteLoopExecutor.java +++ b/src/java/org/apache/cassandra/concurrent/InfiniteLoopExecutor.java @@ -24,40 +24,77 @@ import org.slf4j.LoggerFactory; import java.util.concurrent.TimeUnit; import com.google.common.annotations.VisibleForTesting; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.BiFunction; +import java.util.function.Consumer; -public class InfiniteLoopExecutor +import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; + +import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.InternalState.TERMINATED; +import static org.apache.cassandra.concurrent.Interruptible.State.INTERRUPTED; +import static org.apache.cassandra.concurrent.Interruptible.State.NORMAL; +import static org.apache.cassandra.concurrent.Interruptible.State.SHUTTING_DOWN; + +public class InfiniteLoopExecutor implements Interruptible { private static final Logger logger = LoggerFactory.getLogger(InfiniteLoopExecutor.class); - public interface InterruptibleRunnable + public enum InternalState { TERMINATED } + + private static final AtomicReferenceFieldUpdater stateUpdater = AtomicReferenceFieldUpdater.newUpdater(InfiniteLoopExecutor.class, Object.class, "state"); + private final Thread thread; + private final Task task; + private volatile Object state = NORMAL; + private final Consumer interruptHandler; + + public InfiniteLoopExecutor(String name, Task task) { - void run() throws InterruptedException; + this(ExecutorFactory.Global.executorFactory(), name, task, Thread::interrupt); } - private final Thread thread; - private final InterruptibleRunnable runnable; - private volatile boolean isShutdown = false; - - public InfiniteLoopExecutor(String name, InterruptibleRunnable runnable) + public InfiniteLoopExecutor(ExecutorFactory factory, String name, Task task) { - this.runnable = runnable; - this.thread = new Thread(this::loop, name); - this.thread.setDaemon(true); + this(factory, name, task, Thread::interrupt); + } + + public InfiniteLoopExecutor(ExecutorFactory factory, String name, Task task, Consumer interruptHandler) + { + this.task = task; + this.thread = factory.startThread(name, this::loop); + this.interruptHandler = interruptHandler; + } + + public InfiniteLoopExecutor(BiFunction threadStarter, String name, Task task, Consumer interruptHandler) + { + this.task = task; + this.thread = threadStarter.apply(name, this::loop); + this.interruptHandler = interruptHandler; } private void loop() { - while (!isShutdown) + boolean interrupted = false; + while (true) { try { - runnable.run(); + Object cur = state; + if (cur == TERMINATED) break; + + interrupted |= Thread.interrupted(); + if (cur == NORMAL && interrupted) cur = INTERRUPTED; + task.run((State) cur); + + interrupted = false; + if (cur == SHUTTING_DOWN) state = TERMINATED; } - catch (InterruptedException ie) + catch (TerminateException ignore) { - if (isShutdown) - return; - logger.error("Interrupted while executing {}, but not shutdown; continuing with loop", runnable, ie); + state = TERMINATED; + } + catch (UncheckedInterruptedException | InterruptedException ignore) + { + interrupted = true; } catch (Throwable t) { @@ -66,22 +103,34 @@ public class InfiniteLoopExecutor } } - public InfiniteLoopExecutor start() + public void interrupt() { - thread.start(); - return this; + interruptHandler.accept(thread); } - public void shutdownNow() + public void shutdown() { - isShutdown = true; - thread.interrupt(); + stateUpdater.updateAndGet(this, cur -> cur != TERMINATED ? SHUTTING_DOWN : TERMINATED); + interruptHandler.accept(thread); + } + + public Object shutdownNow() + { + state = TERMINATED; + interruptHandler.accept(thread); + return null; + } + + @Override + public boolean isTerminated() + { + return state == TERMINATED && !thread.isAlive(); } public boolean awaitTermination(long time, TimeUnit unit) throws InterruptedException { thread.join(unit.toMillis(time)); - return !thread.isAlive(); + return isTerminated(); } @VisibleForTesting diff --git a/src/java/org/apache/cassandra/concurrent/Interruptible.java b/src/java/org/apache/cassandra/concurrent/Interruptible.java new file mode 100644 index 0000000000..cc13a636e2 --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/Interruptible.java @@ -0,0 +1,49 @@ +/* + * 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.concurrent; + +import static org.apache.cassandra.concurrent.Interruptible.State.*; + +public interface Interruptible extends Shutdownable +{ + public enum State { NORMAL, INTERRUPTED, SHUTTING_DOWN } + + public static class TerminateException extends InterruptedException {} + + public interface Task + { + void run(State state) throws InterruptedException; + + static Task from(SimpleTask simpleTask) + { + return state -> { if (state == NORMAL) simpleTask.run(); }; + } + } + + /** + * A Task that only runs on NORMAL states + */ + public interface SimpleTask + { + void run() throws InterruptedException; + } + + void interrupt(); +} + diff --git a/src/java/org/apache/cassandra/concurrent/JMXEnabledSingleThreadExecutor.java b/src/java/org/apache/cassandra/concurrent/JMXEnabledSingleThreadExecutor.java deleted file mode 100644 index 1e61aa1152..0000000000 --- a/src/java/org/apache/cassandra/concurrent/JMXEnabledSingleThreadExecutor.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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.concurrent; - -import java.util.concurrent.ThreadFactory; - -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.apache.cassandra.utils.concurrent.BlockingQueues.newBlockingQueue; - -public class JMXEnabledSingleThreadExecutor extends JMXEnabledThreadPoolExecutor -{ - public JMXEnabledSingleThreadExecutor(String threadPoolName, String jmxPath) - { - super(1, Integer.MAX_VALUE, SECONDS, newBlockingQueue(), new SingleThreadFactory(threadPoolName), jmxPath); - } - - @Override - public void setCoreThreads(int number) - { - throw new UnsupportedOperationException("Cannot change core pool size for single threaded executor."); - } - - @Override - public void setMaximumThreads(int number) - { - throw new UnsupportedOperationException("Cannot change max threads for single threaded executor."); - } - - @Override - public void setMaximumPoolSize(int newMaximumPoolSize) - { - setMaximumThreads(newMaximumPoolSize); - } - - public boolean isExecutedBy(Thread test) - { - return getThreadFactory().thread == test; - } - - public SingleThreadFactory getThreadFactory() - { - return (SingleThreadFactory) super.getThreadFactory(); - } - - public void setThreadFactory(ThreadFactory threadFactory) - { - throw new UnsupportedOperationException(); - } - - private static class SingleThreadFactory extends NamedThreadFactory - { - private volatile Thread thread; - SingleThreadFactory(String id) - { - super(id); - } - - @Override - public Thread newThread(Runnable r) - { - Thread thread = super.newThread(r); - this.thread = thread; - return thread; - } - } -} diff --git a/src/java/org/apache/cassandra/concurrent/JMXEnabledThreadPoolExecutor.java b/src/java/org/apache/cassandra/concurrent/JMXEnabledThreadPoolExecutor.java deleted file mode 100644 index 4283d4ff07..0000000000 --- a/src/java/org/apache/cassandra/concurrent/JMXEnabledThreadPoolExecutor.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * 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.concurrent; - -import java.util.List; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.RejectedExecutionHandler; -import java.util.concurrent.TimeUnit; - -import org.apache.cassandra.metrics.ThreadPoolMetrics; -import org.apache.cassandra.utils.MBeanWrapper; - -/** - * This is a wrapper class for the ScheduledThreadPoolExecutor. It provides an implementation - * for the afterExecute() found in the ThreadPoolExecutor class to log any unexpected - * Runtime Exceptions. - */ - -public class JMXEnabledThreadPoolExecutor extends DebuggableThreadPoolExecutor implements JMXEnabledThreadPoolExecutorMBean -{ - private final String mbeanName; - public final ThreadPoolMetrics metrics; - - public JMXEnabledThreadPoolExecutor(String threadPoolName) - { - this(1, Integer.MAX_VALUE, TimeUnit.SECONDS, new LinkedBlockingQueue(), new NamedThreadFactory(threadPoolName), "internal"); - } - - public JMXEnabledThreadPoolExecutor(String threadPoolName, String jmxPath) - { - this(1, Integer.MAX_VALUE, TimeUnit.SECONDS, new LinkedBlockingQueue(), new NamedThreadFactory(threadPoolName), jmxPath); - } - - public JMXEnabledThreadPoolExecutor(String threadPoolName, int priority) - { - this(1, Integer.MAX_VALUE, TimeUnit.SECONDS, new LinkedBlockingQueue(), new NamedThreadFactory(threadPoolName, priority), "internal"); - } - - public JMXEnabledThreadPoolExecutor(NamedThreadFactory threadFactory, String jmxPath) - { - this(1, Integer.MAX_VALUE, TimeUnit.SECONDS, new LinkedBlockingQueue(), threadFactory, jmxPath); - } - - public JMXEnabledThreadPoolExecutor(int corePoolSize, - long keepAliveTime, - TimeUnit unit, - BlockingQueue workQueue, - NamedThreadFactory threadFactory, - String jmxPath) - { - this(corePoolSize, corePoolSize, keepAliveTime, unit, workQueue, threadFactory, jmxPath); - } - - public JMXEnabledThreadPoolExecutor(int corePoolSize, - int maxPoolSize, - long keepAliveTime, - TimeUnit unit, - BlockingQueue workQueue, - NamedThreadFactory threadFactory, - String jmxPath) - { - super(corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue, threadFactory); - super.prestartAllCoreThreads(); - metrics = new ThreadPoolMetrics(this, jmxPath, threadFactory.id).register(); - - mbeanName = "org.apache.cassandra." + jmxPath + ":type=" + threadFactory.id; - MBeanWrapper.instance.registerMBean(this, mbeanName); - } - - public JMXEnabledThreadPoolExecutor(int corePoolSize, - int maxPoolSize, - long keepAliveTime, - TimeUnit unit, - BlockingQueue workQueue, - NamedThreadFactory threadFactory, - String jmxPath, - RejectedExecutionHandler rejectedExecutionHandler) - { - this(corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue, threadFactory, jmxPath); - setRejectedExecutionHandler(rejectedExecutionHandler); - } - - private void unregisterMBean() - { - MBeanWrapper.instance.unregisterMBean(mbeanName); - - // release metrics - metrics.release(); - } - - @Override - public synchronized void shutdown() - { - // synchronized, because there is no way to access super.mainLock, which would be - // the preferred way to make this threadsafe - if (!isShutdown()) - { - unregisterMBean(); - } - super.shutdown(); - } - - @Override - public synchronized List shutdownNow() - { - // synchronized, because there is no way to access super.mainLock, which would be - // the preferred way to make this threadsafe - if (!isShutdown()) - { - unregisterMBean(); - } - return super.shutdownNow(); - } - - public int getTotalBlockedTasks() - { - return (int) metrics.totalBlocked.getCount(); - } - - public int getCurrentlyBlockedTasks() - { - return (int) metrics.currentBlocked.getCount(); - } - - @Deprecated - public int getCoreThreads() - { - return getCorePoolSize(); - } - - @Deprecated - public void setCoreThreads(int number) - { - setCorePoolSize(number); - } - - @Deprecated - public int getMaximumThreads() - { - return getMaximumPoolSize(); - } - - @Deprecated - public void setMaximumThreads(int number) - { - setMaximumPoolSize(number); - } - - @Override - public void setMaximumPoolSize(int newMaximumPoolSize) - { - if (newMaximumPoolSize < getCorePoolSize()) - throw new IllegalArgumentException("maximum pool size cannot be less than core pool size"); - super.setMaximumPoolSize(newMaximumPoolSize); - } - - @Override - protected void onInitialRejection(Runnable task) - { - metrics.totalBlocked.inc(); - metrics.currentBlocked.inc(); - } - - @Override - protected void onFinalAccept(Runnable task) - { - metrics.currentBlocked.dec(); - } - - @Override - protected void onFinalRejection(Runnable task) - { - metrics.currentBlocked.dec(); - } -} diff --git a/src/java/org/apache/cassandra/concurrent/LocalAwareExecutorPlus.java b/src/java/org/apache/cassandra/concurrent/LocalAwareExecutorPlus.java new file mode 100644 index 0000000000..7509619780 --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/LocalAwareExecutorPlus.java @@ -0,0 +1,26 @@ +/* + * 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.concurrent; + +/** + * An {@link ExecutorPlus} that is aware of, and propagates to execution, any ExecutorLocals + */ +public interface LocalAwareExecutorPlus extends ExecutorPlus +{ +} diff --git a/src/java/org/apache/cassandra/concurrent/LocalAwareExecutorService.java b/src/java/org/apache/cassandra/concurrent/LocalAwareExecutorService.java deleted file mode 100644 index d6ac8e4cbc..0000000000 --- a/src/java/org/apache/cassandra/concurrent/LocalAwareExecutorService.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * - * 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.concurrent; - -import java.util.concurrent.ExecutorService; - -public interface LocalAwareExecutorService extends ExecutorService, ResizableThreadPool -{ - // we need a way to inject a TraceState directly into the Executor context without going through - // the global Tracing sessions; see CASSANDRA-5668 - void execute(Runnable command, ExecutorLocals locals); - - // permits executing in the context of the submitting thread - void maybeExecuteImmediately(Runnable command); - - /** - * Returns the approximate number of threads that are actively - * executing tasks. - * - * @return the number of threads - */ - int getActiveTaskCount(); - - /** - * Returns the approximate total number of tasks that have - * completed execution. Because the states of tasks and threads - * may change dynamically during computation, the returned value - * is only an approximation, but one that does not ever decrease - * across successive calls. - * - * @return the number of tasks - */ - long getCompletedTaskCount(); - - /** - * Returns the approximate total of tasks waiting to be executed. - * Because the states of tasks and threads - * may change dynamically during computation, the returned value - * is only an approximation, but one that does not ever decrease - * across successive calls. - * - * @return the number of tasks - */ - int getPendingTaskCount(); - - default int getMaxTasksQueued() - { - return -1; - } - - interface MaximumPoolSizeListener - { - /** - * Listener to follow changes to the maximum pool size - */ - void onUpdateMaximumPoolSize(int maximumPoolSize); - } -} diff --git a/src/java/org/apache/cassandra/concurrent/LocalAwareSequentialExecutorPlus.java b/src/java/org/apache/cassandra/concurrent/LocalAwareSequentialExecutorPlus.java new file mode 100644 index 0000000000..dbcff6ce33 --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/LocalAwareSequentialExecutorPlus.java @@ -0,0 +1,26 @@ +/* + * 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.concurrent; + +/** + * A {@link SequentialExecutorPlus} that is aware of, and propagates to execution, any ExecutorLocals + */ +public interface LocalAwareSequentialExecutorPlus extends LocalAwareExecutorPlus, SequentialExecutorPlus +{ +} diff --git a/src/java/org/apache/cassandra/concurrent/LocalAwareSingleThreadExecutorPlus.java b/src/java/org/apache/cassandra/concurrent/LocalAwareSingleThreadExecutorPlus.java new file mode 100644 index 0000000000..cf67c1a137 --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/LocalAwareSingleThreadExecutorPlus.java @@ -0,0 +1,26 @@ +/* + * 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.concurrent; + +public class LocalAwareSingleThreadExecutorPlus extends SingleThreadExecutorPlus implements LocalAwareSequentialExecutorPlus +{ + LocalAwareSingleThreadExecutorPlus(ThreadPoolExecutorBuilder builder) + { + super(builder, TaskFactory.localAware()); + } +} diff --git a/src/java/org/apache/cassandra/concurrent/LocalAwareThreadPoolExecutorPlus.java b/src/java/org/apache/cassandra/concurrent/LocalAwareThreadPoolExecutorPlus.java new file mode 100644 index 0000000000..10d107e63f --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/LocalAwareThreadPoolExecutorPlus.java @@ -0,0 +1,26 @@ +/* + * 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.concurrent; + +public class LocalAwareThreadPoolExecutorPlus extends ThreadPoolExecutorPlus implements LocalAwareExecutorPlus +{ + LocalAwareThreadPoolExecutorPlus(ThreadPoolExecutorBuilder builder) + { + super(builder, TaskFactory.localAware()); + } +} diff --git a/src/java/org/apache/cassandra/concurrent/NamedThreadFactory.java b/src/java/org/apache/cassandra/concurrent/NamedThreadFactory.java index 5aadb48d9c..32df3f39d7 100644 --- a/src/java/org/apache/cassandra/concurrent/NamedThreadFactory.java +++ b/src/java/org/apache/cassandra/concurrent/NamedThreadFactory.java @@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicInteger; import com.google.common.annotations.VisibleForTesting; import io.netty.util.concurrent.FastThreadLocalThread; +import org.apache.cassandra.utils.JVMStabilityInspector; /** * This class is an implementation of the ThreadFactory interface. This @@ -32,18 +33,49 @@ import io.netty.util.concurrent.FastThreadLocalThread; public class NamedThreadFactory implements ThreadFactory { + private static final AtomicInteger anonymousCounter = new AtomicInteger(); private static volatile String globalPrefix; + public static void setGlobalPrefix(String prefix) { globalPrefix = prefix; } - public static String globalPrefix() { + public static String globalPrefix() + { String prefix = globalPrefix; return prefix == null ? "" : prefix; } + public static class MetaFactory + { + protected ClassLoader contextClassLoader; + protected ThreadGroup threadGroup; + protected Thread.UncaughtExceptionHandler uncaughtExceptionHandler; + + public MetaFactory(ClassLoader contextClassLoader, ThreadGroup threadGroup, Thread.UncaughtExceptionHandler uncaughtExceptionHandler) + { + this.contextClassLoader = contextClassLoader; + if (threadGroup == null) + { + threadGroup = Thread.currentThread().getThreadGroup(); + while (threadGroup.getParent() != null) + threadGroup = threadGroup.getParent(); + } + this.threadGroup = threadGroup; + this.uncaughtExceptionHandler = uncaughtExceptionHandler; + } + + NamedThreadFactory newThreadFactory(String name, int threadPriority) + { + // We create a unique thread group for each factory, so that e.g. executors can determine which threads are members of the executor + ThreadGroup threadGroup = this.threadGroup == null ? null : new ThreadGroup(this.threadGroup, name); + return new NamedThreadFactory(name, threadPriority, contextClassLoader, threadGroup, uncaughtExceptionHandler); + } + } + public final String id; private final int priority; private final ClassLoader contextClassLoader; - private final ThreadGroup threadGroup; + public final ThreadGroup threadGroup; protected final AtomicInteger n = new AtomicInteger(1); + private final Thread.UncaughtExceptionHandler uncaughtExceptionHandler; public NamedThreadFactory(String id) { @@ -52,33 +84,60 @@ public class NamedThreadFactory implements ThreadFactory public NamedThreadFactory(String id, int priority) { - this(id, priority, null, null); + this(id, priority, null, null, JVMStabilityInspector::uncaughtException); + } + + public NamedThreadFactory(String id, ClassLoader contextClassLoader, ThreadGroup threadGroup) + { + this(id, Thread.NORM_PRIORITY, contextClassLoader, threadGroup, JVMStabilityInspector::uncaughtException); } public NamedThreadFactory(String id, int priority, ClassLoader contextClassLoader, ThreadGroup threadGroup) + { + this(id, priority, contextClassLoader, threadGroup, JVMStabilityInspector::uncaughtException); + } + public NamedThreadFactory(String id, int priority, ClassLoader contextClassLoader, ThreadGroup threadGroup, Thread.UncaughtExceptionHandler uncaughtExceptionHandler) { this.id = id; this.priority = priority; this.contextClassLoader = contextClassLoader; this.threadGroup = threadGroup; + this.uncaughtExceptionHandler = uncaughtExceptionHandler; } + @Override public Thread newThread(Runnable runnable) { String name = id + ':' + n.getAndIncrement(); - Thread thread = createThread(threadGroup, runnable, name, true); + return newThread(threadGroup, runnable, name); + } + + protected Thread newThread(ThreadGroup threadGroup, Runnable runnable, String name) + { + return setupThread(createThread(threadGroup, runnable, name, true)); + } + + protected T setupThread(T thread) + { + return setupThread(thread, priority, contextClassLoader, uncaughtExceptionHandler); + } + + public static Thread createThread(ThreadGroup threadGroup, Runnable runnable, String name, int priority, ClassLoader contextClassLoader, Thread.UncaughtExceptionHandler uncaughtExceptionHandler) + { + String prefix = globalPrefix; + Thread thread = createThread(threadGroup, runnable, prefix != null ? prefix + name : name, true); thread.setPriority(priority); if (contextClassLoader != null) thread.setContextClassLoader(contextClassLoader); + if (uncaughtExceptionHandler != null) + thread.setUncaughtExceptionHandler(uncaughtExceptionHandler); return thread; } - private static final AtomicInteger threadCounter = new AtomicInteger(); - @VisibleForTesting - public static Thread createThread(Runnable runnable) + public static Thread createAnonymousThread(Runnable runnable) { - return createThread(null, runnable, "anonymous-" + threadCounter.incrementAndGet()); + return createThread(null, runnable, "anonymous-" + anonymousCounter.incrementAndGet()); } public static Thread createThread(Runnable runnable, String name) @@ -86,7 +145,7 @@ public class NamedThreadFactory implements ThreadFactory return createThread(null, runnable, name); } - public static Thread createThread(Runnable runnable, String name, boolean daemon) + public Thread createThread(Runnable runnable, String name, boolean daemon) { return createThread(null, runnable, name, daemon); } @@ -103,4 +162,37 @@ public class NamedThreadFactory implements ThreadFactory thread.setDaemon(daemon); return thread; } + + public static T setupThread(T thread, int priority, ClassLoader contextClassLoader, Thread.UncaughtExceptionHandler uncaughtExceptionHandler) + { + thread.setPriority(priority); + if (contextClassLoader != null) + thread.setContextClassLoader(contextClassLoader); + if (uncaughtExceptionHandler != null) + thread.setUncaughtExceptionHandler(uncaughtExceptionHandler); + return thread; + } + + @Override + public String toString() + { + return id; + } + + public void close() + { + synchronized (threadGroup) + { + threadGroup.setDaemon(true); + // ThreadGroup API is terrible; setDaemon does not destroy if already empty, and establishing if empty + // otherwise is tortuous - easier to just try to destroy and fail if currently an invalid action + try + { + threadGroup.destroy(); + } + catch (IllegalThreadStateException ignore) + { + } + } + } } diff --git a/src/java/org/apache/cassandra/concurrent/ResizableThreadPool.java b/src/java/org/apache/cassandra/concurrent/ResizableThreadPool.java index bd3b8eabd8..760c06eb69 100644 --- a/src/java/org/apache/cassandra/concurrent/ResizableThreadPool.java +++ b/src/java/org/apache/cassandra/concurrent/ResizableThreadPool.java @@ -21,12 +21,14 @@ package org.apache.cassandra.concurrent; public interface ResizableThreadPool { /** - * Returns maximum pool size of thread pool. + * Returns core pool size of thread pool, the minimum + * number of workers (where that makes sense for a thread pool, + * SEPExecutor does not have a minimum size). */ public int getCorePoolSize(); /** - * Allows user to resize maximum size of the thread pool. + * Allows user to resize minimum size of the thread pool. */ public void setCorePoolSize(int newCorePoolSize); @@ -39,4 +41,37 @@ public interface ResizableThreadPool * Allows user to resize maximum size of the thread pool. */ public void setMaximumPoolSize(int newMaximumPoolSize); + + /** + * Returns the approximate number of threads that are actively + * executing tasks. + * + * @return the number of threads + */ + int getActiveTaskCount(); + + /** + * Returns the approximate total number of tasks that have + * completed execution. Because the states of tasks and threads + * may change dynamically during computation, the returned value + * is only an approximation, but one that does not ever decrease + * across successive calls. + * + * @return the number of tasks + */ + long getCompletedTaskCount(); + + /** + * Returns the approximate total of tasks waiting to be executed. + * Because the states of tasks and threads may change dynamically + * during computation, the returned value is only an approximation. + * + * @return the number of tasks + */ + int getPendingTaskCount(); + + default int getMaxTasksQueued() + { + return -1; + } } diff --git a/src/java/org/apache/cassandra/concurrent/JMXEnabledThreadPoolExecutorMBean.java b/src/java/org/apache/cassandra/concurrent/ResizableThreadPoolMXBean.java similarity index 95% rename from src/java/org/apache/cassandra/concurrent/JMXEnabledThreadPoolExecutorMBean.java rename to src/java/org/apache/cassandra/concurrent/ResizableThreadPoolMXBean.java index c2959dffeb..1c247d6934 100644 --- a/src/java/org/apache/cassandra/concurrent/JMXEnabledThreadPoolExecutorMBean.java +++ b/src/java/org/apache/cassandra/concurrent/ResizableThreadPoolMXBean.java @@ -17,7 +17,7 @@ */ package org.apache.cassandra.concurrent; -public interface JMXEnabledThreadPoolExecutorMBean extends ResizableThreadPool +public interface ResizableThreadPoolMXBean extends ResizableThreadPool { /** * Returns core pool size of thread pool. diff --git a/src/java/org/apache/cassandra/concurrent/SEPExecutor.java b/src/java/org/apache/cassandra/concurrent/SEPExecutor.java index 9085ee4f76..05b59c6ed4 100644 --- a/src/java/org/apache/cassandra/concurrent/SEPExecutor.java +++ b/src/java/org/apache/cassandra/concurrent/SEPExecutor.java @@ -19,6 +19,7 @@ package org.apache.cassandra.concurrent; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -26,6 +27,8 @@ import java.util.concurrent.atomic.AtomicLong; import com.google.common.annotations.VisibleForTesting; +import org.apache.cassandra.utils.WithResources; +import org.apache.cassandra.utils.concurrent.Future; import org.apache.cassandra.utils.MBeanWrapper; import org.apache.cassandra.utils.concurrent.Condition; import org.slf4j.Logger; @@ -37,9 +40,11 @@ import static org.apache.cassandra.concurrent.SEPExecutor.TakeTaskPermitResult.* import static org.apache.cassandra.concurrent.SEPWorker.Work; import static org.apache.cassandra.utils.concurrent.Condition.newOneTimeCondition; -public class SEPExecutor extends AbstractLocalAwareExecutorService implements SEPExecutorMBean +public class SEPExecutor implements LocalAwareExecutorPlus, SEPExecutorMBean { private static final Logger logger = LoggerFactory.getLogger(SEPExecutor.class); + private static final TaskFactory taskFactory = TaskFactory.localAware(); + private final SharedExecutorPool pool; private final AtomicInteger maximumPoolSize; @@ -60,7 +65,7 @@ public class SEPExecutor extends AbstractLocalAwareExecutorService implements SE final Condition shutdown = newOneTimeCondition(); // TODO: see if other queue implementations might improve throughput - protected final ConcurrentLinkedQueue> tasks = new ConcurrentLinkedQueue<>(); + protected final ConcurrentLinkedQueue tasks = new ConcurrentLinkedQueue<>(); SEPExecutor(SharedExecutorPool pool, int maximumPoolSize, MaximumPoolSizeListener maximumPoolSizeListener, String jmxPath, String name) { @@ -96,7 +101,7 @@ public class SEPExecutor extends AbstractLocalAwareExecutorService implements SE return true; } - protected void addTask(FutureTask task) + protected T addTask(T task) { // we add to the queue first, so that when a worker takes a task permit it can be certain there is a task available // this permits us to schedule threads non-spuriously; it also means work is serviced fairly @@ -121,6 +126,7 @@ public class SEPExecutor extends AbstractLocalAwareExecutorService implements SE // worker, we simply start a worker in a spinning state pool.maybeStartSpinningWorker(); } + return task; } public enum TakeTaskPermitResult @@ -128,7 +134,7 @@ public class SEPExecutor extends AbstractLocalAwareExecutorService implements SE NONE_AVAILABLE, // No task permits available TOOK_PERMIT, // Took a permit and reduced task permits RETURNED_WORK_PERMIT // Detected pool shrinking and returned work permit ahead of SEPWorker exit. - }; + } // takes permission to perform a task, if any are available; once taken it is guaranteed // that a proceeding call to tasks.poll() will return some work @@ -194,18 +200,18 @@ public class SEPExecutor extends AbstractLocalAwareExecutorService implements SE } @Override - public void maybeExecuteImmediately(Runnable command) + public void maybeExecuteImmediately(Runnable task) { - FutureTask ft = newTaskFor(command, null); + task = taskFactory.toExecute(task); if (!takeWorkPermit(false)) { - addTask(ft); + addTask(task); } else { try { - ft.run(); + task.run(); } finally { @@ -218,6 +224,60 @@ public class SEPExecutor extends AbstractLocalAwareExecutorService implements SE } } + @Override + public void execute(Runnable run) + { + addTask(taskFactory.toExecute(run)); + } + + @Override + public void execute(WithResources withResources, Runnable run) + { + addTask(taskFactory.toExecute(withResources, run)); + } + + @Override + public Future submit(Runnable run) + { + return addTask(taskFactory.toSubmit(run)); + } + + @Override + public Future submit(Runnable run, T result) + { + return addTask(taskFactory.toSubmit(run, result)); + } + + @Override + public Future submit(Callable call) + { + return addTask(taskFactory.toSubmit(call)); + } + + @Override + public Future submit(WithResources withResources, Runnable run, T result) + { + return addTask(taskFactory.toSubmit(withResources, run, result)); + } + + @Override + public Future submit(WithResources withResources, Runnable run) + { + return addTask(taskFactory.toSubmit(withResources, run)); + } + + @Override + public Future submit(WithResources withResources, Callable call) + { + return addTask(taskFactory.toSubmit(withResources, call)); + } + + @Override + public boolean inExecutor() + { + throw new UnsupportedOperationException(); + } + public synchronized void shutdown() { if (shuttingDown) diff --git a/src/java/org/apache/cassandra/concurrent/SEPWorker.java b/src/java/org/apache/cassandra/concurrent/SEPWorker.java index efb1884827..c7b9abf719 100644 --- a/src/java/org/apache/cassandra/concurrent/SEPWorker.java +++ b/src/java/org/apache/cassandra/concurrent/SEPWorker.java @@ -48,11 +48,11 @@ final class SEPWorker extends AtomicReference implements Runnabl long prevStopCheck = 0; long soleSpinnerSpinTime = 0; - SEPWorker(Long workerId, Work initialState, SharedExecutorPool pool) + SEPWorker(ThreadGroup threadGroup, Long workerId, Work initialState, SharedExecutorPool pool) { this.pool = pool; this.workerId = workerId; - thread = new FastThreadLocalThread(this, pool.poolName + "-Worker-" + workerId); + thread = new FastThreadLocalThread(threadGroup, this, threadGroup.getName() + "-Worker-" + workerId); thread.setDaemon(true); set(initialState); thread.start(); @@ -118,6 +118,7 @@ final class SEPWorker extends AtomicReference implements Runnabl // we know there is work waiting, as we have a work permit, so poll() will always succeed task.run(); + assigned.onCompletion(); task = null; if (shutdown = assigned.shuttingDown) @@ -163,9 +164,14 @@ final class SEPWorker extends AtomicReference implements Runnabl if (assigned != null) assigned.returnWorkPermit(); if (task != null) + { logger.error("Failed to execute task, unexpected exception killed worker", t); + assigned.onCompletion(); + } else + { logger.error("Unexpected exception killed worker", t); + } } } diff --git a/src/java/org/apache/cassandra/concurrent/ScheduledExecutorPlus.java b/src/java/org/apache/cassandra/concurrent/ScheduledExecutorPlus.java new file mode 100644 index 0000000000..0b512acf51 --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/ScheduledExecutorPlus.java @@ -0,0 +1,25 @@ +/* + * 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.concurrent; + +import java.util.concurrent.ScheduledExecutorService; + +public interface ScheduledExecutorPlus extends ExecutorPlus, ScheduledExecutorService +{ +} diff --git a/src/java/org/apache/cassandra/concurrent/ScheduledExecutors.java b/src/java/org/apache/cassandra/concurrent/ScheduledExecutors.java index ff9d1b4341..8da600c790 100644 --- a/src/java/org/apache/cassandra/concurrent/ScheduledExecutors.java +++ b/src/java/org/apache/cassandra/concurrent/ScheduledExecutors.java @@ -24,6 +24,8 @@ import com.google.common.annotations.VisibleForTesting; import org.apache.cassandra.utils.ExecutorUtils; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; + /** * Centralized location for shared executors */ @@ -32,26 +34,26 @@ public class ScheduledExecutors /** * This pool is used for periodic fast (sub-microsecond) tasks. */ - public static final DebuggableScheduledThreadPoolExecutor scheduledFastTasks = new DebuggableScheduledThreadPoolExecutor("ScheduledFastTasks"); + public static final ScheduledExecutorPlus scheduledFastTasks = executorFactory().scheduled("ScheduledFastTasks"); /** * This pool is used for periodic short (sub-second) tasks. */ - public static final DebuggableScheduledThreadPoolExecutor scheduledTasks = new DebuggableScheduledThreadPoolExecutor("ScheduledTasks"); + public static final ScheduledExecutorPlus scheduledTasks = executorFactory().scheduled("ScheduledTasks"); /** * This executor is used for tasks that can have longer execution times, and usually are non periodic. */ - public static final DebuggableScheduledThreadPoolExecutor nonPeriodicTasks = new DebuggableScheduledThreadPoolExecutor("NonPeriodicTasks"); + public static final ScheduledExecutorPlus nonPeriodicTasks = executorFactory().scheduled("NonPeriodicTasks"); /** * This executor is used for tasks that do not need to be waited for on shutdown/drain. */ - public static final DebuggableScheduledThreadPoolExecutor optionalTasks = new DebuggableScheduledThreadPoolExecutor("OptionalTasks"); + public static final ScheduledExecutorPlus optionalTasks = executorFactory().scheduled(false, "OptionalTasks"); @VisibleForTesting - public static void shutdownAndWait(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException + public static void shutdownNowAndWait(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException { - ExecutorUtils.shutdownNowAndWait(timeout, unit, scheduledFastTasks, scheduledTasks, nonPeriodicTasks, optionalTasks); + ExecutorUtils.shutdownNowAndWait(timeout, unit, scheduledTasks, scheduledFastTasks, nonPeriodicTasks, optionalTasks); } } diff --git a/src/java/org/apache/cassandra/concurrent/ScheduledThreadPoolExecutorPlus.java b/src/java/org/apache/cassandra/concurrent/ScheduledThreadPoolExecutorPlus.java new file mode 100644 index 0000000000..efd284ffe3 --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/ScheduledThreadPoolExecutorPlus.java @@ -0,0 +1,240 @@ +/* + * 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.concurrent; + +import java.util.List; +import java.util.concurrent.*; + +import org.apache.cassandra.utils.WithResources; +import org.apache.cassandra.utils.concurrent.Future; +import org.apache.cassandra.utils.concurrent.RunnableFuture; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.cassandra.service.StorageService; + +import static org.apache.cassandra.concurrent.ExecutionFailure.propagating; +import static org.apache.cassandra.concurrent.ExecutionFailure.suppressing; + +/** + * Like ExecutorPlus, ScheduledThreadPoolExecutorPlus always + * logs exceptions from the tasks it is given, even if Future.get is never called elsewhere. + * + * Catches exceptions during Task execution so that they don't suppress subsequent invocations of the task. + * + * Finally, there is a special rejected execution handler for tasks rejected during the shutdown hook. + * - For fire and forget tasks (like ref tidy) we can safely ignore the exceptions. + * - For any callers that care to know their task was rejected we cancel passed task. + */ +public class ScheduledThreadPoolExecutorPlus extends ScheduledThreadPoolExecutor implements ScheduledExecutorPlus +{ + private static final Logger logger = LoggerFactory.getLogger(ScheduledThreadPoolExecutorPlus.class); + private static final TaskFactory taskFactory = TaskFactory.standard(); + + public static final RejectedExecutionHandler rejectedExecutionHandler = (task, executor) -> + { + if (executor.isShutdown()) + { + // TODO: this sequence of events seems poorly thought out + if (!StorageService.instance.isShutdown()) + throw new RejectedExecutionException("ScheduledThreadPoolExecutor has shut down."); + + //Give some notification to the caller the task isn't going to run + if (task instanceof java.util.concurrent.Future) + ((java.util.concurrent.Future) task).cancel(false); + + logger.debug("ScheduledThreadPoolExecutor has shut down as part of C* shutdown"); + } + else + { + throw new AssertionError("Unknown rejection of ScheduledThreadPoolExecutor task"); + } + }; + + ScheduledThreadPoolExecutorPlus(NamedThreadFactory threadFactory) + { + super(1, threadFactory); + setRejectedExecutionHandler(rejectedExecutionHandler); + } + + @Override + public ScheduledFuture schedule(Runnable task, long delay, TimeUnit unit) + { + return super.schedule(propagating(task), delay, unit); + } + + @Override + public ScheduledFuture schedule(Callable task, long delay, TimeUnit unit) + { + return super.schedule(propagating(task), delay, unit); + } + + // override scheduling to suppress exceptions that would cancel future executions + @Override + public ScheduledFuture scheduleAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit) + { + return super.scheduleAtFixedRate(suppressing(task), initialDelay, period, unit); + } + + @Override + public ScheduledFuture scheduleWithFixedDelay(Runnable task, long initialDelay, long delay, TimeUnit unit) + { + return super.scheduleWithFixedDelay(suppressing(task), initialDelay, delay, unit); + } + + /*======== BEGIN DIRECT COPY OF ThreadPoolExecutorPlus ===============*/ + + private T addTask(T task) + { + super.execute(task); + return task; + } + + @Override + public void execute(Runnable run) + { + addTask(taskFactory.toExecute(run)); + } + + @Override + public void execute(WithResources withResources, Runnable run) + { + addTask(taskFactory.toExecute(withResources, run)); + } + + @Override + public Future submit(Runnable run) + { + return addTask(taskFactory.toSubmit(run)); + } + + @Override + public Future submit(Runnable run, T result) + { + return addTask(taskFactory.toSubmit(run, result)); + } + + @Override + public Future submit(Callable call) + { + return addTask(taskFactory.toSubmit(call)); + } + + @Override + public Future submit(WithResources withResources, Runnable run, T result) + { + return addTask(taskFactory.toSubmit(withResources, run, result)); + } + + @Override + public Future submit(WithResources withResources, Runnable run) + { + return addTask(taskFactory.toSubmit(withResources, run)); + } + + @Override + public Future submit(WithResources withResources, Callable call) + { + return addTask(taskFactory.toSubmit(withResources, call)); + } + + @Override + public boolean inExecutor() + { + return Thread.currentThread().getThreadGroup() == getThreadFactory().threadGroup; + } + + @Override + protected RunnableFuture newTaskFor(Runnable runnable, T value) + { + return taskFactory.toSubmit(runnable, value); + } + + @Override + protected RunnableFuture newTaskFor(Callable callable) + { + return taskFactory.toSubmit(callable); + } + + @Override + public NamedThreadFactory getThreadFactory() + { + return (NamedThreadFactory) super.getThreadFactory(); + } + + /*======== DIRECT COPY OF ThreadPoolExecutorBase ===============*/ + + @Override + public List shutdownNow() + { + List cancelled = super.shutdownNow(); + for (Runnable c : cancelled) + { + if (c instanceof java.util.concurrent.Future) + ((java.util.concurrent.Future) c).cancel(true); + } + return cancelled; + } + + @Override + protected void terminated() + { + getThreadFactory().close(); + } + + @Override + public int getActiveTaskCount() + { + return getActiveCount(); + } + + @Override + public int getPendingTaskCount() + { + return getQueue().size(); + } + + /*======== DIRECT COPY OF SingleThreadExecutorPlus ===============*/ + + @Override + public int getCorePoolSize() + { + return 1; + } + @Override + public void setCorePoolSize(int number) + { + throw new UnsupportedOperationException(); + } + @Override + public int getMaximumPoolSize() + { + return 1; + } + @Override + public void setMaximumPoolSize(int number) + { + throw new UnsupportedOperationException(); + } + + @Override + public int getMaxTasksQueued() + { + return Integer.MAX_VALUE; + } +} diff --git a/src/java/org/apache/cassandra/concurrent/SequentialExecutorPlus.java b/src/java/org/apache/cassandra/concurrent/SequentialExecutorPlus.java new file mode 100644 index 0000000000..7ea0e95795 --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/SequentialExecutorPlus.java @@ -0,0 +1,53 @@ +/* + * 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.concurrent; + +/** + * An {@link ExecutorPlus} that guarantees the order of execution matches the order of task submission, + * and provides a simple mechanism for the recurring pattern of ensuring a job is executed at least once + * after some point in time (i.e. ensures that at most one copy of the task is queued, with up to one + * copy running as well) + */ +public interface SequentialExecutorPlus extends ExecutorPlus +{ + public interface AtLeastOnceTrigger + { + /** + * Ensure the job is run at least once in its entirety after this method is invoked (including any already queued) + */ + public boolean trigger(); + + /** + * Run the provided task after all queued and executing jobs have completed + */ + public void runAfter(Runnable run); + + /** + * Wait until all queued and executing jobs have completed + */ + public void sync(); + } + + /** + * Return an object for orchestrating the execution of this task at least once (in its entirety) after + * the trigger is invoked, i.e. saturating the number of pending tasks at 1 (2 including any possibly executing + * at the time of invocation) + */ + public AtLeastOnceTrigger atLeastOnceTrigger(Runnable runnable); +} diff --git a/src/java/org/apache/cassandra/concurrent/SharedExecutorPool.java b/src/java/org/apache/cassandra/concurrent/SharedExecutorPool.java index bba8e8465e..f74854f9cb 100644 --- a/src/java/org/apache/cassandra/concurrent/SharedExecutorPool.java +++ b/src/java/org/apache/cassandra/concurrent/SharedExecutorPool.java @@ -27,6 +27,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.LockSupport; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.concurrent.SEPWorker.Work; import static org.apache.cassandra.utils.Clock.Global.nanoTime; @@ -57,11 +58,10 @@ import static org.apache.cassandra.utils.Clock.Global.nanoTime; */ public class SharedExecutorPool { - public static final SharedExecutorPool SHARED = new SharedExecutorPool("SharedPool"); // the name assigned to workers in the pool, and the id suffix - final String poolName; + final ThreadGroup threadGroup; final AtomicLong workerId = new AtomicLong(); // the collection of executors serviced by this pool; periodically ordered by traffic volume @@ -80,9 +80,14 @@ public class SharedExecutorPool volatile boolean shuttingDown = false; - public SharedExecutorPool(String poolName) + public SharedExecutorPool(String name) { - this.poolName = poolName; + this(executorFactory().newThreadGroup(name)); + } + + public SharedExecutorPool(ThreadGroup threadGroup) + { + this.threadGroup = threadGroup; } void schedule(Work work) @@ -97,7 +102,7 @@ public class SharedExecutorPool return; if (!work.isStop()) - new SEPWorker(workerId.incrementAndGet(), work, this); + new SEPWorker(threadGroup, workerId.incrementAndGet(), work, this); } void maybeStartSpinningWorker() @@ -109,12 +114,12 @@ public class SharedExecutorPool schedule(Work.SPINNING); } - public synchronized LocalAwareExecutorService newExecutor(int maxConcurrency, String jmxPath, String name) + public synchronized LocalAwareExecutorPlus newExecutor(int maxConcurrency, String jmxPath, String name) { return newExecutor(maxConcurrency, i -> {}, jmxPath, name); } - public LocalAwareExecutorService newExecutor(int maxConcurrency, LocalAwareExecutorService.MaximumPoolSizeListener maximumPoolSizeListener, String jmxPath, String name) + public LocalAwareExecutorPlus newExecutor(int maxConcurrency, ExecutorPlus.MaximumPoolSizeListener maximumPoolSizeListener, String jmxPath, String name) { SEPExecutor executor = new SEPExecutor(this, maxConcurrency, maximumPoolSizeListener, jmxPath, name); executors.add(executor); diff --git a/src/java/org/apache/cassandra/concurrent/ExecutorLocal.java b/src/java/org/apache/cassandra/concurrent/Shutdownable.java similarity index 52% rename from src/java/org/apache/cassandra/concurrent/ExecutorLocal.java rename to src/java/org/apache/cassandra/concurrent/Shutdownable.java index 6577b3dfe1..db8921773e 100644 --- a/src/java/org/apache/cassandra/concurrent/ExecutorLocal.java +++ b/src/java/org/apache/cassandra/concurrent/Shutdownable.java @@ -18,26 +18,24 @@ package org.apache.cassandra.concurrent; -import org.apache.cassandra.service.ClientWarn; -import org.apache.cassandra.tracing.Tracing; +import java.util.concurrent.TimeUnit; -public interface ExecutorLocal +public interface Shutdownable { - ExecutorLocal[] all = { Tracing.instance, ClientWarn.instance }; + boolean isTerminated(); /** - * This is called when scheduling the task, and also before calling {@link #set(Object)} when running on a - * executor thread. - * - * @return The thread-local value that we want to copy across executor boundaries; may be null if not set. + * Shutdown once any remaining work has completed (however this is defined for the implementation). */ - T get(); + void shutdown(); /** - * Before a task has been run, this will be called with the value from the thread that scheduled the task, and after - * the task is finished, the value that was previously retrieved from this thread is restored. - * - * @param value Value to use for the executor local state; may be null. + * Shutdown immediately, possibly interrupting ongoing work, and cancelling work that is queued. */ - void set(T value); + Object shutdownNow(); + + /** + * Await termination of this object, i.e. the cessation of all current and future work. + */ + public boolean awaitTermination(long timeout, TimeUnit units) throws InterruptedException; } diff --git a/src/java/org/apache/cassandra/concurrent/SingleThreadExecutorPlus.java b/src/java/org/apache/cassandra/concurrent/SingleThreadExecutorPlus.java new file mode 100644 index 0000000000..e72a6a4f19 --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/SingleThreadExecutorPlus.java @@ -0,0 +1,100 @@ +/* + * 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.concurrent; + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.cassandra.utils.concurrent.Future; + +public class SingleThreadExecutorPlus extends ThreadPoolExecutorPlus implements SequentialExecutorPlus +{ + public static class AtLeastOnce extends AtomicBoolean implements AtLeastOnceTrigger, Runnable + { + protected final SequentialExecutorPlus executor; + protected final Runnable run; + + public AtLeastOnce(SequentialExecutorPlus executor, Runnable run) + { + this.executor = executor; + this.run = run; + } + + public boolean trigger() + { + boolean success; + if (success = compareAndSet(false, true)) + executor.execute(this); + return success; + } + + public void runAfter(Runnable run) + { + executor.execute(run); + } + + public void sync() + { + Future done = executor.submit(() -> {}); + done.awaitThrowUncheckedOnInterrupt(); + done.rethrowIfFailed(); // executor might get shutdown before we execute; propagate cancellation exception + } + + public void run() + { + set(false); + run.run(); + } + } + + SingleThreadExecutorPlus(ThreadPoolExecutorBuilder builder) + { + this(builder, TaskFactory.standard()); + } + + SingleThreadExecutorPlus(ThreadPoolExecutorBuilder builder, TaskFactory taskFactory) + { + super(builder, taskFactory); + } + + @Override + public int getCorePoolSize() + { + return 1; + } + @Override + public void setCorePoolSize(int number) + { + throw new UnsupportedOperationException(); + } + @Override + public int getMaximumPoolSize() + { + return 1; + } + @Override + public void setMaximumPoolSize(int number) + { + throw new UnsupportedOperationException(); + } + + @Override + public AtLeastOnce atLeastOnceTrigger(Runnable run) + { + return new AtLeastOnce(this, run); + } +} diff --git a/src/java/org/apache/cassandra/concurrent/Stage.java b/src/java/org/apache/cassandra/concurrent/Stage.java index a34c3d3003..66cd7cb536 100644 --- a/src/java/org/apache/cassandra/concurrent/Stage.java +++ b/src/java/org/apache/cassandra/concurrent/Stage.java @@ -20,14 +20,7 @@ package org.apache.cassandra.concurrent; import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.concurrent.RejectedExecutionHandler; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.IntSupplier; @@ -41,12 +34,11 @@ import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.net.Verb; import org.apache.cassandra.utils.ExecutorUtils; - import org.apache.cassandra.utils.FBUtilities; +import org.apache.cassandra.utils.concurrent.Future; -import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.stream.Collectors.toMap; -import static org.apache.cassandra.utils.concurrent.BlockingQueues.newBlockingQueue; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; public enum Stage { @@ -57,18 +49,17 @@ public enum Stage GOSSIP ("GossipStage", "internal", () -> 1, null, Stage::singleThreadedStage), REQUEST_RESPONSE ("RequestResponseStage", "request", FBUtilities::getAvailableProcessors, null, Stage::multiThreadedLowSignalStage), ANTI_ENTROPY ("AntiEntropyStage", "internal", () -> 1, null, Stage::singleThreadedStage), - MIGRATION ("MigrationStage", "internal", () -> 1, null, Stage::singleThreadedStage), + MIGRATION ("MigrationStage", "internal", () -> 1, null, Stage::migrationStage), MISC ("MiscStage", "internal", () -> 1, null, Stage::singleThreadedStage), - TRACING ("TracingStage", "internal", () -> 1, null, Stage::tracingExecutor), + TRACING ("TracingStage", "internal", () -> 1, null, Stage::tracingStage), INTERNAL_RESPONSE ("InternalResponseStage", "internal", FBUtilities::getAvailableProcessors, null, Stage::multiThreadedStage), IMMEDIATE ("ImmediateStage", "internal", () -> 0, null, Stage::immediateExecutor); - public static final long KEEP_ALIVE_SECONDS = 60; // seconds to keep "extra" threads alive for when idle public final String jmxName; - private final Supplier initialiser; - private volatile LocalAwareExecutorService executor = null; + private final Supplier initialiser; + private volatile ExecutorPlus executor = null; - Stage(String jmxName, String jmxType, IntSupplier numThreads, LocalAwareExecutorService.MaximumPoolSizeListener onSetMaximumPoolSize, ExecutorServiceInitialiser initialiser) + Stage(String jmxName, String jmxType, IntSupplier numThreads, LocalAwareExecutorPlus.MaximumPoolSizeListener onSetMaximumPoolSize, ExecutorServiceInitialiser initialiser) { this.jmxName = jmxName; this.initialiser = () -> initialiser.init(jmxName, jmxType, numThreads.getAsInt(), onSetMaximumPoolSize); @@ -122,14 +113,14 @@ public enum Stage } // Convenience functions to execute on this stage - public void execute(Runnable command) { executor().execute(command); } - public void execute(Runnable command, ExecutorLocals locals) { executor().execute(command, locals); } - public void maybeExecuteImmediately(Runnable command) { executor().maybeExecuteImmediately(command); } + public void execute(Runnable task) { executor().execute(task); } + public void execute(ExecutorLocals locals, Runnable task) { executor().execute(locals, task); } + public void maybeExecuteImmediately(Runnable task) { executor().maybeExecuteImmediately(task); } public Future submit(Callable task) { return executor().submit(task); } public Future submit(Runnable task) { return executor().submit(task); } public Future submit(Runnable task, T result) { return executor().submit(task, result); } - public LocalAwareExecutorService executor() + public ExecutorPlus executor() { if (executor == null) { @@ -144,7 +135,7 @@ public enum Stage return executor; } - private static List executors() + private static List executors() { return Stream.of(Stage.values()) .map(Stage::executor) @@ -162,44 +153,52 @@ public enum Stage @VisibleForTesting public static void shutdownAndWait(long timeout, TimeUnit units) throws InterruptedException, TimeoutException { - List executors = executors(); + List executors = executors(); ExecutorUtils.shutdownNow(executors); ExecutorUtils.awaitTermination(timeout, units, executors); } - static LocalAwareExecutorService tracingExecutor(String jmxName, String jmxType, int numThreads, LocalAwareExecutorService.MaximumPoolSizeListener onSetMaximumPoolSize) + private static ExecutorPlus tracingStage(String jmxName, String jmxType, int numThreads, LocalAwareExecutorPlus.MaximumPoolSizeListener onSetMaximumPoolSize) { - RejectedExecutionHandler reh = (r, executor) -> MessagingService.instance().metrics.recordSelfDroppedMessage(Verb._TRACE); - return new TracingExecutor(1, - 1, - KEEP_ALIVE_SECONDS, - TimeUnit.SECONDS, - new ArrayBlockingQueue<>(1000), - new NamedThreadFactory(jmxName), - reh); + return executorFactory() + .withJmx(jmxType) + .configureSequential(jmxName) + .withQueueLimit(1000) + .withRejectedExecutionHandler((r, executor) -> MessagingService.instance().metrics.recordSelfDroppedMessage(Verb._TRACE)).build(); } - static LocalAwareExecutorService multiThreadedStage(String jmxName, String jmxType, int numThreads, LocalAwareExecutorService.MaximumPoolSizeListener onSetMaximumPoolSize) + private static ExecutorPlus migrationStage(String jmxName, String jmxType, int numThreads, LocalAwareExecutorPlus.MaximumPoolSizeListener onSetMaximumPoolSize) { - return new JMXEnabledThreadPoolExecutor(numThreads, - KEEP_ALIVE_SECONDS, - SECONDS, - newBlockingQueue(), - new NamedThreadFactory(jmxName), - jmxType); + return executorFactory() + .withJmx(jmxType) + .sequential(jmxName); } - static LocalAwareExecutorService multiThreadedLowSignalStage(String jmxName, String jmxType, int numThreads, LocalAwareExecutorService.MaximumPoolSizeListener onSetMaximumPoolSize) + private static LocalAwareExecutorPlus singleThreadedStage(String jmxName, String jmxType, int numThreads, LocalAwareExecutorPlus.MaximumPoolSizeListener onSetMaximumPoolSize) { - return SharedExecutorPool.SHARED.newExecutor(numThreads, onSetMaximumPoolSize, jmxType, jmxName); + return executorFactory() + .localAware() + .withJmx(jmxType) + .sequential(jmxName); } - static LocalAwareExecutorService singleThreadedStage(String jmxName, String jmxType, int numThreads, LocalAwareExecutorService.MaximumPoolSizeListener onSetMaximumPoolSize) + static LocalAwareExecutorPlus multiThreadedStage(String jmxName, String jmxType, int numThreads, LocalAwareExecutorPlus.MaximumPoolSizeListener onSetMaximumPoolSize) { - return new JMXEnabledSingleThreadExecutor(jmxName, jmxType); + return executorFactory() + .localAware() + .withJmx(jmxType) + .pooled(jmxName, numThreads); } - static LocalAwareExecutorService immediateExecutor(String jmxName, String jmxType, int numThreads, LocalAwareExecutorService.MaximumPoolSizeListener onSetMaximumPoolSize) + static LocalAwareExecutorPlus multiThreadedLowSignalStage(String jmxName, String jmxType, int numThreads, LocalAwareExecutorPlus.MaximumPoolSizeListener onSetMaximumPoolSize) + { + return executorFactory() + .localAware() + .withJmx(jmxType) + .shared(jmxName, numThreads, onSetMaximumPoolSize); + } + + static LocalAwareExecutorPlus immediateExecutor(String jmxName, String jmxType, int numThreads, LocalAwareExecutorPlus.MaximumPoolSizeListener onSetMaximumPoolSize) { return ImmediateExecutor.INSTANCE; } @@ -207,7 +206,7 @@ public enum Stage @FunctionalInterface public interface ExecutorServiceInitialiser { - public LocalAwareExecutorService init(String jmxName, String jmxType, int numThreads, LocalAwareExecutorService.MaximumPoolSizeListener onSetMaximumPoolSize); + public ExecutorPlus init(String jmxName, String jmxType, int numThreads, LocalAwareExecutorPlus.MaximumPoolSizeListener onSetMaximumPoolSize); } /** @@ -241,38 +240,4 @@ public enum Stage { executor().setMaximumPoolSize(newMaximumPoolSize); } - - /** - * The executor used for tracing. - */ - private static class TracingExecutor extends ThreadPoolExecutor implements LocalAwareExecutorService - { - TracingExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) - { - super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler); - } - - public void execute(Runnable command, ExecutorLocals locals) - { - assert locals == null; - super.execute(command); - } - - public void maybeExecuteImmediately(Runnable command) - { - execute(command); - } - - @Override - public int getActiveTaskCount() - { - return getActiveCount(); - } - - @Override - public int getPendingTaskCount() - { - return getQueue().size(); - } - } } diff --git a/src/java/org/apache/cassandra/concurrent/SyncFutureTask.java b/src/java/org/apache/cassandra/concurrent/SyncFutureTask.java new file mode 100644 index 0000000000..19ca27f194 --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/SyncFutureTask.java @@ -0,0 +1,70 @@ +/* + * 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.concurrent; + +import java.util.concurrent.Callable; + +import org.apache.cassandra.utils.Closeable; +import org.apache.cassandra.utils.WithResources; +import org.apache.cassandra.utils.concurrent.RunnableFuture; +import org.apache.cassandra.utils.concurrent.SyncFuture; + +public class SyncFutureTask extends SyncFuture implements RunnableFuture +{ + final Callable call; + + public SyncFutureTask(Callable call) + { + this.call = call; + } + + public SyncFutureTask(WithResources withResources, Callable call) + { + this.call = () -> { + try (Closeable close = withResources.get()) + { + return call.call(); + } + }; + } + + public void run() + { + try + { + if (!setUncancellable()) + throw new IllegalStateException(); + + if (!trySuccess(call.call())) + throw new IllegalStateException(); + } + catch (Throwable t) + { + tryFailure(t); + Thread thread = Thread.currentThread(); + thread.getUncaughtExceptionHandler().uncaughtException(thread, t); + } + } + + @Override + public String description() + { + return call.toString(); + } +} diff --git a/src/java/org/apache/cassandra/concurrent/TaskFactory.java b/src/java/org/apache/cassandra/concurrent/TaskFactory.java new file mode 100644 index 0000000000..a25a45f764 --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/TaskFactory.java @@ -0,0 +1,178 @@ +/* + * 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.concurrent; + +import java.util.concurrent.Callable; + +import org.apache.cassandra.utils.WithResources; +import org.apache.cassandra.utils.concurrent.RunnableFuture; + +import static org.apache.cassandra.concurrent.FutureTask.callable; + +/** + * A simple mechanism to impose our desired semantics on the execution of a task without requiring a specialised + * executor service. We wrap tasks in a suitable {@link FutureTask} or encapsulating {@link Runnable}. + * + * The encapsulations handle any exceptions in our standard way, as well as ensuring {@link ExecutorLocals} are + * propagated in the case of {@link #localAware()} + */ +public interface TaskFactory +{ + Runnable toExecute(Runnable runnable); + RunnableFuture toSubmit(Runnable runnable); + RunnableFuture toSubmit(Runnable runnable, T result); + RunnableFuture toSubmit(Callable callable); + + Runnable toExecute(WithResources withResources, Runnable runnable); + RunnableFuture toSubmit(WithResources withResources, Runnable runnable); + RunnableFuture toSubmit(WithResources withResources, Runnable runnable, T result); + RunnableFuture toSubmit(WithResources withResources, Callable callable); + + static TaskFactory standard() { return Standard.INSTANCE; } + static TaskFactory localAware() { return LocalAware.INSTANCE; } + + public class Standard implements TaskFactory + { + static final Standard INSTANCE = new Standard(); + protected Standard() {} + + @Override + public Runnable toExecute(Runnable runnable) + { + return ExecutionFailure.suppressing(runnable); + } + + @Override + public RunnableFuture toSubmit(Runnable runnable) + { + return newTask(callable(runnable)); + } + + @Override + public RunnableFuture toSubmit(Runnable runnable, T result) + { + return newTask(callable(runnable, result)); + } + + @Override + public RunnableFuture toSubmit(Callable callable) + { + return newTask(callable); + } + + @Override + public Runnable toExecute(WithResources withResources, Runnable runnable) + { + return ExecutionFailure.suppressing(withResources, runnable); + } + + @Override + public RunnableFuture toSubmit(WithResources withResources, Runnable runnable) + { + return withResources.isNoOp() ? newTask(callable(runnable)) + : newTask(withResources, callable(runnable)); + } + + @Override + public RunnableFuture toSubmit(WithResources withResources, Runnable runnable, T result) + { + return withResources.isNoOp() ? newTask(callable(runnable, result)) + : newTask(withResources, callable(runnable, result)); + } + + @Override + public RunnableFuture toSubmit(WithResources withResources, Callable callable) + { + return withResources.isNoOp() ? newTask(callable) + : newTask(withResources, callable); + } + + protected RunnableFuture newTask(Callable call) + { + return new FutureTask<>(call); + } + + protected RunnableFuture newTask(WithResources withResources, Callable call) + { + return new FutureTaskWithResources<>(withResources, call); + } + } + + public class LocalAware extends Standard + { + static final LocalAware INSTANCE = new LocalAware(); + + protected LocalAware() {} + + @Override + public Runnable toExecute(Runnable runnable) + { + // no reason to propagate exception when it is inaccessible to caller + return ExecutionFailure.suppressing(ExecutorLocals.propagate(), runnable); + } + + @Override + public RunnableFuture toSubmit(Runnable runnable) + { + return super.toSubmit(ExecutorLocals.propagate(), runnable); + } + + @Override + public RunnableFuture toSubmit(Runnable runnable, T result) + { + return super.toSubmit(ExecutorLocals.propagate(), runnable, result); + } + + @Override + public RunnableFuture toSubmit(Callable callable) + { + return super.toSubmit(ExecutorLocals.propagate(), callable); + } + + @Override + public Runnable toExecute(WithResources withResources, Runnable runnable) + { + return ExecutionFailure.suppressing(withLocals(withResources), runnable); + } + + @Override + public RunnableFuture toSubmit(WithResources withResources, Runnable runnable) + { + return super.toSubmit(withLocals(withResources), runnable); + } + + @Override + public RunnableFuture toSubmit(WithResources withResources, Runnable runnable, T result) + { + return super.toSubmit(withLocals(withResources), runnable, result); + } + + @Override + public RunnableFuture toSubmit(WithResources withResources, Callable callable) + { + return super.toSubmit(withLocals(withResources), callable); + } + + private static WithResources withLocals(WithResources withResources) + { + return withResources instanceof ExecutorLocals ? withResources : ExecutorLocals.propagate().and(withResources); + } + } + +} diff --git a/src/java/org/apache/cassandra/concurrent/ThreadPoolExecutorBase.java b/src/java/org/apache/cassandra/concurrent/ThreadPoolExecutorBase.java new file mode 100644 index 0000000000..b90485abe6 --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/ThreadPoolExecutorBase.java @@ -0,0 +1,186 @@ +/* + * 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.concurrent; + +import java.util.List; +import java.util.concurrent.*; + +import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; + +/** + * This class incorporates some Executor best practices for Cassandra. Most of the executors in the system + * should use or extend {@link ThreadPoolExecutorPlus}, or in rare exceptions this class. + * + * This class provides some very basic improvements: + *
  • We are configured by {@link ThreadPoolExecutorBuilder} + *
  • Tasks rejected due to overflow of the queue block the submitting thread rather than throwing {@link RejectedExecutionException} + *
  • {@link RunnableFuture} rejected due to executor shutdown will be cancelled + *
  • {@link RunnableFuture} removed by {@link #shutdownNow()} will be cancelled + * + * We also provide a shutdown hook for JMX registration cleanup. + */ +public class ThreadPoolExecutorBase extends ThreadPoolExecutor implements ResizableThreadPool +{ + public static final RejectedExecutionHandler blockingExecutionHandler = (task, executor) -> + { + BlockingQueue queue = executor.getQueue(); + try + { + while (true) + { + try + { + if (executor.isShutdown()) + throw new RejectedExecutionException(executor + " has shut down"); + + if (queue.offer(task, 1, TimeUnit.SECONDS)) + break; + } + catch (InterruptedException e) + { + throw new UncheckedInterruptedException(e); + } + } + } + catch (Throwable t) + { + //Give some notification to the caller the task isn't going to run + if (task instanceof java.util.concurrent.Future) + ((java.util.concurrent.Future) task).cancel(false); + throw t; + } + }; + + private Runnable onShutdown; + + // maximumPoolSize is only used when corePoolSize == 0 + // if keepAliveTime < 0 and unit == null, we forbid core thread timeouts (e.g. single threaded executors by default) + public ThreadPoolExecutorBase(ThreadPoolExecutorBuilder builder) + { + super(builder.coreThreads(), builder.maxThreads(), builder.keepAlive(), builder.keepAliveUnits(), builder.newQueue(), builder.newThreadFactory()); + allowCoreThreadTimeOut(builder.allowCoreThreadTimeouts()); + + // block task submissions until queue has room. + // this is fighting TPE's design a bit because TPE rejects if queue.offer reports a full queue. + // we'll just override this with a handler that retries until it gets in. ugly, but effective. + // (there is an extensive analysis of the options here at + // http://today.java.net/pub/a/today/2008/10/23/creating-a-notifying-blocking-thread-pool-executor.html) + setRejectedExecutionHandler(builder.rejectedExecutionHandler(blockingExecutionHandler)); + } + + // no RejectedExecutionHandler + public ThreadPoolExecutorBase(int threads, int keepAlive, TimeUnit keepAliveUnits, BlockingQueue queue, NamedThreadFactory threadFactory) + { + super(threads, threads, keepAlive, keepAliveUnits, queue, threadFactory); + assert queue.isEmpty() : "Executor initialized with non-empty task queue"; + allowCoreThreadTimeOut(true); + } + + public void onShutdown(Runnable onShutdown) + { + this.onShutdown = onShutdown; + } + + public Runnable onShutdown() + { + return onShutdown; + } + + @Override + protected void terminated() + { + getThreadFactory().close(); + } + + @Override + public void shutdown() + { + try + { + super.shutdown(); + } + finally + { + if (onShutdown != null) + onShutdown.run(); + } + } + + @Override + public List shutdownNow() + { + try + { + List cancelled = super.shutdownNow(); + for (Runnable c : cancelled) + { + if (c instanceof java.util.concurrent.Future) + ((java.util.concurrent.Future) c).cancel(true); + } + return cancelled; + } + finally + { + if (onShutdown != null) + onShutdown.run(); + } + } + + @Override + public int getActiveTaskCount() + { + return getActiveCount(); + } + + @Override + public int getPendingTaskCount() + { + return getQueue().size(); + } + + public int getCoreThreads() + { + return getCorePoolSize(); + } + + public void setCoreThreads(int number) + { + setCorePoolSize(number); + } + + public int getMaximumThreads() + { + return getMaximumPoolSize(); + } + + public void setMaximumThreads(int number) + { + setMaximumPoolSize(number); + } + + @Override + public NamedThreadFactory getThreadFactory() + { + return (NamedThreadFactory) super.getThreadFactory(); + } + + public String toString() + { + return getThreadFactory().id; + } +} diff --git a/src/java/org/apache/cassandra/concurrent/ThreadPoolExecutorBuilder.java b/src/java/org/apache/cassandra/concurrent/ThreadPoolExecutorBuilder.java new file mode 100644 index 0000000000..7c8dd938e3 --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/ThreadPoolExecutorBuilder.java @@ -0,0 +1,204 @@ +/* + * 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.concurrent; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +import org.apache.cassandra.concurrent.NamedThreadFactory.MetaFactory; + +import static java.lang.Thread.NORM_PRIORITY; +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.apache.cassandra.utils.concurrent.BlockingQueues.newBlockingQueue; + +/** + * Configure a {@link ThreadPoolExecutorPlus}, applying Cassandra's best practices by default + *
  • Core threads may timeout, and use a default {@link #keepAlive} time in {@link #keepAliveUnits} + *
  • Threads share the same {@link ThreadGroup}, which may be configurably a child of a specified {@link ThreadGroup} + * descended from the same parent of the {@link MetaFactory} + *
  • By default queues are unbounded in length + *
  • The default {@link RejectedExecutionHandler} is implementation dependent, but may be overridden + *
  • The default {@link UncaughtExceptionHandler} is inherited from {@link MetaFactory}, which in turn receives it + * from the {@link ExecutorBuilderFactory} + */ +class ThreadPoolExecutorBuilder extends MetaFactory implements ExecutorBuilder +{ + static ExecutorBuilder sequential(Function, E> constructor, ClassLoader contextClassLoader, ThreadGroup threadGroup, UncaughtExceptionHandler uncaughtExceptionHandler, String name) + { + ThreadPoolExecutorBuilder result = new ThreadPoolExecutorBuilder<>(constructor, contextClassLoader, threadGroup, uncaughtExceptionHandler, name, 1); + result.withKeepAlive(); + return result; + } + + static ExecutorBuilder sequentialJmx(Function, E> constructor, ClassLoader contextClassLoader, ThreadGroup threadGroup, UncaughtExceptionHandler uncaughtExceptionHandler, String name, String jmxPath) + { + return new ThreadPoolExecutorJMXAdapter.Builder<>(sequential(constructor, contextClassLoader, threadGroup, uncaughtExceptionHandler, name), jmxPath); + } + + static ExecutorBuilder pooled(Function, E> constructor, ClassLoader contextClassLoader, ThreadGroup threadGroup, UncaughtExceptionHandler uncaughtExceptionHandler, String name, int threads) + { + return new ThreadPoolExecutorBuilder<>(constructor, contextClassLoader, threadGroup, uncaughtExceptionHandler, name, threads); + } + + static ExecutorBuilder pooledJmx(Function, E> constructor, ClassLoader contextClassLoader, ThreadGroup threadGroup, UncaughtExceptionHandler uncaughtExceptionHandler, String name, int threads, String jmxPath) + { + return new ThreadPoolExecutorJMXAdapter.Builder<>(pooled(constructor, contextClassLoader, threadGroup, uncaughtExceptionHandler, name, threads), jmxPath); + } + + private final Function, E> constructor; + private final String name; + private final int threads; + private int threadPriority = NORM_PRIORITY; + private Integer queueLimit; + + private long keepAlive = 1; + private TimeUnit keepAliveUnits = MINUTES; + private boolean allowCoreThreadTimeouts = true; + + private RejectedExecutionHandler rejectedExecutionHandler = null; + + protected ThreadPoolExecutorBuilder(Function, E> constructor, ClassLoader contextClassLoader, ThreadGroup overrideThreadGroup, UncaughtExceptionHandler uncaughtExceptionHandler, String name, int threads) + { + super(contextClassLoader, overrideThreadGroup, uncaughtExceptionHandler); + this.constructor = constructor; + this.name = name; + this.threads = threads; + } + + // core and non-core threads will die after this period of inactivity + public ThreadPoolExecutorBuilder withKeepAlive(long keepAlive, TimeUnit keepAliveUnits) + { + this.allowCoreThreadTimeouts = true; + this.keepAlive = keepAlive; + this.keepAliveUnits = keepAliveUnits; + return this; + } + + // once started, core threads will never die + public ThreadPoolExecutorBuilder withKeepAlive() + { + this.allowCoreThreadTimeouts = false; + return this; + } + + public ThreadPoolExecutorBuilder withThreadPriority(int threadPriority) + { + this.threadPriority = threadPriority; + return this; + } + + @Override + public ExecutorBuilder withThreadGroup(ThreadGroup threadGroup) + { + ThreadGroup current = this.threadGroup; + + ThreadGroup parent = threadGroup; + while (parent != null && parent != current) + parent = parent.getParent(); + if (parent != current) + throw new IllegalArgumentException("threadGroup may only be overridden with a child of the default threadGroup"); + + this.threadGroup = threadGroup; + return this; + } + + @Override + public ExecutorBuilder withDefaultThreadGroup() + { + this.threadGroup = null; + return this; + } + + public ThreadPoolExecutorBuilder withQueueLimit(int queueLimit) + { + this.queueLimit = queueLimit; + return this; + } + + public ThreadPoolExecutorBuilder withRejectedExecutionHandler(RejectedExecutionHandler rejectedExecutionHandler) + { + this.rejectedExecutionHandler = rejectedExecutionHandler; + return this; + } + + public ThreadPoolExecutorBuilder withUncaughtExceptionHandler(UncaughtExceptionHandler uncaughtExceptionHandler) + { + this.uncaughtExceptionHandler = uncaughtExceptionHandler; + return this; + } + + @Override + public E build() + { + return constructor.apply(this); + } + + NamedThreadFactory newThreadFactory() + { + return newThreadFactory(name, threadPriority); + } + + BlockingQueue newQueue() + { + // if our pool can have an infinite number of threads, there is no point having an infinite queue length + int size = queueLimit != null + ? queueLimit + : threads == Integer.MAX_VALUE + ? 0 : Integer.MAX_VALUE; + return newBlockingQueue(size); + } + + /** + * If our queue blocks on/rejects all submissions, we can configure our core pool size to 0, + * as new threads will always be created for new work, and core threads timeout at the same + * rate as non-core threads. + */ + int coreThreads() + { + return (queueLimit != null && queueLimit == 0) || threads == Integer.MAX_VALUE ? 0 : threads; + } + + int maxThreads() + { + return threads; + } + + RejectedExecutionHandler rejectedExecutionHandler(RejectedExecutionHandler ifNotSet) + { + return rejectedExecutionHandler == null ? ifNotSet : rejectedExecutionHandler; + } + + long keepAlive() + { + return keepAlive; + } + + TimeUnit keepAliveUnits() + { + return keepAliveUnits; + } + + boolean allowCoreThreadTimeouts() + { + return allowCoreThreadTimeouts; + } +} \ No newline at end of file diff --git a/src/java/org/apache/cassandra/concurrent/ThreadPoolExecutorJMXAdapter.java b/src/java/org/apache/cassandra/concurrent/ThreadPoolExecutorJMXAdapter.java new file mode 100644 index 0000000000..c596d2bfac --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/ThreadPoolExecutorJMXAdapter.java @@ -0,0 +1,246 @@ +/* + * 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.concurrent; + +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.TimeUnit; + +import com.google.common.annotations.VisibleForTesting; + +import org.apache.cassandra.metrics.ThreadPoolMetrics; +import org.apache.cassandra.utils.MBeanWrapper; + +/** + * A {@link ThreadPoolExecutorBase} adapter to expose it via JMX. + * The executor is not itself modified to maximise code re-use. + * Only its rejected execution handler is updated, and a shutdown listener is registered. + */ +@VisibleForTesting +public class ThreadPoolExecutorJMXAdapter implements Runnable, ResizableThreadPoolMXBean +{ + /** + * A builder wrapper that delegates all methods except {@link Builder#build()} + * @param + */ + static class Builder implements ExecutorBuilder + { + final ExecutorBuilder wrapped; + final String jmxPath; + Builder(ExecutorBuilder wrapped, String jmxPath) + { + this.wrapped = wrapped; + this.jmxPath = jmxPath; + } + + @Override + public ExecutorBuilder withKeepAlive(long keepAlive, TimeUnit keepAliveUnits) + { + wrapped.withKeepAlive(keepAlive, keepAliveUnits); + return this; + } + + @Override + public ExecutorBuilder withKeepAlive() + { + wrapped.withKeepAlive(); + return this; + } + + @Override + public ExecutorBuilder withThreadPriority(int threadPriority) + { + wrapped.withThreadPriority(threadPriority); + return this; + } + + @Override + public ExecutorBuilder withQueueLimit(int queueLimit) + { + wrapped.withQueueLimit(queueLimit); + return this; + } + + @Override + public ExecutorBuilder withThreadGroup(ThreadGroup threadGroup) + { + wrapped.withThreadGroup(threadGroup); + return this; + } + + @Override + public ExecutorBuilder withDefaultThreadGroup() + { + wrapped.withDefaultThreadGroup(); + return this; + } + + @Override + public ExecutorBuilder withRejectedExecutionHandler(RejectedExecutionHandler rejectedExecutionHandler) + { + wrapped.withRejectedExecutionHandler(rejectedExecutionHandler); + return this; + } + + @Override + public ExecutorBuilder withUncaughtExceptionHandler(Thread.UncaughtExceptionHandler uncaughtExceptionHandler) + { + wrapped.withUncaughtExceptionHandler(uncaughtExceptionHandler); + return this; + } + + /** + * Invoke {@link ExecutorBuilder#build()} on {@link #wrapped}, and register the resultant + * {@link ThreadPoolExecutorBase} with a new {@link ThreadPoolExecutorJMXAdapter}. + * + * The executor constructed by {@link #wrapped} is returned. + */ + @Override + public E build() + { + E result = wrapped.build(); + register(jmxPath, result); + return result; + } + } + + public static void register(String jmxPath, ThreadPoolExecutorBase executor) + { + new ThreadPoolExecutorJMXAdapter(jmxPath, executor); + } + + final String mbeanName; + final ThreadPoolExecutorBase executor; + final ThreadPoolMetrics metrics; + boolean released; + + private ThreadPoolExecutorJMXAdapter(String jmxPath, ThreadPoolExecutorBase executor) + { + this.executor = executor; + this.mbeanName = "org.apache.cassandra." + jmxPath + ":type=" + executor.getThreadFactory().id; + this.metrics = new ThreadPoolMetrics(executor, jmxPath, executor.getThreadFactory().id).register(); + executor.setRejectedExecutionHandler(rejectedExecutionHandler(metrics, executor.getRejectedExecutionHandler())); + MBeanWrapper.instance.registerMBean(this, mbeanName); + executor.onShutdown(this); + } + + @Override + public synchronized void run() + { + if (released) + return; + + MBeanWrapper.instance.unregisterMBean(mbeanName); + metrics.release(); + released = true; + } + + public ThreadPoolMetrics metrics() + { + return metrics; + } + + @Override + public int getActiveTaskCount() + { + return executor.getActiveTaskCount(); + } + + @Override + public int getPendingTaskCount() + { + return executor.getPendingTaskCount(); + } + + @Override + public int getCoreThreads() + { + return executor.getCoreThreads(); + } + + @Override + public void setCoreThreads(int number) + { + executor.setCoreThreads(number); + } + + @Override + public int getMaximumThreads() + { + return executor.getMaximumThreads(); + } + + @Override + public void setMaximumThreads(int number) + { + executor.setMaximumThreads(number); + } + + @Override + public void setCorePoolSize(int corePoolSize) + { + executor.setCorePoolSize(corePoolSize); + } + + @Override + public int getCorePoolSize() + { + return executor.getCorePoolSize(); + } + + @Override + public void setMaximumPoolSize(int maximumPoolSize) + { + executor.setMaximumPoolSize(maximumPoolSize); + } + + @Override + public int getMaximumPoolSize() + { + return executor.getMaximumPoolSize(); + } + + @Override + public long getCompletedTaskCount() + { + return executor.getCompletedTaskCount(); + } + + @Override + public int getMaxTasksQueued() + { + return executor.getMaxTasksQueued(); + } + + static RejectedExecutionHandler rejectedExecutionHandler(ThreadPoolMetrics metrics, RejectedExecutionHandler wrap) + { + return (task, executor) -> + { + metrics.totalBlocked.inc(); + metrics.currentBlocked.inc(); + try + { + wrap.rejectedExecution(task, executor); + } + finally + { + metrics.currentBlocked.dec(); + } + }; + } +} diff --git a/src/java/org/apache/cassandra/concurrent/ThreadPoolExecutorPlus.java b/src/java/org/apache/cassandra/concurrent/ThreadPoolExecutorPlus.java new file mode 100644 index 0000000000..ad735d945b --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/ThreadPoolExecutorPlus.java @@ -0,0 +1,125 @@ +/* + * 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.concurrent; + +import java.util.concurrent.Callable; +import java.util.concurrent.RunnableFuture; + +import org.apache.cassandra.utils.WithResources; +import org.apache.cassandra.utils.concurrent.Future; + +/** + * This class inherits Executor best practices from {@link ThreadPoolExecutorBase} + * and {@link ThreadPoolExecutorBuilder}. Most Cassandra executors should use or extend this. + * + * This class' addition is to abstract the semantics of task encapsulation to handle + * exceptions and {@link ExecutorLocals}. See {@link TaskFactory} for more detail. + */ +public class ThreadPoolExecutorPlus extends ThreadPoolExecutorBase implements ExecutorPlus +{ + final TaskFactory taskFactory; + + ThreadPoolExecutorPlus(ThreadPoolExecutorBuilder builder) + { + this(builder, TaskFactory.standard()); + } + + ThreadPoolExecutorPlus(ThreadPoolExecutorBuilder builder, TaskFactory taskFactory) + { + super(builder); + this.taskFactory = taskFactory; + } + + private T addTask(T task) + { + super.execute(task); + return task; + } + + @Override + public void execute(Runnable run) + { + addTask(taskFactory.toExecute(run)); + } + + @Override + public void execute(WithResources withResources, Runnable run) + { + addTask(taskFactory.toExecute(withResources, run)); + } + + @Override + public Future submit(Runnable run) + { + return addTask(taskFactory.toSubmit(run)); + } + + @Override + public Future submit(Runnable run, T result) + { + return addTask(taskFactory.toSubmit(run, result)); + } + + @Override + public Future submit(Callable call) + { + return addTask(taskFactory.toSubmit(call)); + } + + @Override + public Future submit(WithResources withResources, Runnable run, T result) + { + return addTask(taskFactory.toSubmit(withResources, run, result)); + } + + @Override + public Future submit(WithResources withResources, Runnable run) + { + return addTask(taskFactory.toSubmit(withResources, run)); + } + + @Override + public Future submit(WithResources withResources, Callable call) + { + return addTask(taskFactory.toSubmit(withResources, call)); + } + + @Override + public boolean inExecutor() + { + return Thread.currentThread().getThreadGroup() == getThreadFactory().threadGroup; + } + + @Override + protected RunnableFuture newTaskFor(Runnable runnable, T value) + { + return taskFactory.toSubmit(runnable, value); + } + + @Override + protected RunnableFuture newTaskFor(Callable callable) + { + return taskFactory.toSubmit(callable); + } + + @Override + public int getMaxTasksQueued() + { + return getQueue().size(); + } +} diff --git a/src/java/org/apache/cassandra/concurrent/WrappedExecutorPlus.java b/src/java/org/apache/cassandra/concurrent/WrappedExecutorPlus.java new file mode 100644 index 0000000000..7408c33cbd --- /dev/null +++ b/src/java/org/apache/cassandra/concurrent/WrappedExecutorPlus.java @@ -0,0 +1,178 @@ +/* + * 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.concurrent; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.apache.cassandra.utils.WithResources; +import org.apache.cassandra.utils.concurrent.Future; + +public class WrappedExecutorPlus implements ExecutorPlus +{ + protected final ExecutorPlus executor; + + public WrappedExecutorPlus(ExecutorPlus executor) + { + this.executor = executor; + } + + public void maybeExecuteImmediately(Runnable task) + { + executor.maybeExecuteImmediately(task); + } + + public void execute(WithResources withResources, Runnable task) + { + executor.execute(withResources, task); + } + + @Override + public Future submit(WithResources withResources, Callable task) + { + return executor.submit(withResources, task); + } + + @Override + public Future submit(WithResources withResources, Runnable task, T result) + { + return executor.submit(withResources, task, result); + } + + @Override + public Future submit(WithResources withResources, Runnable task) + { + return executor.submit(withResources, task); + } + + @Override + public boolean inExecutor() + { + return executor.inExecutor(); + } + + public Future submit(Callable task) + { + return executor.submit(task); + } + + public Future submit(Runnable task, T result) + { + return executor.submit(task, result); + } + + public Future submit(Runnable task) + { + return executor.submit(task); + } + + public int getActiveTaskCount() + { + return executor.getActiveTaskCount(); + } + + public long getCompletedTaskCount() + { + return executor.getCompletedTaskCount(); + } + + public int getPendingTaskCount() + { + return executor.getPendingTaskCount(); + } + + public int getMaxTasksQueued() + { + return executor.getMaxTasksQueued(); + } + + public int getCorePoolSize() + { + return executor.getCorePoolSize(); + } + + public void setCorePoolSize(int newCorePoolSize) + { + executor.setCorePoolSize(newCorePoolSize); + } + + public int getMaximumPoolSize() + { + return executor.getMaximumPoolSize(); + } + + public void setMaximumPoolSize(int newMaximumPoolSize) + { + executor.setMaximumPoolSize(newMaximumPoolSize); + } + + public List> invokeAll(Collection> tasks) throws InterruptedException + { + return executor.invokeAll(tasks); + } + + public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException + { + return executor.invokeAll(tasks, timeout, unit); + } + + public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException + { + return executor.invokeAny(tasks); + } + + public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException + { + return executor.invokeAny(tasks, timeout, unit); + } + + public void shutdown() + { + executor.shutdown(); + } + + public List shutdownNow() + { + return executor.shutdownNow(); + } + + public boolean isShutdown() + { + return executor.isShutdown(); + } + + public boolean isTerminated() + { + return executor.isTerminated(); + } + + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException + { + return executor.awaitTermination(timeout, unit); + } + + public void execute(Runnable task) + { + executor.execute(task); + } +} diff --git a/src/java/org/apache/cassandra/cql3/functions/JavaBasedUDFunction.java b/src/java/org/apache/cassandra/cql3/functions/JavaBasedUDFunction.java index d2bac5fd3e..3200c88440 100644 --- a/src/java/org/apache/cassandra/cql3/functions/JavaBasedUDFunction.java +++ b/src/java/org/apache/cassandra/cql3/functions/JavaBasedUDFunction.java @@ -74,7 +74,7 @@ public final class JavaBasedUDFunction extends UDFunction private static final AtomicInteger classSequence = new AtomicInteger(); - // use a JVM standard ExecutorService as DebuggableThreadPoolExecutor references internal + // use a JVM standard ExecutorService as ExecutorPlus references internal // classes, which triggers AccessControlException from the UDF sandbox private static final UDFExecutorService executor = new UDFExecutorService(new NamedThreadFactory("UserDefinedFunctions", diff --git a/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java b/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java index d7e5eb8e0b..e42fbe9c42 100644 --- a/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java +++ b/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java @@ -94,7 +94,7 @@ final class ScriptBasedUDFunction extends UDFunction "org.apache.cassandra.cql3.functions.types.utils" }; - // use a JVM standard ExecutorService as DebuggableThreadPoolExecutor references internal + // use a JVM standard ExecutorService as ExecutorPlus references internal // classes, which triggers AccessControlException from the UDF sandbox private static final UDFExecutorService executor = new UDFExecutorService(new NamedThreadFactory("UserDefinedScriptFunctions", diff --git a/src/java/org/apache/cassandra/cql3/functions/UDFExecutorService.java b/src/java/org/apache/cassandra/cql3/functions/UDFExecutorService.java index a6e3a92297..3b7631faab 100644 --- a/src/java/org/apache/cassandra/cql3/functions/UDFExecutorService.java +++ b/src/java/org/apache/cassandra/cql3/functions/UDFExecutorService.java @@ -17,37 +17,47 @@ */ package org.apache.cassandra.cql3.functions; -import org.apache.cassandra.concurrent.JMXEnabledThreadPoolExecutor; +import org.apache.cassandra.concurrent.ThreadPoolExecutorJMXAdapter; import org.apache.cassandra.concurrent.NamedThreadFactory; +import org.apache.cassandra.concurrent.ThreadPoolExecutorBase; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.apache.cassandra.utils.FBUtilities.getAvailableProcessors; import static org.apache.cassandra.utils.concurrent.BlockingQueues.newBlockingQueue; /** - * Executor service which exposes stats via JMX, but which doesn't reference - * internal classes in its beforeExecute & afterExecute methods as these are - * forbidden by the UDF execution sandbox + * Executor service which exposes stats via JMX, but which doesn't reference internal classes + * as these are forbidden by the UDF execution sandbox. + * + * TODO: see if we can port to ExecutorPlus to avoid duplication */ -final class UDFExecutorService extends JMXEnabledThreadPoolExecutor +final class UDFExecutorService extends ThreadPoolExecutorBase { - private static int KEEPALIVE = Integer.getInteger("cassandra.udf_executor_thread_keepalive_ms", 30000); + private static final int KEEPALIVE = Integer.getInteger("cassandra.udf_executor_thread_keepalive_ms", 30000); - UDFExecutorService(NamedThreadFactory threadFactory, String jmxPath) + public UDFExecutorService(NamedThreadFactory threadFactory, String jmxPath) { - super(getAvailableProcessors(), - KEEPALIVE, - MILLISECONDS, - newBlockingQueue(), - threadFactory, - jmxPath); + super(getAvailableProcessors(), KEEPALIVE, MILLISECONDS, newBlockingQueue(), threadFactory); + ThreadPoolExecutorJMXAdapter.register(jmxPath, this); } - protected void afterExecute(Runnable r, Throwable t) + public int getCoreThreads() { + return getCorePoolSize(); } - protected void beforeExecute(Thread t, Runnable r) + public void setCoreThreads(int newCorePoolSize) { + setCorePoolSize(newCorePoolSize); + } + + public int getMaximumThreads() + { + return getMaximumPoolSize(); + } + + public void setMaximumThreads(int maxPoolSize) + { + setMaximumPoolSize(maxPoolSize); } } diff --git a/src/java/org/apache/cassandra/cql3/functions/UDFunction.java b/src/java/org/apache/cassandra/cql3/functions/UDFunction.java index 2b3c7aa1d3..172b998c29 100644 --- a/src/java/org/apache/cassandra/cql3/functions/UDFunction.java +++ b/src/java/org/apache/cassandra/cql3/functions/UDFunction.java @@ -32,7 +32,6 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -42,6 +41,7 @@ import com.google.common.collect.Lists; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.cassandra.concurrent.ImmediateExecutor; import org.apache.cassandra.config.Config; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.ColumnIdentifier; @@ -281,7 +281,7 @@ public abstract class UDFunction extends AbstractFunction implements ScalarFunct { protected ExecutorService executor() { - return Executors.newSingleThreadExecutor(); + return ImmediateExecutor.INSTANCE; } protected Object executeAggregateUserDefined(ProtocolVersion protocolVersion, Object firstParam, List parameters) diff --git a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java index 224236b7c3..e46e8e0e6b 100644 --- a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java +++ b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java @@ -27,7 +27,11 @@ import java.nio.file.Files; import java.time.Instant; import java.util.*; import java.util.Objects; -import java.util.concurrent.*; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Pattern; @@ -40,6 +44,10 @@ import com.google.common.base.*; import com.google.common.base.Throwables; import com.google.common.collect.*; import com.google.common.util.concurrent.*; + +import org.apache.cassandra.utils.concurrent.AsyncPromise; +import org.apache.cassandra.utils.concurrent.CountDownLatch; +import org.apache.cassandra.utils.concurrent.Future; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -91,6 +99,7 @@ import org.apache.cassandra.service.snapshot.TableSnapshot; import org.apache.cassandra.streaming.TableStreamManager; import org.apache.cassandra.utils.*; import org.apache.cassandra.utils.concurrent.OpOrder; +import org.apache.cassandra.utils.concurrent.Promise; import org.apache.cassandra.utils.concurrent.Refs; import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; import org.apache.cassandra.utils.memory.MemtableAllocator; @@ -98,6 +107,9 @@ import org.apache.cassandra.utils.memory.MemtableAllocator; import static com.google.common.base.Throwables.propagate; import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; +import static org.apache.cassandra.config.DatabaseDescriptor.getFlushWriters; +import static org.apache.cassandra.db.commitlog.CommitLog.instance; import static org.apache.cassandra.db.commitlog.CommitLogPosition.NONE; import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis; import static org.apache.cassandra.utils.Clock.Global.nanoTime; @@ -117,32 +129,23 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean are finished. By having flushExecutor size the same size as each of the perDiskflushExecutors we make sure we can have that many flushes going at the same time. */ - private static final ThreadPoolExecutor flushExecutor = new JMXEnabledThreadPoolExecutor(DatabaseDescriptor.getFlushWriters(), - Stage.KEEP_ALIVE_SECONDS, - TimeUnit.SECONDS, - new LinkedBlockingQueue<>(), - new NamedThreadFactory("MemtableFlushWriter"), - "internal"); + private static final ExecutorPlus flushExecutor = executorFactory() + .withJmxInternal() + .pooled("MemtableFlushWriter", getFlushWriters()); + + // post-flush executor is single threaded to provide guarantee that any flush Future on a CF will never return until prior flushes have completed + private static final ExecutorPlus postFlushExecutor = executorFactory() + .withJmxInternal() + .sequential("MemtablePostFlush"); + + private static final ExecutorPlus reclaimExecutor = executorFactory() + .withJmxInternal() + .sequential("MemtableReclaimMemory"); private static final PerDiskFlushExecutors perDiskflushExecutors = new PerDiskFlushExecutors(DatabaseDescriptor.getFlushWriters(), DatabaseDescriptor.getNonLocalSystemKeyspacesDataFileLocations(), DatabaseDescriptor.useSpecificLocationForLocalSystemData()); - // post-flush executor is single threaded to provide guarantee that any flush Future on a CF will never return until prior flushes have completed - private static final ThreadPoolExecutor postFlushExecutor = new JMXEnabledThreadPoolExecutor(1, - Stage.KEEP_ALIVE_SECONDS, - TimeUnit.SECONDS, - new LinkedBlockingQueue<>(), - new NamedThreadFactory("MemtablePostFlush"), - "internal"); - - private static final ThreadPoolExecutor reclaimExecutor = new JMXEnabledThreadPoolExecutor(1, - Stage.KEEP_ALIVE_SECONDS, - TimeUnit.SECONDS, - new LinkedBlockingQueue<>(), - new NamedThreadFactory("MemtableReclaimMemory"), - "internal"); - private static final String[] COUNTER_NAMES = new String[]{"table", "count", "error", "value"}; private static final String[] COUNTER_DESCS = new String[] { "keyspace.tablename", @@ -833,7 +836,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean * * @param memtable */ - public ListenableFuture switchMemtableIfCurrent(Memtable memtable) + public Future switchMemtableIfCurrent(Memtable memtable) { synchronized (data) { @@ -851,7 +854,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean * not complete until the Memtable (and all prior Memtables) have been successfully flushed, and the CL * marked clean up to the position owned by the Memtable. */ - public ListenableFuture switchMemtable() + public Future switchMemtable() { synchronized (data) { @@ -900,7 +903,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean * @return a Future yielding the commit log position that can be guaranteed to have been successfully written * to sstables for this table once the future completes */ - public ListenableFuture forceFlush() + public Future forceFlush() { synchronized (data) { @@ -919,7 +922,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean * @return a Future yielding the commit log position that can be guaranteed to have been successfully written * to sstables for this table once the future completes */ - public ListenableFuture forceFlush(CommitLogPosition flushIfDirtyBefore) + public Future forceFlush(CommitLogPosition flushIfDirtyBefore) { // we don't loop through the remaining memtables since here we only care about commit log dirtiness // and this does not vary between a table and its table-backed indexes @@ -933,17 +936,15 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean * @return a Future yielding the commit log position that can be guaranteed to have been successfully written * to sstables for this table once the future completes */ - private ListenableFuture waitForFlushes() + private Future waitForFlushes() { // we grab the current memtable; once any preceding memtables have flushed, we know its // commitLogLowerBound has been set (as this it is set with the upper bound of the preceding memtable) final Memtable current = data.getView().getCurrentMemtable(); - ListenableFutureTask task = ListenableFutureTask.create(() -> { + return postFlushExecutor.submit(() -> { logger.debug("forceFlush requested but everything is clean in {}", name); return current.getCommitLogLowerBound(); }); - postFlushExecutor.execute(task); - return task; } public CommitLogPosition forceBlockingFlush() @@ -957,7 +958,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean */ private final class PostFlush implements Callable { - final org.apache.cassandra.utils.concurrent.CountDownLatch latch = newCountDownLatch(1); + final CountDownLatch latch = newCountDownLatch(1); final List memtables; volatile Throwable flushFailure = null; @@ -1009,7 +1010,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean { final OpOrder.Barrier writeBarrier; final List memtables = new ArrayList<>(); - final ListenableFutureTask postFlushTask; + final FutureTask postFlushTask; final PostFlush postFlush; final boolean truncate; @@ -1054,7 +1055,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean // commit log segment position have also completed, i.e. the memtables are done and ready to flush writeBarrier.issue(); postFlush = new PostFlush(memtables); - postFlushTask = ListenableFutureTask.create(postFlush); + postFlushTask = new FutureTask<>(postFlush); } public void run() @@ -1127,7 +1128,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean { // flush the memtable flushRunnables = memtable.flushRunnables(txn); - ExecutorService[] executors = perDiskflushExecutors.getExecutorsFor(keyspace.getName(), name); + ExecutorPlus[] executors = perDiskflushExecutors.getExecutorsFor(keyspace.getName(), name); for (int i = 0; i < flushRunnables.size(); i++) futures.add(executors[i].submit(flushRunnables.get(i))); @@ -1249,7 +1250,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean * Finds the largest memtable, as a percentage of *either* on- or off-heap memory limits, and immediately * queues it for flushing. If the memtable selected is flushed before this completes, no work is done. */ - public static CompletableFuture flushLargestMemtable() + public static Future flushLargestMemtable() { float largestRatio = 0f; Memtable largest = null; @@ -1285,7 +1286,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean liveOffHeap += offHeap; } - CompletableFuture returnFuture = new CompletableFuture<>(); + Promise returnFuture = new AsyncPromise<>(); if (largest != null) { @@ -1299,24 +1300,24 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean largest.cfs, ratio(usedOnHeap, usedOffHeap), ratio(liveOnHeap, liveOffHeap), ratio(flushingOnHeap, flushingOffHeap), ratio(thisOnHeap, thisOffHeap)); - ListenableFuture flushFuture = largest.cfs.switchMemtableIfCurrent(largest); + Future flushFuture = largest.cfs.switchMemtableIfCurrent(largest); flushFuture.addListener(() -> { try { flushFuture.get(); - returnFuture.complete(true); + returnFuture.trySuccess(true); } catch (Throwable t) { - returnFuture.completeExceptionally(t); + returnFuture.tryFailure(t); } - }, MoreExecutors.directExecutor()); + }); } else { logger.debug("Flushing of largest memtable, not done, no memtable found"); - returnFuture.complete(false); + returnFuture.trySuccess(false); } return returnFuture; @@ -2355,7 +2356,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean } }; - runWithCompactionsDisabled(Executors.callable(truncateRunnable), true, true); + runWithCompactionsDisabled(FutureTask.callable(truncateRunnable), true, true); viewManager.build(); @@ -2897,12 +2898,12 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean /** * The flush executors for non local system keyspaces. */ - private final ExecutorService[] nonLocalSystemflushExecutors; + private final ExecutorPlus[] nonLocalSystemflushExecutors; /** * The flush executors for the local system keyspaces. */ - private final ExecutorService[] localSystemDiskFlushExecutors; + private final ExecutorPlus[] localSystemDiskFlushExecutors; /** * {@code true} if local system keyspaces are stored in their own directory and use an extra flush executor, @@ -2914,32 +2915,26 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean String[] locationsForNonSystemKeyspaces, boolean useSpecificLocationForSystemKeyspaces) { - ExecutorService[] flushExecutors = createPerDiskFlushWriters(locationsForNonSystemKeyspaces.length, flushWriters); + ExecutorPlus[] flushExecutors = createPerDiskFlushWriters(locationsForNonSystemKeyspaces.length, flushWriters); nonLocalSystemflushExecutors = flushExecutors; useSpecificExecutorForSystemKeyspaces = useSpecificLocationForSystemKeyspaces; - localSystemDiskFlushExecutors = useSpecificLocationForSystemKeyspaces ? new ExecutorService[] {newThreadPool("LocalSystemKeyspacesDiskMemtableFlushWriter", flushWriters)} - : new ExecutorService[] {flushExecutors[0]}; + localSystemDiskFlushExecutors = useSpecificLocationForSystemKeyspaces ? new ExecutorPlus[] {newThreadPool("LocalSystemKeyspacesDiskMemtableFlushWriter", flushWriters)} + : new ExecutorPlus[] {flushExecutors[0]}; } - private static ExecutorService[] createPerDiskFlushWriters(int numberOfExecutors, int flushWriters) + private static ExecutorPlus[] createPerDiskFlushWriters(int numberOfExecutors, int flushWriters) { - ExecutorService[] flushExecutors = new ExecutorService[numberOfExecutors]; - - for (int i = 0; i < numberOfExecutors; i++) + ExecutorPlus[] flushExecutors = new ExecutorPlus[numberOfExecutors]; + for (int i = 0; i < DatabaseDescriptor.getAllDataFileLocations().length; i++) { - flushExecutors[i] = newThreadPool("PerDiskMemtableFlushWriter_" + i, flushWriters); + flushExecutors[i] = newThreadPool("PerDiskMemtableFlushWriter_"+i, flushWriters); } return flushExecutors; } - private static JMXEnabledThreadPoolExecutor newThreadPool(String poolName, int size) + private static ExecutorPlus newThreadPool(String poolName, int size) { - return new JMXEnabledThreadPoolExecutor(size, - Stage.KEEP_ALIVE_SECONDS, - TimeUnit.SECONDS, - new LinkedBlockingQueue<>(), - new NamedThreadFactory(poolName), - "internal"); + return executorFactory().withJmxInternal().pooled(poolName, size); } /** @@ -2949,7 +2944,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean * @param tableName the table name * @return the flush executors that should be used for flushing the memtables of the specified keyspace. */ - public ExecutorService[] getExecutorsFor(String keyspaceName, String tableName) + public ExecutorPlus[] getExecutorsFor(String keyspaceName, String tableName) { return Directories.isStoredInLocalSystemKeyspacesDataLocation(keyspaceName, tableName) ? localSystemDiskFlushExecutors : nonLocalSystemflushExecutors; diff --git a/src/java/org/apache/cassandra/db/Keyspace.java b/src/java/org/apache/cassandra/db/Keyspace.java index 39cb746f90..2a996904e0 100644 --- a/src/java/org/apache/cassandra/db/Keyspace.java +++ b/src/java/org/apache/cassandra/db/Keyspace.java @@ -28,10 +28,8 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Set; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Lock; import java.util.stream.Stream; @@ -39,6 +37,9 @@ import java.util.stream.Stream; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.RateLimiter; + +import org.apache.cassandra.utils.concurrent.AsyncPromise; +import org.apache.cassandra.utils.concurrent.Future; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -73,6 +74,7 @@ import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.JVMStabilityInspector; import org.apache.cassandra.utils.concurrent.OpOrder; import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; +import org.apache.cassandra.utils.concurrent.Promise; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; @@ -482,15 +484,15 @@ public class Keyspace } } - public CompletableFuture applyFuture(Mutation mutation, boolean writeCommitLog, boolean updateIndexes) + public Future applyFuture(Mutation mutation, boolean writeCommitLog, boolean updateIndexes) { - return applyInternal(mutation, writeCommitLog, updateIndexes, true, true, new CompletableFuture<>()); + return applyInternal(mutation, writeCommitLog, updateIndexes, true, true, new AsyncPromise<>()); } - public CompletableFuture applyFuture(Mutation mutation, boolean writeCommitLog, boolean updateIndexes, boolean isDroppable, + public Future applyFuture(Mutation mutation, boolean writeCommitLog, boolean updateIndexes, boolean isDroppable, boolean isDeferrable) { - return applyInternal(mutation, writeCommitLog, updateIndexes, isDroppable, isDeferrable, new CompletableFuture<>()); + return applyInternal(mutation, writeCommitLog, updateIndexes, isDroppable, isDeferrable, new AsyncPromise<>()); } public void apply(Mutation mutation, boolean writeCommitLog, boolean updateIndexes) @@ -533,12 +535,12 @@ public class Keyspace * @param isDroppable true if this should throw WriteTimeoutException if it does not acquire lock within write_request_timeout_in_ms * @param isDeferrable true if caller is not waiting for future to complete, so that future may be deferred */ - private CompletableFuture applyInternal(final Mutation mutation, + private Future applyInternal(final Mutation mutation, final boolean makeDurable, boolean updateIndexes, boolean isDroppable, boolean isDeferrable, - CompletableFuture future) + Promise future) { if (TEST_FAIL_WRITES && metadata.name.equals(TEST_FAIL_WRITES_KS)) throw new RuntimeException("Testing write failures"); @@ -582,7 +584,7 @@ public class Keyspace Tracing.trace("Could not acquire MV lock"); if (future != null) { - future.completeExceptionally(new WriteTimeoutException(WriteType.VIEW, ConsistencyLevel.LOCAL_ONE, 0, 1)); + future.tryFailure(new WriteTimeoutException(WriteType.VIEW, ConsistencyLevel.LOCAL_ONE, 0, 1)); return future; } else @@ -595,9 +597,8 @@ public class Keyspace // This view update can't happen right now. so rather than keep this thread busy // we will re-apply ourself to the queue and try again later - final CompletableFuture mark = future; Stage.MUTATION.execute(() -> - applyInternal(mutation, makeDurable, true, isDroppable, true, mark) + applyInternal(mutation, makeDurable, true, isDroppable, true, future) ); return future; } @@ -675,7 +676,7 @@ public class Keyspace } if (future != null) { - future.complete(null); + future.trySuccess(null); } return future; } diff --git a/src/java/org/apache/cassandra/db/Mutation.java b/src/java/org/apache/cassandra/db/Mutation.java index 8a1ffc123c..a30b56799f 100644 --- a/src/java/org/apache/cassandra/db/Mutation.java +++ b/src/java/org/apache/cassandra/db/Mutation.java @@ -19,7 +19,6 @@ package org.apache.cassandra.db; import java.io.IOException; import java.util.*; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; @@ -38,6 +37,7 @@ import org.apache.cassandra.schema.Schema; import org.apache.cassandra.schema.TableId; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.utils.ByteBufferUtil; +import org.apache.cassandra.utils.concurrent.Future; import static org.apache.cassandra.net.MessagingService.VERSION_30; import static org.apache.cassandra.net.MessagingService.VERSION_3014; @@ -204,7 +204,7 @@ public class Mutation implements IMutation return new Mutation(ks, key, modifications.build(), approxTime.now()); } - public CompletableFuture applyFuture() + public Future applyFuture() { Keyspace ks = Keyspace.open(keyspaceName); return ks.applyFuture(this, Keyspace.open(keyspaceName).getMetadata().params.durableWrites, true); diff --git a/src/java/org/apache/cassandra/db/MutationVerbHandler.java b/src/java/org/apache/cassandra/db/MutationVerbHandler.java index 1d4f868b7b..9c0335edff 100644 --- a/src/java/org/apache/cassandra/db/MutationVerbHandler.java +++ b/src/java/org/apache/cassandra/db/MutationVerbHandler.java @@ -55,10 +55,7 @@ public class MutationVerbHandler implements IVerbHandler try { - message.payload.applyFuture().thenAccept(o -> respond(message, respondToAddress)).exceptionally(wto -> { - failed(); - return null; - }); + message.payload.applyFuture().addCallback(o -> respond(message, respondToAddress), wto -> failed()); } catch (WriteTimeoutException wto) { diff --git a/src/java/org/apache/cassandra/db/SystemKeyspace.java b/src/java/org/apache/cassandra/db/SystemKeyspace.java index f7946b9143..279c99ece2 100644 --- a/src/java/org/apache/cassandra/db/SystemKeyspace.java +++ b/src/java/org/apache/cassandra/db/SystemKeyspace.java @@ -37,7 +37,6 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.SetMultimap; import com.google.common.collect.Sets; import com.google.common.io.ByteStreams; -import com.google.common.util.concurrent.ListenableFuture; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -65,6 +64,7 @@ import org.apache.cassandra.service.paxos.PaxosState; import org.apache.cassandra.streaming.StreamOperation; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.*; +import org.apache.cassandra.utils.concurrent.Future; import static java.lang.String.format; import static java.util.Collections.emptyMap; @@ -828,7 +828,7 @@ public final class SystemKeyspace { if (!DatabaseDescriptor.isUnsafeSystem()) { - List> futures = new ArrayList<>(); + List> futures = new ArrayList<>(); for (String cfname : cfnames) { diff --git a/src/java/org/apache/cassandra/db/commitlog/AbstractCommitLogSegmentManager.java b/src/java/org/apache/cassandra/db/commitlog/AbstractCommitLogSegmentManager.java index edba125857..2f5983baee 100755 --- a/src/java/org/apache/cassandra/db/commitlog/AbstractCommitLogSegmentManager.java +++ b/src/java/org/apache/cassandra/db/commitlog/AbstractCommitLogSegmentManager.java @@ -20,30 +20,33 @@ package org.apache.cassandra.db.commitlog; import java.io.File; import java.io.IOException; import java.util.*; -import java.util.concurrent.*; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.function.BooleanSupplier; +import java.util.function.Consumer; -import com.codahale.metrics.Timer.Context; import com.google.common.annotations.VisibleForTesting; -import com.google.common.util.concurrent.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.codahale.metrics.Timer.Context; import net.nicoulaj.compilecommand.annotations.DontInline; -import org.apache.cassandra.concurrent.NamedThreadFactory; +import org.apache.cassandra.concurrent.Interruptible; +import org.apache.cassandra.concurrent.Interruptible.TerminateException; import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.db.ColumnFamilyStore; +import org.apache.cassandra.db.Keyspace; +import org.apache.cassandra.db.Mutation; import org.apache.cassandra.io.compress.BufferType; import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.io.util.SimpleCachedBufferPool; import org.apache.cassandra.schema.Schema; -import org.apache.cassandra.db.*; import org.apache.cassandra.schema.TableId; import org.apache.cassandra.schema.TableMetadata; -import org.apache.cassandra.utils.*; -import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; -import org.apache.cassandra.utils.concurrent.WaitQueue; +import org.apache.cassandra.utils.concurrent.*; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.db.commitlog.CommitLogSegment.Allocation; import static org.apache.cassandra.utils.concurrent.WaitQueue.newWaitQueue; @@ -88,10 +91,9 @@ public abstract class AbstractCommitLogSegmentManager private final AtomicLong size = new AtomicLong(); @VisibleForTesting - Thread managerThread; + Interruptible executor; protected final CommitLog commitLog; - private volatile boolean shutdown; - private final BooleanSupplier managerThreadWaitCondition = () -> (availableSegment == null && !atSegmentBufferLimit()) || shutdown; + private final BooleanSupplier managerThreadWaitCondition = () -> (availableSegment == null && !atSegmentBufferLimit()); private final WaitQueue managerThreadWaitQueue = newWaitQueue(); private volatile SimpleCachedBufferPool bufferPool; @@ -104,52 +106,55 @@ public abstract class AbstractCommitLogSegmentManager void start() { + // used for synchronization to prevent thread interrupts while performing IO operations + final Object monitor = new Object(); // The run loop for the manager thread - Runnable runnable = new WrappedRunnable() - { - public void runMayThrow() throws Exception + Interruptible.Task runnable = state -> { + + try { - while (!shutdown) + switch (state) { - try - { + case SHUTTING_DOWN: + // If shutdown() started and finished during segment creation, we are now left with a + // segment that no one will consume. Discard it. + discardAvailableSegment(); + return; + + case NORMAL: assert availableSegment == null; - logger.trace("No segments in reserve; creating a fresh one"); - availableSegment = createSegment(); - if (shutdown) + + synchronized (monitor) { - // If shutdown() started and finished during segment creation, we are now left with a - // segment that no one will consume. Discard it. - discardAvailableSegment(); - return; + logger.trace("No segments in reserve; creating a fresh one"); + availableSegment = createSegment(); + + segmentPrepared.signalAll(); + Thread.yield(); + + if (availableSegment == null && !atSegmentBufferLimit()) + // Writing threads need another segment now. + return; + + // Writing threads are not waiting for new segments, we can spend time on other tasks. + // flush old Cfs if we're full + maybeFlushToReclaim(); } - - segmentPrepared.signalAll(); - Thread.yield(); - - if (availableSegment == null && !atSegmentBufferLimit()) - // Writing threads need another segment now. - continue; - - // Writing threads are not waiting for new segments, we can spend time on other tasks. - // flush old Cfs if we're full - maybeFlushToReclaim(); - } - catch (Throwable t) - { - if (!CommitLog.handleCommitError("Failed managing commit log segments", t)) - return; - // sleep some arbitrary period to avoid spamming CL - Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS); - - // If we offered a segment, wait for it to be taken before reentering the loop. - // There could be a new segment in next not offered, but only on failure to discard it while - // shutting down-- nothing more can or needs to be done in that case. - } - - WaitQueue.waitOnCondition(managerThreadWaitCondition, managerThreadWaitQueue); } } + catch (Throwable t) + { + if (!CommitLog.handleCommitError("Failed managing commit log segments", t)) + throw new TerminateException(); + + // sleep some arbitrary period to avoid spamming CL + Thread.sleep(TimeUnit.SECONDS.toMillis(1L)); + + // If we offered a segment, wait for it to be taken before reentering the loop. + // There could be a new segment in next not offered, but only on failure to discard it while + // shutting down-- nothing more can or needs to be done in that case. + } + WaitQueue.waitOnCondition(managerThreadWaitCondition, managerThreadWaitQueue); }; // For encrypted segments we want to keep the compression buffers on-heap as we need those bytes for encryption, @@ -162,15 +167,23 @@ public abstract class AbstractCommitLogSegmentManager DatabaseDescriptor.getCommitLogSegmentSize(), bufferType); - shutdown = false; - - managerThread = NamedThreadFactory.createThread(runnable, "COMMIT-LOG-ALLOCATOR"); - managerThread.start(); + Consumer interruptHandler = interruptHandler(monitor); + executor = executorFactory().infiniteLoop("COMMIT-LOG-ALLOCATOR", runnable, true, interruptHandler); // for simplicity, ensure the first segment is allocated before continuing advanceAllocatingFrom(null); } + private Consumer interruptHandler(final Object monitor) + { + return thread -> { + synchronized (monitor) + { + thread.interrupt(); + } + }; + } + private boolean atSegmentBufferLimit() { return CommitLogSegment.usesBufferPool(commitLog) && bufferPool.atLimit(); @@ -387,11 +400,11 @@ public abstract class AbstractCommitLogSegmentManager private Future flushDataFrom(List segments, boolean force) { if (segments.isEmpty()) - return Futures.immediateFuture(null); + return ImmediateFuture.success(null); final CommitLogPosition maxCommitLogPosition = segments.get(segments.size() - 1).getCurrentCommitLogPosition(); // a map of CfId -> forceFlush() to ensure we only queue one flush per cf - final Map> flushes = new LinkedHashMap<>(); + final Map> flushes = new LinkedHashMap<>(); for (CommitLogSegment segment : segments) { @@ -415,7 +428,7 @@ public abstract class AbstractCommitLogSegmentManager } } - return Futures.allAsList(flushes.values()); + return FutureCombiner.allOf(flushes.values()); } /** @@ -477,9 +490,7 @@ public abstract class AbstractCommitLogSegmentManager */ public void shutdown() { - assert !shutdown; - shutdown = true; - + executor.shutdownNow(); // Release the management thread and delete prepared segment. // Do not block as another thread may claim the segment (this can happen during unit test initialization). discardAvailableSegment(); @@ -488,7 +499,7 @@ public abstract class AbstractCommitLogSegmentManager private void discardAvailableSegment() { - CommitLogSegment next = null; + CommitLogSegment next; synchronized (this) { next = availableSegment; @@ -503,12 +514,7 @@ public abstract class AbstractCommitLogSegmentManager */ public void awaitTermination() throws InterruptedException { - if (managerThread != null) - { - managerThread.join(); - managerThread = null; - } - + executor.awaitTermination(1L, TimeUnit.MINUTES); for (CommitLogSegment segment : activeSegments) segment.close(); diff --git a/src/java/org/apache/cassandra/db/commitlog/AbstractCommitLogService.java b/src/java/org/apache/cassandra/db/commitlog/AbstractCommitLogService.java index 07c505be03..9d030d7da9 100644 --- a/src/java/org/apache/cassandra/db/commitlog/AbstractCommitLogService.java +++ b/src/java/org/apache/cassandra/db/commitlog/AbstractCommitLogService.java @@ -17,24 +17,31 @@ */ package org.apache.cassandra.db.commitlog; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.locks.LockSupport; import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.cassandra.concurrent.NamedThreadFactory; +import org.apache.cassandra.concurrent.Interruptible; +import org.apache.cassandra.concurrent.Interruptible.TerminateException; import org.apache.cassandra.config.Config; import org.apache.cassandra.db.commitlog.CommitLogSegment.Allocation; import org.apache.cassandra.utils.MonotonicClock; import org.apache.cassandra.utils.NoSpamLogger; +import org.apache.cassandra.utils.concurrent.Semaphore; import org.apache.cassandra.utils.concurrent.WaitQueue; -import static com.codahale.metrics.Timer.*; +import static com.codahale.metrics.Timer.Context; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; +import static org.apache.cassandra.concurrent.Interruptible.State.NORMAL; +import static org.apache.cassandra.concurrent.Interruptible.State.SHUTTING_DOWN; import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis; import static org.apache.cassandra.utils.Clock.Global.nanoTime; +import static org.apache.cassandra.utils.concurrent.Semaphore.newSemaphore; import static org.apache.cassandra.utils.concurrent.WaitQueue.newWaitQueue; public abstract class AbstractCommitLogService @@ -45,8 +52,7 @@ public abstract class AbstractCommitLogService */ static final long DEFAULT_MARKER_INTERVAL_MILLIS = 100; - private volatile Thread thread; - private volatile boolean shutdown = false; + private volatile Interruptible executor; // all Allocations written before this time will be synced protected volatile long lastSyncedAt = currentTimeMillis(); @@ -57,6 +63,7 @@ public abstract class AbstractCommitLogService // signal that writers can wait on to be notified of a completed sync protected final WaitQueue syncComplete = newWaitQueue(); + protected final Semaphore haveWork = newSemaphore(1); final CommitLog commitLog; private final String name; @@ -106,7 +113,11 @@ public abstract class AbstractCommitLogService this.name = name; final long markerIntervalMillis; - if (markHeadersFaster && syncIntervalMillis > DEFAULT_MARKER_INTERVAL_MILLIS) + if (syncIntervalMillis < 0) + { + markerIntervalMillis = -1; + } + else if (markHeadersFaster && syncIntervalMillis > DEFAULT_MARKER_INTERVAL_MILLIS) { markerIntervalMillis = DEFAULT_MARKER_INTERVAL_MILLIS; long modulo = syncIntervalMillis % markerIntervalMillis; @@ -118,29 +129,28 @@ public abstract class AbstractCommitLogService if (modulo >= markerIntervalMillis / 2) syncIntervalMillis += markerIntervalMillis; } + assert syncIntervalMillis % markerIntervalMillis == 0; logger.debug("Will update the commitlog markers every {}ms and flush every {}ms", markerIntervalMillis, syncIntervalMillis); } else { markerIntervalMillis = syncIntervalMillis; } - assert syncIntervalMillis % markerIntervalMillis == 0; - this.markerIntervalNanos = TimeUnit.NANOSECONDS.convert(markerIntervalMillis, TimeUnit.MILLISECONDS); - this.syncIntervalNanos = TimeUnit.NANOSECONDS.convert(syncIntervalMillis, TimeUnit.MILLISECONDS); + this.markerIntervalNanos = NANOSECONDS.convert(markerIntervalMillis, MILLISECONDS); + this.syncIntervalNanos = NANOSECONDS.convert(syncIntervalMillis, MILLISECONDS); } // Separated into individual method to ensure relevant objects are constructed before this is started. void start() { - if (syncIntervalNanos < 1) + if (syncIntervalNanos < 1 && !(this instanceof BatchCommitLogService)) // permit indefinite waiting with batch, as perfectly sensible throw new IllegalArgumentException(String.format("Commit log flush interval must be positive: %fms", syncIntervalNanos * 1e-6)); - shutdown = false; - thread = NamedThreadFactory.createThread(new SyncRunnable(MonotonicClock.preciseTime), name); - thread.start(); + + executor = executorFactory().infiniteLoop(name, new SyncRunnable(MonotonicClock.preciseTime), true); } - class SyncRunnable implements Runnable + class SyncRunnable implements Interruptible.Task { private final MonotonicClock clock; private long firstLagAt = 0; @@ -154,25 +164,13 @@ public abstract class AbstractCommitLogService this.clock = clock; } - public void run() + public void run(Interruptible.State state) throws InterruptedException { - while (true) - { - if (!sync()) - break; - } - } - - boolean sync() - { - // always run once after shutdown signalled - boolean shutdownRequested = shutdown; - try { // sync and signal long pollStarted = clock.now(); - boolean flushToDisk = lastSyncedAt + syncIntervalNanos <= pollStarted || shutdownRequested || syncRequested; + boolean flushToDisk = lastSyncedAt + syncIntervalNanos <= pollStarted || state != NORMAL || syncRequested; if (flushToDisk) { // in this branch, we want to flush the commit log to disk @@ -188,27 +186,31 @@ public abstract class AbstractCommitLogService commitLog.sync(false); } - long now = clock.now(); - if (flushToDisk) - maybeLogFlushLag(pollStarted, now); + if (state == SHUTTING_DOWN) + return; - if (shutdownRequested) - return false; + if (markerIntervalNanos <= 0) + { + haveWork.acquire(1); + } + else + { + long now = clock.now(); + if (flushToDisk) + maybeLogFlushLag(pollStarted, now); - long wakeUpAt = pollStarted + markerIntervalNanos; - if (wakeUpAt > now) - LockSupport.parkNanos(wakeUpAt - now); + long wakeUpAt = pollStarted + markerIntervalNanos; + if (wakeUpAt > now) + haveWork.tryAcquireUntil(1, wakeUpAt); + } } catch (Throwable t) { if (!CommitLog.handleCommitError("Failed to persist commits to disk", t)) - return false; - - // sleep for full poll-interval after an error, so we don't spam the log file - LockSupport.parkNanos(markerIntervalNanos); + throw new TerminateException(); + else // sleep for full poll-interval after an error, so we don't spam the log file + haveWork.tryAcquire(1, markerIntervalNanos, NANOSECONDS); } - - return true; } /** @@ -242,7 +244,7 @@ public abstract class AbstractCommitLogService boolean logged = NoSpamLogger.log(logger, NoSpamLogger.Level.WARN, 5, - TimeUnit.MINUTES, + MINUTES, "Out of {} commit log syncs over the past {}s with average duration of {}ms, {} have exceeded the configured commit interval by an average of {}ms", syncCount, String.format("%.2f", (now - firstLagAt) * 1e-9d), @@ -278,14 +280,14 @@ public abstract class AbstractCommitLogService */ void requestExtraSync() { + // note: cannot simply invoke executor.interrupt() as some filesystems don't like it (jimfs, at least) syncRequested = true; - LockSupport.unpark(thread); + haveWork.release(1); } public void shutdown() { - shutdown = true; - requestExtraSync(); + executor.shutdown(); } /** @@ -316,8 +318,7 @@ public abstract class AbstractCommitLogService public void awaitTermination() throws InterruptedException { - if (thread != null) - thread.join(); + executor.awaitTermination(5L, MINUTES); } public long getCompletedTasks() diff --git a/src/java/org/apache/cassandra/db/commitlog/CommitLogArchiver.java b/src/java/org/apache/cassandra/db/commitlog/CommitLogArchiver.java index e1d0874336..f94b2699cb 100644 --- a/src/java/org/apache/cassandra/db/commitlog/CommitLogArchiver.java +++ b/src/java/org/apache/cassandra/db/commitlog/CommitLogArchiver.java @@ -32,7 +32,6 @@ import java.util.concurrent.*; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.apache.cassandra.concurrent.JMXEnabledThreadPoolExecutor; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.schema.CompressionParams; @@ -45,6 +44,8 @@ import org.slf4j.LoggerFactory; import com.google.common.base.Strings; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; + public class CommitLogArchiver { private static final Logger logger = LoggerFactory.getLogger(CommitLogArchiver.class); @@ -75,7 +76,11 @@ public class CommitLogArchiver this.restoreDirectories = restoreDirectories; this.restorePointInTime = restorePointInTime; this.precision = precision; - executor = !Strings.isNullOrEmpty(archiveCommand) ? new JMXEnabledThreadPoolExecutor("CommitLogArchiver") : null; + executor = !Strings.isNullOrEmpty(archiveCommand) + ? executorFactory() + .withJmxInternal() + .sequential("CommitLogArchiver") + : null; } public static CommitLogArchiver disabled() diff --git a/src/java/org/apache/cassandra/db/commitlog/CommitLogReplayer.java b/src/java/org/apache/cassandra/db/commitlog/CommitLogReplayer.java index 39777ec46c..501ee02e34 100644 --- a/src/java/org/apache/cassandra/db/commitlog/CommitLogReplayer.java +++ b/src/java/org/apache/cassandra/db/commitlog/CommitLogReplayer.java @@ -21,7 +21,6 @@ package org.apache.cassandra.db.commitlog; import java.io.File; import java.io.IOException; import java.util.*; -import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import com.google.common.annotations.VisibleForTesting; @@ -32,6 +31,8 @@ import com.google.common.collect.Multimap; import com.google.common.collect.Ordering; import org.apache.commons.lang3.StringUtils; + +import org.apache.cassandra.utils.concurrent.Future; import org.cliffc.high_scale_lib.NonBlockingHashSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/java/org/apache/cassandra/db/commitlog/CommitLogSegment.java b/src/java/org/apache/cassandra/db/commitlog/CommitLogSegment.java index 4a68e8be4c..86442d8d47 100644 --- a/src/java/org/apache/cassandra/db/commitlog/CommitLogSegment.java +++ b/src/java/org/apache/cassandra/db/commitlog/CommitLogSegment.java @@ -514,7 +514,7 @@ public abstract class CommitLogSegment { WaitQueue.Signal signal = syncComplete.register(); if (lastSyncedOffset < position) - signal.awaitUninterruptibly(); + signal.awaitThrowUncheckedOnInterrupt(); else signal.cancel(); } diff --git a/src/java/org/apache/cassandra/db/commitlog/CommitLogSegmentManagerCDC.java b/src/java/org/apache/cassandra/db/commitlog/CommitLogSegmentManagerCDC.java index 66c8a39c76..65611376a4 100644 --- a/src/java/org/apache/cassandra/db/commitlog/CommitLogSegmentManagerCDC.java +++ b/src/java/org/apache/cassandra/db/commitlog/CommitLogSegmentManagerCDC.java @@ -39,6 +39,8 @@ import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.utils.DirectorySizeCalculator; import org.apache.cassandra.utils.NoSpamLogger; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; + public class CommitLogSegmentManagerCDC extends AbstractCommitLogSegmentManager { static final Logger logger = LoggerFactory.getLogger(CommitLogSegmentManagerCDC.class); @@ -208,7 +210,11 @@ public class CommitLogSegmentManagerCDC extends AbstractCommitLogSegmentManager public void start() { size = 0; - cdcSizeCalculationExecutor = new ThreadPoolExecutor(1, 1, 1000, TimeUnit.SECONDS, new SynchronousQueue<>(), new ThreadPoolExecutor.DiscardPolicy()); + cdcSizeCalculationExecutor = executorFactory().configureSequential("CDCSizeCalculationExecutor") + .withRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()) + .withQueueLimit(0) + .withKeepAlive(1000, TimeUnit.SECONDS) + .build(); } /** diff --git a/src/java/org/apache/cassandra/db/compaction/CompactionLogger.java b/src/java/org/apache/cassandra/db/compaction/CompactionLogger.java index a533f95199..e8250b4384 100644 --- a/src/java/org/apache/cassandra/db/compaction/CompactionLogger.java +++ b/src/java/org/apache/cassandra/db/compaction/CompactionLogger.java @@ -41,10 +41,13 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.cassandra.concurrent.ExecutorPlus; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.io.sstable.format.SSTableReader; +import org.apache.cassandra.utils.ExecutorUtils; import org.apache.cassandra.utils.NoSpamLogger; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis; public class CompactionLogger @@ -105,7 +108,7 @@ public class CompactionLogger private static final JsonNodeFactory json = JsonNodeFactory.instance; private static final Logger logger = LoggerFactory.getLogger(CompactionLogger.class); - private static final Writer serializer = new CompactionLogSerializer(); + private static final CompactionLogSerializer serializer = new CompactionLogSerializer(); private final WeakReference cfsRef; private final WeakReference csmRef; private final AtomicInteger identifier = new AtomicInteger(0); @@ -297,7 +300,7 @@ public class CompactionLogger private static class CompactionLogSerializer implements Writer { private static final String logDirectory = System.getProperty("cassandra.logdir", "."); - private final ExecutorService loggerService = Executors.newFixedThreadPool(1); + private final ExecutorPlus loggerService = executorFactory().sequential("CompactionLogger"); // This is only accessed on the logger service thread, so it does not need to be thread safe private final Set rolled = new HashSet<>(); private OutputStreamWriter stream; @@ -359,4 +362,10 @@ public class CompactionLogger }); } } + + public static void shutdownNowAndWait(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException + { + ExecutorUtils.shutdownNowAndWait(timeout, unit, serializer.loggerService); + } + } diff --git a/src/java/org/apache/cassandra/db/compaction/CompactionManager.java b/src/java/org/apache/cassandra/db/compaction/CompactionManager.java index ace83d1b97..e1f940b1d3 100644 --- a/src/java/org/apache/cassandra/db/compaction/CompactionManager.java +++ b/src/java/org/apache/cassandra/db/compaction/CompactionManager.java @@ -20,7 +20,11 @@ package org.apache.cassandra.db.compaction; import java.io.File; import java.io.IOException; import java.util.*; -import java.util.concurrent.*; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BooleanSupplier; import java.util.function.Predicate; @@ -31,18 +35,17 @@ import javax.management.openmbean.TabularData; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.*; -import com.google.common.util.concurrent.*; +import com.google.common.util.concurrent.RateLimiter; +import com.google.common.util.concurrent.Uninterruptibles; +import org.apache.cassandra.concurrent.ExecutorFactory; +import org.apache.cassandra.concurrent.WrappedExecutorPlus; import org.apache.cassandra.dht.AbstractBounds; import org.apache.cassandra.locator.RangesAtEndpoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.netty.util.concurrent.FastThreadLocal; import org.apache.cassandra.cache.AutoSavingCache; -import org.apache.cassandra.concurrent.DebuggableThreadPoolExecutor; -import org.apache.cassandra.concurrent.JMXEnabledThreadPoolExecutor; -import org.apache.cassandra.concurrent.NamedThreadFactory; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.repair.NoSuchRepairSessionException; import org.apache.cassandra.schema.TableMetadata; @@ -80,9 +83,15 @@ import org.apache.cassandra.service.ActiveRepairService; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.streaming.PreviewKind; import org.apache.cassandra.utils.*; +import org.apache.cassandra.utils.concurrent.Future; +import org.apache.cassandra.utils.concurrent.ImmediateFuture; import org.apache.cassandra.utils.concurrent.Refs; import static java.util.Collections.singleton; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; +import static org.apache.cassandra.concurrent.FutureTask.callable; +import static org.apache.cassandra.config.DatabaseDescriptor.getConcurrentCompactors; +import static org.apache.cassandra.db.compaction.CompactionManager.CompactionExecutor.compactionThreadGroup; import static org.apache.cassandra.service.ActiveRepairService.NO_PENDING_REPAIR; import static org.apache.cassandra.service.ActiveRepairService.UNREPAIRED_SSTABLE; import static org.apache.cassandra.utils.Clock.Global.nanoTime; @@ -107,17 +116,6 @@ public class CompactionManager implements CompactionManagerMBean public static final int NO_GC = Integer.MIN_VALUE; public static final int GC_ALL = Integer.MAX_VALUE; - // A thread local that tells us if the current thread is owned by the compaction manager. Used - // by CounterContext to figure out if it should log a warning for invalid counter shards. - public static final FastThreadLocal isCompactionManager = new FastThreadLocal() - { - @Override - protected Boolean initialValue() - { - return false; - } - }; - static { instance = new CompactionManager(); @@ -193,7 +191,7 @@ public class CompactionManager implements CompactionManagerMBean * are idle threads stil. (CASSANDRA-4310) */ int count = compactingCF.count(cfs); - if (count > 0 && executor.getActiveCount() >= executor.getMaximumPoolSize()) + if (count > 0 && executor.getActiveTaskCount() >= executor.getMaximumPoolSize()) { logger.trace("Background compaction is still running for {}.{} ({} remaining). Skipping", cfs.keyspace.getName(), cfs.name, count); @@ -719,12 +717,12 @@ public class CompactionManager implements CompactionManagerMBean /** * Splits the given token ranges of the given sstables into a pending repair silo */ - public ListenableFuture submitPendingAntiCompaction(ColumnFamilyStore cfs, - RangesAtEndpoint tokenRanges, - Refs sstables, - LifecycleTransaction txn, - UUID sessionId, - BooleanSupplier isCancelled) + public Future submitPendingAntiCompaction(ColumnFamilyStore cfs, + RangesAtEndpoint tokenRanges, + Refs sstables, + LifecycleTransaction txn, + UUID sessionId, + BooleanSupplier isCancelled) { Runnable runnable = new WrappedRunnable() { @@ -737,7 +735,7 @@ public class CompactionManager implements CompactionManagerMBean } }; - ListenableFuture task = null; + Future task = null; try { task = executor.submitIfRunning(runnable, "pending anticompaction"); @@ -1720,7 +1718,7 @@ public class CompactionManager implements CompactionManagerMBean } @VisibleForTesting - ListenableFuture submitIndexBuild(final SecondaryIndexBuilder builder, ActiveCompactionsTracker activeCompactions) + Future submitIndexBuild(final SecondaryIndexBuilder builder, ActiveCompactionsTracker activeCompactions) { Runnable runnable = new Runnable() { @@ -1744,7 +1742,7 @@ public class CompactionManager implements CompactionManagerMBean /** * Is not scheduled, because it is performing disjoint work from sstable compaction. */ - public ListenableFuture submitIndexBuild(final SecondaryIndexBuilder builder) + public Future submitIndexBuild(final SecondaryIndexBuilder builder) { return submitIndexBuild(builder, active); } @@ -1813,13 +1811,13 @@ public class CompactionManager implements CompactionManagerMBean return cfs.isIndex() ? nowInSec : cfs.gcBefore(nowInSec); } - public ListenableFuture submitViewBuilder(final ViewBuilderTask task) + public Future submitViewBuilder(final ViewBuilderTask task) { return submitViewBuilder(task, active); } @VisibleForTesting - ListenableFuture submitViewBuilder(final ViewBuilderTask task, ActiveCompactionsTracker activeCompactions) + Future submitViewBuilder(final ViewBuilderTask task, ActiveCompactionsTracker activeCompactions) { return viewBuildExecutor.submitIfRunning(() -> { activeCompactions.beginCompaction(task); @@ -1839,63 +1837,39 @@ public class CompactionManager implements CompactionManagerMBean return active.getCompactions().size(); } - static class CompactionExecutor extends JMXEnabledThreadPoolExecutor + public static boolean isCompactor(Thread thread) { - protected CompactionExecutor(int minThreads, int maxThreads, String name, BlockingQueue queue) - { - super(minThreads, maxThreads, 60, TimeUnit.SECONDS, queue, new NamedThreadFactory(name, Thread.MIN_PRIORITY), "internal"); - } + return thread.getThreadGroup().getParent() == compactionThreadGroup; + } - private CompactionExecutor(int threadCount, String name) - { - this(threadCount, threadCount, name, new LinkedBlockingQueue()); - } + // TODO: this is a bit ugly, but no uglier than it was + static class CompactionExecutor extends WrappedExecutorPlus + { + static final ThreadGroup compactionThreadGroup = executorFactory().newThreadGroup("compaction"); + private static final WithResources RESCHEDULE_FAILED = () -> SnapshotDeletingTask::rescheduleFailedTasks; public CompactionExecutor() { - this(Math.max(1, DatabaseDescriptor.getConcurrentCompactors()), "CompactionExecutor"); + this(executorFactory(), getConcurrentCompactors(), "CompactionExecutor", Integer.MAX_VALUE); } - protected void beforeExecute(Thread t, Runnable r) + public CompactionExecutor(int threads, String name, int queueSize) { - // can't set this in Thread factory, so we do it redundantly here - isCompactionManager.set(true); - super.beforeExecute(t, r); + this(executorFactory(), threads, name, queueSize); } - // modified from DebuggableThreadPoolExecutor so that CompactionInterruptedExceptions are not logged - @Override - public void afterExecute(Runnable r, Throwable t) + protected CompactionExecutor(ExecutorFactory executorFactory, int threads, String name, int queueSize) { - DebuggableThreadPoolExecutor.maybeResetTraceSessionWrapper(r); - - if (t == null) - t = DebuggableThreadPoolExecutor.extractThrowable(r); - - if (t != null) - { - if (t instanceof CompactionInterruptedException) - { - logger.info(t.getMessage()); - if (t.getSuppressed() != null && t.getSuppressed().length > 0) - logger.warn("Interruption of compaction encountered exceptions:", t); - else - logger.trace("Full interruption stack trace:", t); - } - else - { - DebuggableThreadPoolExecutor.handleOrLog(t); - } - } - - // Snapshots cannot be deleted on Windows while segments of the root element are mapped in NTFS. Compactions - // unmap those segments which could free up a snapshot for successful deletion. - SnapshotDeletingTask.rescheduleFailedTasks(); + super(executorFactory + .withJmxInternal() + .configurePooled(name, threads) + .withThreadGroup(compactionThreadGroup) + .withQueueLimit(queueSize).build()); } - public ListenableFuture submitIfRunning(Runnable task, String name) + public Future submitIfRunning(Runnable task, String name) { - return submitIfRunning(Executors.callable(task, null), name); + return submitIfRunning(callable(name, task), name); } /** @@ -1908,19 +1882,11 @@ public class CompactionManager implements CompactionManagerMBean * @return the future that will deliver the task result, or a future that has already been * cancelled if the task could not be submitted. */ - public ListenableFuture submitIfRunning(Callable task, String name) + public Future submitIfRunning(Callable task, String name) { - if (isShutdown()) - { - logger.info("Executor has been shut down, not submitting {}", name); - return Futures.immediateCancelledFuture(); - } - try { - ListenableFutureTask ret = ListenableFutureTask.create(task); - execute(ret); - return ret; + return submit(task); } catch (RejectedExecutionException ex) { @@ -1929,15 +1895,35 @@ public class CompactionManager implements CompactionManagerMBean else logger.error("Failed to submit {}", name, ex); - return Futures.immediateCancelledFuture(); + return ImmediateFuture.cancelled(); } } + + public void execute(Runnable command) + { + executor.execute(RESCHEDULE_FAILED, command); + } + + public Future submit(Callable task) + { + return executor.submit(RESCHEDULE_FAILED, task); + } + + public Future submit(Runnable task, T result) + { + return submit(callable(task, result)); + } + + public Future submit(Runnable task) + { + return submit(task, null); + } } // TODO: pull out relevant parts of CompactionExecutor and move to ValidationManager public static class ValidationExecutor extends CompactionExecutor { - // CompactionExecutor, and by extension ValidationExecutor, use DebuggableThreadPoolExecutor's + // CompactionExecutor, and by extension ValidationExecutor, use ExecutorPlus's // default RejectedExecutionHandler which blocks the submitting thread when the work queue is // full. The calling thread in this case is AntiEntropyStage, so in most cases we don't actually // want to block when the ValidationExecutor is saturated as this prevents progress on all @@ -1952,11 +1938,8 @@ public class CompactionManager implements CompactionManagerMBean public ValidationExecutor() { super(DatabaseDescriptor.getConcurrentValidations(), - DatabaseDescriptor.getConcurrentValidations(), "ValidationExecutor", - new LinkedBlockingQueue<>()); - - allowCoreThreadTimeOut(true); + Integer.MAX_VALUE); } public void adjustPoolSize() @@ -1970,7 +1953,7 @@ public class CompactionManager implements CompactionManagerMBean { public ViewBuildExecutor() { - super(DatabaseDescriptor.getConcurrentViewBuilders(), "ViewBuildExecutor"); + super(DatabaseDescriptor.getConcurrentViewBuilders(), "ViewBuildExecutor", Integer.MAX_VALUE); } } @@ -1978,7 +1961,7 @@ public class CompactionManager implements CompactionManagerMBean { public CacheCleanupExecutor() { - super(1, "CacheCleanupExecutor"); + super(1, "CacheCleanupExecutor", Integer.MAX_VALUE); } } diff --git a/src/java/org/apache/cassandra/db/compaction/Scrubber.java b/src/java/org/apache/cassandra/db/compaction/Scrubber.java index 867c23bc34..d271bb3be8 100644 --- a/src/java/org/apache/cassandra/db/compaction/Scrubber.java +++ b/src/java/org/apache/cassandra/db/compaction/Scrubber.java @@ -41,6 +41,7 @@ import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.io.util.RandomAccessReader; import org.apache.cassandra.service.ActiveRepairService; import org.apache.cassandra.utils.*; +import org.apache.cassandra.utils.Closeable; import org.apache.cassandra.utils.concurrent.Refs; import org.apache.cassandra.utils.memory.HeapAllocator; diff --git a/src/java/org/apache/cassandra/db/context/CounterContext.java b/src/java/org/apache/cassandra/db/context/CounterContext.java index 7db8192448..37bd3cac7e 100644 --- a/src/java/org/apache/cassandra/db/context/CounterContext.java +++ b/src/java/org/apache/cassandra/db/context/CounterContext.java @@ -465,7 +465,7 @@ public class CounterContext if (leftClock == rightClock) { // Can happen if an sstable gets lost and disk failure policy is set to 'best effort' - if (leftCount != rightCount && CompactionManager.isCompactionManager.get()) + if (leftCount != rightCount && CompactionManager.isCompactor(Thread.currentThread())) { logger.warn("invalid global counter shard detected; ({}, {}, {}) and ({}, {}, {}) differ only in " + "count; will pick highest to self-heal on compaction", @@ -506,7 +506,7 @@ public class CounterContext // We should never see non-local shards w/ same id+clock but different counts. However, if we do // we should "heal" the problem by being deterministic in our selection of shard - and // log the occurrence so that the operator will know something is wrong. - if (leftCount != rightCount && CompactionManager.isCompactionManager.get()) + if (leftCount != rightCount && CompactionManager.isCompactor(Thread.currentThread())) { logger.warn("invalid remote counter shard detected; ({}, {}, {}) and ({}, {}, {}) differ only in " + "count; will pick highest to self-heal on compaction", diff --git a/src/java/org/apache/cassandra/db/monitoring/MonitoringTask.java b/src/java/org/apache/cassandra/db/monitoring/MonitoringTask.java index d08c62f9f6..52d6160d76 100644 --- a/src/java/org/apache/cassandra/db/monitoring/MonitoringTask.java +++ b/src/java/org/apache/cassandra/db/monitoring/MonitoringTask.java @@ -23,7 +23,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; diff --git a/src/java/org/apache/cassandra/db/repair/CassandraKeyspaceRepairManager.java b/src/java/org/apache/cassandra/db/repair/CassandraKeyspaceRepairManager.java index 4fa8650c57..29950fca35 100644 --- a/src/java/org/apache/cassandra/db/repair/CassandraKeyspaceRepairManager.java +++ b/src/java/org/apache/cassandra/db/repair/CassandraKeyspaceRepairManager.java @@ -19,16 +19,16 @@ package org.apache.cassandra.db.repair; import java.util.Collection; +import java.util.List; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.function.BooleanSupplier; -import com.google.common.util.concurrent.ListenableFuture; - import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.locator.RangesAtEndpoint; import org.apache.cassandra.repair.KeyspaceRepairManager; +import org.apache.cassandra.utils.concurrent.Future; public class CassandraKeyspaceRepairManager implements KeyspaceRepairManager { @@ -40,11 +40,11 @@ public class CassandraKeyspaceRepairManager implements KeyspaceRepairManager } @Override - public ListenableFuture prepareIncrementalRepair(UUID sessionID, - Collection tables, - RangesAtEndpoint tokenRanges, - ExecutorService executor, - BooleanSupplier isCancelled) + public Future> prepareIncrementalRepair(UUID sessionID, + Collection tables, + RangesAtEndpoint tokenRanges, + ExecutorService executor, + BooleanSupplier isCancelled) { PendingAntiCompaction pac = new PendingAntiCompaction(sessionID, tables, tokenRanges, executor, isCancelled); return pac.run(); diff --git a/src/java/org/apache/cassandra/db/repair/PendingAntiCompaction.java b/src/java/org/apache/cassandra/db/repair/PendingAntiCompaction.java index 0d8928289f..59eff556c5 100644 --- a/src/java/org/apache/cassandra/db/repair/PendingAntiCompaction.java +++ b/src/java/org/apache/cassandra/db/repair/PendingAntiCompaction.java @@ -27,18 +27,18 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.function.BooleanSupplier; +import java.util.function.Function; import java.util.stream.Collectors; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; -import com.google.common.util.concurrent.AsyncFunction; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListenableFutureTask; -import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.Uninterruptibles; +import org.apache.cassandra.concurrent.FutureTask; +import org.apache.cassandra.utils.concurrent.Future; +import org.apache.cassandra.utils.concurrent.FutureCombiner; +import org.apache.cassandra.utils.concurrent.ImmediateFuture; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -256,7 +256,7 @@ public class PendingAntiCompaction } } - static class AcquisitionCallback implements AsyncFunction, Object> + static class AcquisitionCallback implements Function, Future>> { private final UUID parentRepairSession; private final RangesAtEndpoint tokenRanges; @@ -269,7 +269,7 @@ public class PendingAntiCompaction this.isCancelled = isCancelled; } - ListenableFuture submitPendingAntiCompaction(AcquireResult result) + Future submitPendingAntiCompaction(AcquireResult result) { return CompactionManager.instance.submitPendingAntiCompaction(result.cfs, tokenRanges, result.refs, result.txn, parentRepairSession, isCancelled); } @@ -288,7 +288,7 @@ public class PendingAntiCompaction }); } - public ListenableFuture apply(List results) throws Exception + public Future> apply(List results) { if (Iterables.any(results, AcquisitionCallback::shouldAbort)) { @@ -306,21 +306,21 @@ public class PendingAntiCompaction "This is usually caused by running multiple incremental repairs on nodes that share token ranges", parentRepairSession); logger.warn(message); - return Futures.immediateFailedFuture(new SSTableAcquisitionException(message)); + return ImmediateFuture.failure(new SSTableAcquisitionException(message)); } else { - List> pendingAntiCompactions = new ArrayList<>(results.size()); + List> pendingAntiCompactions = new ArrayList<>(results.size()); for (AcquireResult result : results) { if (result.txn != null) { - ListenableFuture future = submitPendingAntiCompaction(result); + Future future = submitPendingAntiCompaction(result); pendingAntiCompactions.add(future); } } - return Futures.allAsList(pendingAntiCompactions); + return FutureCombiner.allOf(pendingAntiCompactions); } } } @@ -360,19 +360,19 @@ public class PendingAntiCompaction this.isCancelled = isCancelled; } - public ListenableFuture run() + public Future> run() { - List> tasks = new ArrayList<>(tables.size()); + List> tasks = new ArrayList<>(tables.size()); for (ColumnFamilyStore cfs : tables) { cfs.forceBlockingFlush(); - ListenableFutureTask task = ListenableFutureTask.create(getAcquisitionCallable(cfs, tokenRanges.ranges(), prsId, acquireRetrySeconds, acquireSleepMillis)); + FutureTask task = new FutureTask<>(getAcquisitionCallable(cfs, tokenRanges.ranges(), prsId, acquireRetrySeconds, acquireSleepMillis)); executor.submit(task); tasks.add(task); } - ListenableFuture> acquisitionResults = Futures.successfulAsList(tasks); - ListenableFuture compactionResult = Futures.transformAsync(acquisitionResults, getAcquisitionCallback(prsId, tokenRanges), MoreExecutors.directExecutor()); - return compactionResult; + + Future> acquisitionResults = FutureCombiner.successfulOf(tasks); + return acquisitionResults.andThenAsync(getAcquisitionCallback(prsId, tokenRanges)); } @VisibleForTesting diff --git a/src/java/org/apache/cassandra/db/view/ViewBuilder.java b/src/java/org/apache/cassandra/db/view/ViewBuilder.java index a88ffbecc3..d087224a98 100644 --- a/src/java/org/apache/cassandra/db/view/ViewBuilder.java +++ b/src/java/org/apache/cassandra/db/view/ViewBuilder.java @@ -22,16 +22,12 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,6 +45,9 @@ import org.apache.cassandra.repair.SystemDistributedKeyspace; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.Pair; +import org.apache.cassandra.utils.concurrent.Future; +import org.apache.cassandra.utils.concurrent.FutureCombiner; +import org.apache.cassandra.utils.concurrent.ImmediateFuture; import static java.util.stream.Collectors.toList; @@ -73,7 +72,7 @@ class ViewBuilder private final Set tasks = Sets.newConcurrentHashSet(); private volatile long keysBuilt = 0; private volatile boolean isStopped = false; - private volatile Future future = Futures.immediateFuture(null); + private volatile Future future = ImmediateFuture.success(null); ViewBuilder(ColumnFamilyStore baseCfs, View view) { @@ -162,21 +161,21 @@ class ViewBuilder // Submit a new view build task for each building range. // We keep record of all the submitted tasks to be able of stopping them. - List> futures = pendingRanges.entrySet() - .stream() - .map(e -> new ViewBuilderTask(baseCfs, - view, - e.getKey(), - e.getValue().left, - e.getValue().right)) - .peek(tasks::add) - .map(CompactionManager.instance::submitViewBuilder) - .collect(toList()); + List> futures = pendingRanges.entrySet() + .stream() + .map(e -> new ViewBuilderTask(baseCfs, + view, + e.getKey(), + e.getValue().left, + e.getValue().right)) + .peek(tasks::add) + .map(CompactionManager.instance::submitViewBuilder) + .collect(toList()); // Add a callback to process any eventual new local range and mark the view as built, doing a delayed retry if // the tasks don't succeed - ListenableFuture> future = Futures.allAsList(futures); - Futures.addCallback(future, new FutureCallback>() + Future> future = FutureCombiner.allOf(futures); + future.addCallback(new FutureCallback>() { public void onSuccess(List result) { @@ -200,7 +199,7 @@ class ViewBuilder logger.warn("Materialized View failed to complete, sleeping 5 minutes before restarting", t); } } - }, MoreExecutors.directExecutor()); + }); this.future = future; } @@ -228,10 +227,16 @@ class ViewBuilder /** * Stops the view building. */ - synchronized void stop() + void stop() { - boolean wasStopped = isStopped; - internalStop(false); + boolean wasStopped; + synchronized (this) + { + wasStopped = isStopped; + internalStop(false); + } + // TODO: very unclear what the goal is here. why do we wait only if we were the first to invoke stop? + // but we wait outside the synchronized block to avoid a deadlock with `build` in the future callback if (!wasStopped) FBUtilities.waitOnFuture(future); } diff --git a/src/java/org/apache/cassandra/db/view/ViewBuilderTask.java b/src/java/org/apache/cassandra/db/view/ViewBuilderTask.java index 70f7bb7f46..2f32db0625 100644 --- a/src/java/org/apache/cassandra/db/view/ViewBuilderTask.java +++ b/src/java/org/apache/cassandra/db/view/ViewBuilderTask.java @@ -31,7 +31,6 @@ import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.collect.Iterators; import com.google.common.collect.PeekingIterator; -import com.google.common.util.concurrent.Futures; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/java/org/apache/cassandra/dht/BootStrapper.java b/src/java/org/apache/cassandra/dht/BootStrapper.java index 39ebf78b2b..9575f6ce6d 100644 --- a/src/java/org/apache/cassandra/dht/BootStrapper.java +++ b/src/java/org/apache/cassandra/dht/BootStrapper.java @@ -20,8 +20,7 @@ package org.apache.cassandra.dht; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; -import com.google.common.util.concurrent.ListenableFuture; - +import org.apache.cassandra.utils.concurrent.Future; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,7 +60,7 @@ public class BootStrapper extends ProgressEventNotifierSupport this.tokenMetadata = tmd; } - public ListenableFuture bootstrap(StreamStateStore stateStore, boolean useStrictConsistency) + public Future bootstrap(StreamStateStore stateStore, boolean useStrictConsistency) { logger.trace("Beginning bootstrap process"); diff --git a/src/java/org/apache/cassandra/gms/Gossiper.java b/src/java/org/apache/cassandra/gms/Gossiper.java index 7dd73c7a1e..98068dbf18 100644 --- a/src/java/org/apache/cassandra/gms/Gossiper.java +++ b/src/java/org/apache/cassandra/gms/Gossiper.java @@ -20,7 +20,14 @@ package org.apache.cassandra.gms; import java.net.UnknownHostException; import java.util.*; import java.util.Map.Entry; -import java.util.concurrent.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.ReentrantLock; import java.util.function.BooleanSupplier; import java.util.function.Supplier; @@ -36,10 +43,10 @@ import com.google.common.collect.Iterables; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; -import com.google.common.util.concurrent.ListenableFutureTask; import com.google.common.util.concurrent.Uninterruptibles; -import org.apache.cassandra.concurrent.JMXEnabledSingleThreadExecutor; +import org.apache.cassandra.concurrent.*; +import org.apache.cassandra.concurrent.FutureTask; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.net.NoPayload; import org.apache.cassandra.net.Verb; @@ -53,8 +60,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.netty.util.concurrent.FastThreadLocal; -import org.apache.cassandra.concurrent.DebuggableScheduledThreadPoolExecutor; -import org.apache.cassandra.concurrent.JMXEnabledThreadPoolExecutor; import org.apache.cassandra.concurrent.Stage; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.SystemKeyspace; @@ -73,6 +78,7 @@ import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.RecomputingSupplier; import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.config.CassandraRelevantProperties.GOSSIPER_QUARANTINE_DELAY; import static org.apache.cassandra.net.NoPayload.noPayload; import static org.apache.cassandra.net.Verb.ECHO_REQ; @@ -104,7 +110,7 @@ public class Gossiper implements IFailureDetectionEventListener, GossiperMBean public static final String DISABLE_THREAD_VALIDATION = "cassandra.gossip.disable_thread_validation"; } - private static final DebuggableScheduledThreadPoolExecutor executor = new DebuggableScheduledThreadPoolExecutor("GossipTasks"); + private static final ScheduledExecutorPlus executor = executorFactory().scheduled("GossipTasks"); static final ApplicationState[] STATES = ApplicationState.values(); static final List DEAD_STATES = Arrays.asList(VersionedValue.REMOVING_TOKEN, VersionedValue.REMOVED_TOKEN, @@ -261,7 +267,7 @@ public class Gossiper implements IFailureDetectionEventListener, GossiperMBean private static boolean isInGossipStage() { - return ((JMXEnabledSingleThreadExecutor) Stage.GOSSIP.executor()).isExecutedBy(Thread.currentThread()); + return Stage.GOSSIP.executor().inExecutor(); } private static void checkProperThreadForStateMutation() @@ -526,7 +532,7 @@ public class Gossiper implements IFailureDetectionEventListener, GossiperMBean return; } - ListenableFutureTask task = ListenableFutureTask.create(runnable, null); + FutureTask task = new FutureTask<>(runnable); Stage.GOSSIP.execute(task); try { @@ -1031,7 +1037,7 @@ public class Gossiper implements IFailureDetectionEventListener, GossiperMBean long now = currentTimeMillis(); long nowNano = nanoTime(); - long pending = ((JMXEnabledThreadPoolExecutor) Stage.GOSSIP.executor()).metrics.pendingTasks.getValue(); + long pending = Stage.GOSSIP.executor().getPendingTaskCount(); if (pending > 0 && lastProcessedMessageAt < now - 1000) { // if some new messages just arrived, give the executor some time to work on them diff --git a/src/java/org/apache/cassandra/hadoop/cql3/CqlInputFormat.java b/src/java/org/apache/cassandra/hadoop/cql3/CqlInputFormat.java index 57fb238873..bb6881b905 100644 --- a/src/java/org/apache/cassandra/hadoop/cql3/CqlInputFormat.java +++ b/src/java/org/apache/cassandra/hadoop/cql3/CqlInputFormat.java @@ -55,6 +55,7 @@ import org.apache.cassandra.hadoop.*; import org.apache.cassandra.utils.*; import static org.apache.cassandra.utils.Clock.Global.nanoTime; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; /** * Hadoop InputFormat allowing map/reduce against Cassandra rows within one ColumnFamily. @@ -135,7 +136,7 @@ public class CqlInputFormat extends org.apache.hadoop.mapreduce.InputFormat()); + ExecutorService executor = executorFactory().pooled("HadoopInput", 128); List splits = new ArrayList<>(); String[] inputInitialAddress = ConfigHelper.getInputInitialAddress(conf).split(","); diff --git a/src/java/org/apache/cassandra/hints/Hint.java b/src/java/org/apache/cassandra/hints/Hint.java index 11ac5157d2..3089894b7f 100644 --- a/src/java/org/apache/cassandra/hints/Hint.java +++ b/src/java/org/apache/cassandra/hints/Hint.java @@ -19,7 +19,6 @@ package org.apache.cassandra.hints; import java.io.IOException; import java.nio.ByteBuffer; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import com.google.common.base.Throwables; @@ -34,6 +33,8 @@ import org.apache.cassandra.io.util.DataInputBuffer; import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.schema.TableId; +import org.apache.cassandra.utils.concurrent.Future; +import org.apache.cassandra.utils.concurrent.ImmediateFuture; import org.apache.cassandra.utils.vint.VIntCoding; import org.assertj.core.util.VisibleForTesting; @@ -94,7 +95,7 @@ public final class Hint /** * Applies the contained mutation unless it's expired, filtering out any updates for truncated tables */ - CompletableFuture applyFuture() + Future applyFuture() { if (isLive()) { @@ -108,7 +109,7 @@ public final class Hint return filtered.applyFuture(); } - return CompletableFuture.completedFuture(null); + return ImmediateFuture.success(null); } void apply() diff --git a/src/java/org/apache/cassandra/hints/HintVerbHandler.java b/src/java/org/apache/cassandra/hints/HintVerbHandler.java index 2fbe4754b6..e6758d0f75 100644 --- a/src/java/org/apache/cassandra/hints/HintVerbHandler.java +++ b/src/java/org/apache/cassandra/hints/HintVerbHandler.java @@ -94,7 +94,7 @@ public final class HintVerbHandler implements IVerbHandler else { // the common path - the node is both the destination and a valid replica for the hint. - hint.applyFuture().thenAccept(o -> respond(message)).exceptionally(e -> {logger.debug("Failed to apply hint", e); return null;}); + hint.applyFuture().addCallback(o -> respond(message), e -> logger.debug("Failed to apply hint", e)); } } diff --git a/src/java/org/apache/cassandra/hints/HintsDispatchExecutor.java b/src/java/org/apache/cassandra/hints/HintsDispatchExecutor.java index 2a5239cd12..705715ce43 100644 --- a/src/java/org/apache/cassandra/hints/HintsDispatchExecutor.java +++ b/src/java/org/apache/cassandra/hints/HintsDispatchExecutor.java @@ -20,28 +20,27 @@ package org.apache.cassandra.hints; import java.io.File; import java.util.Map; import java.util.UUID; -import java.util.concurrent.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BooleanSupplier; import java.util.function.Predicate; import java.util.function.Supplier; import com.google.common.util.concurrent.RateLimiter; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.cassandra.concurrent.JMXEnabledThreadPoolExecutor; -import org.apache.cassandra.concurrent.NamedThreadFactory; +import org.apache.cassandra.concurrent.ExecutorPlus; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.io.FSReadError; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; +import org.apache.cassandra.utils.concurrent.Future; -import static java.lang.Thread.MIN_PRIORITY; -import static java.util.concurrent.TimeUnit.MINUTES; -import static org.apache.cassandra.utils.concurrent.BlockingQueues.newBlockingQueue; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; /** * A multi-threaded (by default) executor for dispatching hints. @@ -53,7 +52,7 @@ final class HintsDispatchExecutor private static final Logger logger = LoggerFactory.getLogger(HintsDispatchExecutor.class); private final File hintsDirectory; - private final ExecutorService executor; + private final ExecutorPlus executor; private final AtomicBoolean isPaused; private final Predicate isAlive; private final Map scheduledDispatches; @@ -65,10 +64,11 @@ final class HintsDispatchExecutor this.isAlive = isAlive; scheduledDispatches = new ConcurrentHashMap<>(); - executor = new JMXEnabledThreadPoolExecutor(maxThreads, 1, MINUTES, - newBlockingQueue(), - new NamedThreadFactory("HintsDispatcher", MIN_PRIORITY), - "internal"); + executor = executorFactory() + .withJmxInternal() + .configurePooled("HintsDispatcher", maxThreads) + .withThreadPriority(Thread.MIN_PRIORITY) + .build(); } /* diff --git a/src/java/org/apache/cassandra/hints/HintsService.java b/src/java/org/apache/cassandra/hints/HintsService.java index a399a2f837..b727b351ac 100644 --- a/src/java/org/apache/cassandra/hints/HintsService.java +++ b/src/java/org/apache/cassandra/hints/HintsService.java @@ -23,7 +23,9 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.UUID; -import java.util.concurrent.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -32,6 +34,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.locator.ReplicaLayout; +import org.apache.cassandra.utils.concurrent.Future; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/java/org/apache/cassandra/hints/HintsWriteExecutor.java b/src/java/org/apache/cassandra/hints/HintsWriteExecutor.java index 26e3103225..c4bfff001d 100644 --- a/src/java/org/apache/cassandra/hints/HintsWriteExecutor.java +++ b/src/java/org/apache/cassandra/hints/HintsWriteExecutor.java @@ -20,18 +20,22 @@ package org.apache.cassandra.hints; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Iterator; -import java.util.concurrent.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import org.apache.cassandra.concurrent.ExecutorPlus; +import org.apache.cassandra.utils.concurrent.Future; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.cassandra.concurrent.DebuggableThreadPoolExecutor; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.io.FSError; import org.apache.cassandra.io.FSWriteError; import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; + /** * A single threaded executor that exclusively writes all the hints and otherwise manipulate the writers. * @@ -47,14 +51,14 @@ final class HintsWriteExecutor private final HintsCatalog catalog; private final ByteBuffer writeBuffer; - private final ExecutorService executor; + private final ExecutorPlus executor; HintsWriteExecutor(HintsCatalog catalog) { this.catalog = catalog; writeBuffer = ByteBuffer.allocateDirect(WRITE_BUFFER_SIZE); - executor = DebuggableThreadPoolExecutor.createWithFixedPoolSize("HintsWriteExecutor", 1); + executor = executorFactory().sequential("HintsWriteExecutor"); } /* diff --git a/src/java/org/apache/cassandra/index/SecondaryIndexManager.java b/src/java/org/apache/cassandra/index/SecondaryIndexManager.java index a3fd881965..d75b488f5f 100644 --- a/src/java/org/apache/cassandra/index/SecondaryIndexManager.java +++ b/src/java/org/apache/cassandra/index/SecondaryIndexManager.java @@ -19,7 +19,9 @@ package org.apache.cassandra.index; import java.lang.reflect.Constructor; import java.util.*; -import java.util.concurrent.*; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.stream.Collectors; @@ -28,26 +30,15 @@ import java.util.stream.Stream; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.base.Strings; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; +import com.google.common.collect.*; import com.google.common.primitives.Longs; import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListeningExecutorService; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.common.util.concurrent.SettableFuture; - import org.apache.commons.lang3.StringUtils; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.cassandra.concurrent.JMXEnabledThreadPoolExecutor; -import org.apache.cassandra.concurrent.NamedThreadFactory; +import org.apache.cassandra.concurrent.ExecutorPlus; +import org.apache.cassandra.concurrent.ImmediateExecutor; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.statements.schema.IndexTarget; import org.apache.cassandra.db.*; @@ -58,7 +49,8 @@ import org.apache.cassandra.db.filter.DataLimits; import org.apache.cassandra.db.filter.RowFilter; import org.apache.cassandra.db.lifecycle.SSTableSet; import org.apache.cassandra.db.lifecycle.View; -import org.apache.cassandra.db.partitions.*; +import org.apache.cassandra.db.partitions.PartitionUpdate; +import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator; import org.apache.cassandra.db.rows.*; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.index.Index.IndexBuildingSupport; @@ -76,13 +68,11 @@ import org.apache.cassandra.tracing.Tracing; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.JVMStabilityInspector; -import org.apache.cassandra.utils.concurrent.Refs; +import org.apache.cassandra.utils.concurrent.*; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.apache.cassandra.concurrent.Stage.KEEP_ALIVE_SECONDS; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.utils.ExecutorUtils.awaitTermination; import static org.apache.cassandra.utils.ExecutorUtils.shutdown; -import static org.apache.cassandra.utils.concurrent.BlockingQueues.newBlockingQueue; /** * Handles the core maintenance functionality associated with indexes: adding/removing them to or from @@ -162,16 +152,12 @@ public class SecondaryIndexManager implements IndexRegistry, INotificationConsum private final Map inProgressBuilds = Maps.newConcurrentMap(); // executes tasks returned by Indexer#addIndexColumn which may require index(es) to be (re)built - private static final ListeningExecutorService asyncExecutor = MoreExecutors.listeningDecorator( - new JMXEnabledThreadPoolExecutor(1, - KEEP_ALIVE_SECONDS, - SECONDS, - newBlockingQueue(), - new NamedThreadFactory("SecondaryIndexManagement"), - "internal")); + private static final ExecutorPlus asyncExecutor = executorFactory() + .withJmxInternal() + .sequential("SecondaryIndexManagement"); // executes all blocking tasks produced by Indexers e.g. getFlushTask, getMetadataReloadTask etc - private static final ListeningExecutorService blockingExecutor = MoreExecutors.newDirectExecutorService(); + private static final ExecutorPlus blockingExecutor = ImmediateExecutor.INSTANCE; /** * The underlying column family containing the source data for these indexes @@ -209,12 +195,12 @@ public class SecondaryIndexManager implements IndexRegistry, INotificationConsum Index index = indexes.get(indexDef.name); Callable reloadTask = index.getMetadataReloadTask(indexDef); return reloadTask == null - ? Futures.immediateFuture(null) + ? ImmediateFuture.success(null) : blockingExecutor.submit(reloadTask); } @SuppressWarnings("unchecked") - private synchronized Future createIndex(IndexMetadata indexDef, boolean isNewCF) + private synchronized Future createIndex(IndexMetadata indexDef, boolean isNewCF) { final Index index = createInstance(indexDef); index.register(this); @@ -242,27 +228,21 @@ public class SecondaryIndexManager implements IndexRegistry, INotificationConsum if (initialBuildTask == null) { markIndexBuilt(index, true); - return Futures.immediateFuture(null); + return ImmediateFuture.success(null); } // otherwise run the initialization task asynchronously with a callback to mark it built or failed - final SettableFuture initialization = SettableFuture.create(); - Futures.addCallback(asyncExecutor.submit(initialBuildTask), new FutureCallback() - { - @Override - public void onFailure(Throwable t) - { - logAndMarkIndexesFailed(Collections.singleton(index), t, true); - initialization.setException(t); - } - - @Override - public void onSuccess(Object o) - { - markIndexBuilt(index, true); - initialization.set(o); - } - }, MoreExecutors.directExecutor()); + final Promise initialization = new AsyncPromise<>(); + asyncExecutor.submit(initialBuildTask) + .addCallback( + success -> { + markIndexBuilt(index, true); + initialization.trySuccess(null); + }, + failure -> { + logAndMarkIndexesFailed(Collections.singleton(index), failure, true); + initialization.tryFailure(failure); + }); return initialization; } @@ -520,15 +500,15 @@ public class SecondaryIndexManager implements IndexRegistry, INotificationConsum byType.forEach((buildingSupport, groupedIndexes) -> { SecondaryIndexBuilder builder = buildingSupport.getIndexBuildTask(baseCfs, groupedIndexes, sstables); - final SettableFuture build = SettableFuture.create(); - Futures.addCallback(CompactionManager.instance.submitIndexBuild(builder), new FutureCallback() + final AsyncPromise build = new AsyncPromise<>(); + CompactionManager.instance.submitIndexBuild(builder).addCallback(new FutureCallback() { @Override public void onFailure(Throwable t) { logAndMarkIndexesFailed(groupedIndexes, t, false); unbuiltIndexes.addAll(groupedIndexes); - build.setException(t); + build.tryFailure(t); } @Override @@ -537,9 +517,9 @@ public class SecondaryIndexManager implements IndexRegistry, INotificationConsum groupedIndexes.forEach(i -> markIndexBuilt(i, isFullRebuild)); logger.info("Index build of {} completed", getIndexNames(groupedIndexes)); builtIndexes.addAll(groupedIndexes); - build.set(o); + build.trySuccess(o); } - }, MoreExecutors.directExecutor()); + }); futures.add(build); }); @@ -1526,17 +1506,17 @@ public class SecondaryIndexManager implements IndexRegistry, INotificationConsum } } - private void executeBlocking(Callable task, FutureCallback callback) + private void executeBlocking(Callable task, FutureCallback callback) { if (null != task) { - ListenableFuture f = blockingExecutor.submit(task); - if (callback != null) Futures.addCallback(f, callback, MoreExecutors.directExecutor()); + Future f = blockingExecutor.submit(task); + if (callback != null) f.addCallback(callback); FBUtilities.waitOnFuture(f); } } - private void executeAllBlocking(Stream indexers, Function> function, FutureCallback callback) + private void executeAllBlocking(Stream indexers, Function> function, FutureCallback callback) { if (function == null) { @@ -1550,8 +1530,8 @@ public class SecondaryIndexManager implements IndexRegistry, INotificationConsum Callable task = function.apply(indexer); if (null != task) { - ListenableFuture f = blockingExecutor.submit(task); - if (callback != null) Futures.addCallback(f, callback, MoreExecutors.directExecutor()); + Future f = blockingExecutor.submit(task); + if (callback != null) f.addCallback(callback); waitFor.add(f); } }); diff --git a/src/java/org/apache/cassandra/index/sasi/TermIterator.java b/src/java/org/apache/cassandra/index/sasi/TermIterator.java index 3473234659..c84b209570 100644 --- a/src/java/org/apache/cassandra/index/sasi/TermIterator.java +++ b/src/java/org/apache/cassandra/index/sasi/TermIterator.java @@ -24,7 +24,8 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import io.netty.util.concurrent.FastThreadLocal; -import org.apache.cassandra.concurrent.NamedThreadFactory; +import org.apache.cassandra.concurrent.ExecutorFactory; +import org.apache.cassandra.concurrent.ImmediateExecutor; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.index.sasi.disk.Token; import org.apache.cassandra.index.sasi.plan.Expression; @@ -39,6 +40,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static java.lang.String.format; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.index.sasi.disk.OnDiskIndexBuilder.Mode.CONTAINS; import static org.apache.cassandra.index.sasi.plan.Expression.Op.PREFIX; import static org.apache.cassandra.utils.concurrent.CountDownLatch.newCountDownLatch; @@ -57,16 +59,8 @@ public class TermIterator extends RangeIterator logger.info("Search Concurrency Factor is set to {} for {}", concurrencyFactor, currentThread); return (concurrencyFactor <= 1) - ? MoreExecutors.newDirectExecutorService() - : Executors.newFixedThreadPool(concurrencyFactor, new ThreadFactory() - { - public final AtomicInteger count = new AtomicInteger(); - - public Thread newThread(Runnable task) - { - return NamedThreadFactory.createThread(task, currentThread + "-SEARCH-" + count.incrementAndGet(), true); - } - }); + ? ImmediateExecutor.INSTANCE + : executorFactory().pooled(currentThread + "-SEARCH-", concurrencyFactor); } }; diff --git a/src/java/org/apache/cassandra/index/sasi/disk/PerSSTableIndexWriter.java b/src/java/org/apache/cassandra/index/sasi/disk/PerSSTableIndexWriter.java index e4ac3f3b32..444db83703 100644 --- a/src/java/org/apache/cassandra/index/sasi/disk/PerSSTableIndexWriter.java +++ b/src/java/org/apache/cassandra/index/sasi/disk/PerSSTableIndexWriter.java @@ -24,8 +24,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.*; -import org.apache.cassandra.concurrent.JMXEnabledThreadPoolExecutor; -import org.apache.cassandra.concurrent.NamedThreadFactory; +import org.apache.cassandra.concurrent.ExecutorPlus; import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.db.DecoratedKey; import org.apache.cassandra.db.compaction.OperationType; @@ -43,40 +42,33 @@ import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.concurrent.CountDownLatch; +import org.apache.cassandra.utils.concurrent.ImmediateFuture; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Maps; -import com.google.common.util.concurrent.Futures; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.utils.Clock.Global.nanoTime; -import static java.util.concurrent.TimeUnit.MINUTES; import static org.apache.cassandra.utils.concurrent.CountDownLatch.newCountDownLatch; -import static org.apache.cassandra.utils.concurrent.BlockingQueues.newBlockingQueue; public class PerSSTableIndexWriter implements SSTableFlushObserver { private static final Logger logger = LoggerFactory.getLogger(PerSSTableIndexWriter.class); private static final int POOL_SIZE = 8; - private static final ThreadPoolExecutor INDEX_FLUSHER_MEMTABLE; - private static final ThreadPoolExecutor INDEX_FLUSHER_GENERAL; + private static final ExecutorPlus INDEX_FLUSHER_MEMTABLE; + private static final ExecutorPlus INDEX_FLUSHER_GENERAL; static { - INDEX_FLUSHER_GENERAL = new JMXEnabledThreadPoolExecutor(POOL_SIZE, POOL_SIZE, 1, MINUTES, - newBlockingQueue(), - new NamedThreadFactory("SASI-General"), - "internal"); - INDEX_FLUSHER_GENERAL.allowCoreThreadTimeOut(true); + INDEX_FLUSHER_GENERAL = executorFactory().withJmxInternal() + .pooled("SASI-General", POOL_SIZE); - INDEX_FLUSHER_MEMTABLE = new JMXEnabledThreadPoolExecutor(POOL_SIZE, POOL_SIZE, 1, MINUTES, - newBlockingQueue(), - new NamedThreadFactory("SASI-Memtable"), - "internal"); - INDEX_FLUSHER_MEMTABLE.allowCoreThreadTimeOut(true); + INDEX_FLUSHER_MEMTABLE = executorFactory().withJmxInternal() + .pooled("SASI-Memtable", POOL_SIZE); } private final int nowInSec = FBUtilities.nowInSeconds(); @@ -298,7 +290,7 @@ public class PerSSTableIndexWriter implements SSTableFlushObserver { @SuppressWarnings("resource") OnDiskIndex last = scheduleSegmentFlush(false).call(); - segments.add(Futures.immediateFuture(last)); + segments.add(ImmediateFuture.success(last)); } int index = 0; diff --git a/src/java/org/apache/cassandra/io/sstable/IndexSummaryManager.java b/src/java/org/apache/cassandra/io/sstable/IndexSummaryManager.java index 4147ce5c26..aac0be42c2 100644 --- a/src/java/org/apache/cassandra/io/sstable/IndexSummaryManager.java +++ b/src/java/org/apache/cassandra/io/sstable/IndexSummaryManager.java @@ -30,10 +30,10 @@ import java.util.concurrent.TimeoutException; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; +import org.apache.cassandra.concurrent.ScheduledExecutorPlus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.cassandra.concurrent.DebuggableScheduledThreadPoolExecutor; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.Keyspace; @@ -51,6 +51,7 @@ import org.apache.cassandra.utils.MBeanWrapper; import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.WrappedRunnable; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; /** * 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. @@ -64,7 +65,7 @@ public class IndexSummaryManager implements IndexSummaryManagerMBean private int resizeIntervalInMinutes = 0; private long memoryPoolBytes; - private final DebuggableScheduledThreadPoolExecutor executor; + private final ScheduledExecutorPlus executor; // our next scheduled resizing run private ScheduledFuture future; @@ -77,7 +78,7 @@ public class IndexSummaryManager implements IndexSummaryManagerMBean private IndexSummaryManager() { - executor = new DebuggableScheduledThreadPoolExecutor(1, "IndexSummaryManager", Thread.MIN_PRIORITY); + executor = executorFactory().scheduled(false, "IndexSummaryManager", Thread.MIN_PRIORITY); long indexSummarySizeInMB = DatabaseDescriptor.getIndexSummaryCapacityInMB(); int interval = DatabaseDescriptor.getIndexSummaryResizeIntervalInMinutes(); @@ -278,6 +279,6 @@ public class IndexSummaryManager implements IndexSummaryManagerMBean @VisibleForTesting public void shutdownAndWait(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException { - ExecutorUtils.shutdownAndWait(timeout, unit, executor); + ExecutorUtils.shutdownNowAndWait(timeout, unit, executor); } } diff --git a/src/java/org/apache/cassandra/io/sstable/SSTableSimpleUnsortedWriter.java b/src/java/org/apache/cassandra/io/sstable/SSTableSimpleUnsortedWriter.java index aef054ec7d..1d5aaa05ae 100644 --- a/src/java/org/apache/cassandra/io/sstable/SSTableSimpleUnsortedWriter.java +++ b/src/java/org/apache/cassandra/io/sstable/SSTableSimpleUnsortedWriter.java @@ -22,22 +22,24 @@ import java.io.IOException; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.BlockingQueue; -import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; import com.google.common.base.Throwables; import io.netty.util.concurrent.FastThreadLocalThread; -import org.apache.cassandra.db.*; -import org.apache.cassandra.db.rows.Row; +import org.apache.cassandra.db.DecoratedKey; +import org.apache.cassandra.db.RegularAndStaticColumns; +import org.apache.cassandra.db.SerializationHeader; +import org.apache.cassandra.db.partitions.PartitionUpdate; import org.apache.cassandra.db.rows.EncodingStats; +import org.apache.cassandra.db.rows.Row; import org.apache.cassandra.db.rows.SerializationHelper; import org.apache.cassandra.db.rows.UnfilteredSerializer; -import org.apache.cassandra.db.partitions.PartitionUpdate; import org.apache.cassandra.schema.TableMetadataRef; import org.apache.cassandra.utils.JVMStabilityInspector; import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; +import static org.apache.cassandra.utils.concurrent.BlockingQueues.newBlockingQueue; /** * A SSTable writer that doesn't assume rows are in sorted order. @@ -61,7 +63,7 @@ class SSTableSimpleUnsortedWriter extends AbstractSSTableSimpleWriter private final SerializationHeader header; private final SerializationHelper helper; - private final BlockingQueue writeQueue = new SynchronousQueue(); + private final BlockingQueue writeQueue = newBlockingQueue(0); private final DiskWriter diskWriter = new DiskWriter(); SSTableSimpleUnsortedWriter(File directory, TableMetadataRef metadata, RegularAndStaticColumns columns, long bufferSizeInMB) diff --git a/src/java/org/apache/cassandra/io/sstable/format/SSTableReader.java b/src/java/org/apache/cassandra/io/sstable/format/SSTableReader.java index a946e4fdc4..d5dee48490 100644 --- a/src/java/org/apache/cassandra/io/sstable/format/SSTableReader.java +++ b/src/java/org/apache/cassandra/io/sstable/format/SSTableReader.java @@ -30,6 +30,7 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Ordering; import com.google.common.primitives.Longs; import com.google.common.util.concurrent.RateLimiter; +import org.apache.cassandra.concurrent.ExecutorPlus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,8 +40,8 @@ import com.clearspring.analytics.stream.cardinality.ICardinality; import org.apache.cassandra.cache.InstrumentingCache; import org.apache.cassandra.cache.KeyCacheKey; -import org.apache.cassandra.concurrent.DebuggableThreadPoolExecutor; import org.apache.cassandra.concurrent.NamedThreadFactory; +import org.apache.cassandra.concurrent.ScheduledExecutorPlus; import org.apache.cassandra.concurrent.ScheduledExecutors; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.*; @@ -71,6 +72,7 @@ import org.apache.cassandra.service.CacheService; import org.apache.cassandra.utils.*; import org.apache.cassandra.utils.concurrent.*; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.db.Directories.SECONDARY_INDEX_NAME_SEPARATOR; import static org.apache.cassandra.utils.concurrent.BlockingQueues.newBlockingQueue; @@ -137,17 +139,19 @@ public abstract class SSTableReader extends SSTable implements SelfRefCounted sstables = newBlockingQueue(); - ExecutorService executor = DebuggableThreadPoolExecutor.createWithFixedPoolSize("SSTableBatchOpen", FBUtilities.getAvailableProcessors()); - for (final Map.Entry> entry : entries) + ExecutorPlus executor = executorFactory().pooled("SSTableBatchOpen", FBUtilities.getAvailableProcessors()); + try { - Runnable runnable = new Runnable() + for (final Map.Entry> entry : entries) { - public void run() + Runnable runnable = new Runnable() { - SSTableReader sstable; - try + public void run() { - sstable = open(entry.getKey(), entry.getValue(), metadata); + SSTableReader sstable; + try + { + sstable = open(entry.getKey(), entry.getValue(), metadata); + } + catch (CorruptSSTableException ex) + { + JVMStabilityInspector.inspectThrowable(ex); + logger.error("Corrupt sstable {}; skipping table", entry, ex); + return; + } + catch (FSError ex) + { + JVMStabilityInspector.inspectThrowable(ex); + logger.error("Cannot read sstable {}; file system error, skipping table", entry, ex); + return; + } + sstables.add(sstable); } - catch (CorruptSSTableException ex) - { - JVMStabilityInspector.inspectThrowable(ex); - logger.error("Corrupt sstable {}; skipping table", entry, ex); - return; - } - catch (FSError ex) - { - JVMStabilityInspector.inspectThrowable(ex); - logger.error("Cannot read sstable {}; file system error, skipping table", entry, ex); - return; - } - sstables.add(sstable); - } - }; - executor.submit(runnable); + }; + executor.submit(runnable); + } + } + finally + { + executor.shutdown(); } - executor.shutdown(); try { executor.awaitTermination(7, TimeUnit.DAYS); diff --git a/src/java/org/apache/cassandra/metrics/CompactionMetrics.java b/src/java/org/apache/cassandra/metrics/CompactionMetrics.java index 7d0b9696d2..375adc426a 100644 --- a/src/java/org/apache/cassandra/metrics/CompactionMetrics.java +++ b/src/java/org/apache/cassandra/metrics/CompactionMetrics.java @@ -18,12 +18,12 @@ package org.apache.cassandra.metrics; import java.util.*; -import java.util.concurrent.ThreadPoolExecutor; import com.codahale.metrics.Counter; import com.codahale.metrics.Gauge; import com.codahale.metrics.Meter; +import org.apache.cassandra.concurrent.ExecutorPlus; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.db.compaction.CompactionInfo; @@ -62,7 +62,7 @@ public class CompactionMetrics /** Total number of compactions which have outright failed due to lack of disk space */ public final Counter compactionsAborted; - public CompactionMetrics(final ThreadPoolExecutor... collectors) + public CompactionMetrics(final ExecutorPlus... collectors) { pendingTasks = Metrics.register(factory.createMetricName("PendingTasks"), new Gauge() { @@ -137,7 +137,7 @@ public class CompactionMetrics public Long getValue() { long completedTasks = 0; - for (ThreadPoolExecutor collector : collectors) + for (ExecutorPlus collector : collectors) completedTasks += collector.getCompletedTaskCount(); return completedTasks; } diff --git a/src/java/org/apache/cassandra/metrics/Sampler.java b/src/java/org/apache/cassandra/metrics/Sampler.java index cfe3f3b249..90cc90c217 100644 --- a/src/java/org/apache/cassandra/metrics/Sampler.java +++ b/src/java/org/apache/cassandra/metrics/Sampler.java @@ -19,18 +19,19 @@ package org.apache.cassandra.metrics; import java.io.Serializable; import java.util.List; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; -import org.apache.cassandra.concurrent.JMXEnabledThreadPoolExecutor; -import org.apache.cassandra.concurrent.NamedThreadFactory; +import org.apache.cassandra.concurrent.ExecutorPlus; import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.net.Verb; +import org.apache.cassandra.utils.ExecutorUtils; import org.apache.cassandra.utils.MonotonicClock; import com.google.common.annotations.VisibleForTesting; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; + public abstract class Sampler { public enum SamplerType @@ -42,19 +43,12 @@ public abstract class Sampler MonotonicClock clock = MonotonicClock.approxTime; @VisibleForTesting - static final ThreadPoolExecutor samplerExecutor = new JMXEnabledThreadPoolExecutor(1, 1, - TimeUnit.SECONDS, - new ArrayBlockingQueue(1000), - new NamedThreadFactory("Sampler"), - "internal"); - - static - { - samplerExecutor.setRejectedExecutionHandler((runnable, executor) -> - { - MessagingService.instance().metrics.recordSelfDroppedMessage(Verb._SAMPLE); - }); - } + static final ExecutorPlus samplerExecutor = executorFactory() + .withJmxInternal() + .configureSequential("Sampler") + .withQueueLimit(1000) + .withRejectedExecutionHandler((runnable, executor) -> MessagingService.instance().metrics.recordSelfDroppedMessage(Verb._SAMPLE)) + .build(); public void addSample(final T item, final int value) { @@ -94,4 +88,9 @@ public abstract class Sampler return "Sample [value=" + value + ", count=" + count + ", error=" + error + "]"; } } + + public static void shutdownNowAndWait(long time, TimeUnit units) throws InterruptedException, TimeoutException + { + ExecutorUtils.shutdownNowAndWait(time, units, samplerExecutor); + } } diff --git a/src/java/org/apache/cassandra/metrics/ThreadPoolMetrics.java b/src/java/org/apache/cassandra/metrics/ThreadPoolMetrics.java index 3ba984a69b..62ef1775fb 100644 --- a/src/java/org/apache/cassandra/metrics/ThreadPoolMetrics.java +++ b/src/java/org/apache/cassandra/metrics/ThreadPoolMetrics.java @@ -21,7 +21,7 @@ import java.util.concurrent.ThreadPoolExecutor; import com.codahale.metrics.Counter; import com.codahale.metrics.Gauge; -import org.apache.cassandra.concurrent.LocalAwareExecutorService; +import org.apache.cassandra.concurrent.ResizableThreadPool; import org.apache.cassandra.metrics.CassandraMetricsRegistry.MetricName; import static java.lang.String.format; @@ -75,7 +75,7 @@ public class ThreadPoolMetrics * @param path Type of thread pool * @param poolName Name of thread pool to identify metrics */ - public ThreadPoolMetrics(LocalAwareExecutorService executor, String path, String poolName) + public ThreadPoolMetrics(ResizableThreadPool executor, String path, String poolName) { this.path = path; this.poolName = poolName; diff --git a/src/java/org/apache/cassandra/net/AsyncChannelPromise.java b/src/java/org/apache/cassandra/net/AsyncChannelPromise.java index c652b8c016..142c3e2cf9 100644 --- a/src/java/org/apache/cassandra/net/AsyncChannelPromise.java +++ b/src/java/org/apache/cassandra/net/AsyncChannelPromise.java @@ -31,7 +31,7 @@ import org.apache.cassandra.utils.concurrent.AsyncPromise; * * This class is all boiler plate, just ensuring we return ourselves and invoke the correct Promise method. */ -public class AsyncChannelPromise extends AsyncPromise implements ChannelPromise +public class AsyncChannelPromise extends AsyncPromise.WithExecutor implements ChannelPromise { private final Channel channel; diff --git a/src/java/org/apache/cassandra/net/FutureCombiner.java b/src/java/org/apache/cassandra/net/FutureCombiner.java deleted file mode 100644 index 865ca46aa4..0000000000 --- a/src/java/org/apache/cassandra/net/FutureCombiner.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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.net; - -import java.util.Collection; -import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; -import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; - -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.GenericFutureListener; -import io.netty.util.concurrent.GlobalEventExecutor; -import io.netty.util.concurrent.Promise; -import org.apache.cassandra.utils.concurrent.AsyncPromise; - -/** - * Netty's PromiseCombiner is not threadsafe, and we combine futures from multiple event executors. - * - * This class groups a number of Future into a single logical Future, by registering a listener to each that - * decrements a shared counter; if any of them fail, the FutureCombiner is completed with the first cause, - * but in all scenario only completes when all underlying future have completed (exceptionally or otherwise) - * - * This Future is always uncancellable. - * - * We extend FutureDelegate, and simply provide it an uncancellable Promise that will be completed by the listeners - * registered to the input futures. - */ -class FutureCombiner extends FutureDelegate -{ - private volatile boolean failed; - - private volatile Throwable firstCause; - private static final AtomicReferenceFieldUpdater firstCauseUpdater = - AtomicReferenceFieldUpdater.newUpdater(FutureCombiner.class, Throwable.class, "firstCause"); - - private volatile int waitingOn; - private static final AtomicIntegerFieldUpdater waitingOnUpdater = - AtomicIntegerFieldUpdater.newUpdater(FutureCombiner.class, "waitingOn"); - - FutureCombiner(Collection> combine) - { - this(AsyncPromise.uncancellable(GlobalEventExecutor.INSTANCE), combine); - } - - private FutureCombiner(Promise combined, Collection> combine) - { - super(combined); - - if (0 == (waitingOn = combine.size())) - combined.trySuccess(null); - - GenericFutureListener> listener = result -> - { - if (!result.isSuccess()) - { - firstCauseUpdater.compareAndSet(this, null, result.cause()); - failed = true; - } - - if (0 == waitingOnUpdater.decrementAndGet(this)) - { - if (failed) - combined.tryFailure(firstCause); - else - combined.trySuccess(null); - } - }; - - for (Future future : combine) - future.addListener(listener); - } -} diff --git a/src/java/org/apache/cassandra/net/FutureDelegate.java b/src/java/org/apache/cassandra/net/FutureDelegate.java index f04a43275f..7e6f9c6941 100644 --- a/src/java/org/apache/cassandra/net/FutureDelegate.java +++ b/src/java/org/apache/cassandra/net/FutureDelegate.java @@ -29,7 +29,7 @@ import io.netty.util.concurrent.GenericFutureListener; * * See {@link FutureCombiner} and {@link FutureResult} */ -class FutureDelegate implements Future +public class FutureDelegate implements Future { final Future delegate; @@ -53,26 +53,6 @@ class FutureDelegate implements Future return delegate.cause(); } - public Future addListener(GenericFutureListener> genericFutureListener) - { - return delegate.addListener(genericFutureListener); - } - - public Future addListeners(GenericFutureListener>... genericFutureListeners) - { - return delegate.addListeners(genericFutureListeners); - } - - public Future removeListener(GenericFutureListener> genericFutureListener) - { - return delegate.removeListener(genericFutureListener); - } - - public Future removeListeners(GenericFutureListener>... genericFutureListeners) - { - return delegate.removeListeners(genericFutureListeners); - } - public Future sync() throws InterruptedException { return delegate.sync(); @@ -142,4 +122,28 @@ class FutureDelegate implements Future { return delegate.get(timeout, unit); } + + @Override + public io.netty.util.concurrent.Future addListener(GenericFutureListener> genericFutureListener) + { + return delegate.addListener(genericFutureListener); + } + + @Override + public io.netty.util.concurrent.Future addListeners(GenericFutureListener>... genericFutureListeners) + { + return delegate.addListeners(genericFutureListeners); + } + + @Override + public io.netty.util.concurrent.Future removeListener(GenericFutureListener> genericFutureListener) + { + return delegate.removeListener(genericFutureListener); + } + + @Override + public io.netty.util.concurrent.Future removeListeners(GenericFutureListener>... genericFutureListeners) + { + return delegate.removeListeners(genericFutureListeners); + } } diff --git a/src/java/org/apache/cassandra/net/FutureResult.java b/src/java/org/apache/cassandra/net/FutureResult.java index 8d43dbe39a..8ad2e7b93e 100644 --- a/src/java/org/apache/cassandra/net/FutureResult.java +++ b/src/java/org/apache/cassandra/net/FutureResult.java @@ -21,10 +21,9 @@ import io.netty.util.concurrent.Future; /** * An abstraction for yielding a result performed by an asynchronous task, - * for whom we may wish to offer cancellation, - * but no other access to the underlying task + * for whom we may wish to offer cancellation, but no other access to the underlying task */ -class FutureResult extends FutureDelegate +public class FutureResult extends FutureDelegate { private final Future tryCancel; @@ -32,7 +31,7 @@ class FutureResult extends FutureDelegate * @param result the Future that will be completed by {@link #cancel} * @param cancel the Future that is performing the work, and to whom any cancellation attempts will be proxied */ - FutureResult(Future result, Future cancel) + public FutureResult(Future result, Future cancel) { super(result); this.tryCancel = cancel; diff --git a/src/java/org/apache/cassandra/net/InboundMessageHandler.java b/src/java/org/apache/cassandra/net/InboundMessageHandler.java index f29b3ec947..c1b51be80b 100644 --- a/src/java/org/apache/cassandra/net/InboundMessageHandler.java +++ b/src/java/org/apache/cassandra/net/InboundMessageHandler.java @@ -396,7 +396,7 @@ public class InboundMessageHandler extends AbstractMessageHandler if (state != null) state.trace("{} message received from {}", header.verb, header.from); callbacks.onDispatched(task.size(), header); - header.verb.stage.execute(task, ExecutorLocals.create(state)); + header.verb.stage.execute(ExecutorLocals.create(state), task); } private abstract class ProcessMessage implements Runnable diff --git a/src/java/org/apache/cassandra/net/InboundSockets.java b/src/java/org/apache/cassandra/net/InboundSockets.java index d1a4f7bd5b..d1f76d3ea8 100644 --- a/src/java/org/apache/cassandra/net/InboundSockets.java +++ b/src/java/org/apache/cassandra/net/InboundSockets.java @@ -39,6 +39,7 @@ import org.apache.cassandra.concurrent.NamedThreadFactory; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.concurrent.AsyncPromise; +import org.apache.cassandra.utils.concurrent.FutureCombiner; class InboundSockets { @@ -126,7 +127,7 @@ class InboundSockets if (listen != null) closing.add(listen.close()); closing.add(connections.close()); - new FutureCombiner(closing) + FutureCombiner.nettySuccessListener(closing) .addListener(future -> { executor.shutdownGracefully(); shutdownExecutors.accept(executor); @@ -225,7 +226,7 @@ class InboundSockets for (InboundSocket socket : sockets) opening.add(socket.open(pipelineInjector)); - return new FutureCombiner(opening); + return FutureCombiner.nettySuccessListener(opening); } public Future open() @@ -233,7 +234,7 @@ class InboundSockets List> opening = new ArrayList<>(); for (InboundSocket socket : sockets) opening.add(socket.open()); - return new FutureCombiner(opening); + return FutureCombiner.nettySuccessListener(opening); } public boolean isListening() @@ -249,7 +250,7 @@ class InboundSockets List> closing = new ArrayList<>(); for (InboundSocket address : sockets) closing.add(address.close(shutdownExecutors)); - return new FutureCombiner(closing); + return FutureCombiner.nettySuccessListener(closing); } public Future close() { diff --git a/src/java/org/apache/cassandra/net/MessagingService.java b/src/java/org/apache/cassandra/net/MessagingService.java index 747d740d4b..4359094f10 100644 --- a/src/java/org/apache/cassandra/net/MessagingService.java +++ b/src/java/org/apache/cassandra/net/MessagingService.java @@ -26,6 +26,7 @@ import java.util.concurrent.TimeoutException; import com.google.common.annotations.VisibleForTesting; +import org.apache.cassandra.utils.concurrent.FutureCombiner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -451,7 +452,7 @@ public final class MessagingService extends MessagingServiceMBeanImpl closing.add(pool.close(true)); long deadline = nanoTime() + units.toNanos(timeout); - maybeFail(() -> new FutureCombiner(closing).get(timeout, units), + maybeFail(() -> FutureCombiner.nettySuccessListener(closing).get(timeout, units), () -> { List inboundExecutors = new ArrayList<>(); inboundSockets.close(synchronizedList(inboundExecutors)::add).get(); @@ -475,7 +476,7 @@ public final class MessagingService extends MessagingServiceMBeanImpl closing.add(pool.close(false)); long deadline = nanoTime() + units.toNanos(timeout); - maybeFail(() -> new FutureCombiner(closing).get(timeout, units), + maybeFail(() -> FutureCombiner.nettySuccessListener(closing).get(timeout, units), () -> { if (shutdownExecutors) shutdownExecutors(deadline); diff --git a/src/java/org/apache/cassandra/net/OutboundConnection.java b/src/java/org/apache/cassandra/net/OutboundConnection.java index 017916616e..ca66ed04a6 100644 --- a/src/java/org/apache/cassandra/net/OutboundConnection.java +++ b/src/java/org/apache/cassandra/net/OutboundConnection.java @@ -1100,7 +1100,7 @@ public class OutboundConnection if (hasPending()) { - Promise> result = new AsyncPromise<>(eventLoop); + Promise> result = AsyncPromise.withExecutor(eventLoop); state = new Connecting(state.disconnected(), result, eventLoop.schedule(() -> attempt(result), max(100, retryRateMillis), MILLISECONDS)); retryRateMillis = min(1000, retryRateMillis * 2); } @@ -1229,7 +1229,7 @@ public class OutboundConnection Future> initiate() { - Promise> result = new AsyncPromise<>(eventLoop); + Promise> result = AsyncPromise.withExecutor(eventLoop); state = new Connecting(state.disconnected(), result); attempt(result); return result; diff --git a/src/java/org/apache/cassandra/net/OutboundConnectionInitiator.java b/src/java/org/apache/cassandra/net/OutboundConnectionInitiator.java index 71eb132381..dad85262db 100644 --- a/src/java/org/apache/cassandra/net/OutboundConnectionInitiator.java +++ b/src/java/org/apache/cassandra/net/OutboundConnectionInitiator.java @@ -24,7 +24,10 @@ import java.nio.channels.ClosedChannelException; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.Promise; import org.apache.cassandra.utils.concurrent.AsyncPromise; +import org.apache.cassandra.utils.concurrent.ImmediateFuture; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,9 +49,6 @@ import io.netty.handler.logging.LoggingHandler; import io.netty.handler.ssl.SslClosedEngineException; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslHandler; -import io.netty.util.concurrent.FailedFuture; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.Promise; import io.netty.util.concurrent.ScheduledFuture; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.net.OutboundConnectionInitiator.Result.MessagingSuccess; @@ -108,7 +108,7 @@ public class OutboundConnectionInitiator> initiateStreaming(EventLoop eventLoop, OutboundConnectionSettings settings, int requestMessagingVersion) { - return new OutboundConnectionInitiator(STREAMING, settings, requestMessagingVersion, new AsyncPromise<>(eventLoop)) + return new OutboundConnectionInitiator(STREAMING, settings, requestMessagingVersion, AsyncPromise.withExecutor(eventLoop)) .initiate(eventLoop); } @@ -134,13 +134,13 @@ public class OutboundConnectionInitiator(eventLoop, new IOException("authentication failed to " + settings.connectToId())); + return ImmediateFuture.failure(new IOException("authentication failed to " + settings.connectToId())); } // this is a bit ugly, but is the easiest way to ensure that if we timeout we can propagate a suitable error message // and still guarantee that, if on timing out we raced with success, the successfully created channel is handled AtomicBoolean timedout = new AtomicBoolean(); - Future bootstrap = createBootstrap(eventLoop) + io.netty.util.concurrent.Future bootstrap = createBootstrap(eventLoop) .connect() .addListener(future -> { eventLoop.execute(() -> { diff --git a/src/java/org/apache/cassandra/net/OutboundConnections.java b/src/java/org/apache/cassandra/net/OutboundConnections.java index 68157c3797..9808e3f926 100644 --- a/src/java/org/apache/cassandra/net/OutboundConnections.java +++ b/src/java/org/apache/cassandra/net/OutboundConnections.java @@ -27,6 +27,7 @@ import java.util.function.Function; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; +import org.apache.cassandra.utils.concurrent.FutureCombiner; import org.apache.cassandra.utils.concurrent.Condition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -125,7 +126,7 @@ public class OutboundConnections synchronized Future reconnectWithNewIp(InetAddressAndPort addr) { template = template.withConnectTo(addr); - return new FutureCombiner( + return FutureCombiner.nettySuccessListener( apply(c -> c.reconnectWith(template)) ); } @@ -139,7 +140,7 @@ public class OutboundConnections { // immediately release our metrics, so that if we need to re-open immediately we can safely register a new one releaseMetrics(); - return new FutureCombiner( + return FutureCombiner.nettySuccessListener( apply(c -> c.scheduleClose(time, unit, flushQueues)) ); } @@ -153,7 +154,7 @@ public class OutboundConnections { // immediately release our metrics, so that if we need to re-open immediately we can safely register a new one releaseMetrics(); - return new FutureCombiner( + return FutureCombiner.nettySuccessListener( apply(c -> c.close(flushQueues)) ); } diff --git a/src/java/org/apache/cassandra/net/RequestCallbacks.java b/src/java/org/apache/cassandra/net/RequestCallbacks.java index 9adec9b0c0..8cec096dde 100644 --- a/src/java/org/apache/cassandra/net/RequestCallbacks.java +++ b/src/java/org/apache/cassandra/net/RequestCallbacks.java @@ -20,17 +20,16 @@ package org.apache.cassandra.net; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeoutException; import javax.annotation.Nullable; import com.google.common.annotations.VisibleForTesting; +import org.apache.cassandra.concurrent.ScheduledExecutorPlus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.cassandra.concurrent.DebuggableScheduledThreadPoolExecutor; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.db.Mutation; @@ -47,6 +46,7 @@ import org.apache.cassandra.utils.FBUtilities; import static java.lang.String.format; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.concurrent.Stage.INTERNAL_RESPONSE; import static org.apache.cassandra.utils.Clock.Global.nanoTime; import static org.apache.cassandra.utils.MonotonicClock.preciseTime; @@ -65,7 +65,7 @@ public class RequestCallbacks implements OutboundMessageCallbacks private static final Logger logger = LoggerFactory.getLogger(RequestCallbacks.class); private final MessagingService messagingService; - private final ScheduledExecutorService executor = new DebuggableScheduledThreadPoolExecutor("Callback-Map-Reaper"); + private final ScheduledExecutorPlus executor = executorFactory().scheduled("Callback-Map-Reaper"); private final ConcurrentMap callbacks = new ConcurrentHashMap<>(); RequestCallbacks(MessagingService messagingService) diff --git a/src/java/org/apache/cassandra/net/SocketFactory.java b/src/java/org/apache/cassandra/net/SocketFactory.java index 8300c2a052..f97e2fed08 100644 --- a/src/java/org/apache/cassandra/net/SocketFactory.java +++ b/src/java/org/apache/cassandra/net/SocketFactory.java @@ -24,7 +24,6 @@ import java.nio.channels.ClosedChannelException; import java.nio.channels.spi.SelectorProvider; import java.util.List; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeoutException; import javax.annotation.Nullable; @@ -72,6 +71,7 @@ import org.apache.cassandra.utils.FBUtilities; import static io.netty.channel.unix.Errors.ERRNO_ECONNRESET_NEGATIVE; import static io.netty.channel.unix.Errors.ERROR_ECONNREFUSED_NEGATIVE; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.utils.Throwables.isCausedBy; /** @@ -177,7 +177,7 @@ public final class SocketFactory private final EventLoopGroup defaultGroup; // we need a separate EventLoopGroup for outbound streaming because sendFile is blocking private final EventLoopGroup outboundStreamingGroup; - final ExecutorService synchronousWorkExecutor = Executors.newCachedThreadPool(new NamedThreadFactory("Messaging-SynchronousWork")); + final ExecutorService synchronousWorkExecutor = executorFactory().pooled("Messaging-SynchronousWork", Integer.MAX_VALUE); SocketFactory() { diff --git a/src/java/org/apache/cassandra/repair/KeyspaceRepairManager.java b/src/java/org/apache/cassandra/repair/KeyspaceRepairManager.java index 0739f1082b..c8131cba1c 100644 --- a/src/java/org/apache/cassandra/repair/KeyspaceRepairManager.java +++ b/src/java/org/apache/cassandra/repair/KeyspaceRepairManager.java @@ -19,14 +19,14 @@ package org.apache.cassandra.repair; import java.util.Collection; +import java.util.List; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.function.BooleanSupplier; -import com.google.common.util.concurrent.ListenableFuture; - import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.locator.RangesAtEndpoint; +import org.apache.cassandra.utils.concurrent.Future; /** * Keyspace level hook for repair. @@ -38,9 +38,9 @@ public interface KeyspaceRepairManager * been notified that the repair session has been completed, the data associated with the given session id must * not be combined with repaired or unrepaired data, or data from other repair sessions. */ - ListenableFuture prepareIncrementalRepair(UUID sessionID, - Collection tables, - RangesAtEndpoint tokenRanges, - ExecutorService executor, - BooleanSupplier isCancelled); + Future> prepareIncrementalRepair(UUID sessionID, + Collection tables, + RangesAtEndpoint tokenRanges, + ExecutorService executor, + BooleanSupplier isCancelled); } diff --git a/src/java/org/apache/cassandra/repair/RepairJob.java b/src/java/org/apache/cassandra/repair/RepairJob.java index 7747e18ba7..1e203c41a9 100644 --- a/src/java/org/apache/cassandra/repair/RepairJob.java +++ b/src/java/org/apache/cassandra/repair/RepairJob.java @@ -26,6 +26,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.*; +import org.apache.cassandra.concurrent.ExecutorPlus; import org.apache.cassandra.utils.concurrent.AsyncFuture; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,6 +47,9 @@ import org.apache.cassandra.tracing.Tracing; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.MerkleTrees; import org.apache.cassandra.utils.Pair; +import org.apache.cassandra.utils.concurrent.Future; +import org.apache.cassandra.utils.concurrent.FutureCombiner; +import org.apache.cassandra.utils.concurrent.ImmediateFuture; import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis; @@ -59,8 +63,8 @@ public class RepairJob extends AsyncFuture implements Runnable private final RepairSession session; private final RepairJobDesc desc; private final RepairParallelism parallelismDegree; - private final ListeningExecutorService taskExecutor; - + private final ExecutorPlus taskExecutor; + @VisibleForTesting final List validationTasks = new ArrayList<>(); @@ -106,39 +110,35 @@ public class RepairJob extends AsyncFuture implements Runnable List allEndpoints = new ArrayList<>(session.commonRange.endpoints); allEndpoints.add(FBUtilities.getBroadcastAddressAndPort()); - ListenableFuture> treeResponses; + Future> treeResponses; // Create a snapshot at all nodes unless we're using pure parallel repairs if (parallelismDegree != RepairParallelism.PARALLEL) { - ListenableFuture> allSnapshotTasks; + Future> allSnapshotTasks; if (session.isIncremental) { // consistent repair does it's own "snapshotting" - allSnapshotTasks = Futures.immediateFuture(allEndpoints); + allSnapshotTasks = ImmediateFuture.success(allEndpoints); } else { // Request snapshot to all replica - List> snapshotTasks = new ArrayList<>(allEndpoints.size()); + List> snapshotTasks = new ArrayList<>(allEndpoints.size()); for (InetAddressAndPort endpoint : allEndpoints) { SnapshotTask snapshotTask = new SnapshotTask(desc, endpoint); snapshotTasks.add(snapshotTask); taskExecutor.execute(snapshotTask); } - allSnapshotTasks = Futures.allAsList(snapshotTasks); + allSnapshotTasks = FutureCombiner.allOf(snapshotTasks); } // When all snapshot complete, send validation requests - treeResponses = Futures.transformAsync(allSnapshotTasks, new AsyncFunction, List>() - { - public ListenableFuture> apply(List endpoints) - { - if (parallelismDegree == RepairParallelism.SEQUENTIAL) - return sendSequentialValidationRequest(endpoints); - else - return sendDCAwareValidationRequest(endpoints); - } + treeResponses = allSnapshotTasks.andThenAsync(endpoints -> { + if (parallelismDegree == RepairParallelism.SEQUENTIAL) + return sendSequentialValidationRequest(endpoints); + else + return sendDCAwareValidationRequest(endpoints); }, taskExecutor); } else @@ -148,12 +148,10 @@ public class RepairJob extends AsyncFuture implements Runnable } // When all validations complete, submit sync tasks - ListenableFuture> syncResults = Futures.transformAsync(treeResponses, - session.optimiseStreams && !session.pullRepair ? this::optimisedSyncing : this::standardSyncing, - taskExecutor); + Future> syncResults = treeResponses.andThenAsync(session.optimiseStreams && !session.pullRepair ? this::optimisedSyncing : this::standardSyncing, taskExecutor); // When all sync complete, set the final result - Futures.addCallback(syncResults, new FutureCallback>() + syncResults.addCallback(new FutureCallback>() { public void onSuccess(List stats) { @@ -180,7 +178,9 @@ public class RepairJob extends AsyncFuture implements Runnable SystemDistributedKeyspace.failedRepairJob(session.getId(), desc.keyspace, desc.columnFamily, t); } cfs.metric.repairsCompleted.inc(); - tryFailure(t); + tryFailure(t instanceof NoSuchRepairSessionExceptionWrapper + ? ((NoSuchRepairSessionExceptionWrapper) t).wrapped + : t); } }, taskExecutor); } @@ -190,7 +190,7 @@ public class RepairJob extends AsyncFuture implements Runnable return session.commonRange.transEndpoints.contains(ep); } - private ListenableFuture> standardSyncing(List trees) throws NoSuchRepairSessionException + private Future> standardSyncing(List trees) { List syncTasks = createStandardSyncTasks(desc, trees, @@ -269,7 +269,7 @@ public class RepairJob extends AsyncFuture implements Runnable return syncTasks; } - private ListenableFuture> optimisedSyncing(List trees) throws NoSuchRepairSessionException + private Future> optimisedSyncing(List trees) { List syncTasks = createOptimisedSyncingSyncTasks(desc, trees, @@ -284,17 +284,39 @@ public class RepairJob extends AsyncFuture implements Runnable @SuppressWarnings("UnstableApiUsage") @VisibleForTesting - ListenableFuture> executeTasks(List syncTasks) throws NoSuchRepairSessionException + Future> executeTasks(List syncTasks) { - ActiveRepairService.instance.getParentRepairSession(desc.parentSessionId); - for (SyncTask task : syncTasks) + try { - if (!task.isLocal()) - session.trackSyncCompletion(Pair.create(desc, task.nodePair()), (CompletableRemoteSyncTask) task); - taskExecutor.submit(task); - } + ActiveRepairService.instance.getParentRepairSession(desc.parentSessionId); + for (SyncTask task : syncTasks) + { + if (!task.isLocal()) + session.trackSyncCompletion(Pair.create(desc, task.nodePair()), (CompletableRemoteSyncTask) task); + taskExecutor.execute(task); + } - return Futures.allAsList(syncTasks); + return FutureCombiner.allOf(syncTasks); + } + catch (NoSuchRepairSessionException e) + { + throw new NoSuchRepairSessionExceptionWrapper(e); + } + } + + // provided so we can throw NoSuchRepairSessionException from executeTasks without + // having to make it unchecked. Required as this is called as from standardSyncing/ + // optimisedSyncing passed as a Function to transform merkle tree responses and so + // can't throw checked exceptions. These are unwrapped in the onFailure callback of + // that transformation so as to not pollute the checked usage of + // NoSuchRepairSessionException in the rest of the codebase. + private static class NoSuchRepairSessionExceptionWrapper extends RuntimeException + { + private final NoSuchRepairSessionException wrapped; + private NoSuchRepairSessionExceptionWrapper(NoSuchRepairSessionException wrapped) + { + this.wrapped = wrapped; + } } static List createOptimisedSyncingSyncTasks(RepairJobDesc desc, @@ -372,13 +394,13 @@ public class RepairJob extends AsyncFuture implements Runnable * @param endpoints Endpoint addresses to send validation request * @return Future that can get all {@link TreeResponse} from replica, if all validation succeed. */ - private ListenableFuture> sendValidationRequest(Collection endpoints) + private Future> sendValidationRequest(Collection endpoints) { String message = String.format("Requesting merkle trees for %s (to %s)", desc.columnFamily, endpoints); logger.info("{} {}", session.previewKind.logPrefix(desc.sessionId), message); Tracing.traceRepair(message); int nowInSec = getNowInSeconds(); - List> tasks = new ArrayList<>(endpoints.size()); + List> tasks = new ArrayList<>(endpoints.size()); for (InetAddressAndPort endpoint : endpoints) { ValidationTask task = newValidationTask(endpoint, nowInSec); @@ -386,19 +408,19 @@ public class RepairJob extends AsyncFuture implements Runnable session.trackValidationCompletion(Pair.create(desc, endpoint), task); taskExecutor.execute(task); } - return Futures.allAsList(tasks); + return FutureCombiner.allOf(tasks); } /** * Creates {@link ValidationTask} and submit them to task executor so that tasks run sequentially. */ - private ListenableFuture> sendSequentialValidationRequest(Collection endpoints) + private Future> sendSequentialValidationRequest(Collection endpoints) { String message = String.format("Requesting merkle trees for %s (to %s)", desc.columnFamily, endpoints); logger.info("{} {}", session.previewKind.logPrefix(desc.sessionId), message); Tracing.traceRepair(message); int nowInSec = getNowInSeconds(); - List> tasks = new ArrayList<>(endpoints.size()); + List> tasks = new ArrayList<>(endpoints.size()); Queue requests = new LinkedList<>(endpoints); InetAddressAndPort address = requests.poll(); @@ -412,7 +434,7 @@ public class RepairJob extends AsyncFuture implements Runnable final InetAddressAndPort nextAddress = requests.poll(); final ValidationTask nextTask = newValidationTask(nextAddress, nowInSec); tasks.add(nextTask); - Futures.addCallback(currentTask, new FutureCallback() + currentTask.addCallback(new FutureCallback() { public void onSuccess(TreeResponse result) { @@ -423,24 +445,24 @@ public class RepairJob extends AsyncFuture implements Runnable // failure is handled at root of job chain public void onFailure(Throwable t) {} - }, MoreExecutors.directExecutor()); + }); currentTask = nextTask; } // start running tasks taskExecutor.execute(firstTask); - return Futures.allAsList(tasks); + return FutureCombiner.allOf(tasks); } /** * Creates {@link ValidationTask} and submit them to task executor so that tasks run sequentially within each dc. */ - private ListenableFuture> sendDCAwareValidationRequest(Collection endpoints) + private Future> sendDCAwareValidationRequest(Collection endpoints) { String message = String.format("Requesting merkle trees for %s (to %s)", desc.columnFamily, endpoints); logger.info("{} {}", session.previewKind.logPrefix(desc.sessionId), message); Tracing.traceRepair(message); int nowInSec = getNowInSeconds(); - List> tasks = new ArrayList<>(endpoints.size()); + List> tasks = new ArrayList<>(endpoints.size()); Map> requestsByDatacenter = new HashMap<>(); for (InetAddressAndPort endpoint : endpoints) @@ -469,7 +491,7 @@ public class RepairJob extends AsyncFuture implements Runnable final InetAddressAndPort nextAddress = requests.poll(); final ValidationTask nextTask = newValidationTask(nextAddress, nowInSec); tasks.add(nextTask); - Futures.addCallback(currentTask, new FutureCallback() + currentTask.addCallback(new FutureCallback() { public void onSuccess(TreeResponse result) { @@ -480,13 +502,13 @@ public class RepairJob extends AsyncFuture implements Runnable // failure is handled at root of job chain public void onFailure(Throwable t) {} - }, MoreExecutors.directExecutor()); + }); currentTask = nextTask; } // start running tasks taskExecutor.execute(firstTask); } - return Futures.allAsList(tasks); + return FutureCombiner.allOf(tasks); } private ValidationTask newValidationTask(InetAddressAndPort endpoint, int nowInSec) diff --git a/src/java/org/apache/cassandra/repair/RepairRunnable.java b/src/java/org/apache/cassandra/repair/RepairRunnable.java index 3a9de96da0..faa0a749d3 100644 --- a/src/java/org/apache/cassandra/repair/RepairRunnable.java +++ b/src/java/org/apache/cassandra/repair/RepairRunnable.java @@ -19,14 +19,7 @@ package org.apache.cassandra.repair; import java.io.IOException; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -34,25 +27,14 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import com.google.common.util.concurrent.AsyncFunction; +import com.google.common.collect.*; import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListeningExecutorService; -import com.google.common.util.concurrent.MoreExecutors; import org.apache.commons.lang3.time.DurationFormatUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.codahale.metrics.Timer; -import org.apache.cassandra.concurrent.JMXEnabledThreadPoolExecutor; -import org.apache.cassandra.concurrent.NamedThreadFactory; +import org.apache.cassandra.concurrent.ExecutorPlus; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.exceptions.RepairException; import org.apache.cassandra.metrics.RepairMetrics; @@ -65,20 +47,21 @@ import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.cql3.statements.SelectStatement; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.ConsistencyLevel; +import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.dht.Range; import org.apache.cassandra.dht.Token; +import org.apache.cassandra.gms.FailureDetector; import org.apache.cassandra.locator.EndpointsForRange; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.Replica; +import org.apache.cassandra.metrics.RepairMetrics; import org.apache.cassandra.metrics.StorageMetrics; import org.apache.cassandra.repair.consistent.CoordinatorSession; +import org.apache.cassandra.repair.consistent.SyncStatSummary; import org.apache.cassandra.repair.messages.RepairOption; import org.apache.cassandra.schema.SchemaConstants; -import org.apache.cassandra.service.ActiveRepairService; +import org.apache.cassandra.service.*; import org.apache.cassandra.service.ActiveRepairService.ParentRepairStatus; -import org.apache.cassandra.service.ClientState; -import org.apache.cassandra.service.QueryState; -import org.apache.cassandra.service.StorageService; import org.apache.cassandra.streaming.PreviewKind; import org.apache.cassandra.tracing.TraceKeyspace; import org.apache.cassandra.tracing.TraceState; @@ -98,8 +81,11 @@ import org.apache.cassandra.utils.progress.ProgressListener; import static org.apache.cassandra.service.QueryState.forInternalCalls; import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis; import static org.apache.cassandra.utils.Clock.Global.nanoTime; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.apache.cassandra.utils.concurrent.BlockingQueues.newBlockingQueue; +import org.apache.cassandra.utils.concurrent.Future; +import org.apache.cassandra.utils.concurrent.FutureCombiner; +import org.apache.cassandra.utils.concurrent.ImmediateFuture; + +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; public class RepairRunnable implements Runnable, ProgressEventNotifier { @@ -238,7 +224,7 @@ public class RepairRunnable implements Runnable, ProgressEventNotifier { for (ProgressListener listener : listeners) localState.removeProgressListener(listener); - // Because DebuggableThreadPoolExecutor#afterExecute and this callback + // Because ExecutorPlus#afterExecute and this callback // run in a nondeterministic order (within the same thread), the // TraceState may have been nulled out at this point. The TraceState // should be traceState, so just set it without bothering to check if it @@ -316,7 +302,6 @@ public class RepairRunnable implements Runnable, ProgressEventNotifier traceState.addProgressListener(listener); Thread queryThread = createQueryThread(sessionId); queryThread.setName("RepairTracePolling"); - queryThread.start(); return traceState; } @@ -448,48 +433,40 @@ public class RepairRunnable implements Runnable, ProgressEventNotifier { // Set up RepairJob executor for this repair command. - ListeningExecutorService executor = createExecutor(); + ExecutorPlus executor = createExecutor(); // Setting the repairedAt time to UNREPAIRED_SSTABLE causes the repairedAt times to be preserved across streamed sstables - final ListenableFuture> allSessions = submitRepairSessions(parentSession, false, executor, commonRanges, cfnames); + final Future> allSessions = submitRepairSessions(parentSession, false, executor, commonRanges, cfnames); // After all repair sessions completes(successful or not), // run anticompaction if necessary and send finish notice back to client final Collection> successfulRanges = new ArrayList<>(); final AtomicBoolean hasFailure = new AtomicBoolean(); - ListenableFuture repairResult = Futures.transformAsync(allSessions, new AsyncFunction, Object>() - { - @SuppressWarnings("unchecked") - public ListenableFuture apply(List results) + allSessions.andThenAsync(results -> { + logger.debug("Repair result: {}", results); + // filter out null(=failed) results and get successful ranges + for (RepairSessionResult sessionResult : results) { - logger.debug("Repair result: {}", results); - // filter out null(=failed) results and get successful ranges - for (RepairSessionResult sessionResult : results) + if (sessionResult != null) { - if (sessionResult != null) + // don't record successful repair if we had to skip ranges + if (!sessionResult.skippedReplicas) { - // don't record successful repair if we had to skip ranges - if (!sessionResult.skippedReplicas) - { - successfulRanges.addAll(sessionResult.ranges); - } - } - else - { - hasFailure.compareAndSet(false, true); + successfulRanges.addAll(sessionResult.ranges); } } - return Futures.immediateFuture(null); + else + { + hasFailure.compareAndSet(false, true); + } } - }, MoreExecutors.directExecutor()); - Futures.addCallback(repairResult, - new RepairCompleteCallback(parentSession, - successfulRanges, - preparedEndpoints, - traceState, - hasFailure, - executor), - MoreExecutors.directExecutor()); + return ImmediateFuture.success(null); + }).addCallback(new RepairCompleteCallback(parentSession, + successfulRanges, + preparedEndpoints, + traceState, + hasFailure, + executor)); } private void incrementalRepair(UUID parentSession, @@ -517,18 +494,16 @@ public class RepairRunnable implements Runnable, ProgressEventNotifier fail(e.getMessage()); return; } - ListeningExecutorService executor = createExecutor(); + ExecutorPlus executor = createExecutor(); AtomicBoolean hasFailure = new AtomicBoolean(false); - ListenableFuture repairResult = coordinatorSession.execute(() -> submitRepairSessions(parentSession, true, executor, allRanges, cfnames), + Future repairResult = coordinatorSession.execute(() -> submitRepairSessions(parentSession, true, executor, allRanges, cfnames), hasFailure); Collection> ranges = new HashSet<>(); for (Collection> range : Iterables.transform(allRanges, cr -> cr.ranges)) { ranges.addAll(range); } - Futures.addCallback(repairResult, - new RepairCompleteCallback(parentSession, ranges, preparedEndpoints, traceState, hasFailure, executor), - MoreExecutors.directExecutor()); + repairResult.addCallback(new RepairCompleteCallback(parentSession, ranges, preparedEndpoints, traceState, hasFailure, executor)); } private void previewRepair(UUID parentSession, @@ -539,11 +514,11 @@ public class RepairRunnable implements Runnable, ProgressEventNotifier logger.debug("Starting preview repair for {}", parentSession); // Set up RepairJob executor for this repair command. - ListeningExecutorService executor = createExecutor(); + ExecutorPlus executor = createExecutor(); - final ListenableFuture> allSessions = submitRepairSessions(parentSession, false, executor, commonRanges, cfnames); + final Future> allSessions = submitRepairSessions(parentSession, false, executor, commonRanges, cfnames); - Futures.addCallback(allSessions, new FutureCallback>() + allSessions.addCallback(new FutureCallback>() { public void onSuccess(List results) { @@ -594,7 +569,7 @@ public class RepairRunnable implements Runnable, ProgressEventNotifier fail("Error completing preview repair: " + t.getMessage()); executor.shutdownNow(); } - }, MoreExecutors.directExecutor()); + }); } private void maybeSnapshotReplicas(UUID parentSession, String keyspace, List results) @@ -653,13 +628,13 @@ public class RepairRunnable implements Runnable, ProgressEventNotifier return iter; } - private ListenableFuture> submitRepairSessions(UUID parentSession, - boolean isIncremental, - ListeningExecutorService executor, - List commonRanges, - String... cfnames) + private Future> submitRepairSessions(UUID parentSession, + boolean isIncremental, + ExecutorPlus executor, + List commonRanges, + String... cfnames) { - List> futures = new ArrayList<>(options.getRanges().size()); + List> futures = new ArrayList<>(options.getRanges().size()); for (CommonRange commonRange : commonRanges) { @@ -676,20 +651,18 @@ public class RepairRunnable implements Runnable, ProgressEventNotifier cfnames); if (session == null) continue; - Futures.addCallback(session, new RepairSessionCallback(session), MoreExecutors.directExecutor()); + session.addCallback(new RepairSessionCallback(session)); futures.add(session); } - return Futures.successfulAsList(futures); + return FutureCombiner.successfulOf(futures); } - private ListeningExecutorService createExecutor() + private ExecutorPlus createExecutor() { - return MoreExecutors.listeningDecorator(new JMXEnabledThreadPoolExecutor(options.getJobThreads(), - Integer.MAX_VALUE, - SECONDS, - newBlockingQueue(), - new NamedThreadFactory("Repair#" + cmd), - "internal")); + return executorFactory() + .localAware() + .withJmxInternal() + .pooled("Repair#" + cmd, options.getJobThreads()); } private class RepairSessionCallback implements FutureCallback @@ -788,7 +761,7 @@ public class RepairRunnable implements Runnable, ProgressEventNotifier private Thread createQueryThread(final UUID sessionId) { - return NamedThreadFactory.createThread(new WrappedRunnable() + return executorFactory().startThread("Repair-Runnable-" + threadCounter.incrementAndGet(), new WrappedRunnable() { // Query events within a time interval that overlaps the last by one second. Ignore duplicates. Ignore local traces. // Wake up upon local trace activity. Query when notified of trace activity with a timeout that doubles every two timeouts. @@ -858,7 +831,7 @@ public class RepairRunnable implements Runnable, ProgressEventNotifier seen[si].clear(); } } - }, "Repair-Runnable-" + threadCounter.incrementAndGet()); + }); } private static final class SkipRepairException extends RuntimeException diff --git a/src/java/org/apache/cassandra/repair/RepairSession.java b/src/java/org/apache/cassandra/repair/RepairSession.java index f44ab35b38..1c7d6c9c86 100644 --- a/src/java/org/apache/cassandra/repair/RepairSession.java +++ b/src/java/org/apache/cassandra/repair/RepairSession.java @@ -33,11 +33,12 @@ import java.util.concurrent.atomic.AtomicBoolean; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import com.google.common.util.concurrent.*; +import org.apache.cassandra.concurrent.ExecutorFactory; +import org.apache.cassandra.concurrent.ExecutorPlus; import org.apache.cassandra.utils.concurrent.AsyncFuture; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.cassandra.concurrent.DebuggableThreadPoolExecutor; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.Keyspace; @@ -57,6 +58,7 @@ import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.MerkleTrees; import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.Throwables; +import org.apache.cassandra.utils.concurrent.Future; /** * Coordinates the (active) repair of a list of non overlapping token ranges. @@ -120,7 +122,7 @@ public class RepairSession extends AsyncFuture implements I private final ConcurrentMap, CompletableRemoteSyncTask> syncingTasks = new ConcurrentHashMap<>(); // Tasks(snapshot, validate request, differencing, ...) are run on taskExecutor - public final ListeningExecutorService taskExecutor; + public final ExecutorPlus taskExecutor; public final boolean optimiseStreams; private volatile boolean terminated = false; @@ -158,12 +160,12 @@ public class RepairSession extends AsyncFuture implements I this.previewKind = previewKind; this.pullRepair = pullRepair; this.optimiseStreams = optimiseStreams; - this.taskExecutor = MoreExecutors.listeningDecorator(createExecutor()); + this.taskExecutor = createExecutor(); } - protected DebuggableThreadPoolExecutor createExecutor() + protected ExecutorPlus createExecutor() { - return DebuggableThreadPoolExecutor.createCachedThreadpoolWithMaxSize("RepairJobTask"); + return ExecutorFactory.Global.executorFactory().pooled("RepairJobTask", Integer.MAX_VALUE); } public UUID getId() @@ -262,7 +264,7 @@ public class RepairSession extends AsyncFuture implements I * * @param executor Executor to run validation */ - public void start(ListeningExecutorService executor) + public void start(ExecutorPlus executor) { String message; if (terminated) @@ -306,7 +308,7 @@ public class RepairSession extends AsyncFuture implements I } // Create and submit RepairJob for each ColumnFamily - List> jobs = new ArrayList<>(cfnames.length); + List> jobs = new ArrayList<>(cfnames.length); for (String cfname : cfnames) { RepairJob job = new RepairJob(this, cfname); @@ -315,7 +317,7 @@ public class RepairSession extends AsyncFuture implements I } // When all RepairJobs are done without error, cleanup and set the final result - Futures.addCallback(Futures.allAsList(jobs), new FutureCallback>() + FBUtilities.allOf(jobs).addCallback(new FutureCallback>() { public void onSuccess(List results) { @@ -339,7 +341,7 @@ public class RepairSession extends AsyncFuture implements I Tracing.traceRepair("Session completed with the following error: {}", t); forceShutdown(t); } - }, MoreExecutors.directExecutor()); + }); } public void terminate() diff --git a/src/java/org/apache/cassandra/repair/asymmetric/RangeDenormalizer.java b/src/java/org/apache/cassandra/repair/asymmetric/RangeDenormalizer.java index 2a298710ed..ee69f51715 100644 --- a/src/java/org/apache/cassandra/repair/asymmetric/RangeDenormalizer.java +++ b/src/java/org/apache/cassandra/repair/asymmetric/RangeDenormalizer.java @@ -20,7 +20,6 @@ package org.apache.cassandra.repair.asymmetric; import java.util.Collection; import java.util.HashSet; -import java.util.Iterator; import java.util.Map; import java.util.Set; diff --git a/src/java/org/apache/cassandra/repair/consistent/CoordinatorSession.java b/src/java/org/apache/cassandra/repair/consistent/CoordinatorSession.java index 83d15b8408..1027d0aede 100644 --- a/src/java/org/apache/cassandra/repair/consistent/CoordinatorSession.java +++ b/src/java/org/apache/cassandra/repair/consistent/CoordinatorSession.java @@ -29,12 +29,11 @@ import javax.annotation.Nullable; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.Iterables; -import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.common.util.concurrent.SettableFuture; +import org.apache.cassandra.utils.concurrent.AsyncPromise; +import org.apache.cassandra.utils.concurrent.Future; +import org.apache.cassandra.utils.concurrent.ImmediateFuture; +import org.apache.cassandra.utils.concurrent.Promise; import org.apache.commons.lang3.time.DurationFormatUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -65,8 +64,8 @@ public class CoordinatorSession extends ConsistentSession private static final Logger logger = LoggerFactory.getLogger(CoordinatorSession.class); private final Map participantStates = new HashMap<>(); - private final SettableFuture prepareFuture = SettableFuture.create(); - private final SettableFuture finalizeProposeFuture = SettableFuture.create(); + private final AsyncPromise prepareFuture = AsyncPromise.uncancellable(); + private final AsyncPromise finalizeProposeFuture = AsyncPromise.uncancellable(); private volatile long sessionStart = Long.MIN_VALUE; private volatile long repairStart = Long.MIN_VALUE; @@ -149,7 +148,7 @@ public class CoordinatorSession extends ConsistentSession MessagingService.instance().send(message, destination); } - public ListenableFuture prepare() + public Future prepare() { Preconditions.checkArgument(allStates(State.PREPARING)); @@ -189,12 +188,12 @@ public class CoordinatorSession extends ConsistentSession if (getState() == State.PREPARED) { logger.info("Incremental repair session {} successfully prepared.", sessionID); - prepareFuture.set(true); + prepareFuture.trySuccess(true); } else { fail(); - prepareFuture.set(false); + prepareFuture.trySuccess(false); } } @@ -203,7 +202,7 @@ public class CoordinatorSession extends ConsistentSession setAll(State.REPAIRING); } - public synchronized ListenableFuture finalizePropose() + public synchronized Future finalizePropose() { Preconditions.checkArgument(allStates(State.REPAIRING)); logger.info("Proposing finalization of repair session {}", sessionID); @@ -225,7 +224,7 @@ public class CoordinatorSession extends ConsistentSession { logger.warn("Finalization proposal of session {} rejected by {}. Aborting session", sessionID, participant); fail(); - finalizeProposeFuture.set(false); + finalizeProposeFuture.trySuccess(false); } else { @@ -234,7 +233,7 @@ public class CoordinatorSession extends ConsistentSession if (getState() == State.FINALIZE_PROMISED) { logger.info("Finalization proposal for repair session {} accepted by all participants.", sessionID); - finalizeProposeFuture.set(true); + finalizeProposeFuture.trySuccess(true); } } } @@ -271,8 +270,8 @@ public class CoordinatorSession extends ConsistentSession setAll(State.FAILED); String exceptionMsg = String.format("Incremental repair session %s has failed", sessionID); - finalizeProposeFuture.setException(RepairException.warn(exceptionMsg)); - prepareFuture.setException(RepairException.warn(exceptionMsg)); + finalizeProposeFuture.tryFailure(RepairException.warn(exceptionMsg)); + prepareFuture.tryFailure(RepairException.warn(exceptionMsg)); } private static String formatDuration(long then, long now) @@ -288,63 +287,55 @@ public class CoordinatorSession extends ConsistentSession /** * Runs the asynchronous consistent repair session. Actual repair sessions are scheduled via a submitter to make unit testing easier */ - public ListenableFuture execute(Supplier>> sessionSubmitter, AtomicBoolean hasFailure) + public Future execute(Supplier>> sessionSubmitter, AtomicBoolean hasFailure) { logger.info("Beginning coordination of incremental repair session {}", sessionID); sessionStart = currentTimeMillis(); - ListenableFuture prepareResult = prepare(); + Future prepareResult = prepare(); // run repair sessions normally - ListenableFuture> repairSessionResults = Futures.transformAsync(prepareResult, new AsyncFunction>() + Future> repairSessionResults = prepareResult.andThenAsync(success -> { - public ListenableFuture> apply(Boolean success) throws Exception + if (success) { - if (success) + repairStart = currentTimeMillis(); + if (logger.isDebugEnabled()) { - repairStart = currentTimeMillis(); - if (logger.isDebugEnabled()) - { - logger.debug("Incremental repair {} prepare phase completed in {}", sessionID, formatDuration(sessionStart, repairStart)); - } - setRepairing(); - return sessionSubmitter.get(); + logger.debug("Incremental repair {} prepare phase completed in {}", sessionID, formatDuration(sessionStart, repairStart)); } - else - { - return Futures.immediateFuture(null); - } - + setRepairing(); + return sessionSubmitter.get(); } - }, MoreExecutors.directExecutor()); + else + { + return ImmediateFuture.success(null); + } + }); // mark propose finalization - ListenableFuture proposeFuture = Futures.transformAsync(repairSessionResults, new AsyncFunction, Boolean>() + Future proposeFuture = repairSessionResults.andThenAsync(results -> { - public ListenableFuture apply(List results) throws Exception + if (results == null || results.isEmpty() || Iterables.any(results, r -> r == null)) { - if (results == null || results.isEmpty() || Iterables.any(results, r -> r == null)) + finalizeStart = currentTimeMillis(); + if (logger.isDebugEnabled()) { - finalizeStart = currentTimeMillis(); - if (logger.isDebugEnabled()) - { - logger.debug("Incremental repair {} validation/stream phase completed in {}", sessionID, formatDuration(repairStart, finalizeStart)); - - } - return Futures.immediateFailedFuture(SomeRepairFailedException.INSTANCE); - } - else - { - return finalizePropose(); + logger.debug("Incremental repair {} validation/stream phase completed in {}", sessionID, formatDuration(repairStart, finalizeStart)); } + return ImmediateFuture.failure(SomeRepairFailedException.INSTANCE); } - }, MoreExecutors.directExecutor()); + else + { + return finalizePropose(); + } + }); // return execution result as set by following callback - SettableFuture resultFuture = SettableFuture.create(); + Promise resultFuture = AsyncPromise.uncancellable(); // commit repaired data - Futures.addCallback(proposeFuture, new FutureCallback() + proposeFuture.addCallback(new FutureCallback() { public void onSuccess(@Nullable Boolean result) { @@ -367,11 +358,11 @@ public class CoordinatorSession extends ConsistentSession hasFailure.set(true); fail(); } - resultFuture.set(result); + resultFuture.trySuccess(result); } catch (Exception e) { - resultFuture.setException(e); + resultFuture.tryFailure(e); } } @@ -388,10 +379,10 @@ public class CoordinatorSession extends ConsistentSession } finally { - resultFuture.setException(t); + resultFuture.tryFailure(t); } } - }, MoreExecutors.directExecutor()); + }); return resultFuture; } diff --git a/src/java/org/apache/cassandra/repair/consistent/LocalSessions.java b/src/java/org/apache/cassandra/repair/consistent/LocalSessions.java index daa59e1ce0..741ad9093b 100644 --- a/src/java/org/apache/cassandra/repair/consistent/LocalSessions.java +++ b/src/java/org/apache/cassandra/repair/consistent/LocalSessions.java @@ -32,7 +32,6 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.function.BooleanSupplier; import java.util.function.Predicate; @@ -49,9 +48,6 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.primitives.Ints; import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import org.apache.cassandra.db.compaction.CompactionInterruptedException; import org.apache.cassandra.locator.RangesAtEndpoint; @@ -97,7 +93,9 @@ import org.apache.cassandra.repair.NoSuchRepairSessionException; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.Throwables; +import org.apache.cassandra.utils.concurrent.Future; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.net.Verb.FAILED_SESSION_MSG; import static org.apache.cassandra.net.Verb.FINALIZE_PROMISE_MSG; import static org.apache.cassandra.net.Verb.PREPARE_CONSISTENT_RSP; @@ -739,12 +737,12 @@ public class LocalSessions } @VisibleForTesting - ListenableFuture prepareSession(KeyspaceRepairManager repairManager, - UUID sessionID, - Collection tables, - RangesAtEndpoint tokenRanges, - ExecutorService executor, - BooleanSupplier isCancelled) + Future> prepareSession(KeyspaceRepairManager repairManager, + UUID sessionID, + Collection tables, + RangesAtEndpoint tokenRanges, + ExecutorService executor, + BooleanSupplier isCancelled) { return repairManager.prepareIncrementalRepair(sessionID, tables, tokenRanges, executor, isCancelled); } @@ -802,16 +800,16 @@ public class LocalSessions putSessionUnsafe(session); logger.info("Beginning local incremental repair session {}", session); - ExecutorService executor = Executors.newFixedThreadPool(parentSession.getColumnFamilyStores().size()); + ExecutorService executor = executorFactory().pooled("Repair-" + sessionID, parentSession.getColumnFamilyStores().size()); KeyspaceRepairManager repairManager = parentSession.getKeyspace().getRepairManager(); RangesAtEndpoint tokenRanges = filterLocalRanges(parentSession.getKeyspace().getName(), parentSession.getRanges()); - ListenableFuture repairPreparation = prepareSession(repairManager, sessionID, parentSession.getColumnFamilyStores(), - tokenRanges, executor, () -> session.getState() != PREPARING); + Future> repairPreparation = prepareSession(repairManager, sessionID, parentSession.getColumnFamilyStores(), + tokenRanges, executor, () -> session.getState() != PREPARING); - Futures.addCallback(repairPreparation, new FutureCallback() + repairPreparation.addCallback(new FutureCallback>() { - public void onSuccess(@Nullable Object result) + public void onSuccess(@Nullable List result) { try { @@ -852,7 +850,7 @@ public class LocalSessions executor.shutdown(); } } - }, MoreExecutors.directExecutor()); + }); } public void maybeSetRepairing(UUID sessionID) diff --git a/src/java/org/apache/cassandra/schema/MigrationCoordinator.java b/src/java/org/apache/cassandra/schema/MigrationCoordinator.java index 60fe1b6cf9..f0f700cdb7 100644 --- a/src/java/org/apache/cassandra/schema/MigrationCoordinator.java +++ b/src/java/org/apache/cassandra/schema/MigrationCoordinator.java @@ -30,8 +30,6 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.LongSupplier; @@ -40,7 +38,9 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; -import com.google.common.util.concurrent.Futures; +import org.apache.cassandra.concurrent.FutureTask; +import org.apache.cassandra.utils.concurrent.Future; +import org.apache.cassandra.utils.concurrent.ImmediateFuture; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -67,7 +67,7 @@ import static org.apache.cassandra.utils.concurrent.WaitQueue.newWaitQueue; public class MigrationCoordinator { private static final Logger logger = LoggerFactory.getLogger(MigrationCoordinator.class); - private static final Future FINISHED_FUTURE = Futures.immediateFuture(null); + private static final Future FINISHED_FUTURE = ImmediateFuture.success(null); private static LongSupplier getUptimeFn = () -> ManagementFactory.getRuntimeMXBean().getUptime(); @@ -419,7 +419,7 @@ public class MigrationCoordinator Future scheduleSchemaPull(InetAddressAndPort endpoint, VersionInfo info) { - FutureTask task = new FutureTask<>(() -> pullSchema(new Callback(endpoint, info)), null); + FutureTask task = new FutureTask<>(() -> pullSchema(new Callback(endpoint, info))); if (shouldPullImmediately(endpoint, info.version)) { submitToMigrationIfNotShutdown(task); diff --git a/src/java/org/apache/cassandra/schema/MigrationManager.java b/src/java/org/apache/cassandra/schema/MigrationManager.java index 87fb603106..7229198017 100644 --- a/src/java/org/apache/cassandra/schema/MigrationManager.java +++ b/src/java/org/apache/cassandra/schema/MigrationManager.java @@ -19,12 +19,11 @@ package org.apache.cassandra.schema; import java.io.IOException; import java.util.*; -import java.util.concurrent.*; import java.lang.management.ManagementFactory; import java.util.function.LongSupplier; import com.google.common.annotations.VisibleForTesting; -import com.google.common.util.concurrent.Futures; +import org.apache.cassandra.utils.concurrent.Future; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -225,7 +224,7 @@ public class MigrationManager Future future = MIGRATION.submit(() -> Schema.instance.transform(transformation, locally, now)); - Schema.TransformationResult result = Futures.getUnchecked(future); + Schema.TransformationResult result = future.syncUninterruptibly().getNow(); if (!result.success) throw result.exception; diff --git a/src/java/org/apache/cassandra/service/ActiveRepairService.java b/src/java/org/apache/cassandra/service/ActiveRepairService.java index 9da91115e8..2532c8b8cb 100644 --- a/src/java/org/apache/cassandra/service/ActiveRepairService.java +++ b/src/java/org/apache/cassandra/service/ActiveRepairService.java @@ -20,7 +20,10 @@ package org.apache.cassandra.service; import java.io.IOException; import java.net.UnknownHostException; import java.util.*; -import java.util.concurrent.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import javax.management.openmbean.CompositeData; import java.util.function.Predicate; @@ -32,10 +35,10 @@ import com.google.common.cache.CacheBuilder; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Multimap; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; +import org.apache.cassandra.concurrent.ExecutorPlus; +import org.apache.cassandra.config.Config; import org.apache.cassandra.db.compaction.CompactionManager; import org.apache.cassandra.locator.EndpointsByRange; import org.apache.cassandra.locator.EndpointsForRange; @@ -43,10 +46,7 @@ import org.apache.cassandra.utils.concurrent.CountDownLatch; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.cassandra.concurrent.JMXEnabledThreadPoolExecutor; -import org.apache.cassandra.concurrent.NamedThreadFactory; import org.apache.cassandra.concurrent.ScheduledExecutors; -import org.apache.cassandra.config.Config; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.Keyspace; @@ -93,17 +93,19 @@ import org.apache.cassandra.utils.MBeanWrapper; import org.apache.cassandra.utils.MerkleTrees; import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.UUIDGen; +import org.apache.cassandra.utils.concurrent.Future; import static com.google.common.collect.Iterables.concat; import static com.google.common.collect.Iterables.transform; import static java.util.Collections.synchronizedSet; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.apache.cassandra.config.DatabaseDescriptor.getRpcTimeout; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; +import static org.apache.cassandra.config.Config.RepairCommandPoolFullStrategy.reject; +import static org.apache.cassandra.config.DatabaseDescriptor.*; import static org.apache.cassandra.net.Message.out; import static org.apache.cassandra.net.Verb.PREPARE_MSG; import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis; import static org.apache.cassandra.utils.concurrent.CountDownLatch.newCountDownLatch; -import static org.apache.cassandra.utils.concurrent.BlockingQueues.newBlockingQueue; /** * ActiveRepairService is the starting point for manual "active" repairs. @@ -158,46 +160,23 @@ public class ActiveRepairService implements IEndpointStateChangeSubscriber, IFai public static class RepairCommandExecutorHandle { - private static final ThreadPoolExecutor repairCommandExecutor = - initializeExecutor(DatabaseDescriptor.getRepairCommandPoolSize(), - DatabaseDescriptor.getRepairCommandPoolFullStrategy()); + private static final ExecutorPlus repairCommandExecutor = initializeExecutor(getRepairCommandPoolSize(), getRepairCommandPoolFullStrategy()); } @VisibleForTesting - static ThreadPoolExecutor initializeExecutor(int maxPoolSize, Config.RepairCommandPoolFullStrategy strategy) + static ExecutorPlus initializeExecutor(int maxPoolSize, Config.RepairCommandPoolFullStrategy strategy) { - int corePoolSize = 1; - BlockingQueue queue; - if (strategy == Config.RepairCommandPoolFullStrategy.reject) - { - // new threads will be created on demand up to max pool - // size so we can leave corePoolSize at 1 to start with - queue = new SynchronousQueue<>(); - } - else - { - // new threads are only created if > corePoolSize threads are running - // and the queue is full, so set corePoolSize to the desired max as the - // queue will _never_ be full. Idle core threads will eventually time - // out and may be re-created if/when subsequent tasks are submitted. - corePoolSize = maxPoolSize; - queue = newBlockingQueue(); - } - - ThreadPoolExecutor executor = new JMXEnabledThreadPoolExecutor(corePoolSize, - maxPoolSize, - 1, - TimeUnit.HOURS, - queue, - new NamedThreadFactory("Repair-Task"), - "internal", - new ThreadPoolExecutor.AbortPolicy()); - // allow idle core threads to be terminated - executor.allowCoreThreadTimeOut(true); - return executor; + return executorFactory() + .localAware() // we do trace repair sessions, and seem to rely on local aware propagation (though could do with refactoring) + .withJmxInternal() + .configurePooled("Repair-Task", maxPoolSize) + .withKeepAlive(1, TimeUnit.HOURS) + .withQueueLimit(strategy == reject ? 0 : Integer.MAX_VALUE) + .withRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()) + .build(); } - public static ThreadPoolExecutor repairCommandExecutor() + public static ExecutorPlus repairCommandExecutor() { return RepairCommandExecutorHandle.repairCommandExecutor; } @@ -340,7 +319,7 @@ public class ActiveRepairService implements IEndpointStateChangeSubscriber, IFai boolean pullRepair, PreviewKind previewKind, boolean optimiseStreams, - ListeningExecutorService executor, + ExecutorPlus executor, String... cfnames) { if (range.endpoints.isEmpty()) @@ -386,7 +365,7 @@ public class ActiveRepairService implements IEndpointStateChangeSubscriber, IFai DatabaseDescriptor.useOffheapMerkleTrees(value); } - private void registerOnFdAndGossip(final T task) { @@ -404,7 +383,7 @@ public class ActiveRepairService implements IEndpointStateChangeSubscriber, IFai failureDetector.unregisterFailureDetectionEventListener(task); gossiper.unregister(task); } - }, MoreExecutors.directExecutor()); + }); } public synchronized void terminateSessions() diff --git a/src/java/org/apache/cassandra/service/CacheService.java b/src/java/org/apache/cassandra/service/CacheService.java index a1225fb512..3636c13163 100644 --- a/src/java/org/apache/cassandra/service/CacheService.java +++ b/src/java/org/apache/cassandra/service/CacheService.java @@ -24,9 +24,6 @@ import java.util.Iterator; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; - -import com.google.common.util.concurrent.Futures; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,6 +48,8 @@ import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.MBeanWrapper; import org.apache.cassandra.utils.Pair; +import org.apache.cassandra.utils.concurrent.Future; +import org.apache.cassandra.utils.concurrent.ImmediateFuture; public class CacheService implements CacheServiceMBean { @@ -457,7 +456,7 @@ public class CacheService implements CacheServiceMBean reader.descriptor.version, reader.header); RowIndexEntry entry = indexSerializer.deserializeForCache(input); - return Futures.immediateFuture(Pair.create(new KeyCacheKey(cfs.metadata(), reader.descriptor, key), entry)); + return ImmediateFuture.success(Pair.create(new KeyCacheKey(cfs.metadata(), reader.descriptor, key), entry)); } private SSTableReader findDesc(int generation, Iterable collection) diff --git a/src/java/org/apache/cassandra/service/CassandraDaemon.java b/src/java/org/apache/cassandra/service/CassandraDaemon.java index 3fe672a018..4a53aeef96 100644 --- a/src/java/org/apache/cassandra/service/CassandraDaemon.java +++ b/src/java/org/apache/cassandra/service/CassandraDaemon.java @@ -37,8 +37,7 @@ import javax.management.StandardMBean; import javax.management.remote.JMXConnectorServer; import com.google.common.annotations.VisibleForTesting; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.collect.ImmutableList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -68,20 +67,16 @@ import org.apache.cassandra.db.virtual.VirtualSchemaKeyspace; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.exceptions.StartupException; import org.apache.cassandra.gms.Gossiper; -import org.apache.cassandra.io.FSError; -import org.apache.cassandra.io.sstable.CorruptSSTableException; import org.apache.cassandra.io.sstable.SSTableHeaderFix; import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.metrics.CassandraMetricsRegistry; import org.apache.cassandra.metrics.DefaultNameFactory; -import org.apache.cassandra.metrics.StorageMetrics; import org.apache.cassandra.net.StartupClusterConnectivityChecker; import org.apache.cassandra.schema.Schema; import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.security.ThreadAwareSecurityManager; -import org.apache.cassandra.tracing.Tracing; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.JMXServerUtils; import org.apache.cassandra.utils.JVMStabilityInspector; @@ -89,6 +84,8 @@ import org.apache.cassandra.utils.MBeanWrapper; import org.apache.cassandra.utils.Mx4jTool; import org.apache.cassandra.utils.NativeLibrary; import org.apache.cassandra.utils.WindowsTimer; +import org.apache.cassandra.utils.concurrent.Future; +import org.apache.cassandra.utils.concurrent.FutureCombiner; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static org.apache.cassandra.config.CassandraRelevantProperties.CASSANDRA_FOREGROUND; @@ -274,7 +271,7 @@ public class CassandraDaemon // This should be the first write to SystemKeyspace (CASSANDRA-11742) SystemKeyspace.persistLocalMetadata(); - Thread.setDefaultUncaughtExceptionHandler(CassandraDaemon::uncaughtException); + Thread.setDefaultUncaughtExceptionHandler(JVMStabilityInspector::uncaughtException); SystemKeyspaceMigrator40.migrate(); @@ -574,33 +571,18 @@ public class CassandraDaemon nativeTransportService = new NativeTransportService(); } - @VisibleForTesting - public static void uncaughtException(Thread t, Throwable e) - { - StorageMetrics.uncaughtExceptions.inc(); - logger.error("Exception in thread {}", t, e); - Tracing.trace("Exception in thread {}", t, e); - for (Throwable e2 = e; e2 != null; e2 = e2.getCause()) - { - // make sure error gets logged exactly once. - if (e2 != e && (e2 instanceof FSError || e2 instanceof CorruptSSTableException)) - logger.error("Exception in thread {}", t, e2); - } - JVMStabilityInspector.inspectThrowable(e); - } - /* * Asynchronously load the row and key cache in one off threads and return a compound future of the result. * Error handling is pushed into the cache load since cache loads are allowed to fail and are handled by logging. */ - private ListenableFuture loadRowAndKeyCacheAsync() + private Future loadRowAndKeyCacheAsync() { - final ListenableFuture keyCacheLoad = CacheService.instance.keyCache.loadSavedAsync(); + final Future keyCacheLoad = CacheService.instance.keyCache.loadSavedAsync(); - final ListenableFuture rowCacheLoad = CacheService.instance.rowCache.loadSavedAsync(); + final Future rowCacheLoad = CacheService.instance.rowCache.loadSavedAsync(); @SuppressWarnings("unchecked") - ListenableFuture> retval = Futures.successfulAsList(keyCacheLoad, rowCacheLoad); + Future> retval = FutureCombiner.allOf(ImmutableList.of(keyCacheLoad, rowCacheLoad)); return retval; } diff --git a/src/java/org/apache/cassandra/service/ClientWarn.java b/src/java/org/apache/cassandra/service/ClientWarn.java index ec79854025..46a42c7c80 100644 --- a/src/java/org/apache/cassandra/service/ClientWarn.java +++ b/src/java/org/apache/cassandra/service/ClientWarn.java @@ -20,14 +20,13 @@ package org.apache.cassandra.service; import java.util.ArrayList; import java.util.List; -import io.netty.util.concurrent.FastThreadLocal; -import org.apache.cassandra.concurrent.ExecutorLocal; +import org.apache.cassandra.concurrent.ExecutorLocals; import org.apache.cassandra.utils.FBUtilities; -public class ClientWarn implements ExecutorLocal +@SuppressWarnings("resource") +public class ClientWarn extends ExecutorLocals.Impl { private static final String TRUNCATED = " [truncated]"; - private static final FastThreadLocal warnLocal = new FastThreadLocal<>(); public static ClientWarn instance = new ClientWarn(); private ClientWarn() @@ -36,29 +35,30 @@ public class ClientWarn implements ExecutorLocal public State get() { - return warnLocal.get(); + return ExecutorLocals.current().clientWarnState; } public void set(State value) { - warnLocal.set(value); + ExecutorLocals current = ExecutorLocals.current(); + ExecutorLocals.Impl.set(current.traceState, value); } public void warn(String text) { - State state = warnLocal.get(); + State state = get(); if (state != null) state.add(text); } public void captureWarnings() { - warnLocal.set(new State()); + set(new State()); } public List getWarnings() { - State state = warnLocal.get(); + State state = get(); if (state == null || state.warnings.isEmpty()) return null; return state.warnings; @@ -71,7 +71,7 @@ public class ClientWarn implements ExecutorLocal public void resetWarnings() { - warnLocal.remove(); + set(null); } public static class State @@ -90,6 +90,5 @@ public class ClientWarn implements ExecutorLocal ? warning.substring(0, FBUtilities.MAX_UNSIGNED_SHORT - TRUNCATED.length()) + TRUNCATED : warning; } - } } diff --git a/src/java/org/apache/cassandra/service/PendingRangeCalculatorService.java b/src/java/org/apache/cassandra/service/PendingRangeCalculatorService.java index a75e75e418..cb8dbc3ed2 100644 --- a/src/java/org/apache/cassandra/service/PendingRangeCalculatorService.java +++ b/src/java/org/apache/cassandra/service/PendingRangeCalculatorService.java @@ -18,108 +18,65 @@ package org.apache.cassandra.service; -import org.apache.cassandra.concurrent.JMXEnabledThreadPoolExecutor; -import org.apache.cassandra.concurrent.NamedThreadFactory; -import org.apache.cassandra.schema.Schema; -import org.apache.cassandra.db.Keyspace; -import org.apache.cassandra.locator.AbstractReplicationStrategy; -import org.apache.cassandra.utils.ExecutorUtils; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.List; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; +import org.apache.cassandra.concurrent.SequentialExecutorPlus; +import org.apache.cassandra.concurrent.SequentialExecutorPlus.AtLeastOnceTrigger; +import org.apache.cassandra.db.Keyspace; +import org.apache.cassandra.locator.AbstractReplicationStrategy; +import org.apache.cassandra.schema.Schema; +import org.apache.cassandra.utils.ExecutorUtils; -import com.google.common.annotations.VisibleForTesting; - -import static java.util.concurrent.TimeUnit.SECONDS; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis; -import static org.apache.cassandra.utils.concurrent.BlockingQueues.newBlockingQueue; public class PendingRangeCalculatorService { public static final PendingRangeCalculatorService instance = new PendingRangeCalculatorService(); - private static Logger logger = LoggerFactory.getLogger(PendingRangeCalculatorService.class); + private static final Logger logger = LoggerFactory.getLogger(PendingRangeCalculatorService.class); // the executor will only run a single range calculation at a time while keeping at most one task queued in order // to trigger an update only after the most recent state change and not for each update individually - private final JMXEnabledThreadPoolExecutor executor = new JMXEnabledThreadPoolExecutor(1, Integer.MAX_VALUE, SECONDS, - newBlockingQueue(1), new NamedThreadFactory("PendingRangeCalculator"), "internal"); + private final SequentialExecutorPlus executor = executorFactory() + .withJmxInternal() + .configureSequential("PendingRangeCalculator") + .withRejectedExecutionHandler((r, e) -> {}) // silently handle rejected tasks, this::update takes care of bookkeeping + .build(); - private AtomicInteger updateJobs = new AtomicInteger(0); + private final AtLeastOnceTrigger update = executor.atLeastOnceTrigger(() -> { + PendingRangeCalculatorServiceDiagnostics.taskStarted(1); + long start = currentTimeMillis(); + List keyspaces = Schema.instance.getNonLocalStrategyKeyspaces(); + for (String keyspaceName : keyspaces) + calculatePendingRanges(Keyspace.open(keyspaceName).getReplicationStrategy(), keyspaceName); + if (logger.isTraceEnabled()) + logger.trace("Finished PendingRangeTask for {} keyspaces in {}ms", keyspaces.size(), currentTimeMillis() - start); + PendingRangeCalculatorServiceDiagnostics.taskFinished(); + }); public PendingRangeCalculatorService() { - executor.setRejectedExecutionHandler((r, e) -> - { - PendingRangeCalculatorServiceDiagnostics.taskRejected(instance, updateJobs); - PendingRangeCalculatorService.instance.finishUpdate(); - } - ); - } - - private static class PendingRangeTask implements Runnable - { - private final AtomicInteger updateJobs; - - PendingRangeTask(AtomicInteger updateJobs) - { - this.updateJobs = updateJobs; - } - - public void run() - { - try - { - PendingRangeCalculatorServiceDiagnostics.taskStarted(instance, updateJobs); - long start = currentTimeMillis(); - List keyspaces = Schema.instance.getNonLocalStrategyKeyspaces(); - for (String keyspaceName : keyspaces) - calculatePendingRanges(Keyspace.open(keyspaceName).getReplicationStrategy(), keyspaceName); - if (logger.isTraceEnabled()) - logger.trace("Finished PendingRangeTask for {} keyspaces in {}ms", keyspaces.size(), currentTimeMillis() - start); - PendingRangeCalculatorServiceDiagnostics.taskFinished(instance, updateJobs); - } - finally - { - PendingRangeCalculatorService.instance.finishUpdate(); - } - } - } - - private void finishUpdate() - { - int jobs = updateJobs.decrementAndGet(); - PendingRangeCalculatorServiceDiagnostics.taskCountChanged(instance, jobs); } public void update() { - int jobs = updateJobs.incrementAndGet(); - PendingRangeCalculatorServiceDiagnostics.taskCountChanged(instance, jobs); - executor.execute(new PendingRangeTask(updateJobs)); + boolean success = update.trigger(); + if (!success) PendingRangeCalculatorServiceDiagnostics.taskRejected(1); + else PendingRangeCalculatorServiceDiagnostics.taskCountChanged(1); } public void blockUntilFinished() { - // We want to be sure the job we're blocking for is actually finished and we can't trust the TPE's active job count - while (updateJobs.get() > 0) - { - try - { - Thread.sleep(100); - } - catch (InterruptedException e) - { - throw new RuntimeException(e); - } - } + update.sync(); } - // public & static for testing purposes public static void calculatePendingRanges(AbstractReplicationStrategy strategy, String keyspaceName) { @@ -131,4 +88,5 @@ public class PendingRangeCalculatorService { ExecutorUtils.shutdownNowAndWait(timeout, unit, executor); } + } diff --git a/src/java/org/apache/cassandra/service/PendingRangeCalculatorServiceDiagnostics.java b/src/java/org/apache/cassandra/service/PendingRangeCalculatorServiceDiagnostics.java index ec09e3fdf4..2b677d1fe6 100644 --- a/src/java/org/apache/cassandra/service/PendingRangeCalculatorServiceDiagnostics.java +++ b/src/java/org/apache/cassandra/service/PendingRangeCalculatorServiceDiagnostics.java @@ -18,8 +18,6 @@ package org.apache.cassandra.service; -import java.util.concurrent.atomic.AtomicInteger; - import org.apache.cassandra.diag.DiagnosticEventService; import org.apache.cassandra.service.PendingRangeCalculatorServiceEvent.PendingRangeCalculatorServiceEventType; @@ -34,35 +32,30 @@ final class PendingRangeCalculatorServiceDiagnostics { } - static void taskStarted(PendingRangeCalculatorService calculatorService, AtomicInteger taskCount) + static void taskStarted(int taskCount) { if (isEnabled(PendingRangeCalculatorServiceEventType.TASK_STARTED)) service.publish(new PendingRangeCalculatorServiceEvent(PendingRangeCalculatorServiceEventType.TASK_STARTED, - calculatorService, - taskCount.get())); + taskCount)); } - static void taskFinished(PendingRangeCalculatorService calculatorService, AtomicInteger taskCount) + static void taskFinished() { if (isEnabled(PendingRangeCalculatorServiceEventType.TASK_FINISHED_SUCCESSFULLY)) - service.publish(new PendingRangeCalculatorServiceEvent(PendingRangeCalculatorServiceEventType.TASK_FINISHED_SUCCESSFULLY, - calculatorService, - taskCount.get())); + service.publish(new PendingRangeCalculatorServiceEvent(PendingRangeCalculatorServiceEventType.TASK_FINISHED_SUCCESSFULLY)); } - static void taskRejected(PendingRangeCalculatorService calculatorService, AtomicInteger taskCount) + static void taskRejected(int taskCount) { if (isEnabled(PendingRangeCalculatorServiceEventType.TASK_EXECUTION_REJECTED)) service.publish(new PendingRangeCalculatorServiceEvent(PendingRangeCalculatorServiceEventType.TASK_EXECUTION_REJECTED, - calculatorService, - taskCount.get())); + taskCount)); } - static void taskCountChanged(PendingRangeCalculatorService calculatorService, int taskCount) + static void taskCountChanged(int taskCount) { if (isEnabled(PendingRangeCalculatorServiceEventType.TASK_COUNT_CHANGED)) service.publish(new PendingRangeCalculatorServiceEvent(PendingRangeCalculatorServiceEventType.TASK_COUNT_CHANGED, - calculatorService, taskCount)); } diff --git a/src/java/org/apache/cassandra/service/PendingRangeCalculatorServiceEvent.java b/src/java/org/apache/cassandra/service/PendingRangeCalculatorServiceEvent.java index 3024149b16..f25517347c 100644 --- a/src/java/org/apache/cassandra/service/PendingRangeCalculatorServiceEvent.java +++ b/src/java/org/apache/cassandra/service/PendingRangeCalculatorServiceEvent.java @@ -19,7 +19,9 @@ package org.apache.cassandra.service; import java.io.Serializable; +import java.util.Collections; import java.util.HashMap; +import java.util.Map; import org.apache.cassandra.diag.DiagnosticEvent; @@ -29,7 +31,6 @@ import org.apache.cassandra.diag.DiagnosticEvent; final class PendingRangeCalculatorServiceEvent extends DiagnosticEvent { private final PendingRangeCalculatorServiceEventType type; - private final PendingRangeCalculatorService source; private final int taskCount; public enum PendingRangeCalculatorServiceEventType @@ -40,12 +41,15 @@ final class PendingRangeCalculatorServiceEvent extends DiagnosticEvent TASK_COUNT_CHANGED } + PendingRangeCalculatorServiceEvent(PendingRangeCalculatorServiceEventType type) + { + this(type, -1); + } + PendingRangeCalculatorServiceEvent(PendingRangeCalculatorServiceEventType type, - PendingRangeCalculatorService service, int taskCount) { this.type = type; - this.source = service; this.taskCount = taskCount; } @@ -59,9 +63,11 @@ final class PendingRangeCalculatorServiceEvent extends DiagnosticEvent return type; } - public HashMap toMap() + public Map toMap() { // be extra defensive against nulls and bugs + if (taskCount < 0) + return Collections.emptyMap(); HashMap ret = new HashMap<>(); ret.put("taskCount", taskCount); return ret; diff --git a/src/java/org/apache/cassandra/service/StorageService.java b/src/java/org/apache/cassandra/service/StorageService.java index 54e5f18a57..b41ebea48e 100644 --- a/src/java/org/apache/cassandra/service/StorageService.java +++ b/src/java/org/apache/cassandra/service/StorageService.java @@ -25,7 +25,13 @@ import java.nio.file.Paths; import java.time.Instant; import java.util.*; import java.util.Map.Entry; -import java.util.concurrent.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.MatchResult; @@ -49,11 +55,15 @@ import com.google.common.collect.*; import com.google.common.util.concurrent.*; import org.apache.cassandra.config.CassandraRelevantProperties; +import org.apache.cassandra.concurrent.*; +import org.apache.cassandra.config.ParameterizedClass; import org.apache.cassandra.dht.RangeStreamer.FetchReplica; import org.apache.cassandra.fql.FullQueryLogger; import org.apache.cassandra.fql.FullQueryLoggerOptions; import org.apache.cassandra.fql.FullQueryLoggerOptionsCompositeData; import org.apache.cassandra.locator.ReplicaCollection.Builder.Conflict; +import org.apache.cassandra.utils.concurrent.Future; +import org.apache.cassandra.utils.concurrent.ImmediateFuture; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -64,10 +74,6 @@ import org.apache.cassandra.audit.AuditLogOptions; import org.apache.cassandra.auth.AuthKeyspace; import org.apache.cassandra.auth.AuthSchemaChangeListener; import org.apache.cassandra.batchlog.BatchlogManager; -import org.apache.cassandra.concurrent.ExecutorLocals; -import org.apache.cassandra.concurrent.NamedThreadFactory; -import org.apache.cassandra.concurrent.ScheduledExecutors; -import org.apache.cassandra.concurrent.Stage; import org.apache.cassandra.config.Config; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.Duration; @@ -137,6 +143,7 @@ import static org.apache.cassandra.net.Verb.REPLICATION_DONE_REQ; import static org.apache.cassandra.schema.MigrationManager.evolveSystemKeyspace; import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis; import static org.apache.cassandra.utils.Clock.Global.nanoTime; +import static org.apache.cassandra.service.ActiveRepairService.*; /** * This abstraction contains the token/identifier of this node @@ -1855,8 +1862,8 @@ public class StorageService extends NotificationBroadcasterSupport implements IE // already bootstrapped ranges are filtered during bootstrap BootStrapper bootstrapper = new BootStrapper(FBUtilities.getBroadcastAddressAndPort(), tokens, tokenMetadata); bootstrapper.addProgressListener(progressSupport); - ListenableFuture bootstrapStream = bootstrapper.bootstrap(streamStateStore, useStrictConsistency && !replacing); // handles token update - Futures.addCallback(bootstrapStream, new FutureCallback() + Future bootstrapStream = bootstrapper.bootstrap(streamStateStore, useStrictConsistency && !replacing); // handles token update + bootstrapStream.addCallback(new FutureCallback() { @Override public void onSuccess(StreamState streamState) @@ -1904,7 +1911,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE progressSupport.progress("bootstrap", new ProgressEvent(ProgressEventType.ERROR, 1, 1, message)); progressSupport.progress("bootstrap", new ProgressEvent(ProgressEventType.COMPLETE, 1, 1, "Resume bootstrap complete")); } - }, MoreExecutors.directExecutor()); + }); return true; } else @@ -3225,7 +3232,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE }); }); StreamResultFuture future = stream.execute(); - Futures.addCallback(future, new FutureCallback() + future.addCallback(new FutureCallback() { public void onSuccess(StreamState finalState) { @@ -3238,7 +3245,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE // We still want to send the notification sendReplicationNotification(notifyEndpoint); } - }, MoreExecutors.directExecutor()); + }); } /** @@ -4123,10 +4130,10 @@ public class StorageService extends NotificationBroadcasterSupport implements IE } } if (option.getRanges().isEmpty() || Keyspace.open(keyspace).getReplicationStrategy().getReplicationFactor().allReplicas < 2) - return Pair.create(0, Futures.immediateFuture(null)); + return Pair.create(0, ImmediateFuture.success(null)); int cmd = nextRepairCommand.incrementAndGet(); - return Pair.create(cmd, ActiveRepairService.repairCommandExecutor().submit(createRepairTask(cmd, keyspace, option, listeners))); + return Pair.create(cmd, repairCommandExecutor().submit(createRepairTask(cmd, keyspace, option, listeners))); } /** @@ -4185,21 +4192,8 @@ public class StorageService extends NotificationBroadcasterSupport implements IE task.addProgressListener(listener); if (options.isTraced()) - { - Runnable r = () -> - { - try - { - task.run(); - } - finally - { - ExecutorLocals.set(null); - } - }; - return new FutureTask<>(r, null); - } - return new FutureTask<>(task, null); + return new FutureTaskWithResources<>(() -> ExecutorLocals::clear, task); + return new FutureTask<>(task); } public void forceTerminateAllRepairSessions() @@ -4210,7 +4204,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE @Nullable public List getParentRepairStatus(int cmd) { - Pair> pair = ActiveRepairService.instance.getRepairStatus(cmd); + Pair> pair = ActiveRepairService.instance.getRepairStatus(cmd); return pair == null ? null : ImmutableList.builder().add(pair.left.name()).addAll(pair.right).build(); } diff --git a/src/java/org/apache/cassandra/service/snapshot/SnapshotManager.java b/src/java/org/apache/cassandra/service/snapshot/SnapshotManager.java index dd62a0ef67..554bd51e9e 100644 --- a/src/java/org/apache/cassandra/service/snapshot/SnapshotManager.java +++ b/src/java/org/apache/cassandra/service/snapshot/SnapshotManager.java @@ -29,7 +29,7 @@ import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.cassandra.concurrent.DebuggableScheduledThreadPoolExecutor; +import org.apache.cassandra.concurrent.ScheduledExecutorPlus; import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.Directories; @@ -43,10 +43,11 @@ import java.util.stream.StreamSupport; import com.google.common.annotations.VisibleForTesting; import org.apache.cassandra.utils.ExecutorUtils; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; public class SnapshotManager { - private static final DebuggableScheduledThreadPoolExecutor executor = new DebuggableScheduledThreadPoolExecutor("SnapshotCleanup"); + private static final ScheduledExecutorPlus executor = executorFactory().scheduled(false, "SnapshotCleanup"); private static final Logger logger = LoggerFactory.getLogger(SnapshotManager.class); diff --git a/src/java/org/apache/cassandra/streaming/StreamReceiveTask.java b/src/java/org/apache/cassandra/streaming/StreamReceiveTask.java index d127edb4ef..002e182714 100644 --- a/src/java/org/apache/cassandra/streaming/StreamReceiveTask.java +++ b/src/java/org/apache/cassandra/streaming/StreamReceiveTask.java @@ -18,7 +18,6 @@ package org.apache.cassandra.streaming; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -26,11 +25,11 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.cassandra.concurrent.NamedThreadFactory; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.schema.TableId; import org.apache.cassandra.utils.JVMStabilityInspector; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.utils.ExecutorUtils.awaitTermination; import static org.apache.cassandra.utils.ExecutorUtils.shutdown; @@ -41,7 +40,7 @@ public class StreamReceiveTask extends StreamTask { private static final Logger logger = LoggerFactory.getLogger(StreamReceiveTask.class); - private static final ExecutorService executor = Executors.newCachedThreadPool(new NamedThreadFactory("StreamReceiveTask")); + private static final ExecutorService executor = executorFactory().pooled("StreamReceiveTask", Integer.MAX_VALUE); private final StreamReceiver receiver; diff --git a/src/java/org/apache/cassandra/streaming/StreamResultFuture.java b/src/java/org/apache/cassandra/streaming/StreamResultFuture.java index 2f9ca8ba78..8febff618b 100644 --- a/src/java/org/apache/cassandra/streaming/StreamResultFuture.java +++ b/src/java/org/apache/cassandra/streaming/StreamResultFuture.java @@ -21,8 +21,6 @@ import java.util.Collection; import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.MoreExecutors; import org.apache.cassandra.utils.concurrent.AsyncFuture; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -145,7 +143,7 @@ public final class StreamResultFuture extends AsyncFuture public void addEventListener(StreamEventHandler listener) { - Futures.addCallback(this, listener, MoreExecutors.directExecutor()); + addCallback(listener); eventListeners.add(listener); } diff --git a/src/java/org/apache/cassandra/streaming/StreamSession.java b/src/java/org/apache/cassandra/streaming/StreamSession.java index 7d6e6da9ac..fbb4c616de 100644 --- a/src/java/org/apache/cassandra/streaming/StreamSession.java +++ b/src/java/org/apache/cassandra/streaming/StreamSession.java @@ -20,15 +20,19 @@ package org.apache.cassandra.streaming; import java.io.EOFException; import java.net.SocketTimeoutException; import java.util.*; -import java.util.concurrent.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.*; +import io.netty.util.concurrent.Future; import org.apache.cassandra.concurrent.ScheduledExecutors; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.locator.RangesAtEndpoint; +import org.apache.cassandra.utils.concurrent.FutureCombiner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -171,7 +175,7 @@ public class StreamSession implements IEndpointStateChangeSubscriber // threads(serialization/deserialization) and stream messaging processing thread, causing connection closed before // receiving peer's CompleteMessage. private boolean maybeCompleted = false; - private Future closeFuture; + private Future closeFuture; private final UUID pendingRepair; private final PreviewKind previewKind; @@ -488,7 +492,7 @@ public class StreamSession implements IEndpointStateChangeSubscriber state(finalState); - List futures = new ArrayList<>(); + List> futures = new ArrayList<>(); // ensure aborting the tasks do not happen on the network IO thread (read: netty event loop) // as we don't want any blocking disk IO to stop the network thread @@ -505,7 +509,7 @@ public class StreamSession implements IEndpointStateChangeSubscriber sink.onClose(peer); streamResult.handleSessionComplete(this); - closeFuture = FBUtilities.allOf(futures); + closeFuture = FutureCombiner.allOf(futures); return closeFuture; } diff --git a/src/java/org/apache/cassandra/streaming/StreamTransferTask.java b/src/java/org/apache/cassandra/streaming/StreamTransferTask.java index d12ecfdc7f..2fb1213366 100644 --- a/src/java/org/apache/cassandra/streaming/StreamTransferTask.java +++ b/src/java/org/apache/cassandra/streaming/StreamTransferTask.java @@ -21,7 +21,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -34,10 +33,10 @@ import com.google.common.base.Throwables; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.cassandra.concurrent.NamedThreadFactory; import org.apache.cassandra.schema.TableId; import org.apache.cassandra.streaming.messages.OutgoingStreamMessage; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.utils.ExecutorUtils.awaitTermination; import static org.apache.cassandra.utils.ExecutorUtils.shutdown; @@ -47,7 +46,7 @@ import static org.apache.cassandra.utils.ExecutorUtils.shutdown; public class StreamTransferTask extends StreamTask { private static final Logger logger = LoggerFactory.getLogger(StreamTransferTask.class); - private static final ScheduledExecutorService timeoutExecutor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("StreamingTransferTaskTimeouts")); + private static final ScheduledExecutorService timeoutExecutor = executorFactory().scheduled("StreamingTransferTaskTimeouts"); private final AtomicInteger sequenceNumber = new AtomicInteger(0); private boolean aborted = false; diff --git a/src/java/org/apache/cassandra/streaming/async/NettyStreamingMessageSender.java b/src/java/org/apache/cassandra/streaming/async/NettyStreamingMessageSender.java index f80c84ccb3..e3b2fef630 100644 --- a/src/java/org/apache/cassandra/streaming/async/NettyStreamingMessageSender.java +++ b/src/java/org/apache/cassandra/streaming/async/NettyStreamingMessageSender.java @@ -23,13 +23,11 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedByInterruptException; import java.util.Collection; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ThreadPoolExecutor; import com.google.common.annotations.VisibleForTesting; - import org.apache.cassandra.utils.concurrent.Semaphore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,8 +39,7 @@ import io.netty.channel.ChannelPipeline; import io.netty.util.AttributeKey; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; -import org.apache.cassandra.concurrent.DebuggableThreadPoolExecutor; -import org.apache.cassandra.concurrent.NamedThreadFactory; +import org.apache.cassandra.concurrent.ExecutorPlus; import org.apache.cassandra.io.util.DataOutputBufferFixed; import org.apache.cassandra.io.util.DataOutputStreamPlus; import org.apache.cassandra.net.OutboundConnectionSettings; @@ -64,6 +61,7 @@ import static java.lang.String.format; import static java.lang.System.getProperty; import static java.lang.Thread.currentThread; import static java.util.concurrent.TimeUnit.*; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.config.Config.PROPERTY_PREFIX; import static org.apache.cassandra.config.DatabaseDescriptor.getStreamingKeepAlivePeriod; import static org.apache.cassandra.net.AsyncChannelPromise.writeAndFlush; @@ -122,7 +120,7 @@ public class NettyStreamingMessageSender implements StreamingMessageSender // note: this really doesn't need to be a LBQ, just something that's thread safe private final Collection> channelKeepAlives = newBlockingQueue(); - private final ThreadPoolExecutor fileTransferExecutor; + private final ExecutorPlus fileTransferExecutor; /** * A mapping of each {@link #fileTransferExecutor} thread to a channel that can be written to (on that thread). @@ -146,9 +144,9 @@ public class NettyStreamingMessageSender implements StreamingMessageSender this.isPreview = isPreview; String name = session.peer.toString().replace(':', '.'); - fileTransferExecutor = new DebuggableThreadPoolExecutor(1, MAX_PARALLEL_TRANSFERS, 1L, SECONDS, newBlockingQueue(), - new NamedThreadFactory("NettyStreaming-Outbound-" + name)); - fileTransferExecutor.allowCoreThreadTimeOut(true); + fileTransferExecutor = executorFactory() + .configurePooled("NettyStreaming-Outbound-" + name, MAX_PARALLEL_TRANSFERS) + .withKeepAlive(1L, SECONDS).build(); } @Override diff --git a/src/java/org/apache/cassandra/tools/GenerateTokens.java b/src/java/org/apache/cassandra/tools/GenerateTokens.java index c03a4d0ce7..a6888d76e7 100644 --- a/src/java/org/apache/cassandra/tools/GenerateTokens.java +++ b/src/java/org/apache/cassandra/tools/GenerateTokens.java @@ -21,7 +21,6 @@ package org.apache.cassandra.tools; import java.util.Arrays; import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.GnuParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; diff --git a/src/java/org/apache/cassandra/tracing/TraceStateImpl.java b/src/java/org/apache/cassandra/tracing/TraceStateImpl.java index d9077e26ab..ff158d07b4 100644 --- a/src/java/org/apache/cassandra/tracing/TraceStateImpl.java +++ b/src/java/org/apache/cassandra/tracing/TraceStateImpl.java @@ -17,12 +17,11 @@ */ package org.apache.cassandra.tracing; +import java.util.Arrays; import java.util.Collections; import java.util.Set; import java.util.UUID; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -37,7 +36,8 @@ import org.apache.cassandra.exceptions.OverloadedException; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.service.StorageProxy; import org.apache.cassandra.utils.JVMStabilityInspector; -import org.apache.cassandra.utils.WrappedRunnable; +import org.apache.cassandra.utils.concurrent.Future; +import org.apache.cassandra.utils.concurrent.FutureCombiner; import static java.util.Collections.singletonList; import static org.apache.cassandra.db.ConsistencyLevel.ANY; @@ -86,8 +86,8 @@ public class TraceStateImpl extends TraceState logger.trace("Waiting for up to {} seconds for {} trace events to complete", +WAIT_FOR_PENDING_EVENTS_TIMEOUT_SECS, pendingFutures.size()); - CompletableFuture.allOf(pendingFutures.toArray(new CompletableFuture[pendingFutures.size()])) - .get(WAIT_FOR_PENDING_EVENTS_TIMEOUT_SECS, TimeUnit.SECONDS); + FutureCombiner.allOf(Arrays.asList(pendingFutures.toArray(new Future[pendingFutures.size()]))) + .get(WAIT_FOR_PENDING_EVENTS_TIMEOUT_SECS, TimeUnit.SECONDS); } catch (TimeoutException ex) { @@ -105,14 +105,7 @@ public class TraceStateImpl extends TraceState void executeMutation(final Mutation mutation) { - CompletableFuture fut = CompletableFuture.runAsync(new WrappedRunnable() - { - protected void runMayThrow() - { - mutateWithCatch(mutation); - } - }, Stage.TRACING.executor()); - + Future fut = Stage.TRACING.executor().submit(() -> mutateWithCatch(mutation), null); boolean ret = pendingFutures.add(fut); if (!ret) logger.warn("Failed to insert pending future, tracing synchronization may not work"); diff --git a/src/java/org/apache/cassandra/tracing/Tracing.java b/src/java/org/apache/cassandra/tracing/Tracing.java index 7d72224012..805f5daa2d 100644 --- a/src/java/org/apache/cassandra/tracing/Tracing.java +++ b/src/java/org/apache/cassandra/tracing/Tracing.java @@ -31,8 +31,7 @@ import java.util.concurrent.ConcurrentMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.netty.util.concurrent.FastThreadLocal; -import org.apache.cassandra.concurrent.ExecutorLocal; +import org.apache.cassandra.concurrent.ExecutorLocals; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.marshal.TimeUUIDType; import org.apache.cassandra.io.IVersionedSerializer; @@ -50,7 +49,7 @@ import org.apache.cassandra.utils.UUIDGen; * A trace session context. Able to track and store trace sessions. A session is usually a user initiated query, and may * have multiple local and remote events before it is completed. */ -public abstract class Tracing implements ExecutorLocal +public abstract class Tracing extends ExecutorLocals.Impl { public static final IVersionedSerializer traceTypeSerializer = new IVersionedSerializer() { @@ -105,8 +104,6 @@ public abstract class Tracing implements ExecutorLocal private final InetAddressAndPort localAddress = FBUtilities.getLocalAddressAndPort(); - private final FastThreadLocal state = new FastThreadLocal<>(); - protected final ConcurrentMap sessions = new ConcurrentHashMap<>(); public static final Tracing instance; @@ -134,19 +131,19 @@ public abstract class Tracing implements ExecutorLocal public UUID getSessionId() { assert isTracing(); - return state.get().sessionId; + return get().sessionId; } public TraceType getTraceType() { assert isTracing(); - return state.get().traceType; + return get().traceType; } public int getTTL() { assert isTracing(); - return state.get().ttl; + return get().ttl; } /** @@ -221,7 +218,7 @@ public abstract class Tracing implements ExecutorLocal public TraceState get() { - return state.get(); + return ExecutorLocals.current().traceState; } public TraceState get(UUID sessionId) @@ -229,9 +226,11 @@ public abstract class Tracing implements ExecutorLocal return sessions.get(sessionId); } - public void set(final TraceState tls) + public void set(TraceState tls) { - state.set(tls); + @SuppressWarnings("resource") + ExecutorLocals current = ExecutorLocals.current(); + ExecutorLocals.Impl.set(tls, current.clientWarnState); } public TraceState begin(final String request, final Map parameters) diff --git a/src/java/org/apache/cassandra/transport/Dispatcher.java b/src/java/org/apache/cassandra/transport/Dispatcher.java index 1b141cd8aa..31b750eb7c 100644 --- a/src/java/org/apache/cassandra/transport/Dispatcher.java +++ b/src/java/org/apache/cassandra/transport/Dispatcher.java @@ -30,7 +30,7 @@ import org.slf4j.LoggerFactory; import io.netty.channel.Channel; import io.netty.channel.EventLoop; import io.netty.util.AttributeKey; -import org.apache.cassandra.concurrent.LocalAwareExecutorService; +import org.apache.cassandra.concurrent.LocalAwareExecutorPlus; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.net.FrameEncoder; import org.apache.cassandra.service.ClientWarn; @@ -50,10 +50,10 @@ public class Dispatcher { private static final Logger logger = LoggerFactory.getLogger(Dispatcher.class); - private static final LocalAwareExecutorService requestExecutor = SHARED.newExecutor(DatabaseDescriptor.getNativeTransportMaxThreads(), - DatabaseDescriptor::setNativeTransportMaxThreads, - "transport", - "Native-Transport-Requests"); + private static final LocalAwareExecutorPlus requestExecutor = SHARED.newExecutor(DatabaseDescriptor.getNativeTransportMaxThreads(), + DatabaseDescriptor::setNativeTransportMaxThreads, + "transport", + "Native-Transport-Requests"); private static final ConcurrentMap flusherLookup = new ConcurrentHashMap<>(); private final boolean useLegacyFlusher; diff --git a/src/java/org/apache/cassandra/utils/Closeable.java b/src/java/org/apache/cassandra/utils/Closeable.java new file mode 100644 index 0000000000..ccc33ea9b4 --- /dev/null +++ b/src/java/org/apache/cassandra/utils/Closeable.java @@ -0,0 +1,24 @@ +/* + * 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; + +public interface Closeable extends java.io.Closeable +{ + public void close(); +} diff --git a/src/java/org/apache/cassandra/utils/DiagnosticSnapshotService.java b/src/java/org/apache/cassandra/utils/DiagnosticSnapshotService.java index 7572ac15d6..ab2d67ec9f 100644 --- a/src/java/org/apache/cassandra/utils/DiagnosticSnapshotService.java +++ b/src/java/org/apache/cassandra/utils/DiagnosticSnapshotService.java @@ -28,7 +28,6 @@ import com.google.common.base.Preconditions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.cassandra.concurrent.NamedThreadFactory; import org.apache.cassandra.db.*; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.net.Message; @@ -38,6 +37,7 @@ import org.apache.cassandra.schema.TableId; import org.apache.cassandra.schema.TableMetadata; import static org.apache.cassandra.utils.Clock.Global.nanoTime; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; /** * Provides a means to take snapshots when triggered by anomalous events or when the breaking of invariants is @@ -64,7 +64,7 @@ public class DiagnosticSnapshotService private static final Logger logger = LoggerFactory.getLogger(DiagnosticSnapshotService.class); public static final DiagnosticSnapshotService instance = - new DiagnosticSnapshotService(Executors.newSingleThreadExecutor(new NamedThreadFactory("DiagnosticSnapshot"))); + new DiagnosticSnapshotService(executorFactory().sequential("DiagnosticSnapshot")); public static final String REPAIRED_DATA_MISMATCH_SNAPSHOT_PREFIX = "RepairedDataMismatch-"; public static final String DUPLICATE_ROWS_DETECTED_SNAPSHOT_PREFIX = "DuplicateRows-"; diff --git a/src/java/org/apache/cassandra/utils/ExecutorUtils.java b/src/java/org/apache/cassandra/utils/ExecutorUtils.java index 9ef1df8d57..e26d776e00 100644 --- a/src/java/org/apache/cassandra/utils/ExecutorUtils.java +++ b/src/java/org/apache/cassandra/utils/ExecutorUtils.java @@ -24,7 +24,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import org.apache.cassandra.concurrent.InfiniteLoopExecutor; +import org.apache.cassandra.concurrent.Shutdownable; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static org.apache.cassandra.utils.Clock.Global.nanoTime; @@ -67,8 +67,11 @@ public class ExecutorUtils if (interrupt) ((ExecutorService) executor).shutdownNow(); else ((ExecutorService) executor).shutdown(); } - else if (executor instanceof InfiniteLoopExecutor) - ((InfiniteLoopExecutor) executor).shutdownNow(); + else if (executor instanceof Shutdownable) + { + if (interrupt) ((Shutdownable) executor).shutdownNow(); + else ((Shutdownable) executor).shutdown(); + } else if (executor instanceof Thread) ((Thread) executor).interrupt(); else if (executor != null) @@ -107,9 +110,9 @@ public class ExecutorUtils if (wait <= 0 || !((ExecutorService)executor).awaitTermination(wait, NANOSECONDS)) throw new TimeoutException(executor + " did not terminate on time"); } - else if (executor instanceof InfiniteLoopExecutor) + else if (executor instanceof Shutdownable) { - if (wait <= 0 || !((InfiniteLoopExecutor)executor).awaitTermination(wait, NANOSECONDS)) + if (wait <= 0 || !((Shutdownable)executor).awaitTermination(wait, NANOSECONDS)) throw new TimeoutException(executor + " did not terminate on time"); } else if (executor instanceof Thread) diff --git a/src/java/org/apache/cassandra/utils/FBUtilities.java b/src/java/org/apache/cassandra/utils/FBUtilities.java index 62ab3706c1..66ac00ea2b 100644 --- a/src/java/org/apache/cassandra/utils/FBUtilities.java +++ b/src/java/org/apache/cassandra/utils/FBUtilities.java @@ -25,7 +25,10 @@ import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; -import java.util.concurrent.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.Future; +import java.util.concurrent.TimeoutException; import java.util.zip.CRC32; import java.util.zip.Checksum; @@ -35,7 +38,7 @@ import javax.annotation.Nullable; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.base.Strings; -import com.google.common.util.concurrent.Uninterruptibles; +import org.apache.cassandra.utils.concurrent.*; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -493,11 +496,11 @@ public class FBUtilities } catch (InterruptedException ie) { - throw new AssertionError(ie); + throw new UncheckedInterruptedException(ie); } } - public static Future waitOnFirstFuture(Iterable> futures) + public static > F waitOnFirstFuture(Iterable futures) { return waitOnFirstFuture(futures, 100); } @@ -506,105 +509,49 @@ public class FBUtilities * @param futures The futures to wait on * @return future that completed. */ - public static Future waitOnFirstFuture(Iterable> futures, long delay) + public static > F waitOnFirstFuture(Iterable futures, long delay) { while (true) { - for (Future f : futures) + Iterator iter = futures.iterator(); + if (!iter.hasNext()) + throw new IllegalArgumentException(); + + while (true) { - if (f.isDone()) + F f = iter.next(); + boolean isDone; + if ((isDone = f.isDone()) || !iter.hasNext()) { try { - f.get(); + f.get(delay, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { - throw new AssertionError(e); + throw new UncheckedInterruptedException(e); } catch (ExecutionException e) { throw new RuntimeException(e); } + catch (TimeoutException e) + { + if (!isDone) // prevent infinite loops on bad implementations (not encountered) + break; + } return f; } } - Uninterruptibles.sleepUninterruptibly(delay, TimeUnit.MILLISECONDS); } } /** * Returns a new {@link Future} wrapping the given list of futures and returning a list of their results. */ - public static Future allOf(Collection futures) + public static org.apache.cassandra.utils.concurrent.Future> allOf(Collection> futures) { - if (futures.isEmpty()) - return CompletableFuture.completedFuture(null); - - return new Future() - { - @Override - @SuppressWarnings("unchecked") - public List get() throws InterruptedException, ExecutionException - { - List result = new ArrayList<>(futures.size()); - for (Future current : futures) - { - result.add(current.get()); - } - return result; - } - - @Override - @SuppressWarnings("unchecked") - public List get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException - { - List result = new ArrayList<>(futures.size()); - long deadline = nanoTime() + TimeUnit.NANOSECONDS.convert(timeout, unit); - for (Future current : futures) - { - long remaining = deadline - nanoTime(); - if (remaining <= 0) - throw new TimeoutException(); - - result.add(current.get(remaining, TimeUnit.NANOSECONDS)); - } - return result; - } - - @Override - public boolean cancel(boolean mayInterruptIfRunning) - { - for (Future current : futures) - { - if (!current.cancel(mayInterruptIfRunning)) - return false; - } - return true; - } - - @Override - public boolean isCancelled() - { - for (Future current : futures) - { - if (!current.isCancelled()) - return false; - } - return true; - } - - @Override - public boolean isDone() - { - for (Future current : futures) - { - if (!current.isDone()) - return false; - } - return true; - } - }; + return FutureCombiner.allOf(futures); } /** diff --git a/src/java/org/apache/cassandra/utils/JVMStabilityInspector.java b/src/java/org/apache/cassandra/utils/JVMStabilityInspector.java index 9362cc23b4..158baf2556 100644 --- a/src/java/org/apache/cassandra/utils/JVMStabilityInspector.java +++ b/src/java/org/apache/cassandra/utils/JVMStabilityInspector.java @@ -29,6 +29,8 @@ import java.util.function.Consumer; import com.google.common.annotations.VisibleForTesting; import org.apache.cassandra.exceptions.UnrecoverableIllegalStateException; +import org.apache.cassandra.metrics.StorageMetrics; +import org.apache.cassandra.tracing.Tracing; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,6 +60,20 @@ public final class JVMStabilityInspector private JVMStabilityInspector() {} + public static void uncaughtException(Thread thread, Throwable t) + { + try { StorageMetrics.uncaughtExceptions.inc(); } catch (Throwable ignore) { /* might not be initialised */ } + logger.error("Exception in thread {}", thread, t); + Tracing.trace("Exception in thread {}", thread, t); + for (Throwable t2 = t; t2 != null; t2 = t2.getCause()) + { + // make sure error gets logged exactly once. + if (t2 != t && (t2 instanceof FSError || t2 instanceof CorruptSSTableException)) + logger.error("Exception in thread {}", thread, t2); + } + JVMStabilityInspector.inspectThrowable(t); + } + /** * Certain Throwables and Exceptions represent "Die" conditions for the server. * This recursively checks the input Throwable's cause hierarchy until null. diff --git a/src/java/org/apache/cassandra/utils/RecomputingSupplier.java b/src/java/org/apache/cassandra/utils/RecomputingSupplier.java index ba6a1ff55d..055443923b 100644 --- a/src/java/org/apache/cassandra/utils/RecomputingSupplier.java +++ b/src/java/org/apache/cassandra/utils/RecomputingSupplier.java @@ -18,7 +18,6 @@ package org.apache.cassandra.utils; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; @@ -27,6 +26,10 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; +import org.apache.cassandra.utils.concurrent.AsyncPromise; +import org.apache.cassandra.utils.concurrent.Future; +import org.apache.cassandra.utils.concurrent.Promise; + /** * Supplier that caches the last computed value until it is reset, forcing every caller of * {@link RecomputingSupplier#get(long, TimeUnit)} to wait until this value is computed if @@ -38,7 +41,7 @@ import java.util.function.Supplier; public class RecomputingSupplier { private final Supplier supplier; - private final AtomicReference> cached = new AtomicReference<>(null); + private final AtomicReference> cached = new AtomicReference<>(null); private final AtomicBoolean workInProgress = new AtomicBoolean(false); private final ExecutorService executor; @@ -50,7 +53,7 @@ public class RecomputingSupplier public void recompute() { - CompletableFuture current = cached.get(); + Future current = cached.get(); boolean origWip = workInProgress.get(); if (origWip || (current != null && !current.isDone())) @@ -63,14 +66,14 @@ public class RecomputingSupplier assert current == null || current.isDone(); // The work is not in progress, and current future is done. Try to submit a new task. - CompletableFuture lazyValue = new CompletableFuture<>(); + Promise lazyValue = new AsyncPromise<>(); if (cached.compareAndSet(current, lazyValue)) executor.submit(() -> doWork(lazyValue)); else executor.submit(this::recompute); // Lost CAS, resubmit } - private void doWork(CompletableFuture lazyValue) + private void doWork(Promise lazyValue) { T value = null; Throwable err = null; @@ -89,9 +92,9 @@ public class RecomputingSupplier } if (err == null) - lazyValue.complete(value); + lazyValue.trySuccess(value); else - lazyValue.completeExceptionally(err); + lazyValue.tryFailure(err); } private static void sanityCheck(boolean check) @@ -101,7 +104,7 @@ public class RecomputingSupplier public T get(long timeout, TimeUnit timeUnit) throws InterruptedException, ExecutionException, TimeoutException { - CompletableFuture lazyValue = cached.get(); + Future lazyValue = cached.get(); // recompute was never called yet, return null. if (lazyValue == null) diff --git a/src/java/org/apache/cassandra/utils/WithResources.java b/src/java/org/apache/cassandra/utils/WithResources.java new file mode 100644 index 0000000000..76e218cb4d --- /dev/null +++ b/src/java/org/apache/cassandra/utils/WithResources.java @@ -0,0 +1,88 @@ +/* + * 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 org.apache.cassandra.concurrent.ExecutorPlus; + +/** + * A generic interface for encapsulating a Runnable task with related work before and after execution, + * using the built-in try-with-resources functionality offered by {@link Closeable}. + * + * See {@link ExecutorPlus#execute(WithResources, Runnable)} + */ +public interface WithResources +{ + static class None implements WithResources + { + static final None INSTANCE = new None(); + private None() {} + @Override + public Closeable get() + { + return () -> {}; + } + + @Override + public boolean isNoOp() + { + return true; + } + } + + /** + * Instantiate any necessary resources + * @return an object that closes any instantiated resources + */ + public Closeable get(); + + /** + * A convenience method to avoid unnecessary work. + * @return true iff this object performs no work when {@link #get()} is invoked, nor when {@link Closeable#close()} + * is invoked on the object it returns. + */ + default public boolean isNoOp() { return false; } + default public WithResources and(WithResources withResources) + { + return and(this, withResources); + } + static WithResources none() { return None.INSTANCE; } + + @SuppressWarnings("resource") + public static WithResources and(WithResources first, WithResources second) + { + if (second.isNoOp()) return first; + if (first.isNoOp()) return second; + return () -> { + Closeable a = first.get(); + try + { + Closeable b = second.get(); + return () -> { + try { a.close(); } + finally { b.close(); } + }; + } + catch (Throwable t) + { + try { a.close(); } catch (Throwable t2) { t.addSuppressed(t2); } + throw t; + } + }; + } +} diff --git a/src/java/org/apache/cassandra/utils/binlog/ExternalArchiver.java b/src/java/org/apache/cassandra/utils/binlog/ExternalArchiver.java index b444573d3e..173c539a82 100644 --- a/src/java/org/apache/cassandra/utils/binlog/ExternalArchiver.java +++ b/src/java/org/apache/cassandra/utils/binlog/ExternalArchiver.java @@ -25,7 +25,6 @@ import java.util.concurrent.DelayQueue; import java.util.concurrent.Delayed; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -43,6 +42,7 @@ import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; /** * Archives binary log files immediately when they are rolled using a configure archive command. @@ -61,7 +61,7 @@ public class ExternalArchiver implements BinLogArchiver */ private final DelayQueue archiveQueue = new DelayQueue<>(); private final String archiveCommand; - private final ExecutorService executor = Executors.newSingleThreadExecutor(new NamedThreadFactory("BinLogArchiver")); + private final ExecutorService executor = executorFactory().sequential("BinLogArchiver"); private final Path path; /** * for testing, to be able to make sure that the command is executed diff --git a/src/java/org/apache/cassandra/utils/concurrent/AbstractFuture.java b/src/java/org/apache/cassandra/utils/concurrent/AbstractFuture.java new file mode 100644 index 0000000000..111744546f --- /dev/null +++ b/src/java/org/apache/cassandra/utils/concurrent/AbstractFuture.java @@ -0,0 +1,489 @@ +/* + * 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.concurrent; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import javax.annotation.Nullable; + +import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.AsyncFunction; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.ListenableFuture; + +import io.netty.util.concurrent.GenericFutureListener; +import io.netty.util.internal.ThrowableUtil; +import org.apache.cassandra.utils.concurrent.ListenerList.CallbackBiConsumerListener; +import org.apache.cassandra.utils.concurrent.ListenerList.CallbackLambdaListener; +import org.apache.cassandra.utils.concurrent.ListenerList.CallbackListener; +import org.apache.cassandra.utils.concurrent.ListenerList.CallbackListenerWithExecutor; +import org.apache.cassandra.utils.concurrent.ListenerList.GenericFutureListenerList; +import org.apache.cassandra.utils.concurrent.ListenerList.RunnableWithExecutor; +import org.apache.cassandra.utils.concurrent.ListenerList.RunnableWithNotifyExecutor; + +import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater; +import static org.apache.cassandra.utils.concurrent.ListenerList.notifyListener; + +/** + * Our default {@link Future} implementation, with all state being managed without locks (except those used by the JVM). + * + * Some implementation comments versus Netty's default promise: + * - We permit efficient initial state declaration, avoiding unnecessary CAS or lock acquisitions when mutating + * a Promise we are ourselves constructing (and can easily add more; only those we use have been added) + * - We guarantee the order of invocation of listeners (and callbacks etc, and with respect to each other) + * - We save some space when registering listeners, especially if there is only one listener, as we perform no + * extra allocations in this case. + * - We implement our invocation list as a concurrent stack, that is cleared on notification + * - We handle special values slightly differently. + * - We do not use a special value for null, instead using a special value to indicate the result has not been set. + * This means that once isSuccess() holds, the result must be a correctly typed object (modulo generics pitfalls). + * - All special values are also instances of FailureHolder, which simplifies a number of the logical conditions. + */ +@SuppressWarnings({ "rawtypes", "unchecked" }) +public abstract class AbstractFuture implements Future +{ + protected static final FailureHolder UNSET = new FailureHolder(null); + protected static final FailureHolder UNCANCELLABLE = new FailureHolder(null); + protected static final FailureHolder CANCELLED = new FailureHolder(ThrowableUtil.unknownStackTrace(new CancellationException(), AbstractFuture.class, "cancel(...)")); + + static class FailureHolder + { + final Throwable cause; + FailureHolder(Throwable cause) + { + this.cause = cause; + } + } + + private static Throwable cause(Object result) + { + return result instanceof FailureHolder ? ((FailureHolder) result).cause : null; + } + static boolean isSuccess(Object result) + { + return !(result instanceof FailureHolder); + } + static boolean isCancelled(Object result) + { + return result == CANCELLED; + } + static boolean isDone(Object result) + { + return result != UNSET && result != UNCANCELLABLE; + } + + volatile Object result; + volatile ListenerList listeners; // either a ListenerList or GenericFutureListener (or null) + static final AtomicReferenceFieldUpdater resultUpdater = newUpdater(AbstractFuture.class, Object.class, "result"); + static final AtomicReferenceFieldUpdater listenersUpdater = newUpdater(AbstractFuture.class, ListenerList.class, "listeners"); + + protected AbstractFuture(FailureHolder initialState) + { + // TODO: document visibility of constructor (i.e. must be safe published) + resultUpdater.lazySet(this, initialState); + } + + public AbstractFuture() + { + this(UNSET); + } + + protected AbstractFuture(V immediateSuccess) + { + resultUpdater.lazySet(this, immediateSuccess); + } + + protected AbstractFuture(Throwable immediateFailure) + { + this(new FailureHolder(immediateFailure)); + } + + protected AbstractFuture(GenericFutureListener> listener) + { + this(); + listenersUpdater.lazySet(this, new GenericFutureListenerList(listener)); + } + + protected AbstractFuture(FailureHolder initialState, GenericFutureListener> listener) + { + this(initialState); + listenersUpdater.lazySet(this, new GenericFutureListenerList(listener)); + } + + public Executor notifyExecutor() + { + return null; + } + + protected boolean trySuccess(V v) + { + return trySet(v); + } + + protected boolean tryFailure(Throwable throwable) + { + return trySet(new FailureHolder(throwable)); + } + + protected boolean setUncancellable() + { + if (trySet(UNCANCELLABLE)) + return true; + return isUncancellable(); + } + + protected boolean setUncancellableExclusive() + { + return trySet(UNCANCELLABLE); + } + + protected boolean isUncancellable() + { + Object result = this.result; + return result == UNCANCELLABLE || (isDone(result) && !isCancelled(result)); + } + + public boolean cancel(boolean b) + { + return trySet(CANCELLED); + } + + /** + * Shared implementation of various promise completion methods. + * Updates the result if it is possible to do so, returning success/failure. + * + * If the promise is UNSET the new value will succeed; + * if it is UNCANCELLABLE it will succeed only if the new value is not CANCELLED + * otherwise it will fail, as isDone() is implied + * + * If the update succeeds, and the new state implies isDone(), any listeners and waiters will be notified + */ + abstract boolean trySet(Object v); + + @Override + public boolean isSuccess() + { + return isSuccess(result); + } + + @Override + public boolean isCancelled() + { + return isCancelled(result); + } + + @Override + public boolean isDone() + { + return isDone(result); + } + + @Override + public boolean isCancellable() + { + return result == UNSET; + } + + @Override + public Throwable cause() + { + return cause(result); + } + + /** + * if isSuccess(), returns the value, otherwise returns null + */ + @Override + public V getNow() + { + Object result = this.result; + if (isSuccess(result)) + return (V) result; + return null; + } + + /** + * Shared implementation of get() after suitable await(); assumes isDone(), and returns + * either the success result or throws the suitable exception under failure + */ + protected V getWhenDone() throws ExecutionException + { + Object result = this.result; + if (isSuccess(result)) + return (V) result; + if (result == CANCELLED) + throw new CancellationException(); + throw new ExecutionException(((FailureHolder) result).cause); + } + + @Override + public V get() throws InterruptedException, ExecutionException + { + await(); + return getWhenDone(); + } + + @Override + public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException + { + if (!await(timeout, unit)) + throw new TimeoutException(); + return getWhenDone(); + } + + /** + * Logically append {@code newListener} to {@link #listeners} + * (at this stage it is a stack, so we actually prepend) + */ + abstract void appendListener(ListenerList newListener); + + /** + * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively + * + * See {@link #addListener(GenericFutureListener)} for ordering semantics. + */ + @Override + public AbstractFuture addCallback(FutureCallback callback) + { + appendListener(new CallbackListener<>(this, callback)); + return this; + } + + /** + * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively + * + * See {@link #addListener(GenericFutureListener)} for ordering semantics. + */ + @Override + public AbstractFuture addCallback(BiConsumer callback) + { + appendListener(new CallbackBiConsumerListener<>(this, callback)); + return this; + } + + /** + * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively + * + * See {@link #addListener(GenericFutureListener)} for ordering semantics. + */ + @Override + public AbstractFuture addCallback(FutureCallback callback, Executor executor) + { + Preconditions.checkNotNull(executor); + appendListener(new CallbackListenerWithExecutor<>(this, callback, executor)); + return this; + } + + /** + * Support more fluid version of {@link com.google.common.util.concurrent.Futures#addCallback} + * + * See {@link #addListener(GenericFutureListener)} for ordering semantics. + */ + @Override + public AbstractFuture addCallback(Consumer onSuccess, Consumer onFailure) + { + appendListener(new CallbackLambdaListener<>(this, onSuccess, onFailure)); + return this; + } + + /** + * Support {@link com.google.common.util.concurrent.Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)} natively + * + * See {@link #addListener(GenericFutureListener)} for ordering semantics. + */ + @Override + public Future andThenAsync(Function> andThen) + { + return andThenAsync(andThen, null); + } + + /** + * Support {@link com.google.common.util.concurrent.Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)} natively + * + * See {@link #addListener(GenericFutureListener)} for ordering semantics. + */ + protected Future andThenAsync(AbstractFuture result, Function> andThen, @Nullable Executor executor) + { + addListener(() -> { + try + { + if (isSuccess()) andThen.apply(getNow()).addListener(propagate(result)); + else result.tryFailure(cause()); + } + catch (Throwable t) + { + result.tryFailure(t); + throw t; + } + }, executor); + return result; + } + + /** + * Add a listener to be invoked once this future completes. + * Listeners are submitted to {@link #notifyExecutor} in the order they are added (or the specified executor + * in the case of {@link #addListener(Runnable, Executor)}. + * if {@link #notifyExecutor} is unset, they are invoked in the order they are added. + * The ordering holds across all variants of this method. + */ + public Future addListener(GenericFutureListener> listener) + { + appendListener(new GenericFutureListenerList(listener)); + return this; + } + + /** + * Add a listener to be invoked once this future completes. + * Listeners are submitted to their {@code #executor} (or {@link #notifyExecutor}) in the order they are added; + * if {@link #notifyExecutor} is unset, they are invoked in the order they are added. + * The ordering holds across all variants of this method. + */ + public void addListener(Runnable task, @Nullable Executor executor) + { + appendListener(new RunnableWithExecutor(task, executor)); + } + + /** + * Add a listener to be invoked once this future completes. + * Listeners are submitted to {@link #notifyExecutor} in the order they are added (or the specified executor + * in the case of {@link #addListener(Runnable, Executor)}. + * if {@link #notifyExecutor} is unset, they are invoked in the order they are added. + * The ordering holds across all variants of this method. + */ + public void addListener(Runnable task) + { + appendListener(new RunnableWithNotifyExecutor(task)); + } + + @SuppressWarnings("unchecked") + @Override + public Future addListeners(GenericFutureListener>... listeners) + { + // this could be more efficient if we cared, but we do not + return addListener(future -> { + for (GenericFutureListener> listener : listeners) + notifyListener((GenericFutureListener>)listener, future); + }); + } + + @Override + public Future removeListener(GenericFutureListener> listener) + { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("unchecked") + @Override + public Future removeListeners(GenericFutureListener>... listeners) + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean await(long timeout, TimeUnit unit) throws InterruptedException + { + return Awaitable.await(this, timeout, unit); + } + + @Override + public boolean awaitThrowUncheckedOnInterrupt(long time, TimeUnit units) throws UncheckedInterruptedException + { + return Awaitable.awaitThrowUncheckedOnInterrupt(this, time, units); + } + + @Override + public boolean awaitUninterruptibly(long timeout, TimeUnit unit) + { + return Awaitable.awaitUninterruptibly(this, timeout, unit); + } + + @Override + public boolean awaitUntilThrowUncheckedOnInterrupt(long nanoTimeDeadline) throws UncheckedInterruptedException + { + return Awaitable.awaitUntilThrowUncheckedOnInterrupt(this, nanoTimeDeadline); + } + + @Override + public boolean awaitUntilUninterruptibly(long nanoTimeDeadline) + { + return Awaitable.awaitUntilUninterruptibly(this, nanoTimeDeadline); + } + + /** + * Wait for this future to complete {@link Awaitable#awaitUninterruptibly()} + */ + @Override + public Future awaitUninterruptibly() + { + return Awaitable.awaitUninterruptibly(this); + } + + /** + * Wait for this future to complete {@link Awaitable#awaitThrowUncheckedOnInterrupt()} + */ + @Override + public Future awaitThrowUncheckedOnInterrupt() throws UncheckedInterruptedException + { + return Awaitable.awaitThrowUncheckedOnInterrupt(this); + } + + public String toString() + { + String description = description(); + String state = state(); + return description == null ? state : (state + ' ' + description); + } + + private String state() + { + Object result = this.result; + if (isSuccess(result)) + return "(success: " + result + ')'; + if (result == UNCANCELLABLE) + return "(uncancellable)"; + if (result == CANCELLED) + return "(cancelled)"; + if (isDone(result)) + return "(failure: " + ((FailureHolder) result).cause + ')'; + return "(incomplete)"; + } + + protected String description() + { + return null; + } + + /** + * @return a listener that will propagate to {@code to} the result of the future it is invoked with + */ + private static GenericFutureListener> propagate(AbstractFuture to) + { + return from -> { + if (from.isSuccess()) to.trySuccess(from.getNow()); + else to.tryFailure(from.cause()); + }; + } +} diff --git a/src/java/org/apache/cassandra/utils/concurrent/AsyncFuture.java b/src/java/org/apache/cassandra/utils/concurrent/AsyncFuture.java index 8930a2a3c0..296584b38c 100644 --- a/src/java/org/apache/cassandra/utils/concurrent/AsyncFuture.java +++ b/src/java/org/apache/cassandra/utils/concurrent/AsyncFuture.java @@ -18,22 +18,15 @@ package org.apache.cassandra.utils.concurrent; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Function; import javax.annotation.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.google.common.util.concurrent.AsyncFunction; +import com.google.common.util.concurrent.ListenableFuture; -import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.GenericFutureListener; -import io.netty.util.internal.ThrowableUtil; - -import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater; /** * Our default {@link Future} implementation, with all state being managed without locks (except those used by the JVM). @@ -50,122 +43,41 @@ import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater * This means that once isSuccess() holds, the result must be a correctly typed object (modulo generics pitfalls). * - All special values are also instances of FailureHolder, which simplifies a number of the logical conditions. */ -@SuppressWarnings("rawtypes") -public class AsyncFuture extends Awaitable.AsyncAwaitable implements Future +public class AsyncFuture extends AbstractFuture { - private static final Logger logger = LoggerFactory.getLogger(AsyncFuture.class); - - protected static final FailureHolder UNSET = new FailureHolder(null); - protected static final FailureHolder UNCANCELLABLE = new FailureHolder(null); - protected static final FailureHolder CANCELLED = new FailureHolder(ThrowableUtil.unknownStackTrace(new CancellationException(), AsyncFuture.class, "cancel(...)")); - - static class FailureHolder - { - final Throwable cause; - FailureHolder(Throwable cause) - { - this.cause = cause; - } - } - - protected static Throwable cause(Object result) - { - return result instanceof FailureHolder ? ((FailureHolder) result).cause : null; - } - protected static boolean isSuccess(Object result) - { - return !(result instanceof FailureHolder); - } - protected static boolean isCancelled(Object result) - { - return result == CANCELLED; - } - protected static boolean isDone(Object result) - { - return result != UNSET && result != UNCANCELLABLE; - } - - private final @Nullable Executor notifyExecutor; - private volatile Object result; - private volatile GenericFutureListener> listeners; - private static final AtomicReferenceFieldUpdater resultUpdater = newUpdater(AsyncFuture.class, Object.class, "result"); - private static final AtomicReferenceFieldUpdater listenersUpdater = newUpdater(AsyncFuture.class, GenericFutureListener.class, "listeners"); - - private static final DeferredGenericFutureListener NOTIFYING = future -> {}; - - private static interface DeferredGenericFutureListener> extends GenericFutureListener {} + @SuppressWarnings({ "rawtypes" }) + private static final AtomicReferenceFieldUpdater waitingUpdater = AtomicReferenceFieldUpdater.newUpdater(AsyncFuture.class, WaitQueue.class, "waiting"); + @SuppressWarnings({ "unused" }) + private volatile WaitQueue waiting; public AsyncFuture() { - this(null, UNSET); + super(); } - public AsyncFuture(Executor notifyExecutor) + protected AsyncFuture(V immediateSuccess) { - this(notifyExecutor, UNSET); + super(immediateSuccess); } - protected AsyncFuture(Executor notifyExecutor, V immediateSuccess) + protected AsyncFuture(Throwable immediateFailure) { - resultUpdater.lazySet(this, immediateSuccess); - this.notifyExecutor = notifyExecutor; + super(immediateFailure); } - protected AsyncFuture(Executor notifyExecutor, Throwable immediateFailure) + protected AsyncFuture(FailureHolder initialState) { - resultUpdater.lazySet(this, new FailureHolder(immediateFailure)); - this.notifyExecutor = notifyExecutor; + super(initialState); } - protected AsyncFuture(Executor notifyExecutor, FailureHolder initialState) + protected AsyncFuture(GenericFutureListener> listener) { - resultUpdater.lazySet(this, initialState); - this.notifyExecutor = notifyExecutor; + super(listener); } - protected AsyncFuture(Executor notifyExecutor, GenericFutureListener> listener) + protected AsyncFuture(FailureHolder initialState, GenericFutureListener> listener) { - this(notifyExecutor); - this.listeners = listener; - } - - protected AsyncFuture(Executor notifyExecutor, FailureHolder initialState, GenericFutureListener> listener) - { - this(notifyExecutor, initialState); - this.listeners = listener; - } - - protected boolean trySuccess(V v) - { - return trySet(v); - } - - protected boolean tryFailure(Throwable throwable) - { - return trySet(new FailureHolder(throwable)); - } - - protected boolean setUncancellable() - { - if (trySet(UNCANCELLABLE)) - return true; - return isUncancellable(); - } - - protected boolean setUncancellableExclusive() - { - return trySet(UNCANCELLABLE); - } - - protected boolean isUncancellable() - { - Object result = this.result; - return result == UNCANCELLABLE || (isDone(result) && !isCancelled(result)); - } - - public boolean cancel(boolean b) - { - return trySet(CANCELLED); + super(initialState, listener); } /** @@ -178,203 +90,47 @@ public class AsyncFuture extends Awaitable.AsyncAwaitable implements Future newListener) { - Object result = this.result; - if (isSuccess(result)) - return (V) result; - return null; - } - - /** - * Shared implementation of get() after suitable await(); assumes isDone(), and returns - * either the success result or throws the suitable exception under failure - */ - protected V getWhenDone() throws ExecutionException - { - Object result = this.result; - if (isSuccess(result)) - return (V) result; - if (result == CANCELLED) - throw new CancellationException(); - throw new ExecutionException(((FailureHolder) result).cause); - } - - @Override - public V get() throws InterruptedException, ExecutionException - { - await(); - return getWhenDone(); - } - - @Override - public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException - { - if (!await(timeout, unit)) - throw new TimeoutException(); - return getWhenDone(); - } - - @Override - public Future addListener(GenericFutureListener> listener) - { - listenersUpdater.accumulateAndGet(this, listener, AsyncFuture::appendListener); + ListenerList.push(listenersUpdater, this, newListener); if (isDone()) - notifyListeners(); - return this; + ListenerList.notify(listenersUpdater, this); } - private void notifyListeners() - { - if (notifyExecutor != null) - { - // TODO: could generify to any executor able to say if already executing within - if (notifyExecutor instanceof EventExecutor && ((EventExecutor) notifyExecutor).inEventLoop()) - doNotifyListenersExclusive(); - else if (listeners != null) // submit this method, to guarantee we invoke in the submitted order - notifyExecutor.execute(this::doNotifyListenersExclusive); - } - else - { - doNotifyListeners(); - } - } - - private void doNotifyListeners() - { - @SuppressWarnings("rawtypes") GenericFutureListener listeners; - while (true) - { - listeners = this.listeners; - if (listeners == null || listeners instanceof DeferredGenericFutureListener) - return; // either no listeners, or we are already notifying listeners, so we'll get to the new one when ready - - if (listenersUpdater.compareAndSet(this, listeners, NOTIFYING)) - { - while (true) - { - invokeListener(listeners, this); - if (listenersUpdater.compareAndSet(this, NOTIFYING, null)) - return; - listeners = listenersUpdater.getAndSet(this, NOTIFYING); - } - } - } - } - - private void doNotifyListenersExclusive() - { - if (listeners == null || listeners instanceof DeferredGenericFutureListener) - return; // either no listeners, or we are already notifying listeners, so we'll get to the new one when ready - - while (true) - { - @SuppressWarnings("rawtypes") GenericFutureListener listeners = listenersUpdater.getAndSet(this, NOTIFYING); - if (listeners != null) - invokeListener(listeners, this); - - if (listenersUpdater.compareAndSet(this, NOTIFYING, null)) - return; - } - } - - @SuppressWarnings("unchecked") + /** + * Support {@link com.google.common.util.concurrent.Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)} natively + * + * See {@link #addListener(GenericFutureListener)} for ordering semantics. + */ @Override - public Future addListeners(GenericFutureListener>... listeners) + public Future andThenAsync(Function> andThen, @Nullable Executor executor) { - // this could be more efficient if we cared, but we do not - return addListener(future -> { - for (GenericFutureListener> listener : listeners) - invokeListener((GenericFutureListener>)listener, future); - }); - } - - @Override - public Future removeListener(GenericFutureListener> listener) - { - throw new UnsupportedOperationException(); - } - - @SuppressWarnings("unchecked") - @Override - public Future removeListeners(GenericFutureListener>... listeners) - { - throw new UnsupportedOperationException(); - } - - private static > GenericFutureListener appendListener(GenericFutureListener prevListener, GenericFutureListener newListener) - { - GenericFutureListener result = newListener; - - if (prevListener != null && prevListener != NOTIFYING) - { - result = future -> { - invokeListener(prevListener, future); - // we will wrap the outer invocation with invokeListener, so no need to do it here too - newListener.operationComplete(future); - }; - } - - if (prevListener instanceof DeferredGenericFutureListener) - { - GenericFutureListener wrap = result; - result = (DeferredGenericFutureListener) wrap::operationComplete; - } - - return result; + return andThenAsync(new AsyncFuture<>(), andThen, executor); } /** @@ -383,62 +139,14 @@ public class AsyncFuture extends Awaitable.AsyncAwaitable implements Future await() throws InterruptedException { - super.await(); - return this; + //noinspection unchecked + return AsyncAwaitable.await(waitingUpdater, Future::isDone, this); } - /** - * Wait for this future to complete {@link Awaitable#awaitUninterruptibly()} - */ @Override - public AsyncFuture awaitUninterruptibly() + public boolean awaitUntil(long nanoTimeDeadline) throws InterruptedException { - super.awaitUninterruptibly(); - return this; - } - - /** - * Wait for this future to complete {@link Awaitable#awaitThrowUncheckedOnInterrupt()} - */ - @Override - public AsyncFuture awaitThrowUncheckedOnInterrupt() throws UncheckedInterruptedException - { - super.awaitThrowUncheckedOnInterrupt(); - return this; - } - - /** - * {@link AsyncAwaitable#isSignalled()} - */ - @Override - protected boolean isSignalled() - { - return isDone(result); - } - - public String toString() - { - Object result = this.result; - if (isSuccess(result)) - return "(success: " + result + ')'; - if (result == UNCANCELLABLE) - return "(uncancellable)"; - if (result == CANCELLED) - return "(cancelled)"; - if (isDone(result)) - return "(failure: " + ((FailureHolder) result).cause + ')'; - return "(incomplete)"; - } - - protected static > void invokeListener(GenericFutureListener listener, F future) - { - try - { - listener.operationComplete(future); - } - catch (Throwable t) - { - logger.error("Failed to invoke listener {} to {}", listener, future, t); - } + return AsyncAwaitable.awaitUntil(waitingUpdater, Future::isDone, this, nanoTimeDeadline); } } + diff --git a/src/java/org/apache/cassandra/utils/concurrent/AsyncPromise.java b/src/java/org/apache/cassandra/utils/concurrent/AsyncPromise.java index 3336ccce6a..48c55a8eec 100644 --- a/src/java/org/apache/cassandra/utils/concurrent/AsyncPromise.java +++ b/src/java/org/apache/cassandra/utils/concurrent/AsyncPromise.java @@ -19,8 +19,10 @@ package org.apache.cassandra.utils.concurrent; import java.util.concurrent.Executor; +import java.util.function.Consumer; + +import com.google.common.util.concurrent.FutureCallback; -import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; @@ -29,46 +31,68 @@ import io.netty.util.concurrent.GenericFutureListener; */ public class AsyncPromise extends AsyncFuture implements Promise { + public static class WithExecutor extends AsyncPromise + { + final Executor notifyExecutor; + protected WithExecutor(Executor notifyExecutor) + { + this.notifyExecutor = notifyExecutor; + } + + protected WithExecutor(Executor notifyExecutor, FailureHolder initialState) + { + super(initialState); + this.notifyExecutor = notifyExecutor; + } + + protected WithExecutor(Executor notifyExecutor, GenericFutureListener> listener) + { + super(listener); + this.notifyExecutor = notifyExecutor; + } + + @Override + public Executor notifyExecutor() + { + return notifyExecutor; + } + } + public AsyncPromise() {} - public AsyncPromise(Executor notifyExecutor) + AsyncPromise(FailureHolder initialState) { - super(notifyExecutor); + super(initialState); } - AsyncPromise(Executor notifyExecutor, V immediateSuccess) + public AsyncPromise(GenericFutureListener> listener) { - super(notifyExecutor, immediateSuccess); + super(listener); } - AsyncPromise(Executor notifyExecutor, Throwable immediateFailure) + AsyncPromise(FailureHolder initialState, GenericFutureListener> listener) { - super(notifyExecutor, immediateFailure); + super(initialState, listener); } - AsyncPromise(Executor notifyExecutor, FailureHolder initialState) + public static AsyncPromise withExecutor(Executor executor) { - super(notifyExecutor, initialState); + return new AsyncPromise.WithExecutor<>(executor); } - public AsyncPromise(Executor notifyExecutor, GenericFutureListener> listener) + public static AsyncPromise uncancellable() { - super(notifyExecutor, listener); + return new AsyncPromise<>(UNCANCELLABLE); } - AsyncPromise(Executor notifyExecutor, FailureHolder initialState, GenericFutureListener> listener) + public static AsyncPromise uncancellable(Executor executor) { - super(notifyExecutor, initialState, listener); + return new WithExecutor<>(executor, UNCANCELLABLE); } - public static AsyncPromise uncancellable(EventExecutor executor) + public static AsyncPromise uncancellable(GenericFutureListener> listener) { - return new AsyncPromise<>(executor, UNCANCELLABLE); - } - - public static AsyncPromise uncancellable(EventExecutor executor, GenericFutureListener> listener) - { - return new AsyncPromise<>(executor, UNCANCELLABLE, listener); + return new AsyncPromise<>(UNCANCELLABLE, listener); } /** @@ -164,31 +188,52 @@ public class AsyncPromise extends AsyncFuture implements Promise } @Override - public Promise addListener(GenericFutureListener> listener) + public AsyncPromise addListener(GenericFutureListener> listener) { super.addListener(listener); return this; } @Override - public Promise addListeners(GenericFutureListener>... listeners) + public AsyncPromise addListeners(GenericFutureListener>... listeners) { super.addListeners(listeners); return this; } @Override - public Promise removeListener(GenericFutureListener> listener) + public AsyncPromise removeListener(GenericFutureListener> listener) { throw new UnsupportedOperationException(); } @Override - public Promise removeListeners(GenericFutureListener>... listeners) + public AsyncPromise removeListeners(GenericFutureListener>... listeners) { throw new UnsupportedOperationException(); } + @Override + public AsyncPromise addCallback(FutureCallback callback) + { + super.addCallback(callback); + return this; + } + + @Override + public AsyncPromise addCallback(FutureCallback callback, Executor executor) + { + super.addCallback(callback, executor); + return this; + } + + @Override + public AsyncPromise addCallback(Consumer onSuccess, Consumer onFailure) + { + super.addCallback(onSuccess, onFailure); + return this; + } + /** * Wait for this promise to complete * @throws InterruptedException if interrupted @@ -211,7 +256,7 @@ public class AsyncPromise extends AsyncFuture implements Promise } /** - * Wait for this promise to complete, throwing any interrupt as an UnhandledInterruptedException + * Wait for this promise to complete, throwing any interrupt as an UncheckedInterruptedException * @throws UncheckedInterruptedException if interrupted */ @Override @@ -220,4 +265,5 @@ public class AsyncPromise extends AsyncFuture implements Promise super.awaitThrowUncheckedOnInterrupt(); return this; } -} \ No newline at end of file +} + diff --git a/src/java/org/apache/cassandra/utils/concurrent/Awaitable.java b/src/java/org/apache/cassandra/utils/concurrent/Awaitable.java index 6be7371895..5b3d315534 100644 --- a/src/java/org/apache/cassandra/utils/concurrent/Awaitable.java +++ b/src/java/org/apache/cassandra/utils/concurrent/Awaitable.java @@ -191,7 +191,6 @@ public interface Awaitable abstract class AbstractAwaitable implements Awaitable { - // WARNING: if you extend this class, be sure to consider interaction with WaitManager to ensure Simulator compatibility protected AbstractAwaitable() {} /** @@ -321,7 +320,6 @@ public interface Awaitable private static final AtomicReferenceFieldUpdater waitingUpdater = AtomicReferenceFieldUpdater.newUpdater(AsyncAwaitable.class, WaitQueue.class, "waiting"); private volatile WaitQueue waiting; - // WARNING: if you extend this class, be sure to consider interaction with WaitManager to ensure Simulator compatibility protected AsyncAwaitable() {} /** diff --git a/src/java/org/apache/cassandra/utils/concurrent/Future.java b/src/java/org/apache/cassandra/utils/concurrent/Future.java index c23f9e8239..e21a654d9d 100644 --- a/src/java/org/apache/cassandra/utils/concurrent/Future.java +++ b/src/java/org/apache/cassandra/utils/concurrent/Future.java @@ -19,9 +19,16 @@ package org.apache.cassandra.utils.concurrent; import java.util.concurrent.Executor; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import com.google.common.util.concurrent.AsyncFunction; +import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.ListenableFuture; +import io.netty.util.concurrent.GenericFutureListener; + import io.netty.util.internal.PlatformDependent; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -46,7 +53,7 @@ public interface Future extends io.netty.util.concurrent.Future, Listenabl Future awaitUninterruptibly(); /** - * Wait indefinitely for this promise to complete, throwing any interrupt as an UnhandledInterruptedException + * Wait indefinitely for this promise to complete, throwing any interrupt as an UncheckedInterruptedException * @throws UncheckedInterruptedException if interrupted */ @Override @@ -99,23 +106,56 @@ public interface Future extends io.netty.util.concurrent.Future, Listenabl return awaitUninterruptibly(l, MILLISECONDS); } + /** + * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively + */ + Future addCallback(BiConsumer callback); + + /** + * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively + */ + Future addCallback(FutureCallback callback); + + /** + * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively + */ + Future addCallback(FutureCallback callback, Executor executor); + + /** + * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively + */ + Future addCallback(Consumer onSuccess, Consumer onFailure); + + /** + * Support {@link com.google.common.util.concurrent.Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)} natively + */ + Future andThenAsync(Function> andThen); + + /** + * Support {@link com.google.common.util.concurrent.Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)} natively + */ + Future andThenAsync(Function> andThen, Executor executor); + /** * Invoke {@code runnable} on completion, using {@code executor}. + * * Tasks are submitted to their executors in the order they were added to this Future. */ @Override - default void addListener(Runnable runnable, Executor executor) - { - addListener(future -> executor.execute(runnable)); - } + void addListener(Runnable runnable, Executor executor); /** * Invoke {@code runnable} on completion. Depending on the implementation and its configuration, this * may be executed immediately by the notifying/completing thread, or asynchronously by an executor. * Tasks are executed, or submitted to the executor, in the order they were added to this Future. */ - default void addListener(Runnable runnable) - { - addListener(future -> runnable.run()); - } + void addListener(Runnable runnable); + + Executor notifyExecutor(); + + @Override Future addListener(GenericFutureListener> genericFutureListener); + @Override Future addListeners(GenericFutureListener>... genericFutureListeners); + @Override Future removeListener(GenericFutureListener> genericFutureListener); + @Override Future removeListeners(GenericFutureListener>... genericFutureListeners); } + diff --git a/src/java/org/apache/cassandra/utils/concurrent/FutureCombiner.java b/src/java/org/apache/cassandra/utils/concurrent/FutureCombiner.java new file mode 100644 index 0000000000..e47feff096 --- /dev/null +++ b/src/java/org/apache/cassandra/utils/concurrent/FutureCombiner.java @@ -0,0 +1,262 @@ +/* + * 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.concurrent; + +import io.netty.util.concurrent.GenericFutureListener; +import io.netty.util.concurrent.GlobalEventExecutor; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import com.google.common.base.Preconditions; + +/** + * Netty's PromiseCombiner is not threadsafe, and we combine futures from multiple event executors. + * + * This class groups a number of Future into a single logical Future, by registering a listener to each that + * decrements a shared counter; if any of them fail, the FutureCombiner is completed with the first cause, + * but in all scenario only completes when all underlying future have completed (exceptionally or otherwise) + * + * This Future is always uncancellable. + * + * We extend AsyncFuture, and simply provide it an uncancellable Promise that will be completed by the listeners + * registered to the input futures. + */ +public class FutureCombiner extends AsyncFuture +{ + private interface ListenerFactory + { + Listener create(int count, Supplier onSuccess, FutureCombiner complete); + } + + /** + * Tracks completion; once all futures have completed, invokes {@link Listener#complete#trySuccess(Object)} with {@link Listener#onSuccess}. + * Never invokes failure on {@link Listener#complete}. + */ + private static class Listener extends AtomicInteger implements GenericFutureListener> + { + Supplier onSuccess; // non-final so we can release resources immediately when failing fast + final FutureCombiner complete; + + Listener(int count, Supplier onSuccess, FutureCombiner complete) + { + super(count); + Preconditions.checkNotNull(onSuccess); + this.onSuccess = onSuccess; + this.complete = complete; + } + + @Override + public void operationComplete(io.netty.util.concurrent.Future result) + { + if (0 == decrementAndGet()) + onCompletion(); + } + + void onCompletion() + { + complete.trySuccess(onSuccess.get()); + onSuccess = null; + } + } + + /** + * Tracks completion; once all futures have completed, invokes {@link Listener#complete#trySuccess(Object)} with {@link Listener#onSuccess}. + * If any future fails, immediately propagates this failure and releases associated resources. + */ + private static class FailFastListener extends Listener + { + FailFastListener(int count, Supplier onSuccess, FutureCombiner complete) + { + super(count, onSuccess, complete); + } + + @Override + public void operationComplete(io.netty.util.concurrent.Future result) + { + if (!result.isSuccess()) + { + onSuccess = null; + complete.tryFailure(result.cause()); + } + else + { + super.operationComplete(result); + } + } + } + + /** + * Tracks completion; once all futures have completed, invokes {@link Listener#complete#trySuccess(Object)} with {@link Listener#onSuccess}. + * If any future fails we propagate this failure, but only once all have completed. + */ + private static class FailSlowListener extends Listener + { + private static final AtomicReferenceFieldUpdater firstCauseUpdater = + AtomicReferenceFieldUpdater.newUpdater(FailSlowListener.class, Throwable.class, "firstCause"); + + private volatile Throwable firstCause; + + FailSlowListener(int count, Supplier onSuccess, FutureCombiner complete) + { + super(count, onSuccess, complete); + } + + @Override + void onCompletion() + { + if (onSuccess == null) + complete.tryFailure(firstCause); + else + super.onCompletion(); + } + + @Override + public void operationComplete(io.netty.util.concurrent.Future result) + { + if (!result.isSuccess()) + { + onSuccess = null; + firstCauseUpdater.compareAndSet(FailSlowListener.this, null, result.cause()); + } + + super.operationComplete(result); + } + } + + private volatile Collection> propagateCancellation; + + private FutureCombiner(Collection> combine, Supplier resultSupplier, ListenerFactory listenerFactory) + { + if (combine.isEmpty()) + { + trySuccess(null); + } + else + { + Listener listener = listenerFactory.create(combine.size(), resultSupplier, this); + combine.forEach(f -> { + if (f.isDone()) listener.operationComplete((io.netty.util.concurrent.Future) f); + else f.addListener(listener); + }); + } + } + + @Override + protected boolean setUncancellable() + { + if (!super.setUncancellable()) + return false; + propagateCancellation = null; + return true; + } + + @Override + protected boolean setUncancellableExclusive() + { + if (!super.setUncancellableExclusive()) + return false; + propagateCancellation = null; + return true; + } + + @Override + protected boolean trySuccess(T t) + { + if (!super.trySuccess(t)) + return false; + propagateCancellation = null; + return true; + } + + @Override + protected boolean tryFailure(Throwable throwable) + { + if (!super.tryFailure(throwable)) + return false; + propagateCancellation = null; + return true; + } + + @Override + public boolean cancel(boolean b) + { + if (!super.cancel(b)) + return false; + Collection> propagate = propagateCancellation; + propagateCancellation = null; + if (propagate != null) + propagate.forEach(f -> f.cancel(b)); + return true; + } + + /** + * Waits for all of {@code futures} to complete, only propagating failures on completion + */ + public static FutureCombiner nettySuccessListener(Collection> futures) + { + return new FutureCombiner(futures, () -> null, FailSlowListener::new) + { + @Override + public Executor notifyExecutor() + { + return GlobalEventExecutor.INSTANCE; + } + }; + } + + /** + * Waits only until the first failure, or until all have succeeded. + * Returns a list of results if successful; an exception if any failed. + * + * @param futures futures to wait for completion of + * @return a Future containing all results of {@code futures} + */ + public static Future> allOf(Collection> futures) + { + if (futures.isEmpty()) + return ImmediateFuture.success(Collections.emptyList()); + + return new FutureCombiner<>(futures, () -> futures.stream().map(f -> f.getNow()).collect(Collectors.toList()), FailFastListener::new); + } + + /** + * Waits for all futures to complete, returning a list containing values of all successful input futures. This + * emulates Guava's Futures::successfulAsList in that results will be in the same order as inputs and any + * non-success value (e.g. failure or cancellation) will be replaced by null. + * @param futures futures to wait for completion of + * @return a Future containing all successful results of {@code futures} and nulls for non-successful futures + */ + public static Future> successfulOf(List> futures) + { + if (futures.isEmpty()) + return ImmediateFuture.success(Collections.emptyList()); + + return new FutureCombiner<>(futures, + () -> futures.stream() + .map(f -> f.isSuccess() ? f.getNow() : null) + .collect(Collectors.toList()), + Listener::new); + } +} diff --git a/src/java/org/apache/cassandra/utils/concurrent/ImmediateFuture.java b/src/java/org/apache/cassandra/utils/concurrent/ImmediateFuture.java new file mode 100644 index 0000000000..159d58033f --- /dev/null +++ b/src/java/org/apache/cassandra/utils/concurrent/ImmediateFuture.java @@ -0,0 +1,52 @@ +/* + * 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.concurrent; + +public class ImmediateFuture extends AsyncFuture +{ + private ImmediateFuture(V value) + { + super(value); + } + + private ImmediateFuture(Throwable cause) + { + super(cause); + } + + private ImmediateFuture(FailureHolder failure) + { + super(failure); + } + + public static ImmediateFuture success(V value) + { + return new ImmediateFuture<>(value); + } + + public static ImmediateFuture failure(Throwable cause) + { + return new ImmediateFuture<>(cause); + } + + public static ImmediateFuture cancelled() + { + return new ImmediateFuture<>(CANCELLED); + } +} diff --git a/src/java/org/apache/cassandra/utils/concurrent/IntrusiveStack.java b/src/java/org/apache/cassandra/utils/concurrent/IntrusiveStack.java new file mode 100644 index 0000000000..6f944d2476 --- /dev/null +++ b/src/java/org/apache/cassandra/utils/concurrent/IntrusiveStack.java @@ -0,0 +1,156 @@ +/* + * 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.concurrent; + +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.BiFunction; +import java.util.function.Consumer; + +import net.nicoulaj.compilecommand.annotations.Inline; + +/** + * An efficient stack/list that is expected to be ordinarily either empty or close to, and for which + * we need concurrent insertions and do not need to support removal - i.e. the list is semi immutable. + * + * This is an intrusive stack, and for simplicity we treat all + * + * @param + */ +public class IntrusiveStack> implements Iterable +{ + static class Itr> implements Iterator + { + private T next; + + Itr(T next) + { + this.next = next; + } + + @Override + public boolean hasNext() + { + return next != null; + } + + @Override + public T next() + { + T result = next; + next = result.next; + return result; + } + } + + T next; + + @Inline + protected static > void push(AtomicReferenceFieldUpdater headUpdater, O owner, T prepend) + { + push(headUpdater, owner, prepend, (prev, next) -> { + next.next = prev; + return next; + }); + } + + protected static > void push(AtomicReferenceFieldUpdater headUpdater, O owner, T prepend, BiFunction combine) + { + while (true) + { + T head = headUpdater.get(owner); + if (headUpdater.compareAndSet(owner, head, combine.apply(head, prepend))) + return; + } + } + + protected static > void pushExclusive(AtomicReferenceFieldUpdater headUpdater, O owner, T prepend, BiFunction combine) + { + T head = headUpdater.get(owner); + headUpdater.lazySet(owner, combine.apply(head, prepend)); + } + + protected static , O> Iterable iterable(AtomicReferenceFieldUpdater headUpdater, O owner) + { + return iterable(headUpdater.get(owner)); + } + + protected static > Iterable iterable(T list) + { + return list == null ? () -> iterator(null) : list; + } + + protected static > Iterator iterator(T list) + { + return new Itr<>(list); + } + + protected static int size(IntrusiveStack list) + { + int size = 0; + while (list != null) + { + ++size; + list = list.next; + } + return size; + } + + // requires exclusive ownership (incl. with readers) + protected T reverse() + { + return reverse((T) this); + } + + // requires exclusive ownership (incl. with readers) + protected static > T reverse(T list) + { + T prev = null; + T cur = list; + while (cur != null) + { + T next = cur.next; + cur.next = prev; + prev = cur; + cur = next; + } + return prev; + } + + @Override + public void forEach(Consumer forEach) + { + forEach((T)this, forEach); + } + + protected static > void forEach(T list, Consumer forEach) + { + while (list != null) + { + forEach.accept(list); + list = list.next; + } + } + + @Override + public Iterator iterator() + { + return new Itr<>((T) this); + } +} diff --git a/src/java/org/apache/cassandra/utils/concurrent/ListenerList.java b/src/java/org/apache/cassandra/utils/concurrent/ListenerList.java new file mode 100644 index 0000000000..c150ea2a2f --- /dev/null +++ b/src/java/org/apache/cassandra/utils/concurrent/ListenerList.java @@ -0,0 +1,360 @@ +/* + * 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.concurrent; + +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import javax.annotation.Nullable; + +import com.google.common.util.concurrent.FutureCallback; + +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.GenericFutureListener; +import net.nicoulaj.compilecommand.annotations.Inline; +import org.apache.cassandra.concurrent.ExecutionFailure; +import org.apache.cassandra.concurrent.ExecutorPlus; +import org.apache.cassandra.concurrent.ImmediateExecutor; + +import static org.apache.cassandra.utils.concurrent.ListenerList.Notifying.NOTIFYING; + +/** + * Encapsulate one or more items in a linked-list that is immutable whilst shared, forming a prepend-only list (or stack). + * Once the list is ready to consume, exclusive ownership is taken by clearing the shared variable containing it, after + * which the list may be invoked using {@link #notify}, which reverses the list before invoking the work it contains. + */ +abstract class ListenerList extends IntrusiveStack> +{ + abstract void notifySelf(Executor notifyExecutor, Future future); + + static ListenerList pushHead(ListenerList prev, ListenerList next) + { + if (prev instanceof Notifying) + { + Notifying result = new Notifying(); + result.next = next; + next.next = prev == NOTIFYING ? null : prev; + return result; + } + next.next = prev; + return next; + } + + /** + * Logically append {@code newListener} to {@link #listeners} + * (at this stage it is a stack, so we actually prepend) + * + * @param newListener must be either a {@link ListenerList} or {@link GenericFutureListener} + */ + @Inline + static void push(AtomicReferenceFieldUpdater updater, T in, ListenerList newListener) + { + IntrusiveStack.push(updater, in, newListener, ListenerList::pushHead); + } + + /** + * Logically append {@code newListener} to {@link #listeners} + * (at this stage it is a stack, so we actually prepend) + * + * @param newListener must be either a {@link ListenerList} or {@link GenericFutureListener} + */ + @Inline + static void pushExclusive(AtomicReferenceFieldUpdater updater, T in, ListenerList newListener) + { + IntrusiveStack.pushExclusive(updater, in, newListener, ListenerList::pushHead); + } + + static > void notify(AtomicReferenceFieldUpdater updater, T in) + { + while (true) + { + ListenerList listeners = updater.get(in); + if (listeners == null || listeners instanceof Notifying) + return; // either no listeners, or we are already notifying listeners, so we'll get to the new one when ready + + if (updater.compareAndSet(in, listeners, NOTIFYING)) + { + while (true) + { + notify(listeners, in); + if (updater.compareAndSet(in, NOTIFYING, null)) + return; + + listeners = updater.getAndSet(in, NOTIFYING); + } + } + } + } + + /** + * Requires exclusive ownership of {@code head}. + * + * Task submission occurs in the order the operations were submitted; if all of the executors + * are immediate or unspecified this guarantees execution order. + * Tasks are submitted to the executor individually, as this simplifies semantics and + * we anticipate few listeners in practice, and even fewer with indirect executors. + * + * @param head must be either a {@link ListenerList} or {@link GenericFutureListener} + */ + static void notify(ListenerList head, Future future) + { + Executor notifyExecutor = future.notifyExecutor(); + if (inExecutor(notifyExecutor)) + notifyExecutor = null; + + notify(head, notifyExecutor, future); + } + + private static void notify(ListenerList head, Executor notifyExecutor, Future future) + { + head = reverse(head); + forEach(head, i -> i.notifySelf(notifyExecutor, future)); + } + + /** + * Notify {@code listener} on the invoking thread, handling any exceptions + */ + static > void notifyListener(GenericFutureListener listener, F future) + { + try + { + listener.operationComplete(future); + } + catch (Throwable t) + { + // TODO: suboptimal package interdependency - move FutureTask etc here? + ExecutionFailure.handle(t); + } + } + + /** + * Notify {@code listener} using {@code notifyExecutor} if set, and handling exceptions otherwise + */ + static > void notifyListener(Executor notifyExecutor, GenericFutureListener listener, F future) + { + if (notifyExecutor == null) notifyListener(listener, future); + else notifyExecutor.execute(() -> notifyListener(listener, future)); + } + + /** + * Notify {@code listener} using {@code notifyExecutor} if set, and handling exceptions otherwise + */ + static void notifyListener(@Nullable Executor notifyExecutor, Runnable listener) + { + if (notifyExecutor == null) ImmediateExecutor.INSTANCE.execute(listener); + else notifyExecutor.execute(listener); + } + + /** + * Encapsulate a regular listener in a linked list + */ + static class GenericFutureListenerList extends ListenerList + { + final GenericFutureListener listener; + + GenericFutureListenerList(GenericFutureListener listener) + { + this.listener = listener; + } + + @Override + void notifySelf(Executor notifyExecutor, Future future) + { + notifyListener(notifyExecutor, listener, future); + } + } + + /** + * Encapsulates the invocation of a callback with everything needed to submit for execution + * without incurring significant further overhead as a list + */ + static class CallbackListener extends ListenerList implements Runnable + { + final Future future; + final FutureCallback callback; + + CallbackListener(Future future, FutureCallback callback) + { + this.future = future; + this.callback = callback; + } + + @Override + public void run() + { + if (future.isSuccess()) callback.onSuccess(future.getNow()); + else callback.onFailure(future.cause()); + } + + @Override + void notifySelf(Executor notifyExecutor, Future future) + { + notifyListener(notifyExecutor, this); + } + } + + /** + * Encapsulates the invocation of a callback with everything needed to submit for execution + * without incurring significant further overhead as a list + */ + static class CallbackBiConsumerListener extends ListenerList implements Runnable + { + final Future future; + final BiConsumer callback; + + CallbackBiConsumerListener(Future future, BiConsumer callback) + { + this.future = future; + this.callback = callback; + } + + @Override + public void run() + { + if (future.isSuccess()) callback.accept(future.getNow(), null); + else callback.accept(null, future.cause()); + } + + @Override + void notifySelf(Executor notifyExecutor, Future future) + { + notifyListener(notifyExecutor, this); + } + } + + /** + * Encapsulates the invocation of a callback with everything needed to submit for execution + * without incurring significant further overhead as a list + */ + static class CallbackListenerWithExecutor extends CallbackListener + { + final Executor executor; + CallbackListenerWithExecutor(Future future, FutureCallback callback, Executor executor) + { + super(future, callback); + this.executor = executor; + } + + @Override + void notifySelf(Executor notifyExecutor, Future future) + { + notifyListener(executor, this); + } + } + + /** + * Encapsulates the invocation of a callback with everything needed to submit for execution + * without incurring significant further overhead as a list + */ + static class CallbackLambdaListener extends ListenerList implements Runnable + { + final Future future; + final Consumer onSuccess; + final Consumer onFailure; + + CallbackLambdaListener(Future future, Consumer onSuccess, Consumer onFailure) + { + this.future = future; + this.onSuccess = onSuccess; + this.onFailure = onFailure; + } + + @Override + public void run() + { + if (future.isSuccess()) onSuccess.accept(future.getNow()); + else onFailure.accept(future.cause()); + } + + @Override + void notifySelf(Executor notifyExecutor, Future future) + { + notifyListener(notifyExecutor, this); + } + } + + /** + * Encapsulate a task, executable on completion by {@link Future#notifyExecutor}, in a linked list for storing in + * {@link #listeners}, either as a listener on its own (since we need to encapsulate it anyway), or alongside + * other listeners in a list + */ + static class RunnableWithNotifyExecutor extends ListenerList + { + final Runnable task; + RunnableWithNotifyExecutor(Runnable task) + { + this.task = task; + } + + @Override + void notifySelf(Executor notifyExecutor, Future future) + { + notifyListener(notifyExecutor, task); + } + } + + /** + * Encapsulate a task executable on completion in a linked list for storing in {@link #listeners}, + * either as a listener on its own (since we need to encapsulate it anyway), or alongside other listeners + * in a list + */ + static class RunnableWithExecutor extends ListenerList + { + final Runnable task; + @Nullable final Executor executor; + RunnableWithExecutor(Runnable task, @Nullable Executor executor) + { + this.task = task; + this.executor = executor; + } + + @Override + void notifySelf(Executor notifyExecutor, Future future) + { + notifyListener(inExecutor(executor) ? null : executor, task); + } + } + + /** + * Dummy that indicates listeners are already being notified after the future was completed, + * so we cannot notify them ourselves whilst maintaining the guaranteed invocation order. + * The invocation of the list can be left to the thread already notifying listeners. + */ + static class Notifying extends ListenerList + { + static final Notifying NOTIFYING = new Notifying(); + + @Override + void notifySelf(Executor notifyExecutor, Future future) + { + } + } + + /** + * @return true iff the invoking thread is executing {@code executor} + */ + static boolean inExecutor(Executor executor) + { + return (executor instanceof EventExecutor && ((EventExecutor) executor).inEventLoop()) + || (executor instanceof ExecutorPlus && ((ExecutorPlus) executor).inExecutor()); + } +} + diff --git a/src/java/org/apache/cassandra/utils/concurrent/Promise.java b/src/java/org/apache/cassandra/utils/concurrent/Promise.java index 06620feb55..10d627541b 100644 --- a/src/java/org/apache/cassandra/utils/concurrent/Promise.java +++ b/src/java/org/apache/cassandra/utils/concurrent/Promise.java @@ -18,7 +18,9 @@ package org.apache.cassandra.utils.concurrent; +import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; +import java.util.function.Consumer; import com.google.common.util.concurrent.FutureCallback; @@ -46,6 +48,13 @@ public interface Promise extends io.netty.util.concurrent.Promise, Future< }); } + @Override + Promise addCallback(FutureCallback callback); + + Promise addCallback(FutureCallback callback, Executor executor); + + Promise addCallback(Consumer onSuccess, Consumer onFailure); + @Override Promise addListener(GenericFutureListener> var1); @@ -92,7 +101,7 @@ public interface Promise extends io.netty.util.concurrent.Promise, Future< Promise awaitUninterruptibly(); /** - * Wait indefinitely for this promise to complete, throwing any interrupt as an UnhandledInterruptedException + * Wait indefinitely for this promise to complete, throwing any interrupt as an UncheckedInterruptedException * @throws UncheckedInterruptedException if interrupted */ @Override @@ -112,3 +121,4 @@ public interface Promise extends io.netty.util.concurrent.Promise, Future< @Override Promise syncUninterruptibly(); } + diff --git a/src/java/org/apache/cassandra/utils/concurrent/Ref.java b/src/java/org/apache/cassandra/utils/concurrent/Ref.java index a373347445..26644dc88d 100644 --- a/src/java/org/apache/cassandra/utils/concurrent/Ref.java +++ b/src/java/org/apache/cassandra/utils/concurrent/Ref.java @@ -38,7 +38,7 @@ import org.slf4j.LoggerFactory; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; -import org.apache.cassandra.concurrent.NamedThreadFactory; +import org.apache.cassandra.concurrent.Shutdownable; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.db.lifecycle.View; @@ -52,8 +52,7 @@ import org.cliffc.high_scale_lib.NonBlockingHashMap; import static java.util.Collections.emptyList; -import static org.apache.cassandra.utils.ExecutorUtils.awaitTermination; -import static org.apache.cassandra.utils.ExecutorUtils.shutdownNow; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.utils.Throwables.maybeFail; import static org.apache.cassandra.utils.Throwables.merge; @@ -358,8 +357,8 @@ public final class Ref implements RefCounted static final Set> concurrentIterables = Collections.newSetFromMap(new IdentityHashMap<>()); private static final Set globallyExtant = Collections.newSetFromMap(new ConcurrentHashMap<>()); static final ReferenceQueue referenceQueue = new ReferenceQueue<>(); - private static final InfiniteLoopExecutor EXEC = new InfiniteLoopExecutor("Reference-Reaper", Ref::reapOneReference).start(); - static final ScheduledExecutorService STRONG_LEAK_DETECTOR = !DEBUG_ENABLED ? null : Executors.newScheduledThreadPool(1, new NamedThreadFactory("Strong-Reference-Leak-Detector")); + private static final Shutdownable EXEC = executorFactory().infiniteLoop("Reference-Reaper", Ref::reapOneReference, false); + static final ScheduledExecutorService STRONG_LEAK_DETECTOR = !DEBUG_ENABLED ? null : executorFactory().scheduled("Strong-Reference-Leak-Detector"); static { if (DEBUG_ENABLED) diff --git a/src/java/org/apache/cassandra/utils/concurrent/RunnableFuture.java b/src/java/org/apache/cassandra/utils/concurrent/RunnableFuture.java new file mode 100644 index 0000000000..74d8678c52 --- /dev/null +++ b/src/java/org/apache/cassandra/utils/concurrent/RunnableFuture.java @@ -0,0 +1,23 @@ +/* + * 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.concurrent; + +public interface RunnableFuture extends Future, java.util.concurrent.RunnableFuture +{ +} diff --git a/src/java/org/apache/cassandra/utils/concurrent/SyncFuture.java b/src/java/org/apache/cassandra/utils/concurrent/SyncFuture.java new file mode 100644 index 0000000000..17b0423ee1 --- /dev/null +++ b/src/java/org/apache/cassandra/utils/concurrent/SyncFuture.java @@ -0,0 +1,160 @@ +/* + * 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.concurrent; + +import java.util.concurrent.Executor; +import java.util.function.Function; + +import javax.annotation.Nullable; + +import com.google.common.util.concurrent.AsyncFunction; +import com.google.common.util.concurrent.ListenableFuture; + +import io.netty.util.concurrent.GenericFutureListener; + +import static org.apache.cassandra.utils.concurrent.Awaitable.SyncAwaitable.waitUntil; + +/** + * Netty's DefaultPromise uses a mutex to coordinate notifiers AND waiters between the eventLoop and the other threads. + * Since we register cross-thread listeners, this has the potential to block internode messaging for an unknown + * number of threads for an unknown period of time, if we are unlucky with the scheduler (which will certainly + * happen, just with some unknown but low periodicity) + * + * At the same time, we manage some other efficiencies: + * - We save some space when registering listeners, especially if there is only one listener, as we perform no + * extra allocations in this case. + * - We permit efficient initial state declaration, avoiding unnecessary CAS or lock acquisitions when mutating + * a Promise we are ourselves constructing (and can easily add more; only those we use have been added) + * + * We can also make some guarantees about our behaviour here, although we primarily mirror Netty. + * Specifically, we can guarantee that notifiers are always invoked in the order they are added (which may be true + * for netty, but was unclear and is not declared). This is useful for ensuring the correctness of some of our + * behaviours in OutboundConnection without having to jump through extra hoops. + * + * The implementation loosely follows that of Netty's DefaultPromise, with some slight changes; notably that we have + * no synchronisation on our listeners, instead using a CoW list that is cleared each time we notify listeners. + * + * We handle special values slightly differently. We do not use a special value for null, instead using + * a special value to indicate the result has not been set yet. This means that once isSuccess() holds, + * the result must be a correctly typed object (modulo generics pitfalls). + * All special values are also instances of FailureHolder, which simplifies a number of the logical conditions. + * + * @param + */ +public class SyncFuture extends AbstractFuture +{ + public SyncFuture() + { + super(); + } + + protected SyncFuture(V immediateSuccess) + { + super(immediateSuccess); + } + + protected SyncFuture(Throwable immediateFailure) + { + super(immediateFailure); + } + + protected SyncFuture(FailureHolder initialState) + { + super(initialState); + } + + protected SyncFuture(GenericFutureListener> listener) + { + super(listener); + } + + protected SyncFuture(FailureHolder initialState, GenericFutureListener> listener) + { + super(initialState, listener); + } + + /** + * Support {@link com.google.common.util.concurrent.Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)} natively + * + * See {@link #addListener(GenericFutureListener)} for ordering semantics. + */ + @Override + public Future andThenAsync(Function> andThen, @Nullable Executor executor) + { + return andThenAsync(new SyncFuture<>(), andThen, executor); + } + + /** + * Shared implementation of various promise completion methods. + * Updates the result if it is possible to do so, returning success/failure. + * + * If the promise is UNSET the new value will succeed; + * if it is UNCANCELLABLE it will succeed only if the new value is not CANCELLED + * otherwise it will fail, as isDone() is implied + * + * If the update succeeds, and the new state implies isDone(), any listeners and waiters will be notified + */ + synchronized boolean trySet(Object v) + { + Object current = result; + if (isDone(current) || (current == UNCANCELLABLE && (v == CANCELLED || v == UNCANCELLABLE))) + return false; + + resultUpdater.lazySet(this, v); + if (v != UNCANCELLABLE) + { + notifyListeners(); + notifyAll(); + } + return true; + } + + public synchronized boolean awaitUntil(long deadline) throws InterruptedException + { + if (isDone()) + return true; + + waitUntil(this, deadline); + return isDone(); + } + + public synchronized Future await() throws InterruptedException + { + while (!isDone()) + wait(); + return this; + } + + /** + * Logically append {@code newListener} to {@link #listeners} + * (at this stage it is a stack, so we actually prepend) + */ + synchronized void appendListener(ListenerList newListener) + { + ListenerList.pushExclusive(listenersUpdater, this, newListener); + if (isDone()) + notifyListeners(); + } + + private void notifyListeners() + { + ListenerList.notify(listeners, this); + listenersUpdater.lazySet(this, null); + } +} diff --git a/src/java/org/apache/cassandra/utils/concurrent/WaitQueue.java b/src/java/org/apache/cassandra/utils/concurrent/WaitQueue.java index 53e7e8dee5..4fbe7c6ad1 100644 --- a/src/java/org/apache/cassandra/utils/concurrent/WaitQueue.java +++ b/src/java/org/apache/cassandra/utils/concurrent/WaitQueue.java @@ -165,6 +165,8 @@ public interface WaitQueue // the waiting signals private final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); + protected Standard() {} + /** * The calling thread MUST be the thread that uses the signal * @return x diff --git a/src/java/org/apache/cassandra/utils/memory/BufferPool.java b/src/java/org/apache/cassandra/utils/memory/BufferPool.java index 531b492377..7c1e95e9d5 100644 --- a/src/java/org/apache/cassandra/utils/memory/BufferPool.java +++ b/src/java/org/apache/cassandra/utils/memory/BufferPool.java @@ -38,7 +38,7 @@ import java.util.function.Supplier; import com.google.common.annotations.VisibleForTesting; import net.nicoulaj.compilecommand.annotations.Inline; -import org.apache.cassandra.concurrent.InfiniteLoopExecutor; +import org.apache.cassandra.concurrent.Shutdownable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,6 +51,7 @@ import org.apache.cassandra.utils.NoSpamLogger; import org.apache.cassandra.utils.concurrent.Ref; import static com.google.common.collect.ImmutableList.of; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.utils.ExecutorUtils.*; import static org.apache.cassandra.utils.FBUtilities.prettyPrintMemory; import static org.apache.cassandra.utils.memory.MemoryUtil.isExactlyDirect; @@ -162,7 +163,7 @@ public class BufferPool private final Set localPoolReferences = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final ReferenceQueue localPoolRefQueue = new ReferenceQueue<>(); - private final InfiniteLoopExecutor localPoolCleaner; + private final Shutdownable localPoolCleaner; public BufferPool(String name, long memoryUsageThreshold, boolean recyclePartially) { @@ -172,7 +173,7 @@ public class BufferPool this.globalPool = new GlobalPool(); this.metrics = new BufferPoolMetrics(name, this); this.recyclePartially = recyclePartially; - this.localPoolCleaner = new InfiniteLoopExecutor("LocalPool-Cleaner-" + name, this::cleanupOneReference).start(); + this.localPoolCleaner = executorFactory().infiniteLoop("LocalPool-Cleaner-" + name, this::cleanupOneReference, false); } /** diff --git a/src/java/org/apache/cassandra/utils/memory/MemtableCleaner.java b/src/java/org/apache/cassandra/utils/memory/MemtableCleaner.java index d2cb9c552a..bce042131a 100644 --- a/src/java/org/apache/cassandra/utils/memory/MemtableCleaner.java +++ b/src/java/org/apache/cassandra/utils/memory/MemtableCleaner.java @@ -18,7 +18,7 @@ package org.apache.cassandra.utils.memory; -import java.util.concurrent.CompletableFuture; +import org.apache.cassandra.utils.concurrent.Future; /** * The cleaner is used by {@link MemtableCleanerThread} in order to reclaim space from memtables, normally @@ -36,5 +36,5 @@ public interface MemtableCleaner * The future will complete with an error if the cleaning operation encounters an error. * */ - CompletableFuture clean(); + Future clean(); } \ No newline at end of file diff --git a/src/java/org/apache/cassandra/utils/memory/MemtableCleanerThread.java b/src/java/org/apache/cassandra/utils/memory/MemtableCleanerThread.java index 78c0f0e631..e11ce999e8 100644 --- a/src/java/org/apache/cassandra/utils/memory/MemtableCleanerThread.java +++ b/src/java/org/apache/cassandra/utils/memory/MemtableCleanerThread.java @@ -24,20 +24,23 @@ import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.cassandra.concurrent.InfiniteLoopExecutor; +import java.util.concurrent.TimeUnit; + +import org.apache.cassandra.concurrent.Interruptible; import org.apache.cassandra.utils.concurrent.WaitQueue; import static org.apache.cassandra.utils.concurrent.WaitQueue.newWaitQueue; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; /** * A thread that reclaims memory from a MemtablePool on demand. The actual reclaiming work is delegated to the * cleaner Runnable, e.g., FlushLargestColumnFamily */ -public class MemtableCleanerThread

    extends InfiniteLoopExecutor +public class MemtableCleanerThread

    implements Interruptible { private static final Logger logger = LoggerFactory.getLogger(MemtableCleanerThread.class); - public static class Clean

    implements InterruptibleRunnable + private static class Clean

    implements Interruptible.SimpleTask { /** This is incremented when a cleaner is invoked and decremented when a cleaner has completed */ final AtomicInteger numPendingTasks = new AtomicInteger(0); @@ -81,7 +84,7 @@ public class MemtableCleanerThread

    extends InfiniteLoopE if (logger.isTraceEnabled()) logger.trace("Invoking cleaner with {} tasks pending", numPendingTasks); - cleaner.clean().handle(this::apply); + cleaner.clean().addCallback(this::apply); } } @@ -100,14 +103,20 @@ public class MemtableCleanerThread

    extends InfiniteLoopE return res; } + + public String toString() + { + return pool.toString() + ' ' + cleaner.toString(); + } } + private final Interruptible executor; private final Runnable trigger; private final Clean

    clean; private MemtableCleanerThread(Clean

    clean) { - super(clean.pool.getClass().getSimpleName() + "Cleaner", clean); + this.executor = executorFactory().infiniteLoop(clean.pool.getClass().getSimpleName() + "Cleaner", clean, true); this.trigger = clean.wait::signal; this.clean = clean; } @@ -129,4 +138,34 @@ public class MemtableCleanerThread

    extends InfiniteLoopE { return clean.numPendingTasks(); } + + @Override + public void interrupt() + { + executor.interrupt(); + } + + @Override + public boolean isTerminated() + { + return executor.isTerminated(); + } + + @Override + public void shutdown() + { + executor.shutdown(); + } + + @Override + public Object shutdownNow() + { + return executor.shutdownNow(); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit units) throws InterruptedException + { + return executor.awaitTermination(timeout, units); + } } diff --git a/src/java/org/apache/cassandra/utils/memory/MemtablePool.java b/src/java/org/apache/cassandra/utils/memory/MemtablePool.java index 6793c31257..707f16498d 100644 --- a/src/java/org/apache/cassandra/utils/memory/MemtablePool.java +++ b/src/java/org/apache/cassandra/utils/memory/MemtablePool.java @@ -59,7 +59,6 @@ public abstract class MemtablePool this.onHeap = getSubPool(maxOnHeapMemory, cleanThreshold); this.offHeap = getSubPool(maxOffHeapMemory, cleanThreshold); this.cleaner = getCleaner(cleaner); - this.cleaner.start(); DefaultNameFactory nameFactory = new DefaultNameFactory("MemtablePool"); blockedOnAllocating = CassandraMetricsRegistry.Metrics.timer(nameFactory.createMetricName("BlockedOnAllocation")); numPendingTasks = CassandraMetricsRegistry.Metrics.register(nameFactory.createMetricName("PendingFlushTasks"), diff --git a/src/java/org/apache/cassandra/utils/progress/jmx/JMXBroadcastExecutor.java b/src/java/org/apache/cassandra/utils/progress/jmx/JMXBroadcastExecutor.java index f28609cc6c..c21146ba09 100644 --- a/src/java/org/apache/cassandra/utils/progress/jmx/JMXBroadcastExecutor.java +++ b/src/java/org/apache/cassandra/utils/progress/jmx/JMXBroadcastExecutor.java @@ -19,9 +19,8 @@ package org.apache.cassandra.utils.progress.jmx; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import org.apache.cassandra.concurrent.NamedThreadFactory; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; /** * Holds dedicated executor for JMX event handling. Events will internally queued by ArrayNotificationBuffer, @@ -32,6 +31,6 @@ public final class JMXBroadcastExecutor private JMXBroadcastExecutor() { } - public final static ExecutorService executor = Executors.newSingleThreadExecutor(new NamedThreadFactory("JMX")); + public final static ExecutorService executor = executorFactory().sequential("JMX"); } diff --git a/test/burn/org/apache/cassandra/net/ConnectionBurnTest.java b/test/burn/org/apache/cassandra/net/ConnectionBurnTest.java index 23601c310c..96812241dc 100644 --- a/test/burn/org/apache/cassandra/net/ConnectionBurnTest.java +++ b/test/burn/org/apache/cassandra/net/ConnectionBurnTest.java @@ -43,6 +43,7 @@ import java.util.stream.IntStream; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.Uninterruptibles; +import org.apache.cassandra.utils.concurrent.FutureCombiner; import org.junit.BeforeClass; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -482,7 +483,7 @@ public class ConnectionBurnTest reporters.print(); inbound.sockets.close().get(); - new FutureCombiner(Arrays.stream(connections) + FutureCombiner.allOf(Arrays.stream(connections) .map(c -> c.outbound.close(false)) .collect(Collectors.toList())) .get(); diff --git a/test/distributed/org/apache/cassandra/distributed/impl/Instance.java b/test/distributed/org/apache/cassandra/distributed/impl/Instance.java index 4dc5ffdc1e..d590694e59 100644 --- a/test/distributed/org/apache/cassandra/distributed/impl/Instance.java +++ b/test/distributed/org/apache/cassandra/distributed/impl/Instance.java @@ -46,6 +46,7 @@ import javax.management.NotificationListener; import com.google.common.annotations.VisibleForTesting; import io.netty.util.concurrent.GlobalEventExecutor; +import org.apache.cassandra.auth.AuthCache; import org.apache.cassandra.batchlog.Batch; import org.apache.cassandra.batchlog.BatchlogManager; import org.apache.cassandra.concurrent.ExecutorLocals; @@ -65,6 +66,7 @@ import org.apache.cassandra.db.Memtable; import org.apache.cassandra.db.SystemKeyspace; import org.apache.cassandra.db.SystemKeyspaceMigrator40; import org.apache.cassandra.db.commitlog.CommitLog; +import org.apache.cassandra.db.compaction.CompactionLogger; import org.apache.cassandra.db.compaction.CompactionManager; import org.apache.cassandra.dht.IPartitioner; import org.apache.cassandra.dht.Token; @@ -99,6 +101,7 @@ import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.metrics.CassandraMetricsRegistry; +import org.apache.cassandra.metrics.Sampler; import org.apache.cassandra.net.Message; import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.net.NoPayload; @@ -337,7 +340,7 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance public void uncaughtException(Thread thread, Throwable throwable) { - sync(CassandraDaemon::uncaughtException).accept(thread, throwable); + sync(JVMStabilityInspector::uncaughtException).accept(thread, throwable); } private static IMessage serializeMessage(InetAddressAndPort from, InetAddressAndPort to, Message messageOut) @@ -444,8 +447,8 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance Message.Header header = messageIn.header; TraceState state = Tracing.instance.initializeFromMessage(header); if (state != null) state.trace("{} message received from {}", header.verb, header.from); - header.verb.stage.execute(() -> MessagingService.instance().inboundSink.accept(messageIn), - ExecutorLocals.create(state)); + header.verb.stage.execute(ExecutorLocals.create(state), () -> MessagingService.instance().inboundSink.accept(messageIn) + ); }).run(); } @@ -758,6 +761,9 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance () -> BatchlogManager.instance.shutdownAndWait(1L, MINUTES), HintsService.instance::shutdownBlocking, StreamingInboundHandler::shutdown, + () -> CompactionLogger.shutdownNowAndWait(1L, MINUTES), + () -> AuthCache.shutdownAllAndWait(1L, MINUTES), + () -> Sampler.shutdownNowAndWait(1L, MINUTES), () -> StreamReceiveTask.shutdownAndWait(1L, MINUTES), () -> StreamTransferTask.shutdownAndWait(1L, MINUTES), () -> SecondaryIndexManager.shutdownAndWait(1L, MINUTES), @@ -768,10 +774,9 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance () -> Ref.shutdownReferenceReaper(1L, MINUTES), () -> Memtable.MEMORY_POOL.shutdownAndWait(1L, MINUTES), () -> DiagnosticSnapshotService.instance.shutdownAndWait(1L, MINUTES), - () -> ScheduledExecutors.shutdownAndWait(1L, MINUTES), () -> SSTableReader.shutdownBlocking(1L, MINUTES), () -> shutdownAndWait(Collections.singletonList(ActiveRepairService.repairCommandExecutor())), - () -> ScheduledExecutors.shutdownAndWait(1L, MINUTES), + () -> ScheduledExecutors.shutdownNowAndWait(1L, MINUTES), () -> SnapshotManager.shutdownAndWait(1L, MINUTES) ); diff --git a/test/distributed/org/apache/cassandra/distributed/test/IPMembershipTest.java b/test/distributed/org/apache/cassandra/distributed/test/IPMembershipTest.java index de6eb0d9a8..526f19e539 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/IPMembershipTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/IPMembershipTest.java @@ -35,7 +35,6 @@ import org.apache.cassandra.tools.ToolRunner; import org.assertj.core.api.Assertions; import static org.apache.cassandra.distributed.shared.ClusterUtils.assertRingIs; -import static org.apache.cassandra.distributed.shared.ClusterUtils.awaitRingJoin; import static org.apache.cassandra.distributed.shared.ClusterUtils.getDirectories; import static org.apache.cassandra.distributed.shared.ClusterUtils.stopUnchecked; import static org.apache.cassandra.distributed.shared.ClusterUtils.updateAddress; diff --git a/test/distributed/org/apache/cassandra/distributed/test/JVMDTestTest.java b/test/distributed/org/apache/cassandra/distributed/test/JVMDTestTest.java index ebf00d7371..089d17bcfa 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/JVMDTestTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/JVMDTestTest.java @@ -26,6 +26,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import com.google.common.collect.ImmutableMap; +import org.apache.cassandra.utils.JVMStabilityInspector; import org.junit.Test; import org.apache.cassandra.concurrent.Stage; @@ -35,7 +36,6 @@ import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.api.ConsistencyLevel; import org.apache.cassandra.distributed.api.Feature; import org.apache.cassandra.distributed.api.LogAction; -import org.apache.cassandra.service.CassandraDaemon; import org.apache.cassandra.utils.FBUtilities; import org.assertj.core.api.Assertions; @@ -80,7 +80,7 @@ public class JVMDTestTest extends TestBaseImpl long mark = logs.mark(); // get the current position so watching doesn't see any previous exceptions cluster.get(2).runOnInstance(() -> { // pretend that an uncaught exception was thrown - CassandraDaemon.uncaughtException(Thread.currentThread(), new RuntimeException("fail without fail")); + JVMStabilityInspector.uncaughtException(Thread.currentThread(), new RuntimeException("fail without fail")); }); List errors = logs.watchFor(mark, "^ERROR").getResult(); Assertions.assertThat(errors) diff --git a/test/distributed/org/apache/cassandra/distributed/test/JVMStabilityInspectorCorruptSSTableExceptionTest.java b/test/distributed/org/apache/cassandra/distributed/test/JVMStabilityInspectorCorruptSSTableExceptionTest.java index c7abfc5571..531493972e 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/JVMStabilityInspectorCorruptSSTableExceptionTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/JVMStabilityInspectorCorruptSSTableExceptionTest.java @@ -55,13 +55,13 @@ import static org.apache.cassandra.distributed.api.Feature.NETWORK; public class JVMStabilityInspectorCorruptSSTableExceptionTest extends TestBaseImpl { @Test - public void testAbstractLocalAwareExecutorServiceOnIgnoredDiskFailurePolicy() throws Exception + public void testAbstractLocalAwareExecutorPlusOnIgnoredDiskFailurePolicy() throws Exception { test(DiskFailurePolicy.ignore, true, true); } @Test - public void testAbstractLocalAwareExecutorServiceOnStopParanoidDiskFailurePolicy() throws Exception + public void testAbstractLocalAwareExecutorPlusOnStopParanoidDiskFailurePolicy() throws Exception { test(DiskFailurePolicy.stop_paranoid, false, false); } diff --git a/test/distributed/org/apache/cassandra/distributed/test/SecondaryIndexTest.java b/test/distributed/org/apache/cassandra/distributed/test/SecondaryIndexTest.java new file mode 100644 index 0000000000..7b1c4adf8a --- /dev/null +++ b/test/distributed/org/apache/cassandra/distributed/test/SecondaryIndexTest.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.cassandra.distributed.test; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.apache.cassandra.distributed.Cluster; +import org.apache.cassandra.distributed.api.ConsistencyLevel; + +public class SecondaryIndexTest extends TestBaseImpl +{ + private static final int NUM_NODES = 3; + private static final int REPLICATION_FACTOR = 1; + private static final String CREATE_TABLE = "CREATE TABLE %s(k int, v int, PRIMARY KEY (k))"; + private static final String CREATE_INDEX = "CREATE INDEX v_index ON %s(v)"; + + private static final AtomicInteger seq = new AtomicInteger(); + private static String tableName; + private static Cluster cluster; + + @BeforeClass + public static void setupCluster() throws IOException + { + cluster = init(Cluster.build(NUM_NODES).start(), REPLICATION_FACTOR); + } + + @AfterClass + public static void teardownCluster() + { + if (cluster != null) + cluster.close(); + } + + @Before + public void before() + { + // create the table + tableName = String.format("%s.t_%d", KEYSPACE, seq.getAndIncrement()); + cluster.schemaChange(String.format(CREATE_TABLE, tableName)); + cluster.schemaChange(String.format(CREATE_INDEX, tableName)); + } + + @After + public void after() + { + cluster.schemaChange(String.format("DROP TABLE %s", tableName)); + } + + @Test + public void test_only_coordinator_chooses_index_for_query() throws InterruptedException, UnknownHostException + { + for (int i = 0 ; i < 99 ; ++i) + cluster.coordinator(1).execute(String.format("INSERT INTO %s (k, v) VALUES (?, ?)", tableName), ConsistencyLevel.ALL, i, i/3); + cluster.forEach(i -> i.flush(KEYSPACE)); + + for (int i = 0 ; i < 33 ; ++i) + { + UUID trace = UUID.randomUUID(); + Object[][] result = cluster.coordinator(1).executeWithTracing(trace, String.format("SELECT * FROM %s WHERE v = ?", tableName), ConsistencyLevel.ALL, i); + Assert.assertEquals(3, result.length); + Thread.sleep(100L); + Object[][] traces = cluster.coordinator(1).execute(String.format("SELECT source, activity FROM system_traces.events WHERE session_id = ?", tableName), ConsistencyLevel.ALL, trace); + List scanning = Arrays.stream(traces) + .filter(t -> t[1].toString().matches("Index mean cardinalities are v_index:[0-9]+. Scanning with v_index.")) + .map(t -> (InetAddress) t[0]) + .distinct().collect(Collectors.toList()); + + List executing = Arrays.stream(traces) + .filter(t -> t[1].toString().equals("Executing read on " + tableName + " using index v_index")) + .map(t -> (InetAddress) t[0]) + .distinct().collect(Collectors.toList()); + + Assert.assertEquals(Collections.singletonList(cluster.get(1).broadcastAddress().getAddress()), scanning); + Assert.assertEquals(3, executing.size()); + } + } +} diff --git a/test/distributed/org/apache/cassandra/distributed/test/metrics/HintsServiceMetricsTest.java b/test/distributed/org/apache/cassandra/distributed/test/metrics/HintsServiceMetricsTest.java index a47c782ee4..cf7e9f968c 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/metrics/HintsServiceMetricsTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/metrics/HintsServiceMetricsTest.java @@ -38,6 +38,7 @@ import org.apache.cassandra.distributed.test.TestBaseImpl; import org.apache.cassandra.hints.Hint; import org.apache.cassandra.metrics.HintsServiceMetrics; import org.apache.cassandra.net.Verb; +import org.apache.cassandra.utils.concurrent.Future; import org.awaitility.core.ThrowingRunnable; import static java.util.concurrent.TimeUnit.MINUTES; @@ -221,7 +222,7 @@ public class HintsServiceMetricsTest extends TestBaseImpl .load(cl, ClassLoadingStrategy.Default.INJECTION); } - public static CompletableFuture execute(@SuperCall Callable> r) throws Exception + public static Future execute(@SuperCall Callable> r) throws Exception { if (numHints.incrementAndGet() <= NUM_FAILURES_PER_NODE) throw new RuntimeException("Injected failure"); diff --git a/test/long/org/apache/cassandra/cql3/ViewLongTest.java b/test/long/org/apache/cassandra/cql3/ViewLongTest.java index de888d4df6..7c7a68833c 100644 --- a/test/long/org/apache/cassandra/cql3/ViewLongTest.java +++ b/test/long/org/apache/cassandra/cql3/ViewLongTest.java @@ -121,7 +121,7 @@ public class ViewLongTest extends CQLTester for (int i = 0; i < writers; i++) { final int writer = i; - Thread t = NamedThreadFactory.createThread(new WrappedRunnable() + Thread t = NamedThreadFactory.createAnonymousThread(new WrappedRunnable() { public void runMayThrow() { diff --git a/test/unit/org/apache/cassandra/cache/CacheProviderTest.java b/test/unit/org/apache/cassandra/cache/CacheProviderTest.java index 7ed8a60eae..0355d51376 100644 --- a/test/unit/org/apache/cassandra/cache/CacheProviderTest.java +++ b/test/unit/org/apache/cassandra/cache/CacheProviderTest.java @@ -133,7 +133,7 @@ public class CacheProviderTest List threads = new ArrayList<>(100); for (int i = 0; i < 100; i++) { - Thread thread = NamedThreadFactory.createThread(runnable); + Thread thread = NamedThreadFactory.createAnonymousThread(runnable); threads.add(thread); thread.start(); } diff --git a/test/unit/org/apache/cassandra/concurrent/AbstractExecutorPlusTest.java b/test/unit/org/apache/cassandra/concurrent/AbstractExecutorPlusTest.java new file mode 100644 index 0000000000..eadacd1294 --- /dev/null +++ b/test/unit/org/apache/cassandra/concurrent/AbstractExecutorPlusTest.java @@ -0,0 +1,200 @@ +/* + * 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.concurrent; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +import org.junit.Assert; +import org.junit.Ignore; + +import org.apache.cassandra.utils.Closeable; +import org.apache.cassandra.utils.WithResources; +import org.apache.cassandra.utils.concurrent.Future; +import org.apache.cassandra.utils.concurrent.Semaphore; + +@Ignore +public abstract class AbstractExecutorPlusTest +{ + interface Verify + { + void test(V test) throws Throwable; + } + + static Verify ignoreNull(Verify verify) + { + return test -> { if (test != null) verify.test(test); }; + } + + public void testPooled(Supplier> builders) throws Throwable + { + testSuccess(builders); + testFailure(builders); + } + + public void testSequential(Supplier> builders) throws Throwable + { + testSuccess(builders); + testFailure(builders); + testAtLeastOnce(builders); + } + + Runnable wrapSubmit(Runnable submit) + { + return submit; + } + + public void testSuccess(Supplier> builders) throws Throwable + { + testExecution(builders.get().build(), wrapSubmit(() -> {}), ignoreNull(Future::get)); + testExecution(builders.get().build(), WithResources.none(), wrapSubmit(() -> {}), ignoreNull(Future::get)); + } + + public void testFailure(Supplier> builders) throws Throwable + { + ExecutorBuilder builder = builders.get(); + AtomicReference failure = new AtomicReference<>(); + Thread.UncaughtExceptionHandler ueh = (thread, f) -> failure.set(f); + builder.withUncaughtExceptionHandler(ueh); + Verify> verify = f -> { + int c = 0; + while (f == null && failure.get() == null && c++ < 100000) + Thread.yield(); + Assert.assertTrue(failure.get() instanceof OutOfMemoryError); + if (f != null) + Assert.assertTrue(f.cause() instanceof OutOfMemoryError); + failure.set(null); + }; + Runnable submit = wrapSubmit(() -> { throw new OutOfMemoryError(); }); + testExecution(builder.build(), submit, verify); + testExecution(builder.build(), WithResources.none(), submit, verify); + testFailGetWithResources(builder.build(), () -> { throw new OutOfMemoryError(); }, verify); + testFailCloseWithResources(builder.build(), () -> () -> { throw new OutOfMemoryError(); }, verify); + } + + public void testAtLeastOnce(Supplier> builders) throws Throwable + { + ExecutorBuilder builder = builders.get(); + AtomicReference failure = new AtomicReference<>(); + Thread.UncaughtExceptionHandler ueh = (thread, f) -> failure.set(f); + builder.withUncaughtExceptionHandler(ueh); + + SequentialExecutorPlus exec = builder.build(); + + Semaphore enter = new Semaphore.UnfairAsync(0); + Semaphore exit = new Semaphore.UnfairAsync(0); + Semaphore runAfter = new Semaphore.UnfairAsync(0); + SequentialExecutorPlus.AtLeastOnceTrigger trigger; + trigger = exec.atLeastOnceTrigger(() -> { enter.release(1); exit.acquireThrowUncheckedOnInterrupt(1); }); + + // check runAfter runs immediately + trigger.runAfter(() -> runAfter.release(1)); + Assert.assertTrue(runAfter.tryAcquire(1, 1L, TimeUnit.SECONDS)); + + Assert.assertTrue(trigger.trigger()); + enter.acquire(1); + Assert.assertTrue(trigger.trigger()); + Assert.assertFalse(trigger.trigger()); + trigger.runAfter(() -> runAfter.release(1)); + Assert.assertFalse(runAfter.tryAcquire(1, 10L, TimeUnit.MILLISECONDS)); + exit.release(1); + enter.acquire(1); + Assert.assertFalse(runAfter.tryAcquire(1, 10L, TimeUnit.MILLISECONDS)); + Assert.assertTrue(trigger.trigger()); + Assert.assertFalse(trigger.trigger()); + exit.release(1); + Assert.assertTrue(runAfter.tryAcquire(1, 1L, TimeUnit.SECONDS)); + exit.release(1); + + trigger = exec.atLeastOnceTrigger(() -> { throw new OutOfMemoryError(); }); + trigger.trigger(); + trigger.sync(); + Assert.assertTrue(failure.get() instanceof OutOfMemoryError); + } + + void testExecution(ExecutorPlus e, WithResources withResources, Runnable submit, Verify> verify) throws Throwable + { + AtomicInteger i = new AtomicInteger(); + e.execute(() -> { i.incrementAndGet(); return withResources.get(); } , () -> { i.incrementAndGet(); submit.run(); }); + while (i.get() < 2) Thread.yield(); + verify.test(null); + verify.test(e.submit(() -> { i.incrementAndGet(); return withResources.get(); }, () -> { i.incrementAndGet(); submit.run(); return null; }).await()); + Assert.assertEquals(4, i.get()); + verify.test(e.submit(() -> { i.incrementAndGet(); return withResources.get(); }, () -> { i.incrementAndGet(); submit.run(); }).await()); + Assert.assertEquals(6, i.get()); + verify.test(e.submit(() -> { i.incrementAndGet(); return withResources.get(); }, () -> { i.incrementAndGet(); submit.run(); }, null).await()); + Assert.assertEquals(8, i.get()); + } + + void testExecution(ExecutorPlus e, Runnable submit, Verify> verify) throws Throwable + { + AtomicInteger i = new AtomicInteger(); + e.execute(() -> { i.incrementAndGet(); submit.run(); }); + while (i.get() < 1) Thread.yield(); + verify.test(null); + e.maybeExecuteImmediately(() -> { i.incrementAndGet(); submit.run(); }); + while (i.get() < 2) Thread.yield(); + verify.test(null); + verify.test(e.submit(() -> { i.incrementAndGet(); submit.run(); return null; }).await()); + Assert.assertEquals(3, i.get()); + verify.test(e.submit(() -> { i.incrementAndGet(); submit.run(); }).await()); + Assert.assertEquals(4, i.get()); + verify.test(e.submit(() -> { i.incrementAndGet(); submit.run(); }, null).await()); + Assert.assertEquals(5, i.get()); + } + + void testFailGetWithResources(ExecutorPlus e, WithResources withResources, Verify> verify) throws Throwable + { + AtomicInteger i = new AtomicInteger(); + WithResources countingOnGetResources = () -> { i.incrementAndGet(); return withResources.get(); }; + AtomicBoolean executed = new AtomicBoolean(); + e.execute(countingOnGetResources, () -> executed.set(true)); + while (i.get() < 1) Thread.yield(); + verify.test(null); + Assert.assertFalse(executed.get()); + verify.test(e.submit(countingOnGetResources, () -> { executed.set(true); return null; } ).await()); + Assert.assertEquals(2, i.get()); + Assert.assertFalse(executed.get()); + verify.test(e.submit(countingOnGetResources, () -> { executed.set(true); }).await()); + Assert.assertEquals(3, i.get()); + Assert.assertFalse(executed.get()); + verify.test(e.submit(countingOnGetResources, () -> { executed.set(true); }, null).await()); + Assert.assertEquals(4, i.get()); + Assert.assertFalse(executed.get()); + } + + void testFailCloseWithResources(ExecutorPlus e, WithResources withResources, Verify> verify) throws Throwable + { + AtomicInteger i = new AtomicInteger(); + WithResources countingOnCloseResources = () -> { Closeable close = withResources.get(); return () -> { i.incrementAndGet(); close.close(); }; }; + e.execute(countingOnCloseResources, i::incrementAndGet); + while (i.get() < 2) Thread.yield(); + verify.test(null); + verify.test(e.submit(countingOnCloseResources, () -> { i.incrementAndGet(); return null; } ).await()); + Assert.assertEquals(4, i.get()); + verify.test(e.submit(countingOnCloseResources, () -> { i.incrementAndGet(); }).await()); + Assert.assertEquals(6, i.get()); + verify.test(e.submit(countingOnCloseResources, () -> { i.incrementAndGet(); }, null).await()); + Assert.assertEquals(8, i.get()); + } + +} diff --git a/test/unit/org/apache/cassandra/concurrent/DebuggableScheduledThreadPoolExecutorTest.java b/test/unit/org/apache/cassandra/concurrent/DebuggableScheduledThreadPoolExecutorTest.java index 1aac470a58..b37b014f00 100644 --- a/test/unit/org/apache/cassandra/concurrent/DebuggableScheduledThreadPoolExecutorTest.java +++ b/test/unit/org/apache/cassandra/concurrent/DebuggableScheduledThreadPoolExecutorTest.java @@ -33,6 +33,8 @@ import org.junit.Assert; import org.apache.cassandra.service.EmbeddedCassandraService; import org.apache.cassandra.service.StorageService; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; + public class DebuggableScheduledThreadPoolExecutorTest { @@ -50,7 +52,7 @@ public class DebuggableScheduledThreadPoolExecutorTest @Test public void testShutdown() throws ExecutionException, InterruptedException, IOException { - DebuggableScheduledThreadPoolExecutor testPool = new DebuggableScheduledThreadPoolExecutor("testpool"); + ScheduledExecutorPlus testPool = executorFactory().scheduled("testpool"); final AtomicInteger value = new AtomicInteger(0); diff --git a/test/unit/org/apache/cassandra/concurrent/DebuggableThreadPoolExecutorTest.java b/test/unit/org/apache/cassandra/concurrent/DebuggableThreadPoolExecutorTest.java index b3c29f472b..17253ec3d9 100644 --- a/test/unit/org/apache/cassandra/concurrent/DebuggableThreadPoolExecutorTest.java +++ b/test/unit/org/apache/cassandra/concurrent/DebuggableThreadPoolExecutorTest.java @@ -21,16 +21,14 @@ package org.apache.cassandra.concurrent; */ +import java.lang.Thread.UncaughtExceptionHandler; import java.util.UUID; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.RunnableFuture; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; import com.google.common.base.Throwables; import com.google.common.net.InetAddresses; -import com.google.common.util.concurrent.ListenableFutureTask; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; @@ -43,6 +41,7 @@ import org.apache.cassandra.tracing.Tracing; import org.apache.cassandra.utils.WrappedRunnable; import static org.apache.cassandra.utils.Clock.Global.nanoTime; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; public class DebuggableThreadPoolExecutorTest { @@ -55,12 +54,7 @@ public class DebuggableThreadPoolExecutorTest @Test public void testSerialization() { - LinkedBlockingQueue q = new LinkedBlockingQueue(1); - DebuggableThreadPoolExecutor executor = new DebuggableThreadPoolExecutor(1, - Integer.MAX_VALUE, - TimeUnit.MILLISECONDS, - q, - new NamedThreadFactory("TEST")); + ExecutorPlus executor = executorFactory().configureSequential("TEST").withQueueLimit(1).build(); WrappedRunnable runnable = new WrappedRunnable() { public void runMayThrow() throws InterruptedException @@ -73,7 +67,7 @@ public class DebuggableThreadPoolExecutorTest { executor.execute(runnable); } - assert q.size() > 0 : q.size(); + assert executor.getPendingTaskCount() > 0 : executor.getPendingTaskCount(); while (executor.getCompletedTaskCount() < 10) continue; long delta = TimeUnit.NANOSECONDS.toMillis(nanoTime() - start); @@ -83,21 +77,21 @@ public class DebuggableThreadPoolExecutorTest @Test public void testExecuteFutureTaskWhileTracing() { - LinkedBlockingQueue q = new LinkedBlockingQueue(1); - DebuggableThreadPoolExecutor executor = new DebuggableThreadPoolExecutor(1, - Integer.MAX_VALUE, - TimeUnit.MILLISECONDS, - q, - new NamedThreadFactory("TEST")); + SettableUncaughtExceptionHandler ueh = new SettableUncaughtExceptionHandler(); + ExecutorPlus executor = executorFactory() + .localAware() + .configureSequential("TEST") + .withUncaughtExceptionHandler(ueh) + .withQueueLimit(1).build(); Runnable test = () -> executor.execute(failingTask()); try { // make sure the non-tracing case works - Throwable cause = catchUncaughtExceptions(test); + Throwable cause = catchUncaughtExceptions(ueh, test); Assert.assertEquals(DebuggingThrowsException.class, cause.getClass()); // tracing should have the same semantics - cause = catchUncaughtExceptions(() -> withTracing(test)); + cause = catchUncaughtExceptions(ueh, () -> withTracing(test)); Assert.assertEquals(DebuggingThrowsException.class, cause.getClass()); } finally @@ -109,21 +103,20 @@ public class DebuggableThreadPoolExecutorTest @Test public void testSubmitFutureTaskWhileTracing() { - LinkedBlockingQueue q = new LinkedBlockingQueue(1); - DebuggableThreadPoolExecutor executor = new DebuggableThreadPoolExecutor(1, - Integer.MAX_VALUE, - TimeUnit.MILLISECONDS, - q, - new NamedThreadFactory("TEST")); + SettableUncaughtExceptionHandler ueh = new SettableUncaughtExceptionHandler(); + ExecutorPlus executor = executorFactory().localAware() + .configureSequential("TEST") + .withUncaughtExceptionHandler(ueh) + .withQueueLimit(1).build(); FailingRunnable test = () -> executor.submit(failingTask()).get(); try { // make sure the non-tracing case works - Throwable cause = catchUncaughtExceptions(test); + Throwable cause = catchUncaughtExceptions(ueh, test); Assert.assertEquals(DebuggingThrowsException.class, cause.getClass()); // tracing should have the same semantics - cause = catchUncaughtExceptions(() -> withTracing(test)); + cause = catchUncaughtExceptions(ueh, () -> withTracing(test)); Assert.assertEquals(DebuggingThrowsException.class, cause.getClass()); } finally @@ -136,17 +129,17 @@ public class DebuggableThreadPoolExecutorTest public void testSubmitWithResultFutureTaskWhileTracing() { LinkedBlockingQueue q = new LinkedBlockingQueue(1); - DebuggableThreadPoolExecutor executor = new DebuggableThreadPoolExecutor(1, - Integer.MAX_VALUE, - TimeUnit.MILLISECONDS, - q, - new NamedThreadFactory("TEST")); + SettableUncaughtExceptionHandler ueh = new SettableUncaughtExceptionHandler(); + ExecutorPlus executor = executorFactory().localAware() + .configureSequential("TEST") + .withUncaughtExceptionHandler(ueh) + .withQueueLimit(1).build(); FailingRunnable test = () -> executor.submit(failingTask(), 42).get(); try { - Throwable cause = catchUncaughtExceptions(test); + Throwable cause = catchUncaughtExceptions(ueh, test); Assert.assertEquals(DebuggingThrowsException.class, cause.getClass()); - cause = catchUncaughtExceptions(() -> withTracing(test)); + cause = catchUncaughtExceptions(ueh, () -> withTracing(test)); Assert.assertEquals(DebuggingThrowsException.class, cause.getClass()); } finally @@ -168,14 +161,34 @@ public class DebuggableThreadPoolExecutorTest } } - private static Throwable catchUncaughtExceptions(Runnable fn) + private static class SettableUncaughtExceptionHandler implements UncaughtExceptionHandler + { + volatile Supplier cur; + + @Override + public void uncaughtException(Thread t, Throwable e) + { + cur.get().uncaughtException(t, e); + } + + void set(Supplier set) + { + cur = set; + } + + void clear() + { + cur = Thread::getDefaultUncaughtExceptionHandler; + } + } + + private static Throwable catchUncaughtExceptions(SettableUncaughtExceptionHandler ueh, Runnable fn) { - Thread.UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); try { AtomicReference ref = new AtomicReference<>(null); CountDownLatch latch = new CountDownLatch(1); - Thread.setDefaultUncaughtExceptionHandler((thread, cause) -> { + ueh.set(() -> (thread, cause) -> { ref.set(cause); latch.countDown(); }); @@ -192,7 +205,7 @@ public class DebuggableThreadPoolExecutorTest } finally { - Thread.setDefaultUncaughtExceptionHandler(defaultHandler); + ueh.clear(); } } @@ -203,7 +216,7 @@ public class DebuggableThreadPoolExecutorTest private static RunnableFuture failingTask() { - return ListenableFutureTask.create(DebuggableThreadPoolExecutorTest::failingFunction); + return new FutureTask<>(DebuggableThreadPoolExecutorTest::failingFunction); } private static final class DebuggingThrowsException extends RuntimeException { diff --git a/test/unit/org/apache/cassandra/concurrent/ExecutorPlusTest.java b/test/unit/org/apache/cassandra/concurrent/ExecutorPlusTest.java new file mode 100644 index 0000000000..08af142c79 --- /dev/null +++ b/test/unit/org/apache/cassandra/concurrent/ExecutorPlusTest.java @@ -0,0 +1,38 @@ +/* + * 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.concurrent; + +import org.junit.Test; + +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; + +public class ExecutorPlusTest extends AbstractExecutorPlusTest +{ + @Test + public void testPooled() throws Throwable + { + testPooled(() -> executorFactory().configurePooled("test", 1)); + } + + @Test + public void testSequential() throws Throwable + { + testSequential(() -> executorFactory().configureSequential("test")); + } +} diff --git a/test/unit/org/apache/cassandra/concurrent/InfiniteLoopExecutorTest.java b/test/unit/org/apache/cassandra/concurrent/InfiniteLoopExecutorTest.java new file mode 100644 index 0000000000..73dca7ee24 --- /dev/null +++ b/test/unit/org/apache/cassandra/concurrent/InfiniteLoopExecutorTest.java @@ -0,0 +1,80 @@ +/* + * 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.concurrent; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Assert; +import org.junit.Test; + +public class InfiniteLoopExecutorTest +{ + @Test + public void testShutdownNow() throws InterruptedException, ExecutionException, TimeoutException + { + Semaphore semaphore = new Semaphore(0); + InfiniteLoopExecutor e1 = new InfiniteLoopExecutor("test", ignore -> semaphore.acquire(1)); + ExecutorService exec = Executors.newCachedThreadPool(); + Future f = exec.submit(() -> e1.awaitTermination(1L, TimeUnit.MINUTES)); + e1.shutdownNow(); + f.get(1L, TimeUnit.SECONDS); + } + + @Test + public void testShutdown() throws InterruptedException, ExecutionException, TimeoutException + { + AtomicBoolean active = new AtomicBoolean(false); + Semaphore semaphore = new Semaphore(0); + InfiniteLoopExecutor e1 = new InfiniteLoopExecutor("test", ignore -> { + active.set(true); + semaphore.acquire(1); + active.set(false); + semaphore.release(); + }); + ExecutorService exec = Executors.newCachedThreadPool(); + Future f = exec.submit(() -> e1.awaitTermination(1L, TimeUnit.MINUTES)); + // do ten normal loops + for (int i = 0 ; i < 10 ; ++i) + { + semaphore.release(); + semaphore.acquire(); + } + // confirm we've re-entered the runnable + while (!active.get()) Thread.yield(); + // then shutdown, and expect precisely one more + e1.shutdown(); + try + { + f.get(10L, TimeUnit.MILLISECONDS); + Assert.fail(); + } + catch (TimeoutException ignore) + { + } + semaphore.release(); + f.get(1L, TimeUnit.SECONDS); + } +} diff --git a/test/unit/org/apache/cassandra/concurrent/LocalAwareExecutorPlusTest.java b/test/unit/org/apache/cassandra/concurrent/LocalAwareExecutorPlusTest.java new file mode 100644 index 0000000000..f47046c759 --- /dev/null +++ b/test/unit/org/apache/cassandra/concurrent/LocalAwareExecutorPlusTest.java @@ -0,0 +1,52 @@ +/* + * 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.concurrent; + +import org.junit.Assert; +import org.junit.Test; + +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; + +public class LocalAwareExecutorPlusTest extends AbstractExecutorPlusTest +{ + final ExecutorLocals locals = new ExecutorLocals(null, null); + + @Test + public void testPooled() throws Throwable + { + locals.get(); + testPooled(() -> executorFactory().localAware().configurePooled("test", 1)); + } + + @Test + public void testSequential() throws Throwable + { + locals.get(); + testSequential(() -> executorFactory().localAware().configureSequential("test")); + } + + @Override + Runnable wrapSubmit(Runnable submit) + { + return () -> { + Assert.assertEquals(locals, ExecutorLocals.current()); + submit.run(); + }; + } +} diff --git a/test/unit/org/apache/cassandra/concurrent/SEPExecutorTest.java b/test/unit/org/apache/cassandra/concurrent/SEPExecutorTest.java index b18c328b00..2a8aeb9b5f 100644 --- a/test/unit/org/apache/cassandra/concurrent/SEPExecutorTest.java +++ b/test/unit/org/apache/cassandra/concurrent/SEPExecutorTest.java @@ -87,7 +87,7 @@ public class SEPExecutorTest final AtomicInteger notifiedMaxPoolSize = new AtomicInteger(); SharedExecutorPool sharedPool; - LocalAwareExecutorService executor; + LocalAwareExecutorPlus executor; Thread makeBusy; AtomicBoolean stayBusy; @@ -132,7 +132,7 @@ public class SEPExecutorTest sharedPool.shutdownAndWait(1L, MINUTES); } - public LocalAwareExecutorService getExecutor() + public LocalAwareExecutorPlus getExecutor() { return executor; } @@ -147,7 +147,7 @@ public class SEPExecutorTest public void changingMaxWorkersMeetsConcurrencyGoalsTest() throws InterruptedException, TimeoutException { BusyExecutor busyExecutor = new BusyExecutor("ChangingMaxWorkersMeetsConcurrencyGoalsTest", "resizetest"); - LocalAwareExecutorService executor = busyExecutor.getExecutor(); + LocalAwareExecutorPlus executor = busyExecutor.getExecutor(); busyExecutor.start(); try @@ -179,12 +179,11 @@ public class SEPExecutorTest } } - @Test public void stoppedWorkersProcessTasksWhenConcurrencyIncreases() throws InterruptedException { BusyExecutor busyExecutor = new BusyExecutor("StoppedWorkersProcessTasksWhenConcurrencyIncreases", "stoptest"); - LocalAwareExecutorService executor = busyExecutor.getExecutor(); + LocalAwareExecutorPlus executor = busyExecutor.getExecutor(); busyExecutor.start(); try { @@ -249,7 +248,7 @@ public class SEPExecutorTest } } - void assertMaxTaskConcurrency(LocalAwareExecutorService executor, int concurrency) throws InterruptedException + void assertMaxTaskConcurrency(LocalAwareExecutorPlus executor, int concurrency) throws InterruptedException { executor.setMaximumPoolSize(concurrency); diff --git a/test/unit/org/apache/cassandra/concurrent/WaitQueueTest.java b/test/unit/org/apache/cassandra/concurrent/WaitQueueTest.java index 7379ac52f8..a9049a9d81 100644 --- a/test/unit/org/apache/cassandra/concurrent/WaitQueueTest.java +++ b/test/unit/org/apache/cassandra/concurrent/WaitQueueTest.java @@ -45,7 +45,7 @@ public class WaitQueueTest final AtomicInteger ready = new AtomicInteger(); Thread[] ts = new Thread[4]; for (int i = 0 ; i < ts.length ; i++) - ts[i] = NamedThreadFactory.createThread(new Runnable() + ts[i] = NamedThreadFactory.createAnonymousThread(new Runnable() { @Override public void run() @@ -85,7 +85,7 @@ public class WaitQueueTest final AtomicBoolean ready = new AtomicBoolean(false); final AtomicBoolean condition = new AtomicBoolean(false); final AtomicBoolean fail = new AtomicBoolean(false); - Thread t = NamedThreadFactory.createThread(new Runnable() + Thread t = NamedThreadFactory.createAnonymousThread(new Runnable() { @Override public void run() diff --git a/test/unit/org/apache/cassandra/cql3/ViewTest.java b/test/unit/org/apache/cassandra/cql3/ViewTest.java index a4da51a6fa..03b8f9da04 100644 --- a/test/unit/org/apache/cassandra/cql3/ViewTest.java +++ b/test/unit/org/apache/cassandra/cql3/ViewTest.java @@ -493,7 +493,7 @@ public class ViewTest extends ViewAbstractTest createView(viewName1, "CREATE MATERIALIZED VIEW %s AS SELECT * FROM %%s WHERE val IS NOT NULL AND k IS NOT NULL AND c IS NOT NULL PRIMARY KEY (val,k,c)"); cfs.enableAutoCompaction(); - List> futures = CompactionManager.instance.submitBackground(cfs); + List> futures = CompactionManager.instance.submitBackground(cfs); String viewName2 = viewName1 + "_2"; //Force a second MV on the same base table, which will restart the first MV builder... diff --git a/test/unit/org/apache/cassandra/cql3/validation/miscellaneous/CrcCheckChanceTest.java b/test/unit/org/apache/cassandra/cql3/validation/miscellaneous/CrcCheckChanceTest.java index 246f512f66..8f1ef69567 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/miscellaneous/CrcCheckChanceTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/miscellaneous/CrcCheckChanceTest.java @@ -186,7 +186,7 @@ public class CrcCheckChanceTest extends CQLTester } DatabaseDescriptor.setCompactionThroughputMbPerSec(1); - List> futures = CompactionManager.instance.submitMaximal(cfs, CompactionManager.getDefaultGcBefore(cfs, FBUtilities.nowInSeconds()), false); + List> futures = CompactionManager.instance.submitMaximal(cfs, CompactionManager.getDefaultGcBefore(cfs, FBUtilities.nowInSeconds()), false); execute("DROP TABLE %s"); try diff --git a/test/unit/org/apache/cassandra/db/CellSpecTest.java b/test/unit/org/apache/cassandra/db/CellSpecTest.java index 040f09f802..eac724aa92 100644 --- a/test/unit/org/apache/cassandra/db/CellSpecTest.java +++ b/test/unit/org/apache/cassandra/db/CellSpecTest.java @@ -22,7 +22,6 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; import java.util.stream.Collectors; @@ -41,6 +40,7 @@ import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.utils.ObjectSizes; import org.apache.cassandra.utils.UUIDGen; +import org.apache.cassandra.utils.concurrent.ImmediateFuture; import org.apache.cassandra.utils.concurrent.OpOrder; import org.apache.cassandra.utils.memory.NativeAllocator; import org.apache.cassandra.utils.memory.NativePool; @@ -137,7 +137,7 @@ public class CellSpecTest byte[] rawBytes = { 0, 1, 2, 3, 4, 5, 6 }; ByteBuffer bbBytes = ByteBuffer.wrap(rawBytes); - NativePool pool = new NativePool(1024, 1024, 1, () -> CompletableFuture.completedFuture(true)); + NativePool pool = new NativePool(1024, 1024, 1, () -> ImmediateFuture.success(true)); NativeAllocator allocator = pool.newAllocator(); OpOrder order = new OpOrder(); diff --git a/test/unit/org/apache/cassandra/db/ClusteringHeapSizeTest.java b/test/unit/org/apache/cassandra/db/ClusteringHeapSizeTest.java index f1c3430f50..dbf9166dea 100644 --- a/test/unit/org/apache/cassandra/db/ClusteringHeapSizeTest.java +++ b/test/unit/org/apache/cassandra/db/ClusteringHeapSizeTest.java @@ -20,13 +20,13 @@ package org.apache.cassandra.db; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Collection; -import java.util.concurrent.CompletableFuture; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.apache.cassandra.utils.ObjectSizes; +import org.apache.cassandra.utils.concurrent.ImmediateFuture; import org.apache.cassandra.utils.concurrent.OpOrder; import org.apache.cassandra.utils.memory.NativePool; import org.assertj.core.api.Assertions; @@ -64,7 +64,7 @@ public class ClusteringHeapSizeTest @Parameterized.Parameters(name = "{0}") public static Collection data() { byte[] rawBytes = { 0, 1, 2, 3, 4, 5, 6 }; - NativePool pool = new NativePool(1024, 1024, 1, () -> CompletableFuture.completedFuture(true)); + NativePool pool = new NativePool(1024, 1024, 1, () -> ImmediateFuture.success(true)); OpOrder order = new OpOrder(); ArrayClustering array = ArrayClustering.make(rawBytes); diff --git a/test/unit/org/apache/cassandra/db/NativeCellTest.java b/test/unit/org/apache/cassandra/db/NativeCellTest.java index 1fa8df5e2a..713f26ba34 100644 --- a/test/unit/org/apache/cassandra/db/NativeCellTest.java +++ b/test/unit/org/apache/cassandra/db/NativeCellTest.java @@ -35,6 +35,7 @@ import org.apache.cassandra.db.marshal.BytesType; import org.apache.cassandra.db.marshal.SetType; import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.db.rows.*; +import org.apache.cassandra.utils.concurrent.ImmediateFuture; import org.apache.cassandra.utils.concurrent.OpOrder; import org.apache.cassandra.utils.memory.HeapAllocator; import org.apache.cassandra.utils.memory.NativeAllocator; @@ -47,7 +48,7 @@ public class NativeCellTest private static final NativeAllocator nativeAllocator = new NativePool(Integer.MAX_VALUE, Integer.MAX_VALUE, 1f, - () -> CompletableFuture.completedFuture(true)).newAllocator(); + () -> ImmediateFuture.success(true)).newAllocator(); @SuppressWarnings("resource") private static final OpOrder.Group group = new OpOrder().start(); private static Random rand; diff --git a/test/unit/org/apache/cassandra/db/RecoveryManagerTest.java b/test/unit/org/apache/cassandra/db/RecoveryManagerTest.java index 013f30b14a..c60bb97aa4 100644 --- a/test/unit/org/apache/cassandra/db/RecoveryManagerTest.java +++ b/test/unit/org/apache/cassandra/db/RecoveryManagerTest.java @@ -23,11 +23,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; import org.junit.Assert; @@ -61,6 +57,7 @@ import org.apache.cassandra.schema.KeyspaceParams; import org.apache.cassandra.security.EncryptionContext; import org.apache.cassandra.security.EncryptionContextGenerator; import org.apache.cassandra.utils.ByteBufferUtil; +import org.apache.cassandra.utils.concurrent.AsyncFuture; import static org.junit.Assert.assertEquals; @@ -160,7 +157,7 @@ public class RecoveryManagerTest Assert.assertTrue(Util.getAllUnfiltered(Util.cmd(keyspace2.getColumnFamilyStore(CF_STANDARD3), dk).build()).isEmpty()); final AtomicReference err = new AtomicReference(); - Thread t = NamedThreadFactory.createThread(() -> + Thread t = NamedThreadFactory.createAnonymousThread(() -> { try { @@ -345,32 +342,22 @@ public class RecoveryManagerTest final Semaphore blocked = new Semaphore(0); @Override - protected Future initiateMutation(final Mutation mutation, - final long segmentId, - final int serializedSize, - final int entryLocation, - final CommitLogReplayer clr) + protected org.apache.cassandra.utils.concurrent.Future initiateMutation(final Mutation mutation, + final long segmentId, + final int serializedSize, + final int entryLocation, + final CommitLogReplayer clr) { - final Future toWrap = super.initiateMutation(mutation, - segmentId, - serializedSize, - entryLocation, - clr); - return new Future() + final org.apache.cassandra.utils.concurrent.Future toWrap = + super.initiateMutation(mutation, + segmentId, + serializedSize, + entryLocation, + clr); + + return new AsyncFuture() { - @Override - public boolean cancel(boolean mayInterruptIfRunning) - { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isCancelled() - { - throw new UnsupportedOperationException(); - } - @Override public boolean isDone() { @@ -380,7 +367,6 @@ public class RecoveryManagerTest @Override public Integer get() throws InterruptedException, ExecutionException { - System.out.println("Got blocker once"); blocked.release(); blocker.acquire(); return toWrap.get(); diff --git a/test/unit/org/apache/cassandra/db/commitlog/AbstractCommitLogServiceTest.java b/test/unit/org/apache/cassandra/db/commitlog/AbstractCommitLogServiceTest.java index 741b1454b5..25abbf7b54 100644 --- a/test/unit/org/apache/cassandra/db/commitlog/AbstractCommitLogServiceTest.java +++ b/test/unit/org/apache/cassandra/db/commitlog/AbstractCommitLogServiceTest.java @@ -25,11 +25,14 @@ import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; +import org.apache.cassandra.concurrent.Interruptible; import org.apache.cassandra.config.Config; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.commitlog.AbstractCommitLogService.SyncRunnable; import org.apache.cassandra.utils.FreeRunningClock; +import static org.apache.cassandra.concurrent.Interruptible.State.NORMAL; +import static org.apache.cassandra.concurrent.Interruptible.State.SHUTTING_DOWN; import static org.apache.cassandra.db.commitlog.AbstractCommitLogService.DEFAULT_MARKER_INTERVAL_MILLIS; public class AbstractCommitLogServiceTest @@ -111,7 +114,7 @@ public class AbstractCommitLogServiceTest } @Test - public void testSync() + public void testSync() throws InterruptedException { long syncTimeMillis = AbstractCommitLogService.DEFAULT_MARKER_INTERVAL_MILLIS * 2; FreeRunningClock clock = new FreeRunningClock(); @@ -120,26 +123,25 @@ public class AbstractCommitLogServiceTest FakeCommitLog commitLog = (FakeCommitLog) commitLogService.commitLog; // at time 0 - Assert.assertTrue(syncRunnable.sync()); + syncRunnable.run(NORMAL); Assert.assertEquals(1, commitLog.markCount.get()); Assert.assertEquals(0, commitLog.syncCount.get()); // at time DEFAULT_MARKER_INTERVAL_MILLIS clock.advance(DEFAULT_MARKER_INTERVAL_MILLIS, TimeUnit.MILLISECONDS); - Assert.assertTrue(syncRunnable.sync()); + syncRunnable.run(NORMAL); Assert.assertEquals(2, commitLog.markCount.get()); Assert.assertEquals(0, commitLog.syncCount.get()); // at time DEFAULT_MARKER_INTERVAL_MILLIS * 2 clock.advance(DEFAULT_MARKER_INTERVAL_MILLIS, TimeUnit.MILLISECONDS); - Assert.assertTrue(syncRunnable.sync()); + syncRunnable.run(NORMAL); Assert.assertEquals(2, commitLog.markCount.get()); Assert.assertEquals(1, commitLog.syncCount.get()); // at time DEFAULT_MARKER_INTERVAL_MILLIS * 3, but with shutdown! clock.advance(DEFAULT_MARKER_INTERVAL_MILLIS, TimeUnit.MILLISECONDS); - commitLogService.shutdown(); - Assert.assertFalse(syncRunnable.sync()); + syncRunnable.run(SHUTTING_DOWN); Assert.assertEquals(2, commitLog.markCount.get()); Assert.assertEquals(2, commitLog.syncCount.get()); } diff --git a/test/unit/org/apache/cassandra/db/commitlog/CommitLogInitWithExceptionTest.java b/test/unit/org/apache/cassandra/db/commitlog/CommitLogInitWithExceptionTest.java index 690d9babfc..fbad0b4857 100644 --- a/test/unit/org/apache/cassandra/db/commitlog/CommitLogInitWithExceptionTest.java +++ b/test/unit/org/apache/cassandra/db/commitlog/CommitLogInitWithExceptionTest.java @@ -90,7 +90,7 @@ public class CommitLogInitWithExceptionTest Assert.fail(); } - Assert.assertEquals(Thread.State.TERMINATED, CommitLog.instance.segmentManager.managerThread.getState()); // exit successfully + Assert.assertTrue(CommitLog.instance.segmentManager.executor.isTerminated()); // exit successfully } private static class MockCommitLogSegmentMgr extends CommitLogSegmentManagerStandard { diff --git a/test/unit/org/apache/cassandra/db/commitlog/CommitLogSegmentBackpressureTest.java b/test/unit/org/apache/cassandra/db/commitlog/CommitLogSegmentBackpressureTest.java index 6b167b2500..e28c25e2bc 100644 --- a/test/unit/org/apache/cassandra/db/commitlog/CommitLogSegmentBackpressureTest.java +++ b/test/unit/org/apache/cassandra/db/commitlog/CommitLogSegmentBackpressureTest.java @@ -66,12 +66,12 @@ public class CommitLogSegmentBackpressureTest @Test @BMRules(rules = {@BMRule(name = "Acquire Semaphore before sync", targetClass = "AbstractCommitLogService$SyncRunnable", - targetMethod = "sync", + targetMethod = "run", targetLocation = "AT INVOKE org.apache.cassandra.db.commitlog.CommitLog.sync(boolean)", action = "org.apache.cassandra.db.commitlog.CommitLogSegmentBackpressureTest.allowSync.acquire()"), @BMRule(name = "Release Semaphore after sync", targetClass = "AbstractCommitLogService$SyncRunnable", - targetMethod = "sync", + targetMethod = "run", targetLocation = "AFTER INVOKE org.apache.cassandra.db.commitlog.CommitLog.sync(boolean)", action = "org.apache.cassandra.db.commitlog.CommitLogSegmentBackpressureTest.allowSync.release()")}) public void testCompressedCommitLogBackpressure() throws Throwable diff --git a/test/unit/org/apache/cassandra/db/compaction/CompactionExecutorTest.java b/test/unit/org/apache/cassandra/db/compaction/CompactionExecutorTest.java index ab3d9e5c1e..813d12e196 100644 --- a/test/unit/org/apache/cassandra/db/compaction/CompactionExecutorTest.java +++ b/test/unit/org/apache/cassandra/db/compaction/CompactionExecutorTest.java @@ -21,12 +21,11 @@ package org.apache.cassandra.db.compaction; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import org.apache.cassandra.concurrent.ExecutorFactory; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.apache.cassandra.concurrent.DebuggableThreadPoolExecutor; -import org.apache.cassandra.config.DatabaseDescriptor; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -34,29 +33,15 @@ import static org.junit.Assert.assertNotNull; public class CompactionExecutorTest { static Throwable testTaskThrowable = null; - private static class TestTaskExecutor extends CompactionManager.CompactionExecutor - { - @Override - public void afterExecute(Runnable r, Throwable t) - { - if (t == null) - { - t = DebuggableThreadPoolExecutor.extractThrowable(r); - } - testTaskThrowable = t; - } - @Override - protected void beforeExecute(Thread t, Runnable r) - { - } - } private CompactionManager.CompactionExecutor executor; @Before public void setup() { - DatabaseDescriptor.daemonInitialization(); - executor = new TestTaskExecutor(); + executor = new CompactionManager.CompactionExecutor(new ExecutorFactory.Default(null, null, (thread, throwable) -> { + if (throwable != null) + testTaskThrowable = throwable; + }), 1, "test", Integer.MAX_VALUE); } @After diff --git a/test/unit/org/apache/cassandra/db/compaction/CorruptedSSTablesCompactionsTest.java b/test/unit/org/apache/cassandra/db/compaction/CorruptedSSTablesCompactionsTest.java index d56c48b82c..f5a9e6afa3 100644 --- a/test/unit/org/apache/cassandra/db/compaction/CorruptedSSTablesCompactionsTest.java +++ b/test/unit/org/apache/cassandra/db/compaction/CorruptedSSTablesCompactionsTest.java @@ -109,7 +109,7 @@ public class CorruptedSSTablesCompactionsTest public static void closeStdErr() { // These tests generate an error message per CorruptSSTableException since it goes through - // DebuggableThreadPoolExecutor, which will log it in afterExecute. We could stop that by + // ExecutorPlus, which will log it in afterExecute. We could stop that by // creating custom CompactionStrategy and CompactionTask classes, but that's kind of a // ridiculous amount of effort, especially since those aren't really intended to be wrapped // like that. diff --git a/test/unit/org/apache/cassandra/db/repair/PendingAntiCompactionTest.java b/test/unit/org/apache/cassandra/db/repair/PendingAntiCompactionTest.java index 9d3990a26f..f973ae1fb4 100644 --- a/test/unit/org/apache/cassandra/db/repair/PendingAntiCompactionTest.java +++ b/test/unit/org/apache/cassandra/db/repair/PendingAntiCompactionTest.java @@ -29,7 +29,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; @@ -41,9 +40,11 @@ import com.google.common.collect.Lists; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListenableFutureTask; -import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; +import org.apache.cassandra.concurrent.ExecutorPlus; +import org.apache.cassandra.concurrent.FutureTask; +import org.apache.cassandra.concurrent.ImmediateExecutor; +import org.apache.cassandra.utils.concurrent.Future; import org.junit.Assert; import org.junit.Test; import org.slf4j.Logger; @@ -80,6 +81,7 @@ import org.apache.cassandra.utils.UUIDGen; import org.apache.cassandra.utils.WrappedRunnable; import org.apache.cassandra.utils.concurrent.Transactional; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -100,11 +102,11 @@ public class PendingAntiCompactionTest extends AbstractPendingAntiCompactionTest Set submittedCompactions = new HashSet<>(); - ListenableFuture submitPendingAntiCompaction(PendingAntiCompaction.AcquireResult result) + Future submitPendingAntiCompaction(PendingAntiCompaction.AcquireResult result) { submittedCompactions.add(result.cfs.metadata.id); result.abort(); // prevent ref leak complaints - return ListenableFutureTask.create(() -> {}, null); + return new FutureTask<>(() -> {}); } } @@ -411,7 +413,7 @@ public class PendingAntiCompactionTest extends AbstractPendingAntiCompactionTest cfs.disableAutoCompaction(); makeSSTables(2); UUID prsid = UUID.randomUUID(); - ListeningExecutorService es = MoreExecutors.listeningDecorator(MoreExecutors.newDirectExecutorService()); + ExecutorPlus es = ImmediateExecutor.INSTANCE; PendingAntiCompaction pac = new PendingAntiCompaction(prsid, Collections.singleton(cfs), atEndpoint(FULL_RANGE, NO_RANGES), es, () -> false) { @Override protected AcquisitionCallback getAcquisitionCallback(UUID prsId, RangesAtEndpoint tokenRanges) @@ -419,7 +421,7 @@ public class PendingAntiCompactionTest extends AbstractPendingAntiCompactionTest return new AcquisitionCallback(prsid, tokenRanges, () -> false) { @Override - ListenableFuture submitPendingAntiCompaction(AcquireResult result) + Future submitPendingAntiCompaction(AcquireResult result) { Runnable r = new WrappedRunnable() { @@ -640,7 +642,7 @@ public class PendingAntiCompactionTest extends AbstractPendingAntiCompactionTest ColumnFamilyStore cfs = MockSchema.newCFS(); cfs.addSSTable(MockSchema.sstable(1, true, cfs)); CountDownLatch cdl = new CountDownLatch(5); - ExecutorService es = Executors.newFixedThreadPool(1); + ExecutorPlus es = executorFactory().sequential("test"); CompactionInfo.Holder holder = new CompactionInfo.Holder() { public CompactionInfo getCompactionInfo() @@ -684,7 +686,7 @@ public class PendingAntiCompactionTest extends AbstractPendingAntiCompactionTest { ColumnFamilyStore cfs = MockSchema.newCFS(); cfs.addSSTable(MockSchema.sstable(1, true, cfs)); - ExecutorService es = Executors.newFixedThreadPool(1); + ExecutorPlus es = executorFactory().sequential("test"); CompactionInfo.Holder holder = new CompactionInfo.Holder() { public CompactionInfo getCompactionInfo() diff --git a/test/unit/org/apache/cassandra/db/streaming/CassandraStreamManagerTest.java b/test/unit/org/apache/cassandra/db/streaming/CassandraStreamManagerTest.java index 0b37d667e9..4281278dd8 100644 --- a/test/unit/org/apache/cassandra/db/streaming/CassandraStreamManagerTest.java +++ b/test/unit/org/apache/cassandra/db/streaming/CassandraStreamManagerTest.java @@ -222,7 +222,7 @@ public class CassandraStreamManagerTest } } }; - Thread t = NamedThreadFactory.createThread(r); + Thread t = NamedThreadFactory.createAnonymousThread(r); try { t.start(); diff --git a/test/unit/org/apache/cassandra/hints/HintsBufferTest.java b/test/unit/org/apache/cassandra/hints/HintsBufferTest.java index 0dcf74752a..53c1c77251 100644 --- a/test/unit/org/apache/cassandra/hints/HintsBufferTest.java +++ b/test/unit/org/apache/cassandra/hints/HintsBufferTest.java @@ -116,7 +116,7 @@ public class HintsBufferTest // create HINT_THREADS_COUNT, start them, and wait for them to finish List threads = new ArrayList<>(HINT_THREADS_COUNT); for (int i = 0; i < HINT_THREADS_COUNT; i ++) - threads.add(NamedThreadFactory.createThread(new Writer(buffer, load, hintSize, i, baseTimestamp))); + threads.add(NamedThreadFactory.createAnonymousThread(new Writer(buffer, load, hintSize, i, baseTimestamp))); threads.forEach(java.lang.Thread::start); for (Thread thread : threads) thread.join(); diff --git a/test/unit/org/apache/cassandra/io/sstable/IndexSummaryManagerTest.java b/test/unit/org/apache/cassandra/io/sstable/IndexSummaryManagerTest.java index 08bd67cc18..253337e2e3 100644 --- a/test/unit/org/apache/cassandra/io/sstable/IndexSummaryManagerTest.java +++ b/test/unit/org/apache/cassandra/io/sstable/IndexSummaryManagerTest.java @@ -652,7 +652,7 @@ public class IndexSummaryManagerTest { CompactionManager.instance.active.beginCompaction(ongoingCompaction); - Thread t = NamedThreadFactory.createThread(new Runnable() + Thread t = NamedThreadFactory.createAnonymousThread(new Runnable() { public void run() { diff --git a/test/unit/org/apache/cassandra/io/sstable/SSTableRewriterTest.java b/test/unit/org/apache/cassandra/io/sstable/SSTableRewriterTest.java index 1895653ccd..bc4c16db72 100644 --- a/test/unit/org/apache/cassandra/io/sstable/SSTableRewriterTest.java +++ b/test/unit/org/apache/cassandra/io/sstable/SSTableRewriterTest.java @@ -834,7 +834,7 @@ public class SSTableRewriterTest extends SSTableWriterTestBase } } }; - Thread t = NamedThreadFactory.createThread(r); + Thread t = NamedThreadFactory.createAnonymousThread(r); try { t.start(); diff --git a/test/unit/org/apache/cassandra/metrics/SamplerTest.java b/test/unit/org/apache/cassandra/metrics/SamplerTest.java index 3d24c1b3a5..dba19a3064 100644 --- a/test/unit/org/apache/cassandra/metrics/SamplerTest.java +++ b/test/unit/org/apache/cassandra/metrics/SamplerTest.java @@ -224,7 +224,7 @@ public class SamplerTest public void waitForEmpty(int timeoutMs) throws TimeoutException { int timeout = 0; - while (!Sampler.samplerExecutor.getQueue().isEmpty()) + while (Sampler.samplerExecutor.getPendingTaskCount() > 0) { timeout++; Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS); diff --git a/test/unit/org/apache/cassandra/metrics/ThreadPoolMetricsTest.java b/test/unit/org/apache/cassandra/metrics/ThreadPoolMetricsTest.java index 73026558ce..5c80b8ef0d 100644 --- a/test/unit/org/apache/cassandra/metrics/ThreadPoolMetricsTest.java +++ b/test/unit/org/apache/cassandra/metrics/ThreadPoolMetricsTest.java @@ -17,7 +17,6 @@ */ package org.apache.cassandra.metrics; -import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -28,6 +27,7 @@ import org.junit.Test; import org.apache.cassandra.Util; import org.apache.cassandra.concurrent.*; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.junit.Assert.*; public class ThreadPoolMetricsTest @@ -35,25 +35,23 @@ public class ThreadPoolMetricsTest @Test public void testJMXEnabledThreadPoolMetricsWithNoBlockedThread() { - JMXEnabledThreadPoolExecutor executor = new JMXEnabledThreadPoolExecutor(2, - Integer.MAX_VALUE, - TimeUnit.SECONDS, - new ArrayBlockingQueue<>(2), - new NamedThreadFactory("ThreadPoolMetricsTest-1"), - "internal"); - testMetricsWithNoBlockedThreads(executor, executor.metrics); + ThreadPoolExecutorPlus executor = (ThreadPoolExecutorPlus) executorFactory() + .withJmxInternal() + .configurePooled("ThreadPoolMetricsTest-1", 2) + .withQueueLimit(2) + .build(); + testMetricsWithNoBlockedThreads(executor, ((ThreadPoolExecutorJMXAdapter)executor.onShutdown()).metrics()); } @Test public void testJMXEnabledThreadPoolMetricsWithBlockedThread() { - JMXEnabledThreadPoolExecutor executor = new JMXEnabledThreadPoolExecutor(2, - Integer.MAX_VALUE, - TimeUnit.SECONDS, - new ArrayBlockingQueue<>(2), - new NamedThreadFactory("ThreadPoolMetricsTest-2"), - "internal"); - testMetricsWithBlockedThreads(executor, executor.metrics); + ThreadPoolExecutorPlus executor = (ThreadPoolExecutorPlus) executorFactory() + .withJmxInternal() + .configurePooled("ThreadPoolMetricsTest-2", 2) + .withQueueLimit(2) + .build(); + testMetricsWithBlockedThreads(executor, ((ThreadPoolExecutorJMXAdapter)executor.onShutdown()).metrics()); } @Test @@ -66,7 +64,7 @@ public class ThreadPoolMetricsTest testMetricsWithNoBlockedThreads(executor, executor.metrics); } - private static void testMetricsWithBlockedThreads(LocalAwareExecutorService threadPool, ThreadPoolMetrics metrics) + private static void testMetricsWithBlockedThreads(ExecutorPlus threadPool, ThreadPoolMetrics metrics) { assertEquals(2, metrics.maxPoolSize.getValue().intValue()); @@ -187,7 +185,7 @@ public class ThreadPoolMetricsTest spinAssertEquals(2L, metrics.totalBlocked::getCount); } - private static void testMetricsWithNoBlockedThreads(LocalAwareExecutorService threadPool, ThreadPoolMetrics metrics) + private static void testMetricsWithNoBlockedThreads(ExecutorPlus threadPool, ThreadPoolMetrics metrics) { spinAssertEquals(0, metrics.activeTasks::getValue); spinAssertEquals(0L, metrics.completedTasks::getValue); diff --git a/test/unit/org/apache/cassandra/net/AsyncChannelPromiseTest.java b/test/unit/org/apache/cassandra/net/AsyncChannelPromiseTest.java index aae0c6ab37..4c16f6fe1a 100644 --- a/test/unit/org/apache/cassandra/net/AsyncChannelPromiseTest.java +++ b/test/unit/org/apache/cassandra/net/AsyncChannelPromiseTest.java @@ -17,12 +17,19 @@ */ package org.apache.cassandra.net; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableList; import org.junit.After; +import org.junit.Assert; import org.junit.Test; -import io.netty.channel.ChannelPromise; import io.netty.channel.embedded.EmbeddedChannel; import org.apache.cassandra.utils.concurrent.AbstractTestAsyncPromise; +import org.apache.cassandra.utils.concurrent.Promise; public class AsyncChannelPromiseTest extends AbstractTestAsyncPromise { @@ -32,42 +39,66 @@ public class AsyncChannelPromiseTest extends AbstractTestAsyncPromise exec.shutdownNow(); } - private ChannelPromise newPromise() + private static List>> suppliers(AtomicInteger listeners, boolean includedUncancellable) { - return new AsyncChannelPromise(new EmbeddedChannel()); + List>> cancellable = ImmutableList.of( + () -> new AsyncChannelPromise(new EmbeddedChannel()), + () -> AsyncChannelPromise.withListener(new EmbeddedChannel(), f -> listeners.incrementAndGet()) + ); + + if (!includedUncancellable) + return cancellable; + + return ImmutableList.>>builder() + .addAll(cancellable) + .addAll(cancellable.stream().map(s -> (Supplier>) () -> cancelSuccess(s.get())).collect(Collectors.toList())) + .build(); } @Test public void testSuccess() { - for (boolean setUncancellable : new boolean[] { false, true }) - for (boolean tryOrSet : new boolean[]{ false, true }) - testOneSuccess(newPromise(), setUncancellable, tryOrSet, null, null); + final AtomicInteger initialListeners = new AtomicInteger(); + List>> suppliers = suppliers(initialListeners, true); + for (boolean tryOrSet : new boolean[]{ false, true }) + for (Supplier> supplier : suppliers) + testOneSuccess(supplier.get(), tryOrSet, null, null); + Assert.assertEquals(2 * 2, initialListeners.get()); } @Test public void testFailure() { - for (boolean setUncancellable : new boolean[] { false, true }) - for (boolean tryOrSet : new boolean[] { false, true }) - for (Throwable v : new Throwable[] { null, new NullPointerException() }) - testOneFailure(newPromise(), setUncancellable, tryOrSet, v, null); + final AtomicInteger initialListeners = new AtomicInteger(); + List>> suppliers = suppliers(initialListeners, true); + for (boolean tryOrSet : new boolean[]{ false, true }) + for (Throwable v : new Throwable[] { null, new NullPointerException() }) + for (Supplier> supplier : suppliers) + testOneFailure(supplier.get(), tryOrSet, v, null); + Assert.assertEquals(2 * 2 * 2, initialListeners.get()); } @Test public void testCancellation() { + final AtomicInteger initialListeners = new AtomicInteger(); + List>> suppliers = suppliers(initialListeners, false); for (boolean interruptIfRunning : new boolean[] { true, false }) - testOneCancellation(newPromise(), interruptIfRunning, null); + for (Supplier> supplier : suppliers) + testOneCancellation(supplier.get(), interruptIfRunning, null); + Assert.assertEquals(2, initialListeners.get()); } @Test public void testTimeout() { - for (boolean setUncancellable : new boolean[] { true, false }) - testOneTimeout(newPromise(), setUncancellable); + final AtomicInteger initialListeners = new AtomicInteger(); + List>> suppliers = suppliers(initialListeners, true); + for (Supplier> supplier : suppliers) + testOneTimeout(supplier.get()); + Assert.assertEquals(0, initialListeners.get()); } } diff --git a/test/unit/org/apache/cassandra/net/HandshakeTest.java b/test/unit/org/apache/cassandra/net/HandshakeTest.java index b9014e2b9e..75ae1034c5 100644 --- a/test/unit/org/apache/cassandra/net/HandshakeTest.java +++ b/test/unit/org/apache/cassandra/net/HandshakeTest.java @@ -83,7 +83,7 @@ public class HandshakeTest new OutboundConnectionSettings(endpoint) .withAcceptVersions(acceptOutbound) .withDefaults(ConnectionCategory.MESSAGING), - req, new AsyncPromise<>(eventLoop)); + req, AsyncPromise.withExecutor(eventLoop)); return future.get(); } finally diff --git a/test/unit/org/apache/cassandra/repair/RepairJobTest.java b/test/unit/org/apache/cassandra/repair/RepairJobTest.java index 231ebcd7ff..573934b515 100644 --- a/test/unit/org/apache/cassandra/repair/RepairJobTest.java +++ b/test/unit/org/apache/cassandra/repair/RepairJobTest.java @@ -45,7 +45,8 @@ import org.junit.BeforeClass; import org.junit.Test; import org.apache.cassandra.SchemaLoader; -import org.apache.cassandra.concurrent.DebuggableThreadPoolExecutor; +import org.apache.cassandra.concurrent.ExecutorFactory; +import org.apache.cassandra.concurrent.ExecutorPlus; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.dht.ByteOrderedPartitioner; import org.apache.cassandra.dht.IPartitioner; @@ -118,11 +119,13 @@ public class RepairJobTest super(parentRepairSession, id, commonRange, keyspace, parallelismDegree, isIncremental, pullRepair, previewKind, optimiseStreams, cfnames); } - protected DebuggableThreadPoolExecutor createExecutor() + protected ExecutorPlus createExecutor() { - DebuggableThreadPoolExecutor executor = super.createExecutor(); - executor.setKeepAliveTime(THREAD_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); - return executor; + return ExecutorFactory.Global.executorFactory() + .configurePooled("RepairJobTask", Integer.MAX_VALUE) + .withDefaultThreadGroup() + .withKeepAlive(THREAD_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS) + .build(); } @Override diff --git a/test/unit/org/apache/cassandra/repair/consistent/CoordinatorMessagingTest.java b/test/unit/org/apache/cassandra/repair/consistent/CoordinatorMessagingTest.java index 3c288b0ef5..ef08022836 100644 --- a/test/unit/org/apache/cassandra/repair/consistent/CoordinatorMessagingTest.java +++ b/test/unit/org/apache/cassandra/repair/consistent/CoordinatorMessagingTest.java @@ -31,8 +31,9 @@ import java.util.function.Function; import java.util.function.Supplier; import com.google.common.collect.Lists; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.SettableFuture; +import org.apache.cassandra.utils.concurrent.AsyncPromise; +import org.apache.cassandra.utils.concurrent.Future; +import org.apache.cassandra.utils.concurrent.Promise; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -105,8 +106,8 @@ public class CoordinatorMessagingTest extends AbstractRepairTest UUID uuid = registerSession(cfs, true, true); CoordinatorSession coordinator = ActiveRepairService.instance.consistent.coordinated.registerSession(uuid, PARTICIPANTS, false); AtomicBoolean repairSubmitted = new AtomicBoolean(false); - SettableFuture> repairFuture = SettableFuture.create(); - Supplier>> sessionSupplier = () -> + Promise> repairFuture = AsyncPromise.uncancellable(); + Supplier>> sessionSupplier = () -> { repairSubmitted.set(true); return repairFuture; @@ -117,7 +118,7 @@ public class CoordinatorMessagingTest extends AbstractRepairTest Assert.assertFalse(repairSubmitted.get()); // execute repair and start prepare phase - ListenableFuture sessionResult = coordinator.execute(sessionSupplier, hasFailures); + Future sessionResult = coordinator.execute(sessionSupplier, hasFailures); Assert.assertFalse(sessionResult.isDone()); Assert.assertFalse(hasFailures.get()); // prepare completed @@ -127,7 +128,7 @@ public class CoordinatorMessagingTest extends AbstractRepairTest Assert.assertFalse(hasFailures.get()); // set result from local repair session - repairFuture.set(Lists.newArrayList(createResult(coordinator), createResult(coordinator), createResult(coordinator))); + repairFuture.trySuccess(Lists.newArrayList(createResult(coordinator), createResult(coordinator), createResult(coordinator))); // finalize phase finalizeLatch.countDown(); @@ -196,8 +197,8 @@ public class CoordinatorMessagingTest extends AbstractRepairTest UUID uuid = registerSession(cfs, true, true); CoordinatorSession coordinator = ActiveRepairService.instance.consistent.coordinated.registerSession(uuid, PARTICIPANTS, false); AtomicBoolean repairSubmitted = new AtomicBoolean(false); - SettableFuture> repairFuture = SettableFuture.create(); - Supplier>> sessionSupplier = () -> + Promise> repairFuture = AsyncPromise.uncancellable(); + Supplier>> sessionSupplier = () -> { repairSubmitted.set(true); return repairFuture; @@ -208,7 +209,7 @@ public class CoordinatorMessagingTest extends AbstractRepairTest Assert.assertFalse(repairSubmitted.get()); // execute repair and start prepare phase - ListenableFuture sessionResult = coordinator.execute(sessionSupplier, proposeFailed); + Future sessionResult = coordinator.execute(sessionSupplier, proposeFailed); prepareLatch.countDown(); // prepare completed try @@ -235,8 +236,8 @@ public class CoordinatorMessagingTest extends AbstractRepairTest UUID uuid = registerSession(cfs, true, true); CoordinatorSession coordinator = ActiveRepairService.instance.consistent.coordinated.registerSession(uuid, PARTICIPANTS, false); AtomicBoolean repairSubmitted = new AtomicBoolean(false); - SettableFuture> repairFuture = SettableFuture.create(); - Supplier>> sessionSupplier = () -> + Promise> repairFuture = AsyncPromise.uncancellable(); + Supplier>> sessionSupplier = () -> { repairSubmitted.set(true); return repairFuture; @@ -247,7 +248,7 @@ public class CoordinatorMessagingTest extends AbstractRepairTest Assert.assertFalse(repairSubmitted.get()); // execute repair and start prepare phase - ListenableFuture sessionResult = coordinator.execute(sessionSupplier, hasFailures); + Future sessionResult = coordinator.execute(sessionSupplier, hasFailures); try { sessionResult.get(1, TimeUnit.SECONDS); diff --git a/test/unit/org/apache/cassandra/repair/consistent/CoordinatorSessionTest.java b/test/unit/org/apache/cassandra/repair/consistent/CoordinatorSessionTest.java index 1cee312a0e..42453d44a5 100644 --- a/test/unit/org/apache/cassandra/repair/consistent/CoordinatorSessionTest.java +++ b/test/unit/org/apache/cassandra/repair/consistent/CoordinatorSessionTest.java @@ -28,9 +28,10 @@ import java.util.function.Supplier; import com.google.common.collect.Lists; import com.google.common.collect.Sets; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.SettableFuture; +import org.apache.cassandra.utils.concurrent.AsyncPromise; +import org.apache.cassandra.utils.concurrent.Future; +import org.apache.cassandra.utils.concurrent.Promise; import org.junit.Assert; import org.junit.Test; @@ -209,8 +210,8 @@ public class CoordinatorSessionTest extends AbstractRepairTest { InstrumentedCoordinatorSession coordinator = createInstrumentedSession(); AtomicBoolean repairSubmitted = new AtomicBoolean(false); - SettableFuture> repairFuture = SettableFuture.create(); - Supplier>> sessionSupplier = () -> + Promise> repairFuture = AsyncPromise.uncancellable(); + Supplier>> sessionSupplier = () -> { repairSubmitted.set(true); return repairFuture; @@ -220,7 +221,7 @@ public class CoordinatorSessionTest extends AbstractRepairTest AtomicBoolean hasFailures = new AtomicBoolean(false); Assert.assertFalse(repairSubmitted.get()); Assert.assertTrue(coordinator.sentMessages.isEmpty()); - ListenableFuture sessionResult = coordinator.execute(sessionSupplier, hasFailures); + Future sessionResult = coordinator.execute(sessionSupplier, hasFailures); for (InetAddressAndPort participant : PARTICIPANTS) { @@ -252,7 +253,7 @@ public class CoordinatorSessionTest extends AbstractRepairTest createResult(coordinator)); coordinator.sentMessages.clear(); - repairFuture.set(results); + repairFuture.trySuccess(results); // propose messages should have been sent once all repair sessions completed successfully for (InetAddressAndPort participant : PARTICIPANTS) @@ -293,8 +294,8 @@ public class CoordinatorSessionTest extends AbstractRepairTest { InstrumentedCoordinatorSession coordinator = createInstrumentedSession(); AtomicBoolean repairSubmitted = new AtomicBoolean(false); - SettableFuture> repairFuture = SettableFuture.create(); - Supplier>> sessionSupplier = () -> + Promise> repairFuture = AsyncPromise.uncancellable(); + Supplier>> sessionSupplier = () -> { repairSubmitted.set(true); return repairFuture; @@ -304,7 +305,7 @@ public class CoordinatorSessionTest extends AbstractRepairTest AtomicBoolean hasFailures = new AtomicBoolean(false); Assert.assertFalse(repairSubmitted.get()); Assert.assertTrue(coordinator.sentMessages.isEmpty()); - ListenableFuture sessionResult = coordinator.execute(sessionSupplier, hasFailures); + Future sessionResult = coordinator.execute(sessionSupplier, hasFailures); for (InetAddressAndPort participant : PARTICIPANTS) { PrepareConsistentRequest expected = new PrepareConsistentRequest(coordinator.sessionID, COORDINATOR, new HashSet<>(PARTICIPANTS)); @@ -336,7 +337,7 @@ public class CoordinatorSessionTest extends AbstractRepairTest coordinator.sentMessages.clear(); Assert.assertFalse(coordinator.failCalled); coordinator.onFail = () -> Assert.assertEquals(REPAIRING, coordinator.getState()); - repairFuture.set(results); + repairFuture.trySuccess(results); Assert.assertTrue(coordinator.failCalled); // all participants should have been notified of session failure @@ -355,8 +356,8 @@ public class CoordinatorSessionTest extends AbstractRepairTest { InstrumentedCoordinatorSession coordinator = createInstrumentedSession(); AtomicBoolean repairSubmitted = new AtomicBoolean(false); - SettableFuture> repairFuture = SettableFuture.create(); - Supplier>> sessionSupplier = () -> + Promise> repairFuture = AsyncPromise.uncancellable(); + Supplier>> sessionSupplier = () -> { repairSubmitted.set(true); return repairFuture; @@ -366,7 +367,7 @@ public class CoordinatorSessionTest extends AbstractRepairTest AtomicBoolean hasFailures = new AtomicBoolean(false); Assert.assertFalse(repairSubmitted.get()); Assert.assertTrue(coordinator.sentMessages.isEmpty()); - ListenableFuture sessionResult = coordinator.execute(sessionSupplier, hasFailures); + Future sessionResult = coordinator.execute(sessionSupplier, hasFailures); for (InetAddressAndPort participant : PARTICIPANTS) { PrepareConsistentRequest expected = new PrepareConsistentRequest(coordinator.sessionID, COORDINATOR, new HashSet<>(PARTICIPANTS)); @@ -421,8 +422,8 @@ public class CoordinatorSessionTest extends AbstractRepairTest { InstrumentedCoordinatorSession coordinator = createInstrumentedSession(); AtomicBoolean repairSubmitted = new AtomicBoolean(false); - SettableFuture> repairFuture = SettableFuture.create(); - Supplier>> sessionSupplier = () -> + Promise> repairFuture = AsyncPromise.uncancellable(); + Supplier>> sessionSupplier = () -> { repairSubmitted.set(true); return repairFuture; @@ -432,7 +433,7 @@ public class CoordinatorSessionTest extends AbstractRepairTest AtomicBoolean hasFailures = new AtomicBoolean(false); Assert.assertFalse(repairSubmitted.get()); Assert.assertTrue(coordinator.sentMessages.isEmpty()); - ListenableFuture sessionResult = coordinator.execute(sessionSupplier, hasFailures); + Future sessionResult = coordinator.execute(sessionSupplier, hasFailures); for (InetAddressAndPort participant : PARTICIPANTS) { @@ -464,7 +465,7 @@ public class CoordinatorSessionTest extends AbstractRepairTest createResult(coordinator)); coordinator.sentMessages.clear(); - repairFuture.set(results); + repairFuture.trySuccess(results); // propose messages should have been sent once all repair sessions completed successfully for (InetAddressAndPort participant : PARTICIPANTS) diff --git a/test/unit/org/apache/cassandra/repair/consistent/LocalSessionTest.java b/test/unit/org/apache/cassandra/repair/consistent/LocalSessionTest.java index 80a12c0f30..2243cd8ba8 100644 --- a/test/unit/org/apache/cassandra/repair/consistent/LocalSessionTest.java +++ b/test/unit/org/apache/cassandra/repair/consistent/LocalSessionTest.java @@ -32,8 +32,6 @@ import java.util.function.Consumer; import com.google.common.collect.Lists; import com.google.common.collect.Sets; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.SettableFuture; import org.junit.Assert; import org.junit.Before; @@ -68,6 +66,9 @@ import org.apache.cassandra.schema.KeyspaceParams; import org.apache.cassandra.service.ActiveRepairService; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.UUIDGen; +import org.apache.cassandra.utils.concurrent.AsyncPromise; +import org.apache.cassandra.utils.concurrent.Future; +import org.apache.cassandra.utils.concurrent.Promise; import static org.apache.cassandra.repair.consistent.ConsistentSession.State.*; import static org.psjava.util.AssertStatus.assertTrue; @@ -138,16 +139,16 @@ public class LocalSessionTest extends AbstractRepairTest sentMessages.get(destination).add(message.payload); } - SettableFuture prepareSessionFuture = null; + AsyncPromise> prepareSessionFuture = null; boolean prepareSessionCalled = false; @Override - ListenableFuture prepareSession(KeyspaceRepairManager repairManager, - UUID sessionID, - Collection tables, - RangesAtEndpoint ranges, - ExecutorService executor, - BooleanSupplier isCancelled) + Future> prepareSession(KeyspaceRepairManager repairManager, + UUID sessionID, + Collection tables, + RangesAtEndpoint ranges, + ExecutorService executor, + BooleanSupplier isCancelled) { prepareSessionCalled = true; if (prepareSessionFuture != null) @@ -169,9 +170,9 @@ public class LocalSessionTest extends AbstractRepairTest public LocalSession prepareForTest(UUID sessionID) { - prepareSessionFuture = SettableFuture.create(); + prepareSessionFuture = new AsyncPromise<>(); handlePrepareMessage(PARTICIPANT1, new PrepareConsistentRequest(sessionID, COORDINATOR, PARTICIPANTS)); - prepareSessionFuture.set(new Object()); + prepareSessionFuture.trySuccess(null); sentMessages.clear(); return getSession(sessionID); } @@ -271,7 +272,7 @@ public class LocalSessionTest extends AbstractRepairTest sessions.start(); // replacing future so we can inspect state before and after anti compaction callback - sessions.prepareSessionFuture = SettableFuture.create(); + sessions.prepareSessionFuture = new AsyncPromise<>(); Assert.assertFalse(sessions.prepareSessionCalled); sessions.handlePrepareMessage(PARTICIPANT1, new PrepareConsistentRequest(sessionID, COORDINATOR, PARTICIPANTS)); Assert.assertTrue(sessions.prepareSessionCalled); @@ -284,7 +285,7 @@ public class LocalSessionTest extends AbstractRepairTest Assert.assertEquals(session, sessions.loadUnsafe(sessionID)); // anti compaction has now finished, so state in memory and on disk should be PREPARED - sessions.prepareSessionFuture.set(new Object()); + sessions.prepareSessionFuture.trySuccess(null); session = sessions.getSession(sessionID); Assert.assertNotNull(session); Assert.assertEquals(PREPARED, session.getState()); @@ -306,7 +307,7 @@ public class LocalSessionTest extends AbstractRepairTest sessions.start(); // replacing future so we can inspect state before and after anti compaction callback - sessions.prepareSessionFuture = SettableFuture.create(); + sessions.prepareSessionFuture = new AsyncPromise<>(); Assert.assertFalse(sessions.prepareSessionCalled); sessions.handlePrepareMessage(PARTICIPANT1, new PrepareConsistentRequest(sessionID, COORDINATOR, PARTICIPANTS)); Assert.assertTrue(sessions.prepareSessionCalled); @@ -319,7 +320,7 @@ public class LocalSessionTest extends AbstractRepairTest Assert.assertEquals(session, sessions.loadUnsafe(sessionID)); // anti compaction has now finished, so state in memory and on disk should be PREPARED - sessions.prepareSessionFuture.setException(new RuntimeException()); + sessions.prepareSessionFuture.tryFailure(new RuntimeException()); session = sessions.getSession(sessionID); Assert.assertNotNull(session); Assert.assertEquals(FAILED, session.getState()); @@ -353,10 +354,10 @@ public class LocalSessionTest extends AbstractRepairTest { UUID sessionID = registerSession(); AtomicReference isCancelledRef = new AtomicReference<>(); - SettableFuture future = SettableFuture.create(); + Promise> future = new AsyncPromise<>(); InstrumentedLocalSessions sessions = new InstrumentedLocalSessions() { - ListenableFuture prepareSession(KeyspaceRepairManager repairManager, UUID sessionID, Collection tables, RangesAtEndpoint ranges, ExecutorService executor, BooleanSupplier isCancelled) + Future> prepareSession(KeyspaceRepairManager repairManager, UUID sessionID, Collection tables, RangesAtEndpoint ranges, ExecutorService executor, BooleanSupplier isCancelled) { isCancelledRef.set(isCancelled); return future; @@ -375,7 +376,7 @@ public class LocalSessionTest extends AbstractRepairTest Assert.assertTrue(isCancelled.getAsBoolean()); // now that the session has failed, it send a negative response to the coordinator (even if the anti-compaction completed successfully) - future.set(new Object()); + future.trySuccess(null); assertMessagesSent(sessions, COORDINATOR, new PrepareConsistentResponse(sessionID, PARTICIPANT1, false)); } @@ -708,7 +709,7 @@ public class LocalSessionTest extends AbstractRepairTest UUID sessionID = registerSession(); InstrumentedLocalSessions sessions = new InstrumentedLocalSessions(); sessions.start(); - sessions.prepareSessionFuture = SettableFuture.create(); // prevent moving to prepared + sessions.prepareSessionFuture = new AsyncPromise<>(); // prevent moving to prepared sessions.handlePrepareMessage(PARTICIPANT1, new PrepareConsistentRequest(sessionID, COORDINATOR, PARTICIPANTS)); LocalSession session = sessions.getSession(sessionID); @@ -735,9 +736,9 @@ public class LocalSessionTest extends AbstractRepairTest UUID sessionID = registerSession(); InstrumentedLocalSessions sessions = new InstrumentedLocalSessions(); sessions.start(); - sessions.prepareSessionFuture = SettableFuture.create(); + sessions.prepareSessionFuture = new AsyncPromise<>(); sessions.handlePrepareMessage(PARTICIPANT1, new PrepareConsistentRequest(sessionID, COORDINATOR, PARTICIPANTS)); - sessions.prepareSessionFuture.set(new Object()); + sessions.prepareSessionFuture.trySuccess(null); Assert.assertTrue(sessions.isSessionInProgress(sessionID)); sessions.failSession(sessionID); diff --git a/test/unit/org/apache/cassandra/schema/MigrationCoordinatorTest.java b/test/unit/org/apache/cassandra/schema/MigrationCoordinatorTest.java index 7ad5e2dcc8..19094c979d 100644 --- a/test/unit/org/apache/cassandra/schema/MigrationCoordinatorTest.java +++ b/test/unit/org/apache/cassandra/schema/MigrationCoordinatorTest.java @@ -332,7 +332,7 @@ public class MigrationCoordinatorTest coordinator.shouldPullFromEndpoint = true; Assert.assertEquals(0, coordinator.requests.size()); - List> futures = coordinator.pullUnreceivedSchemaVersions(); + List> futures = coordinator.pullUnreceivedSchemaVersions(); futures.forEach(Futures::getUnchecked); Assert.assertEquals(1, coordinator.requests.size()); } diff --git a/test/unit/org/apache/cassandra/service/ActiveRepairServiceTest.java b/test/unit/org/apache/cassandra/service/ActiveRepairServiceTest.java index 7ace0e024d..0ca69a4796 100644 --- a/test/unit/org/apache/cassandra/service/ActiveRepairServiceTest.java +++ b/test/unit/org/apache/cassandra/service/ActiveRepairServiceTest.java @@ -45,7 +45,7 @@ import org.junit.Test; import org.apache.cassandra.SchemaLoader; import org.apache.cassandra.Util; -import org.apache.cassandra.concurrent.DebuggableThreadPoolExecutor; +import org.apache.cassandra.concurrent.ExecutorPlus; import org.apache.cassandra.config.Config; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.ColumnFamilyStore; @@ -452,12 +452,12 @@ public class ActiveRepairServiceTest allSubmitted.await(TASK_SECONDS + 1, TimeUnit.SECONDS); // Give the tasks we expect to execute immediately chance to be scheduled - Util.spinAssertEquals(2 , ((DebuggableThreadPoolExecutor) validationExecutor)::getActiveTaskCount, 1); - Util.spinAssertEquals(3 , ((DebuggableThreadPoolExecutor) validationExecutor)::getPendingTaskCount, 1); + Util.spinAssertEquals(2 , ((ExecutorPlus) validationExecutor)::getActiveTaskCount, 1); + Util.spinAssertEquals(3 , ((ExecutorPlus) validationExecutor)::getPendingTaskCount, 1); // verify that we've reached a steady state with 2 threads actively processing and 3 queued tasks - Assert.assertEquals(2, ((DebuggableThreadPoolExecutor) validationExecutor).getActiveTaskCount()); - Assert.assertEquals(3, ((DebuggableThreadPoolExecutor) validationExecutor).getPendingTaskCount()); + Assert.assertEquals(2, ((ExecutorPlus) validationExecutor).getActiveTaskCount()); + Assert.assertEquals(3, ((ExecutorPlus) validationExecutor).getPendingTaskCount()); // allow executing tests to complete blocked.signalAll(); completed.await(TASK_SECONDS + 1, TimeUnit.SECONDS); diff --git a/test/unit/org/apache/cassandra/service/RemoveTest.java b/test/unit/org/apache/cassandra/service/RemoveTest.java index ea8c8d86a2..acc54cbe9c 100644 --- a/test/unit/org/apache/cassandra/service/RemoveTest.java +++ b/test/unit/org/apache/cassandra/service/RemoveTest.java @@ -143,7 +143,7 @@ public class RemoveTest { // start removal in background and send replication confirmations final AtomicBoolean success = new AtomicBoolean(false); - Thread remover = NamedThreadFactory.createThread(() -> + Thread remover = NamedThreadFactory.createAnonymousThread(() -> { try { diff --git a/test/unit/org/apache/cassandra/streaming/EntireSSTableStreamingCorrectFilesCountTest.java b/test/unit/org/apache/cassandra/streaming/EntireSSTableStreamingCorrectFilesCountTest.java index 92de5a04dd..9aaf85c988 100644 --- a/test/unit/org/apache/cassandra/streaming/EntireSSTableStreamingCorrectFilesCountTest.java +++ b/test/unit/org/apache/cassandra/streaming/EntireSSTableStreamingCorrectFilesCountTest.java @@ -26,6 +26,8 @@ import java.util.Collection; import java.util.Collections; import java.util.UUID; +import javax.annotation.Nullable; + import org.junit.BeforeClass; import org.junit.Test; @@ -57,8 +59,6 @@ import org.apache.cassandra.schema.KeyspaceParams; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; -import javax.annotation.Nullable; - import static org.apache.cassandra.service.ActiveRepairService.NO_PENDING_REPAIR; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/test/unit/org/apache/cassandra/streaming/StreamTransferTaskTest.java b/test/unit/org/apache/cassandra/streaming/StreamTransferTaskTest.java index 0bf7f2047f..ffab5bf855 100644 --- a/test/unit/org/apache/cassandra/streaming/StreamTransferTaskTest.java +++ b/test/unit/org/apache/cassandra/streaming/StreamTransferTaskTest.java @@ -174,7 +174,7 @@ public class StreamTransferTaskTest //wait for stream to abort asynchronously int tries = 10; - while (ScheduledExecutors.nonPeriodicTasks.getActiveCount() > 0) + while (ScheduledExecutors.nonPeriodicTasks.getActiveTaskCount() > 0) { if(tries < 1) throw new RuntimeException("test did not complete in time"); diff --git a/test/unit/org/apache/cassandra/tools/BulkLoaderTest.java b/test/unit/org/apache/cassandra/tools/BulkLoaderTest.java index a46cbf9529..f2b18ca411 100644 --- a/test/unit/org/apache/cassandra/tools/BulkLoaderTest.java +++ b/test/unit/org/apache/cassandra/tools/BulkLoaderTest.java @@ -36,7 +36,7 @@ public class BulkLoaderTest extends OfflineToolUtils assertEquals(1, tool.getExitCode()); assertThat(tool.getCleanedStderr(), CoreMatchers.containsString("Missing sstable directory argument")); - assertNoUnexpectedThreadsStarted(null, null); + assertNoUnexpectedThreadsStarted(new String[] { "ObjectCleanerThread" }); assertSchemaNotLoaded(); assertCLSMNotLoaded(); assertSystemKSNotLoaded(); @@ -58,7 +58,7 @@ public class BulkLoaderTest extends OfflineToolUtils if (!(tool.getException().getCause().getCause() instanceof NoHostAvailableException)) throw tool.getException(); - assertNoUnexpectedThreadsStarted(null, new String[]{"globalEventExecutor-1-1", "globalEventExecutor-1-2"}); + assertNoUnexpectedThreadsStarted(new String[]{ "ObjectCleanerThread", "globalEventExecutor-[1-9]-[1-9]", "globalEventExecutor-[1-9]-[1-9]" }); assertSchemaNotLoaded(); assertCLSMNotLoaded(); assertSystemKSNotLoaded(); @@ -82,7 +82,7 @@ public class BulkLoaderTest extends OfflineToolUtils if (!(tool.getException().getCause().getCause() instanceof NoHostAvailableException)) throw tool.getException(); - assertNoUnexpectedThreadsStarted(null, new String[] { "globalEventExecutor-1-1", "globalEventExecutor-1-2" }); + assertNoUnexpectedThreadsStarted(new String[]{ "ObjectCleanerThread", "globalEventExecutor-[1-9]-[1-9]", "globalEventExecutor-[1-9]-[1-9]" }); assertSchemaNotLoaded(); assertCLSMNotLoaded(); assertSystemKSNotLoaded(); @@ -106,7 +106,7 @@ public class BulkLoaderTest extends OfflineToolUtils if (!(tool.getException().getCause().getCause() instanceof NoHostAvailableException)) throw tool.getException(); - assertNoUnexpectedThreadsStarted(null, new String[] { "globalEventExecutor-1-1", "globalEventExecutor-1-2" }); + assertNoUnexpectedThreadsStarted(new String[]{ "ObjectCleanerThread", "globalEventExecutor-[1-9]-[1-9]", "globalEventExecutor-[1-9]-[1-9]" }); assertSchemaNotLoaded(); assertCLSMNotLoaded(); assertSystemKSNotLoaded(); diff --git a/test/unit/org/apache/cassandra/tools/GetVersionTest.java b/test/unit/org/apache/cassandra/tools/GetVersionTest.java index b23c1fe170..9eaf57f551 100644 --- a/test/unit/org/apache/cassandra/tools/GetVersionTest.java +++ b/test/unit/org/apache/cassandra/tools/GetVersionTest.java @@ -29,7 +29,7 @@ public class GetVersionTest extends OfflineToolUtils { ToolResult tool = ToolRunner.invokeClass(GetVersion.class); tool.assertOnCleanExit(); - assertNoUnexpectedThreadsStarted(null, null); + assertNoUnexpectedThreadsStarted(null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); assertSystemKSNotLoaded(); diff --git a/test/unit/org/apache/cassandra/tools/OfflineToolUtils.java b/test/unit/org/apache/cassandra/tools/OfflineToolUtils.java index 0fea44866d..e4a3b7f159 100644 --- a/test/unit/org/apache/cassandra/tools/OfflineToolUtils.java +++ b/test/unit/org/apache/cassandra/tools/OfflineToolUtils.java @@ -56,17 +56,12 @@ public abstract class OfflineToolUtils private static List initialThreads; - static final String[] EXPECTED_THREADS_WITH_SCHEMA = { - "PerDiskMemtableFlushWriter_0:[1-9]", - "MemtablePostFlush:[1-9]", - "MemtableFlushWriter:[1-9]", - "MemtableReclaimMemory:[1-9]", - }; static final String[] OPTIONAL_THREADS_WITH_SCHEMA = { "ScheduledTasks:[1-9]", + "ScheduledFastTasks:[1-9]", "OptionalTasks:[1-9]", - "Reference-Reaper:[1-9]", - "LocalPool-Cleaner(-networking|-chunk-cache)?:[1-9]", + "Reference-Reaper", + "LocalPool-Cleaner(-networking|-chunk-cache)", "CacheCleanupExecutor:[1-9]", "CompactionExecutor:[1-9]", "ValidationExecutor:[1-9]", @@ -77,11 +72,11 @@ public abstract class OfflineToolUtils "Background_Reporter:[1-9]", "EXPIRING-MAP-REAPER:[1-9]", "ObjectCleanerThread", - "process reaper" // spawned by the jvm when executing external processes + "process reaper", // spawned by the jvm when executing external processes // and may still be active when we check }; - public void assertNoUnexpectedThreadsStarted(String[] expectedThreadNames, String[] optionalThreadNames) + public void assertNoUnexpectedThreadsStarted(String[] optionalThreadNames) { ThreadMXBean threads = ManagementFactory.getThreadMXBean(); @@ -95,31 +90,20 @@ public abstract class OfflineToolUtils .map(ThreadInfo::getThreadName) .collect(Collectors.toSet()); - List expected = expectedThreadNames != null - ? Arrays.stream(expectedThreadNames).map(Pattern::compile).collect(Collectors.toList()) - : Collections.emptyList(); - List optional = optionalThreadNames != null ? Arrays.stream(optionalThreadNames).map(Pattern::compile).collect(Collectors.toList()) : Collections.emptyList(); current.removeAll(initial); - List notPresent = expected.stream() - .filter(threadNamePattern -> !current.stream().anyMatch(threadName -> threadNamePattern.matcher(threadName).matches())) - .collect(Collectors.toList()); - Set remain = current.stream() - .filter(threadName -> expected.stream().anyMatch(pattern -> pattern.matcher(threadName).matches())) - .filter(threadName -> optional.stream().anyMatch(pattern -> pattern.matcher(threadName).matches())) + .filter(threadName -> optional.stream().noneMatch(pattern -> pattern.matcher(threadName).matches())) .collect(Collectors.toSet()); if (!remain.isEmpty()) System.err.println("Unexpected thread names: " + remain); - if (!notPresent.isEmpty()) - System.err.println("Mandatory thread missing: " + notPresent); - assertTrue("Wrong thread status", remain.isEmpty() && notPresent.isEmpty()); + assertTrue("Wrong thread status, active threads unaccounted for: " + remain, remain.isEmpty()); } public void assertSchemaNotLoaded() @@ -237,7 +221,7 @@ public abstract class OfflineToolUtils protected void assertCorrectEnvPostTest() { - assertNoUnexpectedThreadsStarted(EXPECTED_THREADS_WITH_SCHEMA, OPTIONAL_THREADS_WITH_SCHEMA); + assertNoUnexpectedThreadsStarted(OPTIONAL_THREADS_WITH_SCHEMA); assertSchemaLoaded(); assertServerNotLoaded(); } diff --git a/test/unit/org/apache/cassandra/tools/SSTableExpiredBlockersTest.java b/test/unit/org/apache/cassandra/tools/SSTableExpiredBlockersTest.java index 4fc2cbe1aa..e76769e0ac 100644 --- a/test/unit/org/apache/cassandra/tools/SSTableExpiredBlockersTest.java +++ b/test/unit/org/apache/cassandra/tools/SSTableExpiredBlockersTest.java @@ -37,7 +37,7 @@ public class SSTableExpiredBlockersTest extends OfflineToolUtils Assertions.assertThat(tool.getCleanedStderr()).isEmpty(); assertEquals(1, tool.getExitCode()); - assertNoUnexpectedThreadsStarted(null, null); + assertNoUnexpectedThreadsStarted(null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); assertSystemKSNotLoaded(); diff --git a/test/unit/org/apache/cassandra/tools/SSTableExportSchemaLoadingTest.java b/test/unit/org/apache/cassandra/tools/SSTableExportSchemaLoadingTest.java index f8d0f99fc9..1530a2eca8 100644 --- a/test/unit/org/apache/cassandra/tools/SSTableExportSchemaLoadingTest.java +++ b/test/unit/org/apache/cassandra/tools/SSTableExportSchemaLoadingTest.java @@ -182,7 +182,7 @@ public class SSTableExportSchemaLoadingTest extends OfflineToolUtils */ private void assertPostTestEnv() { - assertNoUnexpectedThreadsStarted(null, OPTIONAL_THREADS_WITH_SCHEMA); + assertNoUnexpectedThreadsStarted(OPTIONAL_THREADS_WITH_SCHEMA); assertCLSMNotLoaded(); assertSystemKSNotLoaded(); assertKeyspaceNotLoaded(); diff --git a/test/unit/org/apache/cassandra/tools/SSTableExportTest.java b/test/unit/org/apache/cassandra/tools/SSTableExportTest.java index 6b5395c939..a5b70a2f76 100644 --- a/test/unit/org/apache/cassandra/tools/SSTableExportTest.java +++ b/test/unit/org/apache/cassandra/tools/SSTableExportTest.java @@ -108,7 +108,7 @@ public class SSTableExportTest extends OfflineToolUtils */ private void assertPostTestEnv() { - assertNoUnexpectedThreadsStarted(null, OPTIONAL_THREADS_WITH_SCHEMA); + assertNoUnexpectedThreadsStarted(OPTIONAL_THREADS_WITH_SCHEMA); assertSchemaNotLoaded(); assertCLSMNotLoaded(); assertSystemKSNotLoaded(); diff --git a/test/unit/org/apache/cassandra/tools/SSTableLevelResetterTest.java b/test/unit/org/apache/cassandra/tools/SSTableLevelResetterTest.java index a6606bf6da..e6d7adc765 100644 --- a/test/unit/org/apache/cassandra/tools/SSTableLevelResetterTest.java +++ b/test/unit/org/apache/cassandra/tools/SSTableLevelResetterTest.java @@ -36,7 +36,7 @@ public class SSTableLevelResetterTest extends OfflineToolUtils assertThat(tool.getStdout(), CoreMatchers.containsStringIgnoringCase("usage:")); Assertions.assertThat(tool.getCleanedStderr()).isEmpty(); assertEquals(1, tool.getExitCode()); - assertNoUnexpectedThreadsStarted(null, null); + assertNoUnexpectedThreadsStarted(null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); assertSystemKSNotLoaded(); diff --git a/test/unit/org/apache/cassandra/tools/SSTableMetadataViewerTest.java b/test/unit/org/apache/cassandra/tools/SSTableMetadataViewerTest.java index 2f6e64baf1..4998b6e81a 100644 --- a/test/unit/org/apache/cassandra/tools/SSTableMetadataViewerTest.java +++ b/test/unit/org/apache/cassandra/tools/SSTableMetadataViewerTest.java @@ -55,7 +55,7 @@ public class SSTableMetadataViewerTest extends OfflineToolUtils assertThat(tool.getCleanedStderr(), CoreMatchers.containsStringIgnoringCase("Options:")); assertEquals(1, tool.getExitCode()); } - assertNoUnexpectedThreadsStarted(null, null); + assertNoUnexpectedThreadsStarted(null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); assertSystemKSNotLoaded(); @@ -218,7 +218,7 @@ public class SSTableMetadataViewerTest extends OfflineToolUtils private void assertGoodEnvPostTest() { - assertNoUnexpectedThreadsStarted(null, OPTIONAL_THREADS_WITH_SCHEMA); + assertNoUnexpectedThreadsStarted(OPTIONAL_THREADS_WITH_SCHEMA); assertSchemaNotLoaded(); assertCLSMNotLoaded(); assertSystemKSNotLoaded(); diff --git a/test/unit/org/apache/cassandra/tools/SSTableOfflineRelevelTest.java b/test/unit/org/apache/cassandra/tools/SSTableOfflineRelevelTest.java index ee07f992b4..3f4314c0d6 100644 --- a/test/unit/org/apache/cassandra/tools/SSTableOfflineRelevelTest.java +++ b/test/unit/org/apache/cassandra/tools/SSTableOfflineRelevelTest.java @@ -36,7 +36,7 @@ public class SSTableOfflineRelevelTest extends OfflineToolUtils assertThat(tool.getStdout(), CoreMatchers.containsStringIgnoringCase("usage:")); Assertions.assertThat(tool.getCleanedStderr()).isEmpty(); assertEquals(1, tool.getExitCode()); - assertNoUnexpectedThreadsStarted(null, null); + assertNoUnexpectedThreadsStarted(null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); assertSystemKSNotLoaded(); diff --git a/test/unit/org/apache/cassandra/tools/SSTableRepairedAtSetterTest.java b/test/unit/org/apache/cassandra/tools/SSTableRepairedAtSetterTest.java index 859b6b8a29..767b7d92cf 100644 --- a/test/unit/org/apache/cassandra/tools/SSTableRepairedAtSetterTest.java +++ b/test/unit/org/apache/cassandra/tools/SSTableRepairedAtSetterTest.java @@ -41,7 +41,7 @@ public class SSTableRepairedAtSetterTest extends OfflineToolUtils assertThat(tool.getStdout(), CoreMatchers.containsStringIgnoringCase("usage:")); Assertions.assertThat(tool.getCleanedStderr()).isEmpty(); assertEquals(1, tool.getExitCode()); - assertNoUnexpectedThreadsStarted(null, null); + assertNoUnexpectedThreadsStarted(null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); assertSystemKSNotLoaded(); @@ -79,7 +79,7 @@ public class SSTableRepairedAtSetterTest extends OfflineToolUtils "--is-repaired", findOneSSTable("legacy_sstables", "legacy_ma_simple")); tool.assertOnCleanExit(); - assertNoUnexpectedThreadsStarted(null, OPTIONAL_THREADS_WITH_SCHEMA); + assertNoUnexpectedThreadsStarted(OPTIONAL_THREADS_WITH_SCHEMA); assertSchemaNotLoaded(); assertCLSMNotLoaded(); assertSystemKSNotLoaded(); @@ -95,7 +95,7 @@ public class SSTableRepairedAtSetterTest extends OfflineToolUtils "--is-unrepaired", findOneSSTable("legacy_sstables", "legacy_ma_simple")); tool.assertOnCleanExit(); - assertNoUnexpectedThreadsStarted(null, OPTIONAL_THREADS_WITH_SCHEMA); + assertNoUnexpectedThreadsStarted(OPTIONAL_THREADS_WITH_SCHEMA); assertSchemaNotLoaded(); assertCLSMNotLoaded(); assertSystemKSNotLoaded(); @@ -113,7 +113,7 @@ public class SSTableRepairedAtSetterTest extends OfflineToolUtils String file = tmpFile.getAbsolutePath(); ToolResult tool = ToolRunner.invokeClass(SSTableRepairedAtSetter.class, "--really-set", "--is-repaired", "-f", file); tool.assertOnCleanExit(); - assertNoUnexpectedThreadsStarted(null, OPTIONAL_THREADS_WITH_SCHEMA); + assertNoUnexpectedThreadsStarted(OPTIONAL_THREADS_WITH_SCHEMA); assertSchemaNotLoaded(); assertCLSMNotLoaded(); assertSystemKSNotLoaded(); diff --git a/test/unit/org/apache/cassandra/tools/ToolsSchemaLoadingTest.java b/test/unit/org/apache/cassandra/tools/ToolsSchemaLoadingTest.java index 58a444ca0c..1a99643674 100644 --- a/test/unit/org/apache/cassandra/tools/ToolsSchemaLoadingTest.java +++ b/test/unit/org/apache/cassandra/tools/ToolsSchemaLoadingTest.java @@ -34,7 +34,7 @@ public class ToolsSchemaLoadingTest extends OfflineToolUtils assertThat(tool.getStdout(), CoreMatchers.containsStringIgnoringCase("usage:")); assertThat(tool.getCleanedStderr(), CoreMatchers.containsStringIgnoringCase("Missing arguments")); assertEquals(1, tool.getExitCode()); - assertNoUnexpectedThreadsStarted(null, null); + assertNoUnexpectedThreadsStarted(null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); assertSystemKSNotLoaded(); @@ -49,7 +49,7 @@ public class ToolsSchemaLoadingTest extends OfflineToolUtils assertThat(tool.getStdout(), CoreMatchers.containsStringIgnoringCase("usage:")); assertThat(tool.getCleanedStderr(), CoreMatchers.containsStringIgnoringCase("Missing arguments")); assertEquals(1, tool.getExitCode()); - assertNoUnexpectedThreadsStarted(null, null); + assertNoUnexpectedThreadsStarted(null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); assertSystemKSNotLoaded(); @@ -64,7 +64,7 @@ public class ToolsSchemaLoadingTest extends OfflineToolUtils assertThat(tool.getStdout(), CoreMatchers.containsStringIgnoringCase("usage:")); assertThat(tool.getCleanedStderr(), CoreMatchers.containsStringIgnoringCase("No sstables to split")); assertEquals(1, tool.getExitCode()); - assertNoUnexpectedThreadsStarted(null, null); + assertNoUnexpectedThreadsStarted(null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); assertSystemKSNotLoaded(); @@ -79,7 +79,7 @@ public class ToolsSchemaLoadingTest extends OfflineToolUtils assertThat(tool.getStdout(), CoreMatchers.containsStringIgnoringCase("usage:")); assertThat(tool.getCleanedStderr(), CoreMatchers.containsStringIgnoringCase("Missing arguments")); assertEquals(1, tool.getExitCode()); - assertNoUnexpectedThreadsStarted(null, null); + assertNoUnexpectedThreadsStarted(null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); assertSystemKSNotLoaded(); @@ -94,7 +94,7 @@ public class ToolsSchemaLoadingTest extends OfflineToolUtils assertThat(tool.getStdout(), CoreMatchers.containsStringIgnoringCase("usage:")); assertThat(tool.getCleanedStderr(), CoreMatchers.containsStringIgnoringCase("Missing arguments")); assertEquals(1, tool.getExitCode()); - assertNoUnexpectedThreadsStarted(null, null); + assertNoUnexpectedThreadsStarted(null); assertSchemaNotLoaded(); assertCLSMNotLoaded(); assertSystemKSNotLoaded(); diff --git a/test/unit/org/apache/cassandra/utils/concurrent/AbstractTestAsyncPromise.java b/test/unit/org/apache/cassandra/utils/concurrent/AbstractTestAsyncPromise.java index 18a540b53c..e1c069b931 100644 --- a/test/unit/org/apache/cassandra/utils/concurrent/AbstractTestAsyncPromise.java +++ b/test/unit/org/apache/cassandra/utils/concurrent/AbstractTestAsyncPromise.java @@ -24,17 +24,52 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; +import java.util.function.Function; +import javax.annotation.Nullable; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.Uninterruptibles; import org.junit.Assert; -import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; -import io.netty.util.concurrent.Promise; public abstract class AbstractTestAsyncPromise extends AbstractTestPromise { - protected void testOneSuccess(Promise promise, boolean setUncancellable, boolean tryOrSet, V value, V otherValue) + public static Promise cancelSuccess(Promise promise) + { + success(promise, Promise::isCancellable, true); + return cancelShared(promise); + } + + public static Promise cancelExclusiveSuccess(Promise promise) + { + success(promise, Promise::isCancellable, true); + success(promise, Promise::setUncancellableExclusive, true); + return cancelShared(promise); + } + + private static Promise cancelShared(Promise promise) + { + success(promise, Promise::setUncancellable, true); + success(promise, Promise::setUncancellable, true); + success(promise, Promise::setUncancellableExclusive, false); + success(promise, p -> p.cancel(true), false); + success(promise, p -> p.cancel(false), false); + success(promise, Promise::isCancellable, false); + success(promise, Promise::isCancelled, false); + return promise; + } + + public static Promise cancelFailure(Promise promise) + { + success(promise, Promise::isCancellable, false); + return cancelShared(promise); + } + + protected void testOneSuccess(Promise promise, boolean tryOrSet, V value, V otherValue) { List results = new ArrayList<>(); List order = new ArrayList<>(); @@ -47,30 +82,120 @@ public abstract class AbstractTestAsyncPromise extends AbstractTestPromise int id = count++; return p -> { results.add(p.getNow()); order.add(id); }; } + public GenericFutureListener> getListenerToFailure(Promise promise) + { + int id = count++; + return p -> { Assert.assertTrue(p.cause() instanceof RuntimeException); results.add(promise.getNow()); order.add(id); }; + } public GenericFutureListener> getRecursive() { int id = count++; return p -> { promise.addListener(get()); results.add(p.getNow()); order.add(id); }; } + public Runnable getRunnable(Future p) + { + int id = count++; + return () -> { results.add(p.getNow()); order.add(id); }; + } + public Runnable getRecursiveRunnable(Future p) + { + int id = count++; + return () -> { promise.addListener(getRunnable(p)); results.add(p.getNow()); order.add(id); }; + } + public Consumer getConsumer() + { + int id = count++; + return result -> { results.add(result); order.add(id); }; + } + public Consumer getRecursiveConsumer() + { + int id = count++; + return result -> { promise.addCallback(getConsumer(), fail -> Assert.fail()); results.add(result); order.add(id); }; + } + public Function> getAsyncFunction() + { + int id = count++; + return result -> { results.add(result); order.add(id); return ImmediateFuture.success(result); }; + } + public Function> getRecursiveAsyncFunction(Promise promise) + { + int id = count++; + return result -> { promise.andThenAsync(getAsyncFunction()); results.add(result); order.add(id); return ImmediateFuture.success(result); }; + } + public Function> getAsyncFailingFunction() + { + int id = count++; + return result -> { results.add(result); order.add(id); return ImmediateFuture.failure(new RuntimeException()); }; + } + public Function> getRecursiveAsyncFailingFunction(Promise promise) + { + int id = count++; + return result -> { promise.andThenAsync(getAsyncFailingFunction()); results.add(result); order.add(id); return ImmediateFuture.failure(new RuntimeException()); }; + } + public FutureCallback getCallback(Future p) + { + int id = count++; + return new FutureCallback() + { + @Override + public void onSuccess(@Nullable Object o) + { + results.add(p.getNow()); + order.add(id); + } + + @Override + public void onFailure(Throwable throwable) + { + Assert.fail(); + } + }; + } + public FutureCallback getRecursiveCallback(Future p) + { + int id = count++; + return new FutureCallback() + { + @Override + public void onSuccess(@Nullable Object o) + { + promise.addCallback(getCallback(p)); + results.add(p.getNow()); + order.add(id); + } + + @Override + public void onFailure(Throwable throwable) + { + Assert.fail(); + } + }; + } } ListenerFactory listeners = new ListenerFactory(); Async async = new Async(); promise.addListener(listeners.get()); + promise.addListener(listeners.getRunnable(promise)); + promise.addListener(listeners.getRunnable(promise), MoreExecutors.directExecutor()); promise.addListeners(listeners.getRecursive(), listeners.get()); + promise.addListener(listeners.getRecursiveRunnable(promise), MoreExecutors.directExecutor()); promise.addListener(listeners.getRecursive()); + promise.addCallback(listeners.getCallback(promise)); + promise.addCallback(listeners.getRecursiveCallback(promise)); + promise.addCallback(listeners.getConsumer(), fail -> Assert.fail()); + promise.addCallback(listeners.getRecursiveConsumer(), fail -> Assert.fail()); + promise.andThenAsync(listeners.getAsyncFunction()).addListener(listeners.get()); + promise.andThenAsync(listeners.getAsyncFunction(), MoreExecutors.directExecutor()).addListener(listeners.get()); + promise.andThenAsync(listeners.getRecursiveAsyncFunction(promise)).addListener(listeners.get()); + promise.andThenAsync(listeners.getRecursiveAsyncFunction(promise), MoreExecutors.directExecutor()).addListener(listeners.get()); + promise.andThenAsync(listeners.getAsyncFailingFunction()).addListener(listeners.getListenerToFailure(promise)); + promise.andThenAsync(listeners.getAsyncFailingFunction(), MoreExecutors.directExecutor()).addListener(listeners.getListenerToFailure(promise)); + promise.andThenAsync(listeners.getRecursiveAsyncFailingFunction(promise)).addListener(listeners.getListenerToFailure(promise)); + promise.andThenAsync(listeners.getRecursiveAsyncFailingFunction(promise), MoreExecutors.directExecutor()).addListener(listeners.getListenerToFailure(promise)); success(promise, Promise::getNow, null); success(promise, Promise::isSuccess, false); success(promise, Promise::isDone, false); success(promise, Promise::isCancelled, false); - success(promise, Promise::isCancellable, true); - if (setUncancellable) - { - success(promise, Promise::setUncancellable, true); - success(promise, Promise::setUncancellable, true); - success(promise, p -> p.cancel(true), false); - success(promise, p -> p.cancel(false), false); - } - success(promise, Promise::isCancellable, !setUncancellable); async.success(promise, Promise::get, value); async.success(promise, p -> p.get(1L, TimeUnit.SECONDS), value); async.success(promise, Promise::await, promise); @@ -93,12 +218,26 @@ public abstract class AbstractTestAsyncPromise extends AbstractTestPromise success(promise, Promise::getNow, value); success(promise, Promise::cause, null); promise.addListener(listeners.get()); + promise.addListener(listeners.getRunnable(promise)); + promise.addListener(listeners.getRunnable(promise), MoreExecutors.directExecutor()); promise.addListeners(listeners.getRecursive(), listeners.get()); + promise.addListener(listeners.getRecursiveRunnable(promise), MoreExecutors.directExecutor()); promise.addListener(listeners.getRecursive()); + promise.addCallback(listeners.getCallback(promise)); + promise.addCallback(listeners.getRecursiveCallback(promise)); + promise.addCallback(listeners.getConsumer(), fail -> Assert.fail()); + promise.addCallback(listeners.getRecursiveConsumer(), fail -> Assert.fail()); + promise.andThenAsync(listeners.getAsyncFunction()).addListener(listeners.get()); + promise.andThenAsync(listeners.getAsyncFunction(), MoreExecutors.directExecutor()).addListener(listeners.get()); + promise.andThenAsync(listeners.getRecursiveAsyncFunction(promise)).addListener(listeners.get()); + promise.andThenAsync(listeners.getRecursiveAsyncFunction(promise), MoreExecutors.directExecutor()).addListener(listeners.get()); + promise.andThenAsync(listeners.getAsyncFailingFunction()).addListener(listeners.getListenerToFailure(promise)); + promise.andThenAsync(listeners.getAsyncFailingFunction(), MoreExecutors.directExecutor()).addListener(listeners.getListenerToFailure(promise)); + promise.andThenAsync(listeners.getRecursiveAsyncFailingFunction(promise)).addListener(listeners.getListenerToFailure(promise)); + promise.andThenAsync(listeners.getRecursiveAsyncFailingFunction(promise), MoreExecutors.directExecutor()).addListener(listeners.getListenerToFailure(promise)); success(promise, Promise::isSuccess, true); success(promise, Promise::isDone, true); success(promise, Promise::isCancelled, false); - success(promise, Promise::isCancellable, false); async.verify(); Assert.assertEquals(listeners.count, results.size()); Assert.assertEquals(listeners.count, order.size()); @@ -108,7 +247,7 @@ public abstract class AbstractTestAsyncPromise extends AbstractTestPromise Assert.assertEquals(i, order.get(i).intValue()); } - protected void testOneFailure(Promise promise, boolean setUncancellable, boolean tryOrSet, Throwable cause, V otherValue) + protected void testOneFailure(Promise promise, boolean tryOrSet, Throwable cause, V otherValue) { List results = new ArrayList<>(); List order = new ArrayList<>(); @@ -127,35 +266,110 @@ public abstract class AbstractTestAsyncPromise extends AbstractTestPromise int id = count++; return p -> { promise.addListener(get()); results.add(p.cause()); order.add(id); }; } + public Runnable getRunnable(Future p) + { + int id = count++; + return () -> { results.add(p.cause()); order.add(id); }; + } + public Runnable getRecursiveRunnable(Future p) + { + int id = count++; + return () -> { promise.addListener(getRunnable(p)); results.add(p.cause()); order.add(id); }; + } + public Consumer getConsumer() + { + int id = count++; + return result -> { results.add(result); order.add(id); }; + } + public Consumer getRecursiveConsumer() + { + int id = count++; + return result -> { promise.addCallback(fail -> Assert.fail(), getConsumer()); results.add(result); order.add(id); }; + } + public Function> getAsyncFunction() + { + return result -> { Assert.fail(); return ImmediateFuture.success(result); }; + } + public Function> getRecursiveAsyncFunction() + { + return result -> { promise.andThenAsync(getAsyncFunction()); return ImmediateFuture.success(result); }; + } + public FutureCallback getCallback(Future p) + { + int id = count++; + return new FutureCallback() + { + @Override + public void onSuccess(@Nullable Object o) + { + Assert.fail(); + } + + @Override + public void onFailure(Throwable throwable) + { + results.add(p.cause()); + order.add(id); + } + }; + } + public FutureCallback getRecursiveCallback(Future p) + { + int id = count++; + return new FutureCallback() + { + @Override + public void onSuccess(@Nullable Object o) + { + Assert.fail(); + } + + @Override + public void onFailure(Throwable throwable) + { + promise.addCallback(getCallback(p)); + results.add(p.cause()); + order.add(id); + } + }; + } } ListenerFactory listeners = new ListenerFactory(); promise.addListener(listeners.get()); promise.addListeners(listeners.getRecursive(), listeners.get()); promise.addListener(listeners.getRecursive()); + promise.addListener(listeners.getRunnable(promise)); + promise.addListener(listeners.getRunnable(promise), MoreExecutors.directExecutor()); + promise.addListener(listeners.getRecursiveRunnable(promise), MoreExecutors.directExecutor()); + promise.addListener(listeners.getRecursive()); + promise.addCallback(listeners.getCallback(promise)); + promise.addCallback(listeners.getRecursiveCallback(promise)); + promise.addCallback(fail -> Assert.fail(), listeners.getConsumer()); + promise.addCallback(fail -> Assert.fail(), listeners.getRecursiveConsumer()); + promise.andThenAsync(listeners.getAsyncFunction()).addListener(listeners.get()); + promise.andThenAsync(listeners.getAsyncFunction(), MoreExecutors.directExecutor()).addListener(listeners.get()); + promise.andThenAsync(listeners.getRecursiveAsyncFunction()).addListener(listeners.get()); + promise.andThenAsync(listeners.getRecursiveAsyncFunction(), MoreExecutors.directExecutor()).addListener(listeners.get()); success(promise, Promise::isSuccess, false); success(promise, Promise::isDone, false); success(promise, Promise::isCancelled, false); - success(promise, Promise::isCancellable, true); - if (setUncancellable) - { - success(promise, Promise::setUncancellable, true); - success(promise, Promise::setUncancellable, true); - success(promise, p -> p.cancel(true), false); - success(promise, p -> p.cancel(false), false); - } - success(promise, Promise::isCancellable, !setUncancellable); success(promise, Promise::getNow, null); success(promise, Promise::cause, null); - async.failure(promise, Promise::get, ExecutionException.class); + async.failure(promise, p -> p.get(), ExecutionException.class); async.failure(promise, p -> p.get(1L, TimeUnit.SECONDS), ExecutionException.class); async.success(promise, Promise::await, promise); + async.success(promise, Promise::awaitThrowUncheckedOnInterrupt, promise); async.success(promise, Promise::awaitUninterruptibly, promise); async.success(promise, p -> p.await(1L, TimeUnit.SECONDS), true); async.success(promise, p -> p.await(1000L), true); async.success(promise, p -> p.awaitUninterruptibly(1L, TimeUnit.SECONDS), true); + async.success(promise, p -> p.awaitThrowUncheckedOnInterrupt(1L, TimeUnit.SECONDS), true); async.success(promise, p -> p.awaitUninterruptibly(1000L), true); - async.failure(promise, Promise::sync, cause); - async.failure(promise, Promise::syncUninterruptibly, cause); + async.success(promise, p -> p.awaitUntil(System.nanoTime() + TimeUnit.SECONDS.toNanos(1L)), true); + async.success(promise, p -> p.awaitUntilUninterruptibly(System.nanoTime() + TimeUnit.SECONDS.toNanos(1L)), true); + async.success(promise, p -> p.awaitUntilThrowUncheckedOnInterrupt(System.nanoTime() + TimeUnit.SECONDS.toNanos(1L)), true); + async.failure(promise, p -> p.sync(), cause); + async.failure(promise, p -> p.syncUninterruptibly(), cause); if (tryOrSet) promise.tryFailure(cause); else promise.setFailure(cause); success(promise, p -> p.cancel(true), false); @@ -171,6 +385,16 @@ public abstract class AbstractTestAsyncPromise extends AbstractTestPromise promise.addListener(listeners.get()); promise.addListeners(listeners.getRecursive(), listeners.get()); promise.addListener(listeners.getRecursive()); + promise.addListener(listeners.getRunnable(promise)); + promise.addListener(listeners.getRunnable(promise), MoreExecutors.directExecutor()); + promise.addListener(listeners.getRecursiveRunnable(promise), MoreExecutors.directExecutor()); + promise.addListener(listeners.getRecursive()); + promise.addCallback(listeners.getCallback(promise)); + promise.addCallback(listeners.getRecursiveCallback(promise)); + promise.addCallback(fail -> Assert.fail(), listeners.getConsumer()); + promise.addCallback(fail -> Assert.fail(), listeners.getRecursiveConsumer()); + promise.andThenAsync(listeners.getAsyncFunction()).addListener(listeners.get()); + promise.andThenAsync(listeners.getAsyncFunction(), MoreExecutors.directExecutor()).addListener(listeners.get()); success(promise, Promise::isSuccess, false); success(promise, Promise::isDone, true); success(promise, Promise::isCancelled, false); @@ -184,22 +408,28 @@ public abstract class AbstractTestAsyncPromise extends AbstractTestPromise Assert.assertEquals(i, order.get(i).intValue()); } + // TODO: test listeners? public void testOneCancellation(Promise promise, boolean interruptIfRunning, V otherValue) { Async async = new Async(); success(promise, Promise::isCancellable, true); success(promise, Promise::getNow, null); success(promise, Promise::cause, null); - async.failure(promise, Promise::get, CancellationException.class); + async.failure(promise, p -> p.get(), CancellationException.class); async.failure(promise, p -> p.get(1L, TimeUnit.SECONDS), CancellationException.class); async.success(promise, Promise::await, promise); + async.success(promise, Promise::awaitThrowUncheckedOnInterrupt, promise); async.success(promise, Promise::awaitUninterruptibly, promise); async.success(promise, p -> p.await(1L, TimeUnit.SECONDS), true); async.success(promise, p -> p.await(1000L), true); async.success(promise, p -> p.awaitUninterruptibly(1L, TimeUnit.SECONDS), true); + async.success(promise, p -> p.awaitThrowUncheckedOnInterrupt(1L, TimeUnit.SECONDS), true); async.success(promise, p -> p.awaitUninterruptibly(1000L), true); - async.failure(promise, Promise::sync, CancellationException.class); - async.failure(promise, Promise::syncUninterruptibly, CancellationException.class); + async.success(promise, p -> p.awaitUntil(System.nanoTime() + TimeUnit.SECONDS.toNanos(1L)), true); + async.success(promise, p -> p.awaitUntilUninterruptibly(System.nanoTime() + TimeUnit.SECONDS.toNanos(1L)), true); + async.success(promise, p -> p.awaitUntilThrowUncheckedOnInterrupt(System.nanoTime() + TimeUnit.SECONDS.toNanos(1L)), true); + async.failure(promise, p -> p.sync(), CancellationException.class); + async.failure(promise, p -> p.syncUninterruptibly(), CancellationException.class); promise.cancel(interruptIfRunning); failure(promise, p -> p.setFailure(null), IllegalStateException.class); failure(promise, p -> p.setFailure(null), IllegalStateException.class); @@ -216,19 +446,22 @@ public abstract class AbstractTestAsyncPromise extends AbstractTestPromise } - public void testOneTimeout(Promise promise, boolean setUncancellable) + public void testOneTimeout(Promise promise) { Async async = new Async(); - if (setUncancellable) - success(promise, Promise::setUncancellable, true); - success(promise, Promise::isCancellable, !setUncancellable); async.failure(promise, p -> p.get(1L, TimeUnit.MILLISECONDS), TimeoutException.class); async.success(promise, p -> p.await(1L, TimeUnit.MILLISECONDS), false); + async.success(promise, p -> p.awaitThrowUncheckedOnInterrupt(1L, TimeUnit.MILLISECONDS), false); async.success(promise, p -> p.await(1L), false); async.success(promise, p -> p.awaitUninterruptibly(1L, TimeUnit.MILLISECONDS), false); + async.success(promise, p -> p.awaitThrowUncheckedOnInterrupt(1L, TimeUnit.MILLISECONDS), false); async.success(promise, p -> p.awaitUninterruptibly(1L), false); + async.success(promise, p -> p.awaitUntil(System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(1L)), false); + async.success(promise, p -> p.awaitUntilUninterruptibly(System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(1L)), false); + async.success(promise, p -> p.awaitUntilThrowUncheckedOnInterrupt(System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(1L)), false); Uninterruptibles.sleepUninterruptibly(10L, TimeUnit.MILLISECONDS); async.verify(); } } + diff --git a/test/unit/org/apache/cassandra/utils/concurrent/AsyncPromiseTest.java b/test/unit/org/apache/cassandra/utils/concurrent/AsyncPromiseTest.java index f9e84415d5..cb1da098cd 100644 --- a/test/unit/org/apache/cassandra/utils/concurrent/AsyncPromiseTest.java +++ b/test/unit/org/apache/cassandra/utils/concurrent/AsyncPromiseTest.java @@ -18,9 +18,23 @@ package org.apache.cassandra.utils.concurrent; import org.junit.After; +import org.junit.Assert; import org.junit.Test; -import io.netty.util.concurrent.Promise; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.MoreExecutors; + +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; +import org.apache.cassandra.concurrent.ExecutorPlus; +import org.apache.cassandra.utils.WithResources; public class AsyncPromiseTest extends AbstractTestAsyncPromise { @@ -30,43 +44,207 @@ public class AsyncPromiseTest extends AbstractTestAsyncPromise exec.shutdownNow(); } - private Promise newPromise() + private static List>> suppliers(AtomicInteger listeners, boolean includeUncancellable) { - return new AsyncPromise<>(null); + List>> cancellable = ImmutableList.of( + () -> new AsyncPromise<>(), + () -> new AsyncPromise<>(f -> listeners.incrementAndGet()), + () -> AsyncPromise.withExecutor(TestInExecutor.INSTANCE)); + List>> uncancellable = ImmutableList.of( + () -> AsyncPromise.uncancellable(), + () -> AsyncPromise.uncancellable((GenericFutureListener>) f -> listeners.incrementAndGet()), + () -> AsyncPromise.uncancellable(MoreExecutors.directExecutor()), + () -> AsyncPromise.uncancellable(TestInExecutor.INSTANCE) + ); + + if (!includeUncancellable) + return cancellable; + + ImmutableList.Builder>> builder = ImmutableList.builder(); + builder.addAll(cancellable) + .addAll(cancellable.stream().map(s -> (Supplier>) () -> cancelSuccess(s.get())).collect(Collectors.toList())) + .addAll(cancellable.stream().map(s -> (Supplier>) () -> cancelExclusiveSuccess(s.get())).collect(Collectors.toList())) + .addAll(uncancellable) + .addAll(uncancellable.stream().map(s -> (Supplier>) () -> cancelFailure(s.get())).collect(Collectors.toList())); + return builder.build(); } @Test public void testSuccess() { - for (boolean setUncancellable : new boolean[] { false, true }) - for (boolean tryOrSet : new boolean[]{ false, true }) - for (Integer v : new Integer[]{ null, 1 }) - testOneSuccess(newPromise(), setUncancellable, tryOrSet, v, 2); + final AtomicInteger initialListeners = new AtomicInteger(); + List>> suppliers = suppliers(initialListeners, true); + for (boolean tryOrSet : new boolean[]{ false, true }) + for (Integer v : new Integer[]{ null, 1 }) + for (Supplier> supplier : suppliers) + testOneSuccess(supplier.get(), tryOrSet, v, 2); + Assert.assertEquals(5 * 2 * 2, initialListeners.get()); } @Test public void testFailure() { - for (boolean setUncancellable : new boolean[] { false, true }) - for (boolean tryOrSet : new boolean[] { false, true }) - for (Throwable v : new Throwable[] { null, new NullPointerException() }) - testOneFailure(newPromise(), setUncancellable, tryOrSet, v, 2); + final AtomicInteger initialListeners = new AtomicInteger(); + List>> suppliers = suppliers(initialListeners, true); + for (boolean tryOrSet : new boolean[] { false, true }) + for (Throwable v : new Throwable[] { null, new NullPointerException() }) + for (Supplier> supplier : suppliers) + testOneFailure(supplier.get(), tryOrSet, v, 2); + Assert.assertEquals(5 * 2 * 2, initialListeners.get()); } @Test public void testCancellation() { + final AtomicInteger initialListeners = new AtomicInteger(); + List>> suppliers = suppliers(initialListeners, false); for (boolean interruptIfRunning : new boolean[] { true, false }) - testOneCancellation(newPromise(), interruptIfRunning, 2); + for (Supplier> supplier : suppliers) + testOneCancellation(supplier.get(), interruptIfRunning, 2); + Assert.assertEquals(2, initialListeners.get()); } @Test public void testTimeout() { - for (boolean setUncancellable : new boolean[] { true, false }) - testOneTimeout(newPromise(), setUncancellable); + final AtomicInteger initialListeners = new AtomicInteger(); + List>> suppliers = suppliers(initialListeners, true); + for (Supplier> supplier : suppliers) + testOneTimeout(supplier.get()); + Assert.assertEquals(0, initialListeners.get()); + } + + private static final class TestInExecutor implements ExecutorPlus + { + static final TestInExecutor INSTANCE = new TestInExecutor(); + @Override + public void shutdown() + { + throw new UnsupportedOperationException(); + } + + @Override + public List shutdownNow() + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isShutdown() + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isTerminated() + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException + { + throw new UnsupportedOperationException(); + } + + @Override + public org.apache.cassandra.utils.concurrent.Future submit(Callable task) + { + throw new UnsupportedOperationException(); + } + + @Override + public org.apache.cassandra.utils.concurrent.Future submit(Runnable task, T result) + { + throw new UnsupportedOperationException(); + } + + @Override + public org.apache.cassandra.utils.concurrent.Future submit(Runnable task) + { + throw new UnsupportedOperationException(); + } + + @Override + public void execute(WithResources withResources, Runnable task) + { + throw new UnsupportedOperationException(); + } + + @Override + public org.apache.cassandra.utils.concurrent.Future submit(WithResources withResources, Callable task) + { + throw new UnsupportedOperationException(); + } + + @Override + public org.apache.cassandra.utils.concurrent.Future submit(WithResources withResources, Runnable task) + { + throw new UnsupportedOperationException(); + } + + @Override + public org.apache.cassandra.utils.concurrent.Future submit(WithResources withResources, Runnable task, T result) + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean inExecutor() + { + return true; + } + + @Override + public void execute(Runnable command) + { + throw new UnsupportedOperationException(); + } + + @Override + public int getCorePoolSize() + { + throw new UnsupportedOperationException(); + } + + @Override + public void setCorePoolSize(int newCorePoolSize) + { + throw new UnsupportedOperationException(); + } + + @Override + public int getMaximumPoolSize() + { + throw new UnsupportedOperationException(); + } + + @Override + public void setMaximumPoolSize(int newMaximumPoolSize) + { + throw new UnsupportedOperationException(); + } + + @Override + public int getActiveTaskCount() + { + throw new UnsupportedOperationException(); + } + + @Override + public long getCompletedTaskCount() + { + throw new UnsupportedOperationException(); + } + + @Override + public int getPendingTaskCount() + { + throw new UnsupportedOperationException(); + } } } + diff --git a/test/unit/org/apache/cassandra/utils/concurrent/ImmediateFutureTest.java b/test/unit/org/apache/cassandra/utils/concurrent/ImmediateFutureTest.java new file mode 100644 index 0000000000..b042a28768 --- /dev/null +++ b/test/unit/org/apache/cassandra/utils/concurrent/ImmediateFutureTest.java @@ -0,0 +1,106 @@ +/* + * 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.concurrent; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Predicate; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.cassandra.distributed.impl.IsolatedExecutor; + +public class ImmediateFutureTest +{ + + private void testSimple(ImmediateFuture p, boolean isCancelled) throws InterruptedException + { + Assert.assertEquals(p, p.await()); + Assert.assertEquals(p, p.awaitUninterruptibly()); + Assert.assertEquals(p, p.awaitThrowUncheckedOnInterrupt()); + Assert.assertTrue(p.await(1L, TimeUnit.MILLISECONDS)); + Assert.assertTrue(p.await(1L)); + Assert.assertTrue(p.awaitUntil(Long.MAX_VALUE)); + Assert.assertTrue(p.awaitUntilUninterruptibly(Long.MAX_VALUE)); + Assert.assertTrue(p.awaitUntilThrowUncheckedOnInterrupt(Long.MAX_VALUE)); + Assert.assertTrue(p.isDone()); + Assert.assertFalse(p.isCancellable()); + Assert.assertEquals(isCancelled, p.isCancelled()); + Assert.assertEquals(!isCancelled, p.setUncancellable()); + Assert.assertFalse(p.setUncancellableExclusive()); + Assert.assertFalse(p.cancel(true)); + Assert.assertFalse(p.cancel(false)); + Assert.assertFalse(p.trySuccess(false)); + Assert.assertFalse(p.tryFailure(new InterruptedException())); + } + + @Test + public void testSucceeded() throws InterruptedException, ExecutionException, TimeoutException + { + ImmediateFuture p = ImmediateFuture.success(true); + Assert.assertTrue(p.getNow()); + Assert.assertTrue(p.get()); + Assert.assertTrue(p.get(1L, TimeUnit.MILLISECONDS)); + Assert.assertEquals(p, p.sync()); + Assert.assertEquals(p, p.syncUninterruptibly()); + Assert.assertFalse(p.isCancelled()); + testSimple(p, false); + } + + @Test + public void testFailed() throws InterruptedException + { + testFailed(ImmediateFuture.failure(new RuntimeException()), false, t -> t instanceof ExecutionException, t -> t instanceof RuntimeException); + } + + @Test + public void testCancelled() throws InterruptedException + { + testFailed(ImmediateFuture.cancelled(), true, t -> t instanceof CancellationException, t -> t instanceof CancellationException); + } + + private void testFailed(ImmediateFuture p, boolean isCancelled, Predicate get, Predicate sync) throws InterruptedException + { + Assert.assertNull(p.getNow()); + assertFailure(p::get, get); + assertFailure(() -> p.get(1L, TimeUnit.MILLISECONDS), get); + assertFailure(p::sync, sync); + assertFailure(p::syncUninterruptibly, sync); + testSimple(p, isCancelled); + } + + private static void assertFailure(IsolatedExecutor.ThrowingRunnable run, Predicate test) + { + Throwable failure = null; + try + { + run.run(); + } + catch (Throwable t) + { + failure = t; + } + if (failure == null || !test.test(failure)) + Assert.fail(); + } + +} diff --git a/test/unit/org/apache/cassandra/utils/memory/MemtableCleanerThreadTest.java b/test/unit/org/apache/cassandra/utils/memory/MemtableCleanerThreadTest.java index 7100a2ac59..04d3da0465 100644 --- a/test/unit/org/apache/cassandra/utils/memory/MemtableCleanerThreadTest.java +++ b/test/unit/org/apache/cassandra/utils/memory/MemtableCleanerThreadTest.java @@ -29,6 +29,10 @@ import org.junit.Before; import org.junit.Test; import org.apache.cassandra.utils.FBUtilities; +import org.apache.cassandra.utils.concurrent.AsyncPromise; +import org.apache.cassandra.utils.concurrent.Future; +import org.apache.cassandra.utils.concurrent.ImmediateFuture; +import org.apache.cassandra.utils.concurrent.SyncFuture; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -46,9 +50,8 @@ public class MemtableCleanerThreadTest @Mock private MemtablePool pool; - @Mock +// @Mock private MemtableCleaner cleaner; - private MemtableCleanerThread cleanerThread; @Before @@ -57,14 +60,9 @@ public class MemtableCleanerThreadTest MockitoAnnotations.initMocks(this); } - private void startThread() + private void startThread(MemtableCleaner cleaner) { cleanerThread = new MemtableCleanerThread<>(pool, cleaner); - assertNotNull(cleanerThread); - cleanerThread.start(); - - for (int i = 0; i < TIMEOUT_MILLIS && !cleanerThread.isAlive(); i++) - FBUtilities.sleepQuietly(1); } private void stopThread() throws InterruptedException @@ -86,20 +84,19 @@ public class MemtableCleanerThreadTest public void testCleanerInvoked() throws Exception { CountDownLatch cleanerExecutedLatch = new CountDownLatch(1); - CompletableFuture fut = new CompletableFuture<>(); + AsyncPromise fut = new AsyncPromise<>(); AtomicBoolean needsCleaning = new AtomicBoolean(false); when(pool.needsCleaning()).thenAnswer(invocation -> needsCleaning.get()); - - when(cleaner.clean()).thenAnswer(invocation -> { + cleaner = () -> { needsCleaning.set(false); cleanerExecutedLatch.countDown(); return fut; - }); + }; // start the thread with needsCleaning returning false, the cleaner should not be invoked needsCleaning.set(false); - startThread(); + startThread(cleaner); assertFalse(cleanerExecutedLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS)); assertEquals(1, cleanerExecutedLatch.getCount()); assertEquals(0, cleanerThread.numPendingTasks()); @@ -113,7 +110,7 @@ public class MemtableCleanerThreadTest // now complete the cleaning task needsCleaning.set(false); - fut.complete(true); + fut.setSuccess(true); waitForPendingTasks(); stopThread(); @@ -123,22 +120,22 @@ public class MemtableCleanerThreadTest public void testCleanerError() throws Exception { AtomicReference cleanerLatch = new AtomicReference<>(new CountDownLatch(1)); - AtomicReference> fut = new AtomicReference<>(new CompletableFuture<>()); + AtomicReference> fut = new AtomicReference<>(new AsyncPromise<>()); AtomicBoolean needsCleaning = new AtomicBoolean(false); AtomicInteger numTimeCleanerInvoked = new AtomicInteger(0); when(pool.needsCleaning()).thenAnswer(invocation -> needsCleaning.get()); - when(cleaner.clean()).thenAnswer(invocation -> { + cleaner = () -> { needsCleaning.set(false); numTimeCleanerInvoked.incrementAndGet(); cleanerLatch.get().countDown(); return fut.get(); - }); + }; // start the thread with needsCleaning returning true, the cleaner should be invoked needsCleaning.set(true); - startThread(); + startThread(cleaner); assertTrue(cleanerLatch.get().await(TIMEOUT_SECONDS, TimeUnit.SECONDS)); assertEquals(0, cleanerLatch.get().getCount()); assertEquals(1, cleanerThread.numPendingTasks()); @@ -146,17 +143,17 @@ public class MemtableCleanerThreadTest // complete the cleaning task with an error, no other cleaning task should be invoked cleanerLatch.set(new CountDownLatch(1)); - CompletableFuture oldFut = fut.get(); - fut.set(new CompletableFuture<>()); + AsyncPromise oldFut = fut.get(); + fut.set(new AsyncPromise<>()); needsCleaning.set(false); - oldFut.completeExceptionally(new RuntimeException("Test")); + oldFut.setFailure(new RuntimeException("Test")); assertFalse(cleanerLatch.get().await(TIMEOUT_SECONDS, TimeUnit.SECONDS)); assertEquals(1, cleanerLatch.get().getCount()); assertEquals(1, numTimeCleanerInvoked.get()); // now trigger cleaning again and verify that a new task is invoked cleanerLatch.set(new CountDownLatch(1)); - fut.set(new CompletableFuture<>()); + fut.set(new AsyncPromise<>()); needsCleaning.set(true); cleanerThread.trigger(); assertTrue(cleanerLatch.get().await(TIMEOUT_SECONDS, TimeUnit.SECONDS)); @@ -166,16 +163,16 @@ public class MemtableCleanerThreadTest // complete the cleaning task with false (nothing should be scheduled) cleanerLatch.set(new CountDownLatch(1)); oldFut = fut.get(); - fut.set(new CompletableFuture<>()); + fut.set(new AsyncPromise<>()); needsCleaning.set(false); - oldFut.complete(false); + oldFut.setSuccess(false); assertFalse(cleanerLatch.get().await(TIMEOUT_SECONDS, TimeUnit.SECONDS)); assertEquals(1, cleanerLatch.get().getCount()); assertEquals(2, numTimeCleanerInvoked.get()); // now trigger cleaning again and verify that a new task is invoked cleanerLatch.set(new CountDownLatch(1)); - fut.set(new CompletableFuture<>()); + fut.set(new AsyncPromise<>()); needsCleaning.set(true); cleanerThread.trigger(); assertTrue(cleanerLatch.get().await(TIMEOUT_SECONDS, TimeUnit.SECONDS)); diff --git a/test/unit/org/apache/cassandra/utils/memory/NativeAllocatorTest.java b/test/unit/org/apache/cassandra/utils/memory/NativeAllocatorTest.java index 031e089275..79a54cd84e 100644 --- a/test/unit/org/apache/cassandra/utils/memory/NativeAllocatorTest.java +++ b/test/unit/org/apache/cassandra/utils/memory/NativeAllocatorTest.java @@ -26,6 +26,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.apache.cassandra.utils.concurrent.ImmediateFuture; import org.apache.cassandra.utils.concurrent.OpOrder; public class NativeAllocatorTest @@ -65,7 +66,7 @@ public class NativeAllocatorTest allocatorRef.get().offHeap().released(80); isClean.countDown(); } - return CompletableFuture.completedFuture(true); + return ImmediateFuture.success(true); }); allocator = new NativeAllocator(pool); allocatorRef.set(allocator); diff --git a/tools/fqltool/src/org/apache/cassandra/fqltool/QueryReplayer.java b/tools/fqltool/src/org/apache/cassandra/fqltool/QueryReplayer.java index 4524e331ab..1cff804ebd 100644 --- a/tools/fqltool/src/org/apache/cassandra/fqltool/QueryReplayer.java +++ b/tools/fqltool/src/org/apache/cassandra/fqltool/QueryReplayer.java @@ -27,7 +27,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -47,11 +46,13 @@ import com.datastax.driver.core.Session; import com.datastax.driver.core.Statement; import org.apache.cassandra.utils.FBUtilities; +import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; + public class QueryReplayer implements Closeable { private static final Logger logger = LoggerFactory.getLogger(QueryReplayer.class); private static final int PRINT_RATE = 5000; - private final ExecutorService es = Executors.newFixedThreadPool(1); + private final ExecutorService es = executorFactory().sequential("QueryReplayer"); private final Iterator> queryIterator; private final List> filters; private final List sessions; diff --git a/tools/stress/src/org/apache/cassandra/stress/CompactionStress.java b/tools/stress/src/org/apache/cassandra/stress/CompactionStress.java index 88aa6a4aeb..a42829a84f 100644 --- a/tools/stress/src/org/apache/cassandra/stress/CompactionStress.java +++ b/tools/stress/src/org/apache/cassandra/stress/CompactionStress.java @@ -22,7 +22,10 @@ import java.io.File; import java.io.IOError; import java.net.URI; import java.util.*; -import java.util.concurrent.*; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import javax.inject.Inject; import com.google.common.collect.Lists; @@ -54,6 +57,7 @@ import org.apache.cassandra.stress.settings.StressSettings; import org.apache.cassandra.tools.nodetool.CompactionStats; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.JVMStabilityInspector; +import org.apache.cassandra.utils.concurrent.Future; /** * Tool that allows fast route to loading data for arbitrary schemas to disk