2014-05-30 18:43:40 +09:00
|
|
|
/*
|
|
|
|
* Copyright 2014 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;
|
|
|
|
|
|
|
|
import io.netty.util.concurrent.DefaultThreadFactory;
|
2015-10-30 10:59:33 +01:00
|
|
|
import io.netty.util.internal.StringUtil;
|
|
|
|
import io.netty.util.internal.SystemPropertyUtil;
|
2014-05-30 18:43:40 +09:00
|
|
|
import io.netty.util.internal.logging.InternalLogger;
|
|
|
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
|
|
|
|
2018-01-24 14:06:19 -07:00
|
|
|
import java.security.AccessController;
|
|
|
|
import java.security.PrivilegedAction;
|
2014-05-30 18:43:40 +09:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Queue;
|
2016-12-20 15:15:57 +01:00
|
|
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
2014-05-30 18:43:40 +09:00
|
|
|
import java.util.concurrent.ThreadFactory;
|
2014-06-02 19:23:50 +09:00
|
|
|
import java.util.concurrent.TimeUnit;
|
2014-05-30 18:43:40 +09:00
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if a thread is alive periodically and runs a task when a thread dies.
|
|
|
|
* <p>
|
|
|
|
* This thread starts a daemon thread to check the state of the threads being watched and to invoke their
|
|
|
|
* associated {@link Runnable}s. When there is no thread to watch (i.e. all threads are dead), the daemon thread
|
|
|
|
* will terminate itself, and a new daemon thread will be started again when a new watch is added.
|
|
|
|
* </p>
|
2017-12-01 16:37:30 +01:00
|
|
|
*
|
|
|
|
* @deprecated will be removed in the next major release
|
2014-05-30 18:43:40 +09:00
|
|
|
*/
|
2017-12-01 16:37:30 +01:00
|
|
|
@Deprecated
|
2014-05-30 18:43:40 +09:00
|
|
|
public final class ThreadDeathWatcher {
|
|
|
|
|
|
|
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ThreadDeathWatcher.class);
|
Non-sticky thread groups in DefaultThreadFactory
Motivation:
A recent change to DefaultThreadFactory modified it so that it is sticky
with respect to thread groups. In particular, this change made it so
that DefaultThreadFactory would hold on to the thread group that created
it, and then use that thread group to create threads.
This can have problematic semantics since it can lead to violations of a
tenet of thread groups that a thread can only modify threads in its own
thread group and descendant thread groups. With a sticky thread group, a
thread triggering the creation of a new thread via
DefaultThreadFactory#newThread will be modifying a thread from the
sticky thread group which will not necessarily be its own nor a
descendant thread group. When a security manager is in place that
enforces this requirement, these modifications are now impossible. This
is especially problematic in the context of Netty because certain global
singletons like GlobalEventExecutor will create a
DefaultThreadFactory. If all DefaultThreadFactory instances are sticky
about their thread groups, it means that submitting tasks to the
GlobalEventExecutor singleton can cause a thread to be created from the
DefaultThreadFactory sticky thread group, exactly the problem with
DefaultThreadFactory being sticky about thread groups. A similar problem
arises from the ThreadDeathWatcher.
Modifications:
This commit modifies DefaultThreadFactory so that a null thread group
can be set with the behavior that all threads created by such an
instance will inherit the default thread group (the thread group
provided by the security manager if there is one, otherwise the thread
group of the creating thread). The construction of the instances of
DefaultThreadFactory used by the GlobalEventExecutor singleton and
ThreadDeathWatcher are modified to use this behavior. Additionally, we
also modify the chained constructor invocations of the
DefaultThreadFactory that do not have a parameter to specify a thread
group to use the thread group from the security manager is available,
otherwise the creating thread's thread group. We also add unit tests
ensuring that all of this behavior is maintained.
Result:
It will be possible to have DefaultThreadFactory instances that are not
sticky about the thread group that led to their creation. Instead,
threads created by such a DefaultThreadFactory will inherit the default
thread group which will either be the thread group from the security
manager or the current thread's thread group.
2016-07-13 15:00:06 -04:00
|
|
|
// visible for testing
|
|
|
|
static final ThreadFactory threadFactory;
|
2014-05-30 18:43:40 +09:00
|
|
|
|
2016-12-20 15:15:57 +01:00
|
|
|
// Use a MPMC queue as we may end up checking isEmpty() from multiple threads which may not be allowed to do
|
2017-04-20 01:37:03 +05:00
|
|
|
// concurrently depending on the implementation of it in a MPSC queue.
|
2016-12-20 15:15:57 +01:00
|
|
|
private static final Queue<Entry> pendingEntries = new ConcurrentLinkedQueue<Entry>();
|
2014-05-30 18:43:40 +09:00
|
|
|
private static final Watcher watcher = new Watcher();
|
|
|
|
private static final AtomicBoolean started = new AtomicBoolean();
|
2014-06-02 19:23:50 +09:00
|
|
|
private static volatile Thread watcherThread;
|
2014-05-30 18:43:40 +09:00
|
|
|
|
2015-10-30 10:59:33 +01:00
|
|
|
static {
|
|
|
|
String poolName = "threadDeathWatcher";
|
|
|
|
String serviceThreadPrefix = SystemPropertyUtil.get("io.netty.serviceThreadPrefix");
|
|
|
|
if (!StringUtil.isNullOrEmpty(serviceThreadPrefix)) {
|
|
|
|
poolName = serviceThreadPrefix + poolName;
|
|
|
|
}
|
Non-sticky thread groups in DefaultThreadFactory
Motivation:
A recent change to DefaultThreadFactory modified it so that it is sticky
with respect to thread groups. In particular, this change made it so
that DefaultThreadFactory would hold on to the thread group that created
it, and then use that thread group to create threads.
This can have problematic semantics since it can lead to violations of a
tenet of thread groups that a thread can only modify threads in its own
thread group and descendant thread groups. With a sticky thread group, a
thread triggering the creation of a new thread via
DefaultThreadFactory#newThread will be modifying a thread from the
sticky thread group which will not necessarily be its own nor a
descendant thread group. When a security manager is in place that
enforces this requirement, these modifications are now impossible. This
is especially problematic in the context of Netty because certain global
singletons like GlobalEventExecutor will create a
DefaultThreadFactory. If all DefaultThreadFactory instances are sticky
about their thread groups, it means that submitting tasks to the
GlobalEventExecutor singleton can cause a thread to be created from the
DefaultThreadFactory sticky thread group, exactly the problem with
DefaultThreadFactory being sticky about thread groups. A similar problem
arises from the ThreadDeathWatcher.
Modifications:
This commit modifies DefaultThreadFactory so that a null thread group
can be set with the behavior that all threads created by such an
instance will inherit the default thread group (the thread group
provided by the security manager if there is one, otherwise the thread
group of the creating thread). The construction of the instances of
DefaultThreadFactory used by the GlobalEventExecutor singleton and
ThreadDeathWatcher are modified to use this behavior. Additionally, we
also modify the chained constructor invocations of the
DefaultThreadFactory that do not have a parameter to specify a thread
group to use the thread group from the security manager is available,
otherwise the creating thread's thread group. We also add unit tests
ensuring that all of this behavior is maintained.
Result:
It will be possible to have DefaultThreadFactory instances that are not
sticky about the thread group that led to their creation. Instead,
threads created by such a DefaultThreadFactory will inherit the default
thread group which will either be the thread group from the security
manager or the current thread's thread group.
2016-07-13 15:00:06 -04:00
|
|
|
// because the ThreadDeathWatcher is a singleton, tasks submitted to it can come from arbitrary threads and
|
|
|
|
// this can trigger the creation of a thread from arbitrary thread groups; for this reason, the thread factory
|
|
|
|
// must not be sticky about its thread group
|
|
|
|
threadFactory = new DefaultThreadFactory(poolName, true, Thread.MIN_PRIORITY, null);
|
2015-10-30 10:59:33 +01:00
|
|
|
}
|
|
|
|
|
2014-05-30 18:43:40 +09:00
|
|
|
/**
|
|
|
|
* Schedules the specified {@code task} to run when the specified {@code thread} dies.
|
|
|
|
*
|
|
|
|
* @param thread the {@link Thread} to watch
|
|
|
|
* @param task the {@link Runnable} to run when the {@code thread} dies
|
|
|
|
*
|
|
|
|
* @throws IllegalArgumentException if the specified {@code thread} is not alive
|
|
|
|
*/
|
|
|
|
public static void watch(Thread thread, Runnable task) {
|
|
|
|
if (thread == null) {
|
|
|
|
throw new NullPointerException("thread");
|
|
|
|
}
|
|
|
|
if (task == null) {
|
|
|
|
throw new NullPointerException("task");
|
|
|
|
}
|
|
|
|
if (!thread.isAlive()) {
|
|
|
|
throw new IllegalArgumentException("thread must be alive.");
|
|
|
|
}
|
|
|
|
|
2014-06-17 18:37:58 +09:00
|
|
|
schedule(thread, task, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cancels the task scheduled via {@link #watch(Thread, Runnable)}.
|
|
|
|
*/
|
|
|
|
public static void unwatch(Thread thread, Runnable task) {
|
|
|
|
if (thread == null) {
|
|
|
|
throw new NullPointerException("thread");
|
|
|
|
}
|
|
|
|
if (task == null) {
|
|
|
|
throw new NullPointerException("task");
|
|
|
|
}
|
|
|
|
|
|
|
|
schedule(thread, task, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void schedule(Thread thread, Runnable task, boolean isWatch) {
|
|
|
|
pendingEntries.add(new Entry(thread, task, isWatch));
|
2014-05-30 18:43:40 +09:00
|
|
|
|
|
|
|
if (started.compareAndSet(false, true)) {
|
2018-01-24 14:06:19 -07:00
|
|
|
final Thread watcherThread = threadFactory.newThread(watcher);
|
2017-12-11 15:18:34 +01:00
|
|
|
// Set to null to ensure we not create classloader leaks by holds a strong reference to the inherited
|
|
|
|
// classloader.
|
|
|
|
// See:
|
|
|
|
// - https://github.com/netty/netty/issues/7290
|
|
|
|
// - https://bugs.openjdk.java.net/browse/JDK-7008595
|
2018-01-24 14:06:19 -07:00
|
|
|
AccessController.doPrivileged(new PrivilegedAction<Void>() {
|
|
|
|
@Override
|
|
|
|
public Void run() {
|
|
|
|
watcherThread.setContextClassLoader(null);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
});
|
2017-12-11 15:18:34 +01:00
|
|
|
|
2014-05-30 18:43:40 +09:00
|
|
|
watcherThread.start();
|
2014-06-02 19:23:50 +09:00
|
|
|
ThreadDeathWatcher.watcherThread = watcherThread;
|
2014-05-30 18:43:40 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-02 19:23:50 +09:00
|
|
|
/**
|
|
|
|
* Waits until the thread of this watcher has no threads to watch and terminates itself.
|
|
|
|
* Because a new watcher thread will be started again on {@link #watch(Thread, Runnable)},
|
|
|
|
* this operation is only useful when you want to ensure that the watcher thread is terminated
|
|
|
|
* <strong>after</strong> your application is shut down and there's no chance of calling
|
|
|
|
* {@link #watch(Thread, Runnable)} afterwards.
|
|
|
|
*
|
|
|
|
* @return {@code true} if and only if the watcher thread has been terminated
|
|
|
|
*/
|
2014-06-17 15:59:11 +09:00
|
|
|
public static boolean awaitInactivity(long timeout, TimeUnit unit) throws InterruptedException {
|
2014-06-02 19:23:50 +09:00
|
|
|
if (unit == null) {
|
|
|
|
throw new NullPointerException("unit");
|
|
|
|
}
|
|
|
|
|
|
|
|
Thread watcherThread = ThreadDeathWatcher.watcherThread;
|
|
|
|
if (watcherThread != null) {
|
|
|
|
watcherThread.join(unit.toMillis(timeout));
|
2014-06-17 18:37:58 +09:00
|
|
|
return !watcherThread.isAlive();
|
|
|
|
} else {
|
|
|
|
return true;
|
2014-06-02 19:23:50 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-30 18:43:40 +09:00
|
|
|
private ThreadDeathWatcher() { }
|
|
|
|
|
|
|
|
private static final class Watcher implements Runnable {
|
|
|
|
|
|
|
|
private final List<Entry> watchees = new ArrayList<Entry>();
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
for (;;) {
|
|
|
|
fetchWatchees();
|
|
|
|
notifyWatchees();
|
|
|
|
|
2014-06-17 18:37:58 +09:00
|
|
|
// Try once again just in case notifyWatchees() triggered watch() or unwatch().
|
|
|
|
fetchWatchees();
|
|
|
|
notifyWatchees();
|
|
|
|
|
2014-05-30 18:43:40 +09:00
|
|
|
try {
|
|
|
|
Thread.sleep(1000);
|
|
|
|
} catch (InterruptedException ignore) {
|
|
|
|
// Ignore the interrupt; do not terminate until all tasks are run.
|
|
|
|
}
|
|
|
|
|
|
|
|
if (watchees.isEmpty() && pendingEntries.isEmpty()) {
|
|
|
|
|
|
|
|
// Mark the current worker thread as stopped.
|
|
|
|
// The following CAS must always success and must be uncontended,
|
|
|
|
// because only one watcher thread should be running at the same time.
|
|
|
|
boolean stopped = started.compareAndSet(true, false);
|
|
|
|
assert stopped;
|
|
|
|
|
|
|
|
// Check if there are pending entries added by watch() while we do CAS above.
|
|
|
|
if (pendingEntries.isEmpty()) {
|
|
|
|
// A) watch() was not invoked and thus there's nothing to handle
|
|
|
|
// -> safe to terminate because there's nothing left to do
|
|
|
|
// B) a new watcher thread started and handled them all
|
|
|
|
// -> safe to terminate the new watcher thread will take care the rest
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// There are pending entries again, added by watch()
|
|
|
|
if (!started.compareAndSet(false, true)) {
|
|
|
|
// watch() started a new watcher thread and set 'started' to true.
|
|
|
|
// -> terminate this thread so that the new watcher reads from pendingEntries exclusively.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// watch() added an entry, but this worker was faster to set 'started' to true.
|
|
|
|
// i.e. a new watcher thread was not started
|
|
|
|
// -> keep this thread alive to handle the newly added entries.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void fetchWatchees() {
|
|
|
|
for (;;) {
|
|
|
|
Entry e = pendingEntries.poll();
|
|
|
|
if (e == null) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2014-06-17 18:37:58 +09:00
|
|
|
if (e.isWatch) {
|
|
|
|
watchees.add(e);
|
|
|
|
} else {
|
|
|
|
watchees.remove(e);
|
|
|
|
}
|
2014-05-30 18:43:40 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void notifyWatchees() {
|
|
|
|
List<Entry> watchees = this.watchees;
|
|
|
|
for (int i = 0; i < watchees.size();) {
|
|
|
|
Entry e = watchees.get(i);
|
|
|
|
if (!e.thread.isAlive()) {
|
|
|
|
watchees.remove(i);
|
|
|
|
try {
|
|
|
|
e.task.run();
|
|
|
|
} catch (Throwable t) {
|
|
|
|
logger.warn("Thread death watcher task raised an exception:", t);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
i ++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-27 13:25:39 +01:00
|
|
|
private static final class Entry {
|
2014-05-30 18:43:40 +09:00
|
|
|
final Thread thread;
|
|
|
|
final Runnable task;
|
2014-06-17 18:37:58 +09:00
|
|
|
final boolean isWatch;
|
2014-05-30 18:43:40 +09:00
|
|
|
|
2014-06-17 18:37:58 +09:00
|
|
|
Entry(Thread thread, Runnable task, boolean isWatch) {
|
2014-05-30 18:43:40 +09:00
|
|
|
this.thread = thread;
|
|
|
|
this.task = task;
|
2014-06-17 18:37:58 +09:00
|
|
|
this.isWatch = isWatch;
|
2014-05-30 18:43:40 +09:00
|
|
|
}
|
|
|
|
|
2014-06-17 18:37:58 +09:00
|
|
|
@Override
|
|
|
|
public int hashCode() {
|
|
|
|
return thread.hashCode() ^ task.hashCode();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean equals(Object obj) {
|
|
|
|
if (obj == this) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(obj instanceof Entry)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Entry that = (Entry) obj;
|
|
|
|
return thread == that.thread && task == that.task;
|
|
|
|
}
|
2014-05-30 18:43:40 +09:00
|
|
|
}
|
|
|
|
}
|