/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2009-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.internal;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.atomic.AtomicInteger;
import org.geotoolkit.lang.Debug;
import org.apache.sis.util.logging.Logging;
/**
* Utilities methods for threads. This class declares in a single place every {@link ThreadGroup}
* used in Geotk. Their purpose is only to put a little bit of order in debugger informations, by
* grouping the threads created by Geotk together under the same parent tree node.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.19
*
* @since 3.03
* @module
*/
@SuppressWarnings("serial")
public final class Threads extends AtomicInteger implements ThreadFactory, RejectedExecutionHandler {
/**
* The parent of every threads declared in this class. This parent will be declared as close
* as possible to the root of all thread groups (i.e. not as an application thread subgroup).
*/
public static final ThreadGroup GEOTOOLKIT;
static {
/*
* Tries to put the ThreadGroup at the root, if we are allowed to do so. The intend is to
* separate the Geotk thread groups from the user application thread groups. Without this
* code, the Geotk thread group would appear as an user application sub-group.
*/
ThreadGroup parent = Thread.currentThread().getThreadGroup();
try {
ThreadGroup candidate;
while ((candidate = parent.getParent()) != null) {
parent = candidate;
}
} catch (SecurityException e) {
// If we are not allowed to get the parent, stop there.
// We went up in the tree as much as we were allowed to.
}
GEOTOOLKIT = new ThreadGroup(parent, "Geotoolkit.org");
}
/**
* The group of threads disposing resources, typically after a timeout.
*/
public static final ThreadGroup RESOURCE_DISPOSERS = new ThreadGroup(GEOTOOLKIT, "ResourceDisposers");
static {
RESOURCE_DISPOSERS.setMaxPriority(Thread.NORM_PRIORITY + 2);
}
/**
* The group of workers to run in a background thread.
*
* @since 3.05
*/
public static final ThreadGroup WORKERS = new ThreadGroup(GEOTOOLKIT, "Workers");
/**
* The executor for non-disposal works. This executor is suitable for small tasks
* that complete relatively rapidly. We don't need too many threads, because the
* amount of tasks to submit is not that high.
* <p>
* Current implementation uses 2 threads and enqueue any additional tasks arriving
* faster than what the 2 threads can process. If 1000 tasks have been enqueued,
* then we will start creating more threads. If the maximal number of threads have
* been reached, then any additional tasks will be blocked until a slot is made
* available.
* <p>
* The optimal number of threads for CPU-bounds tasks is typically <va>n</var>+1
* where <va>n</var> is the number of processors. Here, we limit to <va>n</var>
* threads since the caller thread is the "+1".
*/
private static final ExecutorService WORK_EXECUTOR;
static {
final Threads handlers = new Threads(false, true, "Pooled thread #");
final int n = Math.max(2, Runtime.getRuntime().availableProcessors());
final ThreadPoolExecutor ex = new ThreadPoolExecutor(2, n, 5L, TimeUnit.MINUTES,
new LinkedBlockingQueue<Runnable>(1000), handlers, handlers);
ex.allowCoreThreadTimeOut(true);
WORK_EXECUTOR = ex;
}
/**
* The executor for disposal tasks. The tasks submitted to this executor should be only
* house-keeping works. The threads in this executor have a priority slightly higher than
* the normal priority. Their execution should complete quickly.
* <p>
* A single thread is sufficient since there is not so many tasks to submit, and they
* are expected to complete quickly. Because this executor acts as a fixed-size pool,
* we don't want to have too many threads spending 99% of their time idle.
*/
private static final ScheduledExecutorService DISPOSAL_EXECUTOR =
Executors.newScheduledThreadPool(1, new Threads(true, true, "Disposer thread #"));
/**
* {@code true} if the threads to be created are part of the {@link #RESOURCE_DISPOSERS}
* group, or {@code false} if they are {@link #WORKERS} threads. This information is used
* mostly for reporting in debugger - it has no functional impact (except on the threads
* priority).
*/
private final boolean disposer;
/**
* {@code true} if the threads to be created should be daemon threads.
*/
private final boolean daemon;
/**
* The prefix to put at the beginning of thread names.
*/
private final String prefix;
/**
* For internal usage only.
*/
private Threads(final boolean disposer, final boolean daemon, final String prefix) {
this.disposer = disposer;
this.daemon = daemon;
this.prefix = prefix;
}
/**
* Creates a factory for worker threads created by the Geotk library. This factory should be
* used by every method that use {@link java.util.concurrent.Executors} directly rather than
* the {@code execute} methods provided in this class.
*
* @param prefix The prefix to put in front of thread names.
* @return The thread factory.
*
* @since 3.17
*/
public static ThreadFactory createThreadFactory(final String prefix) {
return new Threads(false, false, prefix);
}
/**
* Executes the given task in a worker thread.
*
* @param task The work to execute.
*
* @since 3.19
*/
public static void executeWork(final Runnable task) {
WORK_EXECUTOR.execute(task);
}
/**
* Executes the given task in a worker thread.
*
* @param <T>
* @param task The work to execute.
* @return Futur result of the Callable
*
* @since 4.0.0
*/
public static <T> Future<T> submitWork(final Callable<T> task) {
return WORK_EXECUTOR.submit(task);
}
/**
* Executes the given task in a disposer thread after the given delay. The task
* is executed in a thread from the {@link #RESOURCE_DISPOSERS} group. They have
* a higher priority than the worker group and may be available even if the worker
* group is full.
*
* @param task The task to execute.
* @param delay The delay to wait before to execute the task, in milliseconds.
* May be zero for immediate execution.
*
* @since 3.19
*/
public static void executeDisposal(final Runnable task, final long delay) {
DISPOSAL_EXECUTOR.schedule(task, delay, TimeUnit.MILLISECONDS);
}
/**
* Invoked when a new thread needs to be created. This method is public as an
* implementation side-effect and should not be invoked directly.
*
* @param task The task to execute.
* @return A new thread running the given task.
*/
@Override
public Thread newThread(final Runnable task) {
final String name = prefix + incrementAndGet();
final Thread thread = new Thread(disposer ? RESOURCE_DISPOSERS : WORKERS, task, name);
thread.setPriority(Thread.NORM_PRIORITY + 1); // WORKERS group will lower this value.
thread.setDaemon(daemon);
return thread;
}
/**
* Invoked when a task can not be accepted because the queue is full and the maximal number
* of threads have been reached. This method blocks until a slot is made available.
*
* @param task The task to execute.
* @param executor The executor that invoked this method.
*/
@Override
public void rejectedExecution(final Runnable task, final ThreadPoolExecutor executor) {
try {
executor.getQueue().put(task);
} catch (InterruptedException e) {
throw new RejectedExecutionException(e);
}
}
/**
* Ensures that the shutdown hook is registered. The shutdown process is implemented in the
* {@link org.geotoolkit.factory.ShutdownHook#run()} method. This method should be invoked
* once by classes that require the service provided in the shutdown hook.
*
* @since 3.16
*/
static {
try {
Class.forName("org.geotoolkit.factory.ShutdownHook", true, Threads.class.getClassLoader());
} catch (Exception e) {
Logging.unexpectedException(null, Threads.class, "<init>", e);
}
}
/**
* Shutdowns the executors and wait for the non-daemon threads to complete.
* This method should be invoked only when we think that no more tasks are
* going to be submitted to the executor (it is actually hard to ensure that).
*
* @see org.geotoolkit.factory.ShutdownHook#run()
*
* @since 3.06
*/
public static synchronized void shutdown() {
/*
* We can wait for the work executors to terminate, because its tasks are
* executed immediately. However we can't wait for delayed tasks, because
* the delay may be long. Executes those tasks now in this current thread.
* We execute them in the same order than they would be executed if the
* delay were honored.
*/
WORK_EXECUTOR.shutdown();
DISPOSAL_EXECUTOR.shutdown();
final ThreadPoolExecutor ex = (ThreadPoolExecutor) DISPOSAL_EXECUTOR;
for (final Runnable task : ex.getQueue()) {
if (ex.remove(task)) try {
task.run();
} catch (Exception e) {
// Too late for logging since we are in process of shuting down.
System.err.println(e);
}
}
try {
// Wait for work completion. In theory, there is no disposal
// completion to wait for, but we check anyway as a safety.
if (!WORK_EXECUTOR .awaitTermination(8, TimeUnit.SECONDS) ||
!DISPOSAL_EXECUTOR.awaitTermination(2, TimeUnit.SECONDS))
{
// Check again in case one executor finished while we waited for the other.
if (!WORK_EXECUTOR.isTerminated() || !DISPOSAL_EXECUTOR.isTerminated()) {
// We can't use java.util.logging at this point since we are shutting down.
System.err.println("NOTE: Some background threads didn't completed.");
}
}
} catch (InterruptedException e) {
// Too late for logging since we are in process of shuting down.
System.err.println(e);
}
}
/**
* Returns every threads that are not {@linkplain Thread#isDaemon() daemon}. This method is
* used only for debugging purpose in order to identify the threads which may be preventing
* an application to quit.
*
* @return All non-daemon threads found.
*
* @since 3.17
*/
@Debug
public static Thread[] getNonDaemonThreads() {
ThreadGroup root = Thread.currentThread().getThreadGroup();
for (ThreadGroup parent; (parent = root.getParent()) != null;) {
root = parent;
}
Thread[] threads;
int n = root.activeCount();
do {
threads = new Thread[n << 1];
n = root.enumerate(threads);
} while (n == threads.length);
/*
* Filter the threads, keeping only the non-daemon ones.
*/
int nc = 0;
for (int i=0; i<n; i++) {
final Thread thread = threads[i];
if (!thread.isDaemon()) {
threads[nc++] = thread;
}
}
return Arrays.copyOf(threads, nc);
}
}