/* * Copyright (C) 2012-2015 DataStax Inc. * * Licensed 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 com.datastax.driver.core; import com.datastax.driver.core.policies.ConstantReconnectionPolicy; import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.netty.util.concurrent.DefaultThreadFactory; import org.assertj.core.api.iterable.Extractor; import org.mockito.Mockito; import org.testng.annotations.Test; import java.util.Set; import java.util.concurrent.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.verify; import static org.scassandra.http.client.ClosedConnectionReport.CloseType.CLOSE; public class ThreadingOptionsTest extends ScassandraTestBase { private String customPrefix = "custom"; private ThreadingOptions threadingOptions = new ThreadingOptions() { @Override public ThreadFactory createThreadFactory(String clusterName, String executorName) { return new ThreadFactoryBuilder() .setNameFormat(clusterName + "-" + customPrefix + "-" + executorName + "-%d") // Back with Netty's thread factory in order to create FastThreadLocalThread instances. This allows // an optimization around ThreadLocals (we could use DefaultThreadFactory directly but it creates // slightly different thread names, so keep we keep a ThreadFactoryBuilder wrapper for backward // compatibility). .setThreadFactory(new DefaultThreadFactory("ignored name")) .setDaemon(true) .build(); } @Override public ExecutorService createExecutor(String clusterName) { return new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), createThreadFactory(clusterName, "myExecutor") ); } @Override public ExecutorService createBlockingExecutor(String clusterName) { return new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), createThreadFactory(clusterName, "myBlockingExecutor") ); } @Override public ScheduledExecutorService createReconnectionExecutor(String clusterName) { return new ScheduledThreadPoolExecutor(1, createThreadFactory(clusterName, "myReconnection")); } @Override public ScheduledExecutorService createScheduledTasksExecutor(String clusterName) { return new ScheduledThreadPoolExecutor(1, createThreadFactory(clusterName, "myScheduled-task-worker")); } @Override public ScheduledExecutorService createReaperExecutor(String clusterName) { return new ScheduledThreadPoolExecutor(1, createThreadFactory(clusterName, "myConnection-reaper")); } }; /** * Validates that when using a provided {@link ThreadingOptions} that its methods are used for creating * executors and that its {@link ThreadingOptions#createThreadFactory(String, String)} is used for initializing * netty resources. * * @test_category configuration */ @Test(groups = "short") public void should_use_provided_threading_options() { ThreadingOptions spy = Mockito.spy(threadingOptions); Cluster cluster = createClusterBuilder().withPoolingOptions(new PoolingOptions() .setConnectionsPerHost(HostDistance.LOCAL, 1, 1)) .withReconnectionPolicy(new ConstantReconnectionPolicy(100)) .withThreadingOptions(spy).build(); try { String clusterName = cluster.getClusterName(); cluster.init(); // Ensure each method was invoked appropriately: // 1) 1 time for each create*Executor. // 2) createThreadFactory for netty executor group and timeouter. verify(spy).createExecutor(clusterName); verify(spy).createBlockingExecutor(clusterName); verify(spy).createReconnectionExecutor(clusterName); verify(spy).createScheduledTasksExecutor(clusterName); verify(spy).createReaperExecutor(clusterName); verify(spy).createThreadFactory(clusterName, "nio-worker"); verify(spy).createThreadFactory(clusterName, "timeouter"); cluster.connect(); // Close all connections bringing the host down, this should cause some activity on // executor and reconnection executor. currentClient.disableListener(); currentClient.closeConnections(CLOSE); TestUtils.waitForDown(TestUtils.IP_PREFIX + "1", cluster); currentClient.enableListener(); TestUtils.waitForUp(TestUtils.IP_PREFIX + "1", cluster); Set<Thread> threads = Thread.getAllStackTraces().keySet(); for(Thread thread : threads) { // all threads should use the custom factory and thus be marked daemon if(thread.getName().startsWith(clusterName + "-" + customPrefix)) { // all created threads should be daemon this should indicate that our custom thread factory was // used. assertThat(thread.isDaemon()).isTrue(); } } final Pattern threadNamePattern = Pattern.compile(clusterName + "-" + customPrefix + "-(.*)-0"); // Custom executor threads should be present. // NOTE: we don't validate blocking executor since it is hard to deterministically cause it to be used. assertThat(threads).extracting(new Extractor<Thread, String>() { @Override public String extract(Thread thread) { Matcher matcher = threadNamePattern.matcher(thread.getName()); if(matcher.matches()) { return matcher.group(1); } else { return thread.getName(); } } }).contains( "nio-worker", "timeouter", "myExecutor", "myReconnection", "myScheduled-task-worker", "myConnection-reaper" ); } finally { cluster.close(); } } }