/* * 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.testframework; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicLong; import org.apache.ignite.IgniteLogger; import org.apache.ignite.internal.IgniteInterruptedCheckedException; import org.apache.ignite.internal.util.GridBusyLock; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.P1; import org.apache.ignite.internal.util.typedef.internal.U; /** * Threads factory for safe test-threads management. */ public final class GridTestSafeThreadFactory implements ThreadFactory { /** Collection to hold all started threads across the JVM. */ private static final BlockingQueue<Thread> startedThreads = new LinkedBlockingQueue<>(); /* Lock protection of the started across the JVM threads collection. */ private static final GridBusyLock startedThreadsLock = new GridBusyLock(); /** Threads name prefix. */ private final String threadName; /** Flag to interrupt all factory threads if any thread fails with unexpected exception. */ private final boolean interruptAll; /** Created threads counter. */ private final AtomicLong cnt = new AtomicLong(); /** Collection of ALL created threads in this factory. */ private final Collection<GridTestThread> threads = new ArrayList<>(); /** The first thrown error during threads from this factory execution. */ private final BlockingQueue<Throwable> errors = new LinkedBlockingQueue<>(); /** * Constructs threads factory for safe test-threads management. * * @param threadName threads name prefix. */ public GridTestSafeThreadFactory(String threadName) { this(threadName, true); } /** * Constructs threads factory for safe test-threads management. * * @param threadName Threads name prefix. * @param interruptAll Interrupt all threads in factory if any thread fails with unexpected exception. */ public GridTestSafeThreadFactory(String threadName, boolean interruptAll) { this.threadName = threadName; this.interruptAll = interruptAll; } /** * Create new thread around callable task. * * @param c Callable task to execute in the thread. * @return New thread around callable task. * @see GridTestThread */ public Thread newThread(final Callable<?> c) { // Create new thread around the task. GridTestThread thread = new GridTestThread(c, threadName + '-' + cnt.incrementAndGet()) { @Override protected void onError(Throwable err) { // Save the exception. errors.add(err); // Interrupt execution of all other threads in this factory. if (interruptAll) for (Thread t : threads) t.interrupt(); } @Override protected void onFinished() { super.onFinished(); // No need to acquire lock here since it is a concurrent collection. startedThreads.remove(this); } }; // Add this thread into the collection of managed threads. startedThreadsLock.enterBusy(); try { startedThreads.add(thread); } finally { startedThreadsLock.leaveBusy(); } // Register new thread in this factory. threads.add(thread); return thread; } /** * Create new thread around runnable task. * * @param r Runnable task to execute in the thread. * @return New thread around runnable task. * @see GridTestThread */ @Override public Thread newThread(final Runnable r) { return newThread(GridTestUtils.makeCallable(r, null)); } /** * Check and throws an exception if happens during this factory threads execution. * * @throws Exception If there is error. */ public void checkError() throws Exception { Throwable err = errors.peek(); if (err != null) { if (err instanceof Error) throw (Error)err; throw (Exception)err; } for (GridTestThread thread : threads) { thread.checkError(); } } /** * Interrupts all threads, created by this thread factory. */ public void interruptAllThreads() { for (Thread t : threads) U.interrupt(t); try { for (Thread t : threads) U.join(t); } catch (IgniteInterruptedCheckedException ignored) { // No-op. } } /** * Interrupts and waits for termination of all the threads started * so far by current test. * * @param log Logger. */ static void stopAllThreads(IgniteLogger log) { startedThreadsLock.block(); List<Thread> all; try { all = new ArrayList<>(startedThreads.size()); startedThreads.drainTo(all); } finally { startedThreadsLock.unblock(); } boolean aliveThreads = F.forAny( all, new P1<Thread>() { @Override public boolean apply(Thread t) { return t.isAlive(); } } ); if (!aliveThreads) return; U.warn(log, "Interrupting threads started so far: " + all.size()); U.interrupt(all); U.joinThreads(all, log); Iterator<Thread> it = all.iterator(); for (Thread thread = it.next(); it.hasNext(); thread = it.next()) if (!thread.isAlive()) it.remove(); if (all.isEmpty()) U.warn(log, "Finished interrupting threads."); else U.error(log, "Finished interrupting threads, but some threads are still alive" + " [size=" + all.size() + ", threads=" + all + "]"); } }