mirror of https://github.com/apache/cassandra
[CASSANDRA-16925] CEP-10 Phase 1: Mockable Task Execution
Co-authored-by: Benedict Elliott Smith <benedict@apache.org> Co-authored-by: Sam Tunnicliffe <samt@apache.org>
This commit is contained in:
parent
e5b92e1088
commit
be1f050bc8
21
build.xml
21
build.xml
|
|
@ -716,17 +716,18 @@
|
|||
artifactId="cassandra-parent"
|
||||
version="${version}"
|
||||
relativePath="${final.name}-parent.pom"/>
|
||||
<dependency groupId="junit" artifactId="junit"/>
|
||||
<dependency groupId="commons-io" artifactId="commons-io"/>
|
||||
<dependency groupId="org.mockito" artifactId="mockito-core"/>
|
||||
<dependency groupId="junit" artifactId="junit" scope="test"/>
|
||||
<dependency groupId="commons-io" artifactId="commons-io" scope="test"/>
|
||||
<dependency groupId="org.mockito" artifactId="mockito-core" scope="test"/>
|
||||
<dependency groupId="com.puppycrawl.tools" artifactId="checkstyle" scope="test"/>
|
||||
<dependency groupId="org.quicktheories" artifactId="quicktheories"/>
|
||||
<dependency groupId="org.reflections" artifactId="reflections"/>
|
||||
<dependency groupId="com.google.code.java-allocation-instrumenter" artifactId="java-allocation-instrumenter" version="${allocation-instrumenter.version}"/>
|
||||
<dependency groupId="org.apache.cassandra" artifactId="dtest-api"/>
|
||||
<dependency groupId="org.openjdk.jmh" artifactId="jmh-core"/>
|
||||
<dependency groupId="org.openjdk.jmh" artifactId="jmh-generator-annprocess"/>
|
||||
<dependency groupId="org.apache.ant" artifactId="ant-junit"/>
|
||||
<dependency groupId="org.quicktheories" artifactId="quicktheories" scope="test"/>
|
||||
<dependency groupId="org.reflections" artifactId="reflections" scope="test"/>
|
||||
<dependency groupId="com.google.code.java-allocation-instrumenter" artifactId="java-allocation-instrumenter" version="${allocation-instrumenter.version}" scope="test"/>
|
||||
<dependency groupId="org.apache.cassandra" artifactId="dtest-api" scope="test"/>
|
||||
<dependency groupId="org.openjdk.jmh" artifactId="jmh-core" scope="test"/>
|
||||
<dependency groupId="org.openjdk.jmh" artifactId="jmh-generator-annprocess" scope="test"/>
|
||||
<dependency groupId="net.ju-n.compile-command-annotations" artifactId="compile-command-annotations" scope="test"/>
|
||||
<dependency groupId="org.apache.ant" artifactId="ant-junit" version="1.9.7" scope="test"/>
|
||||
<!-- adding this dependency is necessary for assertj. When updating assertj, need to also update the version of
|
||||
this that the new assertj's `assertj-parent-pom` depends on. -->
|
||||
<dependency groupId="org.junit" artifactId="junit-bom" type="pom"/>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,20 @@
|
|||
<?xml version="1.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.
|
||||
-->
|
||||
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.
|
||||
-->
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
|
||||
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
|
|
@ -39,10 +38,17 @@
|
|||
<module name="TreeWalker">
|
||||
|
||||
<module name="RegexpSinglelineJava">
|
||||
<!-- To prevent static imports -->
|
||||
<property name="format" value="System\.(currentTimeMillis|nanoTime)"/>
|
||||
<!-- To prevent static imports and System.nanoTime or System.currentTimeMillis -->
|
||||
<property name="format" value="(newSingleThreadExecutor|newFixedThreadPool|newCachedThreadPool|newSingleThreadScheduledExecutor|newWorkStealingPool|newScheduledThreadPool|defaultThreadFactory)\(|System\.(currentTimeMillis|nanoTime)"/>
|
||||
<property name="ignoreComments" value="true"/>
|
||||
</module>
|
||||
<module name="IllegalImport">
|
||||
<property name="illegalPkgs" value=""/>
|
||||
<property name="illegalClasses" value="java.util.concurrent.Semaphore,java.util.concurrent.CountDownLatch,java.util.concurrent.Executors,java.util.concurrent.LinkedBlockingQueue,java.util.concurrent.SynchronousQueue,java.util.concurrent.ArrayBlockingQueue,com.google.common.util.concurrent.Futures"/>
|
||||
</module>
|
||||
<module name="IllegalInstantiation">
|
||||
<property name="classes" value="java.lang.Thread,java.util.concurrent.FutureTask,java.util.concurrent.Semaphore,java.util.concurrent.CountDownLatch,java.util.concurrent.ScheduledThreadPoolExecutor,java.util.concurrent.ThreadPoolExecutor,java.util.concurrent.ForkJoinPool))"/>
|
||||
</module>
|
||||
</module>
|
||||
|
||||
</module>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,20 @@
|
|||
<?xml version="1.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.
|
||||
-->
|
||||
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.
|
||||
-->
|
||||
|
||||
<!DOCTYPE suppressions PUBLIC
|
||||
"-//Checkstyle//DTD SuppressionFilter Configuration 1.1//EN"
|
||||
|
|
@ -23,4 +22,5 @@
|
|||
|
||||
<suppressions>
|
||||
<suppress checks="RegexpSinglelineJava" files="Clock\.java|Semaphore\.java"/>
|
||||
<suppress checks="IllegalImport|IllegalInstantiation" files="File\.java|[\\/]binlog[\\/]|Semaphore\.java|DataStructureFactory\.java|PathUtils\.java|UDFunction\.java|BlockingQueues\.java"/>
|
||||
</suppressions>
|
||||
|
|
|
|||
|
|
@ -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<K, V> implements AuthCacheMBean
|
||||
public class AuthCache<K, V> 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<Shutdownable> 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<K, V> cache;
|
||||
private DebuggableThreadPoolExecutor cacheRefreshExecutor;
|
||||
private ExecutorPlus cacheRefreshExecutor;
|
||||
|
||||
private String name;
|
||||
private IntConsumer setValidityDelegate;
|
||||
|
|
@ -94,9 +108,10 @@ public class AuthCache<K, V> 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<K, V> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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<K extends CacheKey, V> extends InstrumentingCache<K
|
|||
}
|
||||
}
|
||||
|
||||
public ListenableFuture<Integer> loadSavedAsync()
|
||||
public Future<Integer> loadSavedAsync()
|
||||
{
|
||||
final ListeningExecutorService es = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
|
||||
final ExecutorPlus es = executorFactory().sequential("loadSavedCache");
|
||||
final long start = nanoTime();
|
||||
|
||||
ListenableFuture<Integer> cacheLoad = es.submit(this::loadSaved);
|
||||
Future<Integer> cacheLoad = es.submit(this::loadSaved);
|
||||
cacheLoad.addListener(() -> {
|
||||
if (size() > 0)
|
||||
logger.info("Completed loading ({} ms; {} keys) {} cache",
|
||||
|
|
@ -168,7 +171,7 @@ public class AutoSavingCache<K extends CacheKey, V> extends InstrumentingCache<K
|
|||
CacheService.instance.keyCache.size(),
|
||||
cacheType);
|
||||
es.shutdown();
|
||||
}, MoreExecutors.directExecutor());
|
||||
});
|
||||
|
||||
return cacheLoad;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,229 +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.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.apache.cassandra.utils.concurrent.Condition;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
import org.apache.cassandra.utils.JVMStabilityInspector;
|
||||
|
||||
import static org.apache.cassandra.tracing.Tracing.isTracing;
|
||||
|
||||
public abstract class AbstractLocalAwareExecutorService implements LocalAwareExecutorService
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(AbstractLocalAwareExecutorService.class);
|
||||
|
||||
protected abstract void addTask(FutureTask<?> futureTask);
|
||||
protected abstract void onCompletion();
|
||||
|
||||
/** Task Submission / Creation / Objects **/
|
||||
|
||||
public <T> FutureTask<T> submit(Callable<T> task)
|
||||
{
|
||||
return submit(newTaskFor(task));
|
||||
}
|
||||
|
||||
public FutureTask<?> submit(Runnable task)
|
||||
{
|
||||
return submit(newTaskFor(task, null));
|
||||
}
|
||||
|
||||
public <T> FutureTask<T> submit(Runnable task, T result)
|
||||
{
|
||||
return submit(newTaskFor(task, result));
|
||||
}
|
||||
|
||||
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
protected <T> FutureTask<T> newTaskFor(Runnable runnable, T result)
|
||||
{
|
||||
return newTaskFor(runnable, result, ExecutorLocals.create());
|
||||
}
|
||||
|
||||
protected <T> FutureTask<T> newTaskFor(Runnable runnable, T result, ExecutorLocals locals)
|
||||
{
|
||||
if (locals != null)
|
||||
{
|
||||
if (runnable instanceof LocalSessionFutureTask)
|
||||
return (LocalSessionFutureTask<T>) runnable;
|
||||
return new LocalSessionFutureTask<T>(runnable, result, locals);
|
||||
}
|
||||
if (runnable instanceof FutureTask)
|
||||
return (FutureTask<T>) runnable;
|
||||
return new FutureTask<>(runnable, result);
|
||||
}
|
||||
|
||||
protected <T> FutureTask<T> newTaskFor(Callable<T> callable)
|
||||
{
|
||||
if (isTracing())
|
||||
{
|
||||
if (callable instanceof LocalSessionFutureTask)
|
||||
return (LocalSessionFutureTask<T>) callable;
|
||||
return new LocalSessionFutureTask<T>(callable, ExecutorLocals.create());
|
||||
}
|
||||
if (callable instanceof FutureTask)
|
||||
return (FutureTask<T>) callable;
|
||||
return new FutureTask<>(callable);
|
||||
}
|
||||
|
||||
private class LocalSessionFutureTask<T> extends FutureTask<T>
|
||||
{
|
||||
private final ExecutorLocals locals;
|
||||
|
||||
public LocalSessionFutureTask(Callable<T> 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<T> extends Condition.Async implements Future<T>, Runnable
|
||||
{
|
||||
private boolean failure;
|
||||
private Object result = this;
|
||||
private final Callable<T> callable;
|
||||
|
||||
public FutureTask(Callable<T> 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 <T> FutureTask<T> submit(FutureTask<T> 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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Runnable> 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<Runnable>(), new NamedThreadFactory(threadPoolName, priority));
|
||||
}
|
||||
|
||||
public DebuggableThreadPoolExecutor(int corePoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> queue, ThreadFactory factory)
|
||||
{
|
||||
this(corePoolSize, corePoolSize, keepAliveTime, unit, queue, factory);
|
||||
}
|
||||
|
||||
public DebuggableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> 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<Runnable>(),
|
||||
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<Runnable>(), 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 <T> RunnableFuture<T> 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 <T> RunnableFuture<T> newTaskFor(Callable<T> 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 <T>
|
||||
*/
|
||||
private static class LocalSessionWrapper<T> extends FutureTask<T>
|
||||
{
|
||||
private final ExecutorLocals locals;
|
||||
|
||||
private LocalSessionWrapper(Callable<T> callable, ExecutorLocals locals)
|
||||
{
|
||||
super(callable);
|
||||
this.locals = locals;
|
||||
}
|
||||
|
||||
static LocalSessionWrapper<Object> create(Runnable command)
|
||||
{
|
||||
return create(command, null, ExecutorLocals.create());
|
||||
}
|
||||
|
||||
static <T> LocalSessionWrapper<T> create(Runnable command, T result)
|
||||
{
|
||||
return create(command, result, ExecutorLocals.create());
|
||||
}
|
||||
|
||||
static <T> LocalSessionWrapper<T> 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 <T> LocalSessionWrapper<T> create(Callable<T> command)
|
||||
{
|
||||
return new LocalSessionWrapper<>(command, ExecutorLocals.create());
|
||||
}
|
||||
|
||||
private void setupContext()
|
||||
{
|
||||
ExecutorLocals.set(locals);
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
ExecutorLocals.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static class FutureLocalSessionWrapper<T> extends LocalSessionWrapper<T> implements HasDelegateFuture
|
||||
{
|
||||
private final RunnableFuture<T> delegate;
|
||||
|
||||
private FutureLocalSessionWrapper(RunnableFuture command, T result, ExecutorLocals locals)
|
||||
{
|
||||
super(() -> {
|
||||
command.run();
|
||||
return result;
|
||||
}, locals);
|
||||
this.delegate = command;
|
||||
}
|
||||
|
||||
public Future<T> 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 <T>
|
||||
*/
|
||||
private static class ForwardingRunnableFuture<T> extends FutureTask<T> implements HasDelegateFuture
|
||||
{
|
||||
private final RunnableFuture<T> delegate;
|
||||
|
||||
public ForwardingRunnableFuture(RunnableFuture<T> delegate, T result)
|
||||
{
|
||||
super(delegate, result);
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public Future<T> getDelegate()
|
||||
{
|
||||
return delegate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <V> Callable<V> propagating(Callable<V> 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 <V> Callable<V> enforceOptions(WithResources withResources, Callable<V> wrap)
|
||||
{
|
||||
return new Callable<V>()
|
||||
{
|
||||
@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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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<E extends ExecutorService>
|
||||
{
|
||||
/**
|
||||
* Threads for the executor built by this factory will timeout (terminate) after the specified period.
|
||||
*/
|
||||
ExecutorBuilder<E> 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<E> withKeepAlive();
|
||||
|
||||
/**
|
||||
* Specify the priority of threads that service the executor built by this factory (default to {@link Thread#NORM_PRIORITY})
|
||||
*/
|
||||
ExecutorBuilder<E> 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<E> 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<E> 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<E> withQueueLimit(int queueLimit);
|
||||
|
||||
/**
|
||||
* Set the {@link RejectedExecutionHandler} for the executor built by this factory.
|
||||
* By default this is executor-specific, either:
|
||||
* <li> {@link ThreadPoolExecutorBase#blockingExecutionHandler}
|
||||
* <li> {@link ScheduledThreadPoolExecutorPlus#rejectedExecutionHandler}
|
||||
* <li> and maybe wrapped by {@link ThreadPoolExecutorJMXAdapter#rejectedExecutionHandler}
|
||||
*/
|
||||
ExecutorBuilder<E> withRejectedExecutionHandler(RejectedExecutionHandler rejectedExecutionHandler);
|
||||
|
||||
/**
|
||||
* Set the {@link UncaughtExceptionHandler} for threads that service executors built by this factory.
|
||||
* By default {@link JVMStabilityInspector#uncaughtException(Thread, Throwable)}
|
||||
*/
|
||||
ExecutorBuilder<E> withUncaughtExceptionHandler(UncaughtExceptionHandler uncaughtExceptionHandler);
|
||||
|
||||
/**
|
||||
* Build the configured executor
|
||||
*/
|
||||
E build();
|
||||
}
|
||||
|
|
@ -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
|
||||
* <li>{@link #sequential(String)}
|
||||
* <li>{@link #pooled(String, int)}
|
||||
*
|
||||
* Supports custom configuration of executors via
|
||||
* <li>{@link #configureSequential(String)}
|
||||
* <li>{@link #configurePooled(String, int)}
|
||||
*/
|
||||
public interface ExecutorBuilderFactory<E extends ExecutorPlus, S extends SequentialExecutorPlus>
|
||||
{
|
||||
/**
|
||||
* Configure a sequential (single threaded) executor
|
||||
*/
|
||||
ExecutorBuilder<? extends S> configureSequential(String name);
|
||||
|
||||
/**
|
||||
* Configure a pooled executor with the requested number of threads
|
||||
*/
|
||||
ExecutorBuilder<? extends E> 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
|
||||
* <li>{@link #sequential(String)}
|
||||
* <li>{@link #pooled(String, int)}
|
||||
*
|
||||
* Supports custom configuration of executors via
|
||||
* <li>{@link #configureSequential(String)}
|
||||
* <li>{@link #configurePooled(String, int)}
|
||||
*
|
||||
* Supports any of the above with added JMX registration via sub-factories
|
||||
* <li>{@link #withJmx(String)}
|
||||
* <li>{@link #withJmxInternal()}
|
||||
*/
|
||||
interface Jmxable<E extends ExecutorPlus, S extends SequentialExecutorPlus> extends ExecutorBuilderFactory<E, S>
|
||||
{
|
||||
/**
|
||||
* @return a factory that configures executors that register against JMX using the provided jmx path
|
||||
*/
|
||||
ExecutorBuilderFactory<E, S> withJmx(String jmxPath);
|
||||
|
||||
/**
|
||||
* @return a factory that configures executors that register against JMX using the "internal" jmx path
|
||||
*/
|
||||
default ExecutorBuilderFactory<E, S> withJmxInternal() { return withJmx("internal"); }
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
* <li>{@link #sequential(String)}
|
||||
* <li>{@link #pooled(String, int)}
|
||||
* <li>{@link #scheduled(String)}
|
||||
* <li>{@link #scheduled(boolean, String)}
|
||||
* <li>{@link #scheduled(boolean, String, int)}
|
||||
*
|
||||
* Supports custom configuration of executors via
|
||||
* <li>{@link #configureSequential(String)}
|
||||
* <li>{@link #configurePooled(String, int)}
|
||||
*
|
||||
* Supports any of the above with added JMX registration via sub-factories
|
||||
* <li>{@link #withJmx(String)}
|
||||
* <li>{@link #withJmxInternal()}
|
||||
*
|
||||
* Supports any of the above with the resultant executor propagating {@link ExecutorLocals} via sub-factory
|
||||
* <li>{@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<ExecutorPlus, SequentialExecutorPlus>
|
||||
{
|
||||
public interface LocalAwareSubFactoryWithJMX extends ExecutorBuilderFactory<LocalAwareExecutorPlus, LocalAwareSequentialExecutorPlus>
|
||||
{
|
||||
LocalAwareExecutorPlus shared(String name, int threads, ExecutorPlus.MaximumPoolSizeListener onSetMaxSize);
|
||||
}
|
||||
|
||||
public interface LocalAwareSubFactory extends ExecutorBuilderFactory<LocalAwareExecutorPlus, LocalAwareSequentialExecutorPlus>
|
||||
{
|
||||
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<Thread> 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<? extends LocalAwareSequentialExecutorPlus> configureSequential(String name)
|
||||
{
|
||||
return ThreadPoolExecutorBuilder.sequential(LocalAwareSingleThreadExecutorPlus::new, contextClassLoader, threadGroup, uncaughtExceptionHandler, name);
|
||||
}
|
||||
|
||||
public ExecutorBuilder<LocalAwareThreadPoolExecutorPlus> 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<LocalAwareSingleThreadExecutorPlus> configureSequential(String name)
|
||||
{
|
||||
return sequentialJmx(LocalAwareSingleThreadExecutorPlus::new, contextClassLoader, threadGroup, uncaughtExceptionHandler, name, jmxPath);
|
||||
}
|
||||
|
||||
public ExecutorBuilder<LocalAwareThreadPoolExecutorPlus> 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<ExecutorPlus, SequentialExecutorPlus> withJmx(String jmxPath)
|
||||
{
|
||||
return new ExecutorBuilderFactory<ExecutorPlus, SequentialExecutorPlus>()
|
||||
{
|
||||
@Override
|
||||
public ExecutorBuilder<? extends SequentialExecutorPlus> configureSequential(String name)
|
||||
{
|
||||
return ThreadPoolExecutorBuilder.sequentialJmx(SingleThreadExecutorPlus::new, contextClassLoader, threadGroup, uncaughtExceptionHandler, name, jmxPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutorBuilder<? extends ExecutorPlus> configurePooled(String name, int threads)
|
||||
{
|
||||
return ThreadPoolExecutorBuilder.pooledJmx(ThreadPoolExecutorPlus::new, contextClassLoader, threadGroup, uncaughtExceptionHandler, name, threads, jmxPath);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public ExecutorBuilder<SingleThreadExecutorPlus> configureSequential(String name)
|
||||
{
|
||||
return ThreadPoolExecutorBuilder.sequential(SingleThreadExecutorPlus::new, contextClassLoader, threadGroup, uncaughtExceptionHandler, name);
|
||||
}
|
||||
|
||||
public ExecutorBuilder<ThreadPoolExecutorPlus> 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<Thread> interruptHandler)
|
||||
{
|
||||
return new InfiniteLoopExecutor(this, name, task, interruptHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThreadGroup newThreadGroup(String name)
|
||||
{
|
||||
return threadGroup == null ? null : new ThreadGroup(threadGroup, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<TraceState> tracing = Tracing.instance;
|
||||
private static final ExecutorLocal<ClientWarn.State> clientWarn = ClientWarn.instance;
|
||||
private static final ExecutorLocals none = new ExecutorLocals(null, null);
|
||||
private static final FastThreadLocal<ExecutorLocals> locals = new FastThreadLocal<ExecutorLocals>()
|
||||
{
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
<T> Future<T> submit(Callable<T> task);
|
||||
|
||||
/**
|
||||
* Overrides {@link ExecutorService#submit(Runnable, Object)} to return a Cassandra {@link Future}
|
||||
*/
|
||||
@Override
|
||||
<T> Future<T> 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:
|
||||
*
|
||||
* <code>
|
||||
* try (Closeable close = withResources.get())
|
||||
* {
|
||||
* task.run();
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @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:
|
||||
*
|
||||
* <code>
|
||||
* try (Closeable close = withResources.get())
|
||||
* {
|
||||
* return task.call();
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @param withResources the resources to create and hold while executing {@code task}
|
||||
* @param task the task to execute
|
||||
*/
|
||||
<T> Future<T> submit(WithResources withResources, Callable<T> 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
|
||||
*
|
||||
* <code>
|
||||
* try (Closeable close = withResources.get())
|
||||
* {
|
||||
* task.run();
|
||||
* return null;
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @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:
|
||||
*
|
||||
* <code>
|
||||
* try (Closeable close = withResources.get())
|
||||
* {
|
||||
* task.run();
|
||||
* return result;
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @param withResources the resources to create and hold while executing {@code task}
|
||||
* @param task the task to execute
|
||||
* @param result the result if successful
|
||||
*/
|
||||
<T> Future<T> submit(WithResources withResources, Runnable task, T result);
|
||||
|
||||
/**
|
||||
* @return true iff the caller is a worker thread actively serving this executor
|
||||
*/
|
||||
boolean inExecutor();
|
||||
|
||||
default <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
default <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
default <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
default <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<V> extends AsyncFuture<V> implements RunnableFuture<V>
|
||||
{
|
||||
private Callable<V> call;
|
||||
|
||||
public FutureTask(Callable<V> 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 <T> Callable<T> callable(Runnable run)
|
||||
{
|
||||
return new Callable<T>()
|
||||
{
|
||||
public T call()
|
||||
{
|
||||
run.run();
|
||||
return null;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return run.toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Callable<T> callable(Object id, Runnable run)
|
||||
{
|
||||
return new Callable<T>()
|
||||
{
|
||||
public T call()
|
||||
{
|
||||
run.run();
|
||||
return null;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return id.toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Callable<T> callable(Runnable run, T result)
|
||||
{
|
||||
return new Callable<T>()
|
||||
{
|
||||
public T call()
|
||||
{
|
||||
run.run();
|
||||
return result;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return run + "->" + result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Callable<T> callable(Object id, Runnable run, T result)
|
||||
{
|
||||
return new Callable<T>()
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<V> extends FutureTask<V>
|
||||
{
|
||||
private final WithResources withResources;
|
||||
|
||||
public FutureTaskWithResources(WithResources withResources, Callable<V> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <T> Future<T> submit(Callable<T> 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 <T> Future<T> 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 <T> Future<T> submit(WithResources withResources, Callable<T> 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 <T> Future<T> 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; }
|
||||
|
|
|
|||
|
|
@ -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<InfiniteLoopExecutor, Object> stateUpdater = AtomicReferenceFieldUpdater.newUpdater(InfiniteLoopExecutor.class, Object.class, "state");
|
||||
private final Thread thread;
|
||||
private final Task task;
|
||||
private volatile Object state = NORMAL;
|
||||
private final Consumer<Thread> 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<Thread> interruptHandler)
|
||||
{
|
||||
this.task = task;
|
||||
this.thread = factory.startThread(name, this::loop);
|
||||
this.interruptHandler = interruptHandler;
|
||||
}
|
||||
|
||||
public InfiniteLoopExecutor(BiFunction<String, Runnable, Thread> threadStarter, String name, Task task, Consumer<Thread> 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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <i>ScheduledThreadPoolExecutor</i>. It provides an implementation
|
||||
* for the <i>afterExecute()</i> found in the <i>ThreadPoolExecutor</i> 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<Runnable>(), new NamedThreadFactory(threadPoolName), "internal");
|
||||
}
|
||||
|
||||
public JMXEnabledThreadPoolExecutor(String threadPoolName, String jmxPath)
|
||||
{
|
||||
this(1, Integer.MAX_VALUE, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new NamedThreadFactory(threadPoolName), jmxPath);
|
||||
}
|
||||
|
||||
public JMXEnabledThreadPoolExecutor(String threadPoolName, int priority)
|
||||
{
|
||||
this(1, Integer.MAX_VALUE, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new NamedThreadFactory(threadPoolName, priority), "internal");
|
||||
}
|
||||
|
||||
public JMXEnabledThreadPoolExecutor(NamedThreadFactory threadFactory, String jmxPath)
|
||||
{
|
||||
this(1, Integer.MAX_VALUE, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory, jmxPath);
|
||||
}
|
||||
|
||||
public JMXEnabledThreadPoolExecutor(int corePoolSize,
|
||||
long keepAliveTime,
|
||||
TimeUnit unit,
|
||||
BlockingQueue<Runnable> workQueue,
|
||||
NamedThreadFactory threadFactory,
|
||||
String jmxPath)
|
||||
{
|
||||
this(corePoolSize, corePoolSize, keepAliveTime, unit, workQueue, threadFactory, jmxPath);
|
||||
}
|
||||
|
||||
public JMXEnabledThreadPoolExecutor(int corePoolSize,
|
||||
int maxPoolSize,
|
||||
long keepAliveTime,
|
||||
TimeUnit unit,
|
||||
BlockingQueue<Runnable> 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<Runnable> 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<Runnable> 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
|
|
@ -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<LocalAwareSingleThreadExecutorPlus> builder)
|
||||
{
|
||||
super(builder, TaskFactory.localAware());
|
||||
}
|
||||
}
|
||||
|
|
@ -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<? extends LocalAwareThreadPoolExecutorPlus> builder)
|
||||
{
|
||||
super(builder, TaskFactory.localAware());
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <i>ThreadFactory</i> 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 extends Thread> 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 extends Thread> 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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<FutureTask<?>> tasks = new ConcurrentLinkedQueue<>();
|
||||
protected final ConcurrentLinkedQueue<Runnable> 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 extends Runnable> 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 <T> Future<T> submit(Runnable run, T result)
|
||||
{
|
||||
return addTask(taskFactory.toSubmit(run, result));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Future<T> submit(Callable<T> call)
|
||||
{
|
||||
return addTask(taskFactory.toSubmit(call));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Future<T> 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 <T> Future<T> submit(WithResources withResources, Callable<T> call)
|
||||
{
|
||||
return addTask(taskFactory.toSubmit(withResources, call));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inExecutor()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public synchronized void shutdown()
|
||||
{
|
||||
if (shuttingDown)
|
||||
|
|
|
|||
|
|
@ -48,11 +48,11 @@ final class SEPWorker extends AtomicReference<SEPWorker.Work> 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<SEPWorker.Work> 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<SEPWorker.Work> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <V> ScheduledFuture<V> schedule(Callable<V> 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 extends Runnable> 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 <T> Future<T> submit(Runnable run, T result)
|
||||
{
|
||||
return addTask(taskFactory.toSubmit(run, result));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Future<T> submit(Callable<T> call)
|
||||
{
|
||||
return addTask(taskFactory.toSubmit(call));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Future<T> 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 <T> Future<T> submit(WithResources withResources, Callable<T> call)
|
||||
{
|
||||
return addTask(taskFactory.toSubmit(withResources, call));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inExecutor()
|
||||
{
|
||||
return Thread.currentThread().getThreadGroup() == getThreadFactory().threadGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value)
|
||||
{
|
||||
return taskFactory.toSubmit(runnable, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable)
|
||||
{
|
||||
return taskFactory.toSubmit(callable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NamedThreadFactory getThreadFactory()
|
||||
{
|
||||
return (NamedThreadFactory) super.getThreadFactory();
|
||||
}
|
||||
|
||||
/*======== DIRECT COPY OF ThreadPoolExecutorBase ===============*/
|
||||
|
||||
@Override
|
||||
public List<Runnable> shutdownNow()
|
||||
{
|
||||
List<Runnable> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<T>
|
||||
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;
|
||||
}
|
||||
|
|
@ -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<? extends SingleThreadExecutorPlus> builder)
|
||||
{
|
||||
this(builder, TaskFactory.standard());
|
||||
}
|
||||
|
||||
SingleThreadExecutorPlus(ThreadPoolExecutorBuilder<? extends SingleThreadExecutorPlus> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<LocalAwareExecutorService> initialiser;
|
||||
private volatile LocalAwareExecutorService executor = null;
|
||||
private final Supplier<ExecutorPlus> 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 <T> Future<T> submit(Callable<T> task) { return executor().submit(task); }
|
||||
public Future<?> submit(Runnable task) { return executor().submit(task); }
|
||||
public <T> Future<T> 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<ExecutorService> executors()
|
||||
private static List<ExecutorPlus> 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<ExecutorService> executors = executors();
|
||||
List<ExecutorPlus> 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<Runnable> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<T> extends SyncFuture<T> implements RunnableFuture<T>
|
||||
{
|
||||
final Callable<T> call;
|
||||
|
||||
public SyncFutureTask(Callable<T> call)
|
||||
{
|
||||
this.call = call;
|
||||
}
|
||||
|
||||
public SyncFutureTask(WithResources withResources, Callable<T> 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
<T> RunnableFuture<T> toSubmit(Runnable runnable);
|
||||
<T> RunnableFuture<T> toSubmit(Runnable runnable, T result);
|
||||
<T> RunnableFuture<T> toSubmit(Callable<T> callable);
|
||||
|
||||
Runnable toExecute(WithResources withResources, Runnable runnable);
|
||||
<T> RunnableFuture<T> toSubmit(WithResources withResources, Runnable runnable);
|
||||
<T> RunnableFuture<T> toSubmit(WithResources withResources, Runnable runnable, T result);
|
||||
<T> RunnableFuture<T> toSubmit(WithResources withResources, Callable<T> 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 <T> RunnableFuture<T> toSubmit(Runnable runnable)
|
||||
{
|
||||
return newTask(callable(runnable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> RunnableFuture<T> toSubmit(Runnable runnable, T result)
|
||||
{
|
||||
return newTask(callable(runnable, result));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> RunnableFuture<T> toSubmit(Callable<T> callable)
|
||||
{
|
||||
return newTask(callable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable toExecute(WithResources withResources, Runnable runnable)
|
||||
{
|
||||
return ExecutionFailure.suppressing(withResources, runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> RunnableFuture<T> toSubmit(WithResources withResources, Runnable runnable)
|
||||
{
|
||||
return withResources.isNoOp() ? newTask(callable(runnable))
|
||||
: newTask(withResources, callable(runnable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> RunnableFuture<T> toSubmit(WithResources withResources, Runnable runnable, T result)
|
||||
{
|
||||
return withResources.isNoOp() ? newTask(callable(runnable, result))
|
||||
: newTask(withResources, callable(runnable, result));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> RunnableFuture<T> toSubmit(WithResources withResources, Callable<T> callable)
|
||||
{
|
||||
return withResources.isNoOp() ? newTask(callable)
|
||||
: newTask(withResources, callable);
|
||||
}
|
||||
|
||||
protected <T> RunnableFuture<T> newTask(Callable<T> call)
|
||||
{
|
||||
return new FutureTask<>(call);
|
||||
}
|
||||
|
||||
protected <T> RunnableFuture<T> newTask(WithResources withResources, Callable<T> 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 <T> RunnableFuture<T> toSubmit(Runnable runnable)
|
||||
{
|
||||
return super.toSubmit(ExecutorLocals.propagate(), runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> RunnableFuture<T> toSubmit(Runnable runnable, T result)
|
||||
{
|
||||
return super.toSubmit(ExecutorLocals.propagate(), runnable, result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> RunnableFuture<T> toSubmit(Callable<T> callable)
|
||||
{
|
||||
return super.toSubmit(ExecutorLocals.propagate(), callable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable toExecute(WithResources withResources, Runnable runnable)
|
||||
{
|
||||
return ExecutionFailure.suppressing(withLocals(withResources), runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> RunnableFuture<T> toSubmit(WithResources withResources, Runnable runnable)
|
||||
{
|
||||
return super.toSubmit(withLocals(withResources), runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> RunnableFuture<T> toSubmit(WithResources withResources, Runnable runnable, T result)
|
||||
{
|
||||
return super.toSubmit(withLocals(withResources), runnable, result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> RunnableFuture<T> toSubmit(WithResources withResources, Callable<T> callable)
|
||||
{
|
||||
return super.toSubmit(withLocals(withResources), callable);
|
||||
}
|
||||
|
||||
private static WithResources withLocals(WithResources withResources)
|
||||
{
|
||||
return withResources instanceof ExecutorLocals ? withResources : ExecutorLocals.propagate().and(withResources);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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:
|
||||
* <li>We are configured by {@link ThreadPoolExecutorBuilder}
|
||||
* <li>Tasks rejected due to overflow of the queue block the submitting thread rather than throwing {@link RejectedExecutionException}
|
||||
* <li>{@link RunnableFuture} rejected due to executor shutdown will be cancelled
|
||||
* <li>{@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<Runnable> 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<Runnable> 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<Runnable> shutdownNow()
|
||||
{
|
||||
try
|
||||
{
|
||||
List<Runnable> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
* <li>Core threads may timeout, and use a default {@link #keepAlive} time in {@link #keepAliveUnits}
|
||||
* <li>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}
|
||||
* <li>By default queues are unbounded in length
|
||||
* <li>The default {@link RejectedExecutionHandler} is implementation dependent, but may be overridden
|
||||
* <li>The default {@link UncaughtExceptionHandler} is inherited from {@link MetaFactory}, which in turn receives it
|
||||
* from the {@link ExecutorBuilderFactory}
|
||||
*/
|
||||
class ThreadPoolExecutorBuilder<E extends ExecutorPlus> extends MetaFactory implements ExecutorBuilder<E>
|
||||
{
|
||||
static <E extends SequentialExecutorPlus> ExecutorBuilder<E> sequential(Function<ThreadPoolExecutorBuilder<E>, E> constructor, ClassLoader contextClassLoader, ThreadGroup threadGroup, UncaughtExceptionHandler uncaughtExceptionHandler, String name)
|
||||
{
|
||||
ThreadPoolExecutorBuilder<E> result = new ThreadPoolExecutorBuilder<>(constructor, contextClassLoader, threadGroup, uncaughtExceptionHandler, name, 1);
|
||||
result.withKeepAlive();
|
||||
return result;
|
||||
}
|
||||
|
||||
static <E extends SingleThreadExecutorPlus> ExecutorBuilder<E> sequentialJmx(Function<ThreadPoolExecutorBuilder<E>, E> constructor, ClassLoader contextClassLoader, ThreadGroup threadGroup, UncaughtExceptionHandler uncaughtExceptionHandler, String name, String jmxPath)
|
||||
{
|
||||
return new ThreadPoolExecutorJMXAdapter.Builder<>(sequential(constructor, contextClassLoader, threadGroup, uncaughtExceptionHandler, name), jmxPath);
|
||||
}
|
||||
|
||||
static <E extends ExecutorPlus> ExecutorBuilder<E> pooled(Function<ThreadPoolExecutorBuilder<E>, E> constructor, ClassLoader contextClassLoader, ThreadGroup threadGroup, UncaughtExceptionHandler uncaughtExceptionHandler, String name, int threads)
|
||||
{
|
||||
return new ThreadPoolExecutorBuilder<>(constructor, contextClassLoader, threadGroup, uncaughtExceptionHandler, name, threads);
|
||||
}
|
||||
|
||||
static <E extends ThreadPoolExecutorPlus> ExecutorBuilder<E> pooledJmx(Function<ThreadPoolExecutorBuilder<E>, 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<ThreadPoolExecutorBuilder<E>, 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<ThreadPoolExecutorBuilder<E>, 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<E> 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<E> withKeepAlive()
|
||||
{
|
||||
this.allowCoreThreadTimeouts = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ThreadPoolExecutorBuilder<E> withThreadPriority(int threadPriority)
|
||||
{
|
||||
this.threadPriority = threadPriority;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutorBuilder<E> 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<E> withDefaultThreadGroup()
|
||||
{
|
||||
this.threadGroup = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ThreadPoolExecutorBuilder<E> withQueueLimit(int queueLimit)
|
||||
{
|
||||
this.queueLimit = queueLimit;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ThreadPoolExecutorBuilder<E> withRejectedExecutionHandler(RejectedExecutionHandler rejectedExecutionHandler)
|
||||
{
|
||||
this.rejectedExecutionHandler = rejectedExecutionHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ThreadPoolExecutorBuilder<E> withUncaughtExceptionHandler(UncaughtExceptionHandler uncaughtExceptionHandler)
|
||||
{
|
||||
this.uncaughtExceptionHandler = uncaughtExceptionHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public E build()
|
||||
{
|
||||
return constructor.apply(this);
|
||||
}
|
||||
|
||||
NamedThreadFactory newThreadFactory()
|
||||
{
|
||||
return newThreadFactory(name, threadPriority);
|
||||
}
|
||||
|
||||
BlockingQueue<Runnable> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <E>
|
||||
*/
|
||||
static class Builder<E extends ThreadPoolExecutorBase> implements ExecutorBuilder<E>
|
||||
{
|
||||
final ExecutorBuilder<E> wrapped;
|
||||
final String jmxPath;
|
||||
Builder(ExecutorBuilder<E> wrapped, String jmxPath)
|
||||
{
|
||||
this.wrapped = wrapped;
|
||||
this.jmxPath = jmxPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutorBuilder<E> withKeepAlive(long keepAlive, TimeUnit keepAliveUnits)
|
||||
{
|
||||
wrapped.withKeepAlive(keepAlive, keepAliveUnits);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutorBuilder<E> withKeepAlive()
|
||||
{
|
||||
wrapped.withKeepAlive();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutorBuilder<E> withThreadPriority(int threadPriority)
|
||||
{
|
||||
wrapped.withThreadPriority(threadPriority);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutorBuilder<E> withQueueLimit(int queueLimit)
|
||||
{
|
||||
wrapped.withQueueLimit(queueLimit);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutorBuilder<E> withThreadGroup(ThreadGroup threadGroup)
|
||||
{
|
||||
wrapped.withThreadGroup(threadGroup);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutorBuilder<E> withDefaultThreadGroup()
|
||||
{
|
||||
wrapped.withDefaultThreadGroup();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutorBuilder<E> withRejectedExecutionHandler(RejectedExecutionHandler rejectedExecutionHandler)
|
||||
{
|
||||
wrapped.withRejectedExecutionHandler(rejectedExecutionHandler);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutorBuilder<E> 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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<? extends ThreadPoolExecutorPlus> builder)
|
||||
{
|
||||
this(builder, TaskFactory.standard());
|
||||
}
|
||||
|
||||
ThreadPoolExecutorPlus(ThreadPoolExecutorBuilder<? extends ThreadPoolExecutorPlus> builder, TaskFactory taskFactory)
|
||||
{
|
||||
super(builder);
|
||||
this.taskFactory = taskFactory;
|
||||
}
|
||||
|
||||
private <T extends Runnable> 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 <T> Future<T> submit(Runnable run, T result)
|
||||
{
|
||||
return addTask(taskFactory.toSubmit(run, result));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Future<T> submit(Callable<T> call)
|
||||
{
|
||||
return addTask(taskFactory.toSubmit(call));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Future<T> 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 <T> Future<T> submit(WithResources withResources, Callable<T> call)
|
||||
{
|
||||
return addTask(taskFactory.toSubmit(withResources, call));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inExecutor()
|
||||
{
|
||||
return Thread.currentThread().getThreadGroup() == getThreadFactory().threadGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value)
|
||||
{
|
||||
return taskFactory.toSubmit(runnable, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable)
|
||||
{
|
||||
return taskFactory.toSubmit(callable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxTasksQueued()
|
||||
{
|
||||
return getQueue().size();
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <T> Future<T> submit(WithResources withResources, Callable<T> task)
|
||||
{
|
||||
return executor.submit(withResources, task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Future<T> 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 <T> Future<T> submit(Callable<T> task)
|
||||
{
|
||||
return executor.submit(task);
|
||||
}
|
||||
|
||||
public <T> Future<T> 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 <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException
|
||||
{
|
||||
return executor.invokeAll(tasks);
|
||||
}
|
||||
|
||||
public <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException
|
||||
{
|
||||
return executor.invokeAll(tasks, timeout, unit);
|
||||
}
|
||||
|
||||
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException
|
||||
{
|
||||
return executor.invokeAny(tasks);
|
||||
}
|
||||
|
||||
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
return executor.invokeAny(tasks, timeout, unit);
|
||||
}
|
||||
|
||||
public void shutdown()
|
||||
{
|
||||
executor.shutdown();
|
||||
}
|
||||
|
||||
public List<Runnable> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ByteBuffer> parameters)
|
||||
|
|
|
|||
|
|
@ -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<CommitLogPosition> switchMemtableIfCurrent(Memtable memtable)
|
||||
public Future<CommitLogPosition> 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<CommitLogPosition> switchMemtable()
|
||||
public Future<CommitLogPosition> 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<CommitLogPosition> forceFlush()
|
||||
public Future<CommitLogPosition> 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<CommitLogPosition> waitForFlushes()
|
||||
private Future<CommitLogPosition> 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<CommitLogPosition> 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<CommitLogPosition>
|
||||
{
|
||||
final org.apache.cassandra.utils.concurrent.CountDownLatch latch = newCountDownLatch(1);
|
||||
final CountDownLatch latch = newCountDownLatch(1);
|
||||
final List<Memtable> memtables;
|
||||
volatile Throwable flushFailure = null;
|
||||
|
||||
|
|
@ -1009,7 +1010,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
{
|
||||
final OpOrder.Barrier writeBarrier;
|
||||
final List<Memtable> memtables = new ArrayList<>();
|
||||
final ListenableFutureTask<CommitLogPosition> postFlushTask;
|
||||
final FutureTask<CommitLogPosition> 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<Boolean> flushLargestMemtable()
|
||||
public static Future<Boolean> flushLargestMemtable()
|
||||
{
|
||||
float largestRatio = 0f;
|
||||
Memtable largest = null;
|
||||
|
|
@ -1285,7 +1286,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
liveOffHeap += offHeap;
|
||||
}
|
||||
|
||||
CompletableFuture<Boolean> returnFuture = new CompletableFuture<>();
|
||||
Promise<Boolean> 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<CommitLogPosition> flushFuture = largest.cfs.switchMemtableIfCurrent(largest);
|
||||
Future<CommitLogPosition> 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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -55,10 +55,7 @@ public class MutationVerbHandler implements IVerbHandler<Mutation>
|
|||
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<ListenableFuture<CommitLogPosition>> futures = new ArrayList<>();
|
||||
List<Future<CommitLogPosition>> futures = new ArrayList<>();
|
||||
|
||||
for (String cfname : cfnames)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<Thread> 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<Thread> 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<CommitLogSegment> 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<TableId, ListenableFuture<?>> flushes = new LinkedHashMap<>();
|
||||
final Map<TableId, Future<?>> 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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -514,7 +514,7 @@ public abstract class CommitLogSegment
|
|||
{
|
||||
WaitQueue.Signal signal = syncComplete.register();
|
||||
if (lastSyncedOffset < position)
|
||||
signal.awaitUninterruptibly();
|
||||
signal.awaitThrowUncheckedOnInterrupt();
|
||||
else
|
||||
signal.cancel();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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<ColumnFamilyStore> cfsRef;
|
||||
private final WeakReference<CompactionStrategyManager> 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<Object> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Boolean> isCompactionManager = new FastThreadLocal<Boolean>()
|
||||
{
|
||||
@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<SSTableReader> sstables,
|
||||
LifecycleTransaction txn,
|
||||
UUID sessionId,
|
||||
BooleanSupplier isCancelled)
|
||||
public Future<Void> submitPendingAntiCompaction(ColumnFamilyStore cfs,
|
||||
RangesAtEndpoint tokenRanges,
|
||||
Refs<SSTableReader> sstables,
|
||||
LifecycleTransaction txn,
|
||||
UUID sessionId,
|
||||
BooleanSupplier isCancelled)
|
||||
{
|
||||
Runnable runnable = new WrappedRunnable()
|
||||
{
|
||||
|
|
@ -737,7 +735,7 @@ public class CompactionManager implements CompactionManagerMBean
|
|||
}
|
||||
};
|
||||
|
||||
ListenableFuture<?> task = null;
|
||||
Future<Void> 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<Long> submitViewBuilder(final ViewBuilderTask task)
|
||||
public Future<Long> submitViewBuilder(final ViewBuilderTask task)
|
||||
{
|
||||
return submitViewBuilder(task, active);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ListenableFuture<Long> submitViewBuilder(final ViewBuilderTask task, ActiveCompactionsTracker activeCompactions)
|
||||
Future<Long> 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<Runnable> 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<Runnable>());
|
||||
}
|
||||
// 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<Void> 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 <T> ListenableFuture<T> submitIfRunning(Callable<T> task, String name)
|
||||
public <T> Future<T> submitIfRunning(Callable<T> task, String name)
|
||||
{
|
||||
if (isShutdown())
|
||||
{
|
||||
logger.info("Executor has been shut down, not submitting {}", name);
|
||||
return Futures.immediateCancelledFuture();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ListenableFutureTask<T> 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 <T> Future<T> submit(Callable<T> task)
|
||||
{
|
||||
return executor.submit(RESCHEDULE_FAILED, task);
|
||||
}
|
||||
|
||||
public <T> Future<T> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<ColumnFamilyStore> tables,
|
||||
RangesAtEndpoint tokenRanges,
|
||||
ExecutorService executor,
|
||||
BooleanSupplier isCancelled)
|
||||
public Future<List<Void>> prepareIncrementalRepair(UUID sessionID,
|
||||
Collection<ColumnFamilyStore> tables,
|
||||
RangesAtEndpoint tokenRanges,
|
||||
ExecutorService executor,
|
||||
BooleanSupplier isCancelled)
|
||||
{
|
||||
PendingAntiCompaction pac = new PendingAntiCompaction(sessionID, tables, tokenRanges, executor, isCancelled);
|
||||
return pac.run();
|
||||
|
|
|
|||
|
|
@ -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<List<AcquireResult>, Object>
|
||||
static class AcquisitionCallback implements Function<List<AcquireResult>, Future<List<Void>>>
|
||||
{
|
||||
private final UUID parentRepairSession;
|
||||
private final RangesAtEndpoint tokenRanges;
|
||||
|
|
@ -269,7 +269,7 @@ public class PendingAntiCompaction
|
|||
this.isCancelled = isCancelled;
|
||||
}
|
||||
|
||||
ListenableFuture<?> submitPendingAntiCompaction(AcquireResult result)
|
||||
Future<Void> 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<AcquireResult> results) throws Exception
|
||||
public Future<List<Void>> apply(List<AcquireResult> 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<ListenableFuture<?>> pendingAntiCompactions = new ArrayList<>(results.size());
|
||||
List<Future<Void>> pendingAntiCompactions = new ArrayList<>(results.size());
|
||||
for (AcquireResult result : results)
|
||||
{
|
||||
if (result.txn != null)
|
||||
{
|
||||
ListenableFuture<?> future = submitPendingAntiCompaction(result);
|
||||
Future<Void> 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<List<Void>> run()
|
||||
{
|
||||
List<ListenableFutureTask<AcquireResult>> tasks = new ArrayList<>(tables.size());
|
||||
List<FutureTask<AcquireResult>> tasks = new ArrayList<>(tables.size());
|
||||
for (ColumnFamilyStore cfs : tables)
|
||||
{
|
||||
cfs.forceBlockingFlush();
|
||||
ListenableFutureTask<AcquireResult> task = ListenableFutureTask.create(getAcquisitionCallable(cfs, tokenRanges.ranges(), prsId, acquireRetrySeconds, acquireSleepMillis));
|
||||
FutureTask<AcquireResult> task = new FutureTask<>(getAcquisitionCallable(cfs, tokenRanges.ranges(), prsId, acquireRetrySeconds, acquireSleepMillis));
|
||||
executor.submit(task);
|
||||
tasks.add(task);
|
||||
}
|
||||
ListenableFuture<List<AcquireResult>> acquisitionResults = Futures.successfulAsList(tasks);
|
||||
ListenableFuture compactionResult = Futures.transformAsync(acquisitionResults, getAcquisitionCallback(prsId, tokenRanges), MoreExecutors.directExecutor());
|
||||
return compactionResult;
|
||||
|
||||
Future<List<AcquireResult>> acquisitionResults = FutureCombiner.successfulOf(tasks);
|
||||
return acquisitionResults.andThenAsync(getAcquisitionCallback(prsId, tokenRanges));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
|
|
|||
|
|
@ -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<ViewBuilderTask> 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<ListenableFuture<Long>> 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<Future<Long>> 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<List<Long>> future = Futures.allAsList(futures);
|
||||
Futures.addCallback(future, new FutureCallback<List<Long>>()
|
||||
Future<List<Long>> future = FutureCombiner.allOf(futures);
|
||||
future.addCallback(new FutureCallback<List<Long>>()
|
||||
{
|
||||
public void onSuccess(List<Long> 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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<StreamState> bootstrap(StreamStateStore stateStore, boolean useStrictConsistency)
|
||||
public Future<StreamState> bootstrap(StreamStateStore stateStore, boolean useStrictConsistency)
|
||||
{
|
||||
logger.trace("Beginning bootstrap process");
|
||||
|
||||
|
|
|
|||
|
|
@ -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<String> 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
|
||||
|
|
|
|||
|
|
@ -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<Long
|
|||
logger.trace("partitioner is {}", partitioner);
|
||||
|
||||
// canonical ranges, split into pieces, fetching the splits in parallel
|
||||
ExecutorService executor = new ThreadPoolExecutor(0, 128, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
|
||||
ExecutorService executor = executorFactory().pooled("HadoopInput", 128);
|
||||
List<org.apache.hadoop.mapreduce.InputSplit> splits = new ArrayList<>();
|
||||
|
||||
String[] inputInitialAddress = ConfigHelper.getInputInitialAddress(conf).split(",");
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ public final class HintVerbHandler implements IVerbHandler<HintMessage>
|
|||
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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<InetAddressAndPort> isAlive;
|
||||
private final Map<UUID, Future> 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();
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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<String, AtomicInteger> 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<Void> 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<Void> 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<Object> 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<Object> 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<Index> indexers, Function<Index, Callable<?>> function, FutureCallback<Object> callback)
|
||||
private void executeAllBlocking(Stream<Index> indexers, Function<Index, Callable<?>> 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);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<Long, Token>
|
|||
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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Buffer> writeQueue = new SynchronousQueue<Buffer>();
|
||||
private final BlockingQueue<Buffer> writeQueue = newBlockingQueue(0);
|
||||
private final DiskWriter diskWriter = new DiskWriter();
|
||||
|
||||
SSTableSimpleUnsortedWriter(File directory, TableMetadataRef metadata, RegularAndStaticColumns columns, long bufferSizeInMB)
|
||||
|
|
|
|||
|
|
@ -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<SS
|
|||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(SSTableReader.class);
|
||||
|
||||
private static final ScheduledThreadPoolExecutor syncExecutor = initSyncExecutor();
|
||||
private static ScheduledThreadPoolExecutor initSyncExecutor()
|
||||
private static final ScheduledExecutorPlus syncExecutor = initSyncExecutor();
|
||||
private static ScheduledExecutorPlus initSyncExecutor()
|
||||
{
|
||||
if (DatabaseDescriptor.isClientOrToolInitialized())
|
||||
return null;
|
||||
|
||||
// Do NOT start this thread pool in client mode
|
||||
|
||||
ScheduledThreadPoolExecutor syncExecutor = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("read-hotness-tracker"));
|
||||
ScheduledExecutorPlus syncExecutor = executorFactory().scheduled("read-hotness-tracker");
|
||||
// Immediately remove readMeter sync task when cancelled.
|
||||
syncExecutor.setRemoveOnCancelPolicy(true);
|
||||
// TODO: should we set this by default on all scheduled executors?
|
||||
if (syncExecutor instanceof ScheduledThreadPoolExecutor)
|
||||
((ScheduledThreadPoolExecutor)syncExecutor).setRemoveOnCancelPolicy(true);
|
||||
return syncExecutor;
|
||||
}
|
||||
private static final RateLimiter meterSyncThrottle = RateLimiter.create(100.0);
|
||||
|
|
@ -525,37 +529,43 @@ public abstract class SSTableReader extends SSTable implements SelfRefCounted<SS
|
|||
{
|
||||
final Collection<SSTableReader> sstables = newBlockingQueue();
|
||||
|
||||
ExecutorService executor = DebuggableThreadPoolExecutor.createWithFixedPoolSize("SSTableBatchOpen", FBUtilities.getAvailableProcessors());
|
||||
for (final Map.Entry<Descriptor, Set<Component>> entry : entries)
|
||||
ExecutorPlus executor = executorFactory().pooled("SSTableBatchOpen", FBUtilities.getAvailableProcessors());
|
||||
try
|
||||
{
|
||||
Runnable runnable = new Runnable()
|
||||
for (final Map.Entry<Descriptor, Set<Component>> 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);
|
||||
|
|
|
|||
|
|
@ -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<Integer>()
|
||||
{
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<T>
|
||||
{
|
||||
public enum SamplerType
|
||||
|
|
@ -42,19 +43,12 @@ public abstract class Sampler<T>
|
|||
MonotonicClock clock = MonotonicClock.approxTime;
|
||||
|
||||
@VisibleForTesting
|
||||
static final ThreadPoolExecutor samplerExecutor = new JMXEnabledThreadPoolExecutor(1, 1,
|
||||
TimeUnit.SECONDS,
|
||||
new ArrayBlockingQueue<Runnable>(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<T>
|
|||
return "Sample [value=" + value + ", count=" + count + ", error=" + error + "]";
|
||||
}
|
||||
}
|
||||
|
||||
public static void shutdownNowAndWait(long time, TimeUnit units) throws InterruptedException, TimeoutException
|
||||
{
|
||||
ExecutorUtils.shutdownNowAndWait(time, units, samplerExecutor);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<Void> implements ChannelPromise
|
||||
public class AsyncChannelPromise extends AsyncPromise.WithExecutor<Void> implements ChannelPromise
|
||||
{
|
||||
private final Channel channel;
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Void>
|
||||
{
|
||||
private volatile boolean failed;
|
||||
|
||||
private volatile Throwable firstCause;
|
||||
private static final AtomicReferenceFieldUpdater<FutureCombiner, Throwable> firstCauseUpdater =
|
||||
AtomicReferenceFieldUpdater.newUpdater(FutureCombiner.class, Throwable.class, "firstCause");
|
||||
|
||||
private volatile int waitingOn;
|
||||
private static final AtomicIntegerFieldUpdater<FutureCombiner> waitingOnUpdater =
|
||||
AtomicIntegerFieldUpdater.newUpdater(FutureCombiner.class, "waitingOn");
|
||||
|
||||
FutureCombiner(Collection<? extends Future<?>> combine)
|
||||
{
|
||||
this(AsyncPromise.uncancellable(GlobalEventExecutor.INSTANCE), combine);
|
||||
}
|
||||
|
||||
private FutureCombiner(Promise<Void> combined, Collection<? extends Future<?>> combine)
|
||||
{
|
||||
super(combined);
|
||||
|
||||
if (0 == (waitingOn = combine.size()))
|
||||
combined.trySuccess(null);
|
||||
|
||||
GenericFutureListener<? extends Future<Object>> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ import io.netty.util.concurrent.GenericFutureListener;
|
|||
*
|
||||
* See {@link FutureCombiner} and {@link FutureResult}
|
||||
*/
|
||||
class FutureDelegate<V> implements Future<V>
|
||||
public class FutureDelegate<V> implements Future<V>
|
||||
{
|
||||
final Future<V> delegate;
|
||||
|
||||
|
|
@ -53,26 +53,6 @@ class FutureDelegate<V> implements Future<V>
|
|||
return delegate.cause();
|
||||
}
|
||||
|
||||
public Future<V> addListener(GenericFutureListener<? extends Future<? super V>> genericFutureListener)
|
||||
{
|
||||
return delegate.addListener(genericFutureListener);
|
||||
}
|
||||
|
||||
public Future<V> addListeners(GenericFutureListener<? extends Future<? super V>>... genericFutureListeners)
|
||||
{
|
||||
return delegate.addListeners(genericFutureListeners);
|
||||
}
|
||||
|
||||
public Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> genericFutureListener)
|
||||
{
|
||||
return delegate.removeListener(genericFutureListener);
|
||||
}
|
||||
|
||||
public Future<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... genericFutureListeners)
|
||||
{
|
||||
return delegate.removeListeners(genericFutureListeners);
|
||||
}
|
||||
|
||||
public Future<V> sync() throws InterruptedException
|
||||
{
|
||||
return delegate.sync();
|
||||
|
|
@ -142,4 +122,28 @@ class FutureDelegate<V> implements Future<V>
|
|||
{
|
||||
return delegate.get(timeout, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public io.netty.util.concurrent.Future<V> addListener(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> genericFutureListener)
|
||||
{
|
||||
return delegate.addListener(genericFutureListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public io.netty.util.concurrent.Future<V> addListeners(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>>... genericFutureListeners)
|
||||
{
|
||||
return delegate.addListeners(genericFutureListeners);
|
||||
}
|
||||
|
||||
@Override
|
||||
public io.netty.util.concurrent.Future<V> removeListener(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> genericFutureListener)
|
||||
{
|
||||
return delegate.removeListener(genericFutureListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public io.netty.util.concurrent.Future<V> removeListeners(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>>... genericFutureListeners)
|
||||
{
|
||||
return delegate.removeListeners(genericFutureListeners);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<V> extends FutureDelegate<V>
|
||||
public class FutureResult<V> extends FutureDelegate<V>
|
||||
{
|
||||
private final Future<?> tryCancel;
|
||||
|
||||
|
|
@ -32,7 +31,7 @@ class FutureResult<V> extends FutureDelegate<V>
|
|||
* @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<V> result, Future<?> cancel)
|
||||
public FutureResult(Future<V> result, Future<?> cancel)
|
||||
{
|
||||
super(result);
|
||||
this.tryCancel = cancel;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<Void> open()
|
||||
|
|
@ -233,7 +234,7 @@ class InboundSockets
|
|||
List<Future<Void>> 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<Future<Void>> closing = new ArrayList<>();
|
||||
for (InboundSocket address : sockets)
|
||||
closing.add(address.close(shutdownExecutors));
|
||||
return new FutureCombiner(closing);
|
||||
return FutureCombiner.nettySuccessListener(closing);
|
||||
}
|
||||
public Future<Void> close()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<ExecutorService> 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);
|
||||
|
|
|
|||
|
|
@ -1100,7 +1100,7 @@ public class OutboundConnection
|
|||
|
||||
if (hasPending())
|
||||
{
|
||||
Promise<Result<MessagingSuccess>> result = new AsyncPromise<>(eventLoop);
|
||||
Promise<Result<MessagingSuccess>> 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<Result<MessagingSuccess>> initiate()
|
||||
{
|
||||
Promise<Result<MessagingSuccess>> result = new AsyncPromise<>(eventLoop);
|
||||
Promise<Result<MessagingSuccess>> result = AsyncPromise.withExecutor(eventLoop);
|
||||
state = new Connecting(state.disconnected(), result);
|
||||
attempt(result);
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -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<SuccessType extends OutboundConnectionI
|
|||
*/
|
||||
public static Future<Result<StreamingSuccess>> initiateStreaming(EventLoop eventLoop, OutboundConnectionSettings settings, int requestMessagingVersion)
|
||||
{
|
||||
return new OutboundConnectionInitiator<StreamingSuccess>(STREAMING, settings, requestMessagingVersion, new AsyncPromise<>(eventLoop))
|
||||
return new OutboundConnectionInitiator<StreamingSuccess>(STREAMING, settings, requestMessagingVersion, AsyncPromise.withExecutor(eventLoop))
|
||||
.initiate(eventLoop);
|
||||
}
|
||||
|
||||
|
|
@ -134,13 +134,13 @@ public class OutboundConnectionInitiator<SuccessType extends OutboundConnectionI
|
|||
{
|
||||
// interrupt other connections, so they must attempt to re-authenticate
|
||||
MessagingService.instance().interruptOutbound(settings.to);
|
||||
return new FailedFuture<>(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<Void> bootstrap = createBootstrap(eventLoop)
|
||||
io.netty.util.concurrent.Future<Void> bootstrap = createBootstrap(eventLoop)
|
||||
.connect()
|
||||
.addListener(future -> {
|
||||
eventLoop.execute(() -> {
|
||||
|
|
|
|||
|
|
@ -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<Void> 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))
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<CallbackKey, CallbackInfo> callbacks = new ConcurrentHashMap<>();
|
||||
|
||||
RequestCallbacks(MessagingService messagingService)
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue