/* * Copyright 2016 The Netty Project * * The Netty Project 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 io.netty.util.concurrent; import org.junit.Test; import java.security.Permission; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; public class DefaultThreadFactoryTest { @Test(timeout = 2000) public void testDescendantThreadGroups() throws InterruptedException { final SecurityManager current = System.getSecurityManager(); try { // install security manager that only allows parent thread groups to mess with descendant thread groups System.setSecurityManager(new SecurityManager() { @Override public void checkAccess(ThreadGroup g) { final ThreadGroup source = Thread.currentThread().getThreadGroup(); if (source != null) { if (!source.parentOf(g)) { throw new SecurityException("source group is not an ancestor of the target group"); } super.checkAccess(g); } } // so we can restore the security manager at the end of the test @Override public void checkPermission(Permission perm) { } }); // holder for the thread factory, plays the role of a global singleton final AtomicReference<DefaultThreadFactory> factory = new AtomicReference<DefaultThreadFactory>(); final AtomicInteger counter = new AtomicInteger(); final Runnable task = new Runnable() { @Override public void run() { counter.incrementAndGet(); } }; final AtomicReference<Throwable> interrupted = new AtomicReference<Throwable>(); // create the thread factory, since we are running the thread group brother, the thread // factory will now forever be tied to that group // we then create a thread from the factory to run a "task" for us final Thread first = new Thread(new ThreadGroup("brother"), new Runnable() { @Override public void run() { factory.set(new DefaultThreadFactory("test", false, Thread.NORM_PRIORITY, null)); final Thread t = factory.get().newThread(task); t.start(); try { t.join(); } catch (InterruptedException e) { interrupted.set(e); Thread.currentThread().interrupt(); } } }); first.start(); first.join(); assertNull(interrupted.get()); // now we will use factory again, this time from a sibling thread group sister // if DefaultThreadFactory is "sticky" about thread groups, a security manager // that forbids sibling thread groups from messing with each other will strike this down final Thread second = new Thread(new ThreadGroup("sister"), new Runnable() { @Override public void run() { final Thread t = factory.get().newThread(task); t.start(); try { t.join(); } catch (InterruptedException e) { interrupted.set(e); Thread.currentThread().interrupt(); } } }); second.start(); second.join(); assertNull(interrupted.get()); assertEquals(2, counter.get()); } finally { System.setSecurityManager(current); } } // test that when DefaultThreadFactory is constructed with a sticky thread group, threads // created by it have the sticky thread group @Test(timeout = 2000) public void testDefaultThreadFactoryStickyThreadGroupConstructor() throws InterruptedException { final ThreadGroup sticky = new ThreadGroup("sticky"); runStickyThreadGroupTest( new Callable<DefaultThreadFactory>() { @Override public DefaultThreadFactory call() throws Exception { return new DefaultThreadFactory("test", false, Thread.NORM_PRIORITY, sticky); } }, sticky); } // test that when DefaultThreadFactory is constructed it is sticky to the thread group from the thread group of the // thread that created it @Test(timeout = 2000) public void testDefaultThreadFactoryInheritsThreadGroup() throws InterruptedException { final ThreadGroup sticky = new ThreadGroup("sticky"); runStickyThreadGroupTest( new Callable<DefaultThreadFactory>() { @Override public DefaultThreadFactory call() throws Exception { final AtomicReference<DefaultThreadFactory> factory = new AtomicReference<DefaultThreadFactory>(); final Thread thread = new Thread(sticky, new Runnable() { @Override public void run() { factory.set(new DefaultThreadFactory("test")); } }); thread.start(); thread.join(); return factory.get(); } }, sticky); } // test that when a security manager is installed that provides a ThreadGroup, DefaultThreadFactory inherits from // the security manager @Test(timeout = 2000) public void testDefaultThreadFactoryInheritsThreadGroupFromSecurityManager() throws InterruptedException { final SecurityManager current = System.getSecurityManager(); try { final ThreadGroup sticky = new ThreadGroup("sticky"); System.setSecurityManager(new SecurityManager() { @Override public ThreadGroup getThreadGroup() { return sticky; } // so we can restore the security manager at the end of the test @Override public void checkPermission(Permission perm) { } }); runStickyThreadGroupTest( new Callable<DefaultThreadFactory>() { @Override public DefaultThreadFactory call() throws Exception { return new DefaultThreadFactory("test"); } }, sticky); } finally { System.setSecurityManager(current); } } private void runStickyThreadGroupTest( final Callable<DefaultThreadFactory> callable, final ThreadGroup expected) throws InterruptedException { final AtomicReference<ThreadGroup> captured = new AtomicReference<ThreadGroup>(); final AtomicReference<Throwable> exception = new AtomicReference<Throwable>(); final Thread first = new Thread(new ThreadGroup("wrong"), new Runnable() { @Override public void run() { final DefaultThreadFactory factory; try { factory = callable.call(); } catch (Exception e) { exception.set(e); throw new RuntimeException(e); } final Thread t = factory.newThread(new Runnable() { @Override public void run() { } }); captured.set(t.getThreadGroup()); } }); first.start(); first.join(); assertNull(exception.get()); assertEquals(expected, captured.get()); } // test that when DefaultThreadFactory is constructed without a sticky thread group, threads // created by it inherit the correct thread group @Test(timeout = 2000) public void testDefaultThreadFactoryNonStickyThreadGroupConstructor() throws InterruptedException { final AtomicReference<DefaultThreadFactory> factory = new AtomicReference<DefaultThreadFactory>(); final AtomicReference<ThreadGroup> firstCaptured = new AtomicReference<ThreadGroup>(); final ThreadGroup firstGroup = new ThreadGroup("first"); final Thread first = new Thread(firstGroup, new Runnable() { @Override public void run() { factory.set(new DefaultThreadFactory("sticky", false, Thread.NORM_PRIORITY, null)); final Thread t = factory.get().newThread(new Runnable() { @Override public void run() { } }); firstCaptured.set(t.getThreadGroup()); } }); first.start(); first.join(); assertEquals(firstGroup, firstCaptured.get()); final AtomicReference<ThreadGroup> secondCaptured = new AtomicReference<ThreadGroup>(); final ThreadGroup secondGroup = new ThreadGroup("second"); final Thread second = new Thread(secondGroup, new Runnable() { @Override public void run() { final Thread t = factory.get().newThread(new Runnable() { @Override public void run() { } }); secondCaptured.set(t.getThreadGroup()); } }); second.start(); second.join(); assertEquals(secondGroup, secondCaptured.get()); } }