| /* |
| * 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.ignite.internal; |
| |
| import java.lang.management.ManagementFactory; |
| import java.lang.management.ThreadMXBean; |
| import java.lang.reflect.Field; |
| import java.util.Arrays; |
| import java.util.Set; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.concurrent.atomic.AtomicLong; |
| import java.util.stream.Collectors; |
| import org.apache.ignite.IgniteCache; |
| import org.apache.ignite.IgniteSystemProperties; |
| import org.apache.ignite.configuration.DataRegionConfiguration; |
| import org.apache.ignite.configuration.DataStorageConfiguration; |
| import org.apache.ignite.configuration.IgniteConfiguration; |
| import org.apache.ignite.internal.util.typedef.internal.U; |
| import org.apache.ignite.testframework.junits.GridAbstractTest; |
| import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; |
| import org.junit.ClassRule; |
| import org.junit.Test; |
| import org.junit.rules.RuleChain; |
| import org.junit.rules.TestRule; |
| import org.junit.runners.model.Statement; |
| |
| /** |
| * Check threads for default names in single and thread pool instances. |
| * Actually, this checks may be moved to the base test, but we already have: |
| * 1) A lot of tests with default Threads/ThreadPools |
| * 2) Part of functionality uses integrations, which may creates Threads/ThreadPools without name specification. |
| */ |
| public class ThreadNameValidationTest extends GridCommonAbstractTest { |
| |
| /** {@link Executors.DefaultThreadFactory} count before test. */ |
| private static transient int defaultThreadFactoryCountBeforeTest; |
| |
| /** {@link Thread#threadInitNumber} count before test. */ |
| private static transient int anonymousThreadCountBeforeTest; |
| |
| /** Sequence for sets objects. */ |
| private static final transient AtomicLong SEQUENCE = new AtomicLong(); |
| |
| /** */ |
| private static final TestRule beforeAllTestRule = (base, description) -> new Statement() { |
| @Override public void evaluate() throws Throwable { |
| defaultThreadFactoryCountBeforeTest = getDefaultPoolCount(); |
| base.evaluate(); |
| } |
| }; |
| |
| /** Manages before first test execution. */ |
| @ClassRule public static transient RuleChain firstLastTestRule |
| = RuleChain.outerRule(beforeAllTestRule).around(GridAbstractTest.firstLastTestRule); |
| |
| /** */ |
| private final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); |
| |
| /** */ |
| private Set<Long> externalAnonymousThreads; |
| |
| /** {@inheritDoc} */ |
| @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { |
| |
| IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); |
| |
| cfg.setDataStorageConfiguration( |
| new DataStorageConfiguration() |
| .setDefaultDataRegionConfiguration( |
| new DataRegionConfiguration() |
| .setPersistenceEnabled(true) |
| ) |
| ); |
| |
| return cfg; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override protected void beforeTest() throws Exception { |
| cleanPersistenceDir(); |
| |
| System.setProperty(IgniteSystemProperties.IGNITE_USE_ASYNC_FILE_IO_FACTORY, "false"); |
| |
| // MBean used LogManager with anonymous shutdown hook Thread, |
| // init here if required for same behavior in runs in suite and test only |
| if (!U.IGNITE_MBEANS_DISABLED) { |
| ManagementFactory.getPlatformMBeanServer(); |
| } |
| |
| anonymousThreadCountBeforeTest = getAnonymousThreadCount(); |
| |
| super.beforeTest(); |
| externalAnonymousThreads = Arrays |
| .stream(threadMXBean.dumpAllThreads(false, false)) |
| .filter(t -> t.getThreadName().startsWith("Thread-")) |
| .map(thread -> thread.getThreadId()) |
| .collect(Collectors.toSet()); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override protected void afterTest() throws Exception { |
| try { |
| assertEquals("Executors.DefaultThreadFactory usage detected, IgniteThreadPoolExecutor is preferred", |
| defaultThreadFactoryCountBeforeTest, getDefaultPoolCount()); |
| |
| assertEquals("Thread without specific name detected", |
| anonymousThreadCountBeforeTest, getAnonymousThreadCount()); |
| |
| } finally { |
| System.clearProperty(IgniteSystemProperties.IGNITE_USE_ASYNC_FILE_IO_FACTORY); |
| |
| super.afterTest(); |
| |
| stopAllGrids(); |
| |
| cleanPersistenceDir(); |
| } |
| } |
| |
| /** |
| * @throws Exception If failed. |
| */ |
| @Test |
| public void testThreadsWithDefaultNames() throws Exception { |
| validateThreadNames(); |
| |
| IgniteEx ignite = startGrids(1); |
| ignite.active(true); |
| |
| IgniteCache<Object, Object> cache = ignite.createCache(DEFAULT_CACHE_NAME); |
| |
| final int ENTRY_CNT = 10; |
| |
| for (int i = 0; i < ENTRY_CNT; i++) |
| cache.put(i, userObject("user-" + i)); |
| |
| validateThreadNames(); |
| |
| cache.removeAll(); |
| |
| validateThreadNames(); |
| } |
| |
| /** |
| * @param userName User name. |
| * @return Binary object. |
| */ |
| private UserEntry userObject(String userName) { |
| return new UserEntry(SEQUENCE.getAndIncrement(), userName); |
| } |
| |
| /** |
| * Validates current existed thread names. |
| */ |
| private void validateThreadNames() { |
| Arrays.stream(threadMXBean.dumpAllThreads(false, false)) |
| .filter(t -> t.getThreadName().startsWith("Thread-") |
| && !externalAnonymousThreads.contains(t.getThreadId())) |
| .forEach(threadInfo -> |
| { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("Thread with default name detected. StackTrace: "); |
| |
| for (StackTraceElement element : threadInfo.getStackTrace()) { |
| sb.append(System.lineSeparator()) |
| .append(element.toString()); |
| } |
| |
| fail(sb.toString()); |
| }); |
| } |
| |
| /** |
| * Gets pools count with {@link Executors.DefaultThreadFactory}. |
| * @return count |
| */ |
| private static int getDefaultPoolCount() throws ReflectiveOperationException { |
| Class<?> defaultThreadFacktory = Class.forName("java.util.concurrent.Executors$DefaultThreadFactory"); |
| Field poolNumber = defaultThreadFacktory.getDeclaredField("poolNumber"); |
| poolNumber.setAccessible(true); |
| AtomicInteger counter = (AtomicInteger)poolNumber.get(null); |
| return counter.get(); |
| } |
| |
| /** |
| * Gets anonymous threads count since JVM start. |
| * @return count |
| */ |
| private static int getAnonymousThreadCount() throws ReflectiveOperationException { |
| Field threadInitNumberField = Thread.class.getDeclaredField("threadInitNumber"); |
| threadInitNumberField.setAccessible(true); |
| return threadInitNumberField.getInt(null); |
| } |
| |
| /** Entity for tests. */ |
| private static class UserEntry { |
| |
| /** Id. */ |
| long id; |
| |
| /** Name. */ |
| String name; |
| |
| /** |
| * Constructor. |
| * |
| * @param id user ID |
| * @param name user name |
| */ |
| public UserEntry(Long id, String name) { |
| this.id = id; |
| this.name = name; |
| } |
| } |
| } |