netty5/common/src/main/java/io/netty/util/concurrent/SingleThreadEventExecutor.java

1008 lines
34 KiB
Java
Raw Normal View History

2012-06-04 22:31:44 +02:00
/*
* Copyright 2012 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 io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.internal.UnstableApi;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.lang.Thread.State;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
2012-08-28 02:10:17 +02:00
/**
[#5486] Not operate on serial execution assumption when using EventExecutor in the DefaultChannelPipeline. Motivation: In commit f984870ccca133d6056e8b0df0b2352f8f90b0fe I made a change which operated under invalide assumption that tasks executed by an EventExecutor will always be processed in a serial fashion. This is true for SingleThreadEventExecutor sub-classes but not part of the EventExecutor interface contract. Because of this change implementations of EventExecutor which not strictly execute tasks in a serial fashion may miss events before handlerAdded(...) is called. This is strictly speaking not correct as there is not guarantee in this case that handlerAdded(...) will be called as first task (as there is no ordering guarentee). Cassandra itself ships such an EventExecutor implementation which has no strict ordering to spread load across multiple threads. Modifications: - Add new OrderedEventExecutor interface and let SingleThreadEventExecutor / EventLoop implement / extend it. - Only expose "restriction" of skipping events until handlerAdded(...) is called for OrderedEventExecutor implementations - Add ThreadPoolEventExecutor implementation which executes tasks in an unordered fashion. This is used in added unit test but can also be used for protocols which not expose an strict ordering. - Add unit test. Result: Resurrect the possibility to implement an EventExecutor which does not enforce serial execution of events and be able to use it with the DefaultChannelPipeline.
2016-07-03 22:28:47 +02:00
* Abstract base class for {@link OrderedEventExecutor}'s that execute all its submitted tasks in a single thread.
2012-08-28 02:10:17 +02:00
*
*/
[#5486] Not operate on serial execution assumption when using EventExecutor in the DefaultChannelPipeline. Motivation: In commit f984870ccca133d6056e8b0df0b2352f8f90b0fe I made a change which operated under invalide assumption that tasks executed by an EventExecutor will always be processed in a serial fashion. This is true for SingleThreadEventExecutor sub-classes but not part of the EventExecutor interface contract. Because of this change implementations of EventExecutor which not strictly execute tasks in a serial fashion may miss events before handlerAdded(...) is called. This is strictly speaking not correct as there is not guarantee in this case that handlerAdded(...) will be called as first task (as there is no ordering guarentee). Cassandra itself ships such an EventExecutor implementation which has no strict ordering to spread load across multiple threads. Modifications: - Add new OrderedEventExecutor interface and let SingleThreadEventExecutor / EventLoop implement / extend it. - Only expose "restriction" of skipping events until handlerAdded(...) is called for OrderedEventExecutor implementations - Add ThreadPoolEventExecutor implementation which executes tasks in an unordered fashion. This is used in added unit test but can also be used for protocols which not expose an strict ordering. - Add unit test. Result: Resurrect the possibility to implement an EventExecutor which does not enforce serial execution of events and be able to use it with the DefaultChannelPipeline.
2016-07-03 22:28:47 +02:00
public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {
static final int DEFAULT_MAX_PENDING_EXECUTOR_TASKS = Math.max(16,
SystemPropertyUtil.getInt("io.netty.eventexecutor.maxPendingTasks", Integer.MAX_VALUE));
private static final InternalLogger logger =
InternalLoggerFactory.getInstance(SingleThreadEventExecutor.class);
Reduce the chance of RejectedExecutionException When a Netty application shuts down, a user often sees a REE (RejectedExecutionException). A REE is raised due to various reasons we don't have control over, such as: - A client connects to a server while the server is shutting down. - An event is triggered for a closed Channel while its event loop is also shutting down. Some of them are: - channelDeregistered (triggered after a channel is closed) - freeIn/OutboundBuffer (triggered after channelDeregistered) - userEventTriggered (triggered anytime) To address this issue, a new method called confirmShutdown() has been added to SingleThreadEventExecutor. After a user calls shutdown(), confirmShutdown() runs any remaining tasks in the task queue and ensures no events are triggered for last 2 seconds. If any task are added to the task queue before 2 seconds passes, confirmShutdown() prevents the event loop from terminating by returning false. Now that SingleThreadEventExecutor needs to accept tasks even after shutdown(), its execute() method only rejects the task after the event loop is terminated (i.e. isTerminated() returns true.) Except that, there's no change in semantics. SingleThreadEventExecutor also checks if its subclass called confirmShutdown() in its run() implementation, so that Netty developers can make sure they shut down their event loop impementation correctly. It also fixes a bug in AioSocketChannel, revealed by delayed shutdown, where an inboundBufferUpdated() event is triggered on a closed Channel with deallocated buffers. Caveats: Because SingleThreadEventExecutor.takeTask() does not have a notion of timeout, confirmShutdown() adds a dummy task (WAKEUP_TASK) to wake up takeTask() immediately and instead sleeps hard-coded 100ms. I'll address this issue later by modifying takeTask() times out dynamically. Miscellaneous changes: SingleThreadEventExecutor.wakeup() now has the default implementation. Instead of interrupting the current thread, it simply adds a dummy task (WAKEUP_TASK) to the task queue, which is more elegant and efficient. NioEventLoop is the only implementation that overrides it. All other implementations' wakeup()s were removed thanks to this change.
2012-11-22 12:45:49 +01:00
private static final int ST_NOT_STARTED = 1;
private static final int ST_STARTED = 2;
private static final int ST_SHUTTING_DOWN = 3;
private static final int ST_SHUTDOWN = 4;
private static final int ST_TERMINATED = 5;
Reduce the chance of RejectedExecutionException When a Netty application shuts down, a user often sees a REE (RejectedExecutionException). A REE is raised due to various reasons we don't have control over, such as: - A client connects to a server while the server is shutting down. - An event is triggered for a closed Channel while its event loop is also shutting down. Some of them are: - channelDeregistered (triggered after a channel is closed) - freeIn/OutboundBuffer (triggered after channelDeregistered) - userEventTriggered (triggered anytime) To address this issue, a new method called confirmShutdown() has been added to SingleThreadEventExecutor. After a user calls shutdown(), confirmShutdown() runs any remaining tasks in the task queue and ensures no events are triggered for last 2 seconds. If any task are added to the task queue before 2 seconds passes, confirmShutdown() prevents the event loop from terminating by returning false. Now that SingleThreadEventExecutor needs to accept tasks even after shutdown(), its execute() method only rejects the task after the event loop is terminated (i.e. isTerminated() returns true.) Except that, there's no change in semantics. SingleThreadEventExecutor also checks if its subclass called confirmShutdown() in its run() implementation, so that Netty developers can make sure they shut down their event loop impementation correctly. It also fixes a bug in AioSocketChannel, revealed by delayed shutdown, where an inboundBufferUpdated() event is triggered on a closed Channel with deallocated buffers. Caveats: Because SingleThreadEventExecutor.takeTask() does not have a notion of timeout, confirmShutdown() adds a dummy task (WAKEUP_TASK) to wake up takeTask() immediately and instead sleeps hard-coded 100ms. I'll address this issue later by modifying takeTask() times out dynamically. Miscellaneous changes: SingleThreadEventExecutor.wakeup() now has the default implementation. Instead of interrupting the current thread, it simply adds a dummy task (WAKEUP_TASK) to the task queue, which is more elegant and efficient. NioEventLoop is the only implementation that overrides it. All other implementations' wakeup()s were removed thanks to this change.
2012-11-22 12:45:49 +01:00
private static final Runnable WAKEUP_TASK = new Runnable() {
@Override
public void run() {
// Do nothing.
}
};
private static final Runnable NOOP_TASK = new Runnable() {
@Override
public void run() {
// Do nothing.
}
};
Reduce the chance of RejectedExecutionException When a Netty application shuts down, a user often sees a REE (RejectedExecutionException). A REE is raised due to various reasons we don't have control over, such as: - A client connects to a server while the server is shutting down. - An event is triggered for a closed Channel while its event loop is also shutting down. Some of them are: - channelDeregistered (triggered after a channel is closed) - freeIn/OutboundBuffer (triggered after channelDeregistered) - userEventTriggered (triggered anytime) To address this issue, a new method called confirmShutdown() has been added to SingleThreadEventExecutor. After a user calls shutdown(), confirmShutdown() runs any remaining tasks in the task queue and ensures no events are triggered for last 2 seconds. If any task are added to the task queue before 2 seconds passes, confirmShutdown() prevents the event loop from terminating by returning false. Now that SingleThreadEventExecutor needs to accept tasks even after shutdown(), its execute() method only rejects the task after the event loop is terminated (i.e. isTerminated() returns true.) Except that, there's no change in semantics. SingleThreadEventExecutor also checks if its subclass called confirmShutdown() in its run() implementation, so that Netty developers can make sure they shut down their event loop impementation correctly. It also fixes a bug in AioSocketChannel, revealed by delayed shutdown, where an inboundBufferUpdated() event is triggered on a closed Channel with deallocated buffers. Caveats: Because SingleThreadEventExecutor.takeTask() does not have a notion of timeout, confirmShutdown() adds a dummy task (WAKEUP_TASK) to wake up takeTask() immediately and instead sleeps hard-coded 100ms. I'll address this issue later by modifying takeTask() times out dynamically. Miscellaneous changes: SingleThreadEventExecutor.wakeup() now has the default implementation. Instead of interrupting the current thread, it simply adds a dummy task (WAKEUP_TASK) to the task queue, which is more elegant and efficient. NioEventLoop is the only implementation that overrides it. All other implementations' wakeup()s were removed thanks to this change.
2012-11-22 12:45:49 +01:00
private static final AtomicIntegerFieldUpdater<SingleThreadEventExecutor> STATE_UPDATER =
AtomicIntegerFieldUpdater.newUpdater(SingleThreadEventExecutor.class, "state");
private static final AtomicReferenceFieldUpdater<SingleThreadEventExecutor, ThreadProperties> PROPERTIES_UPDATER =
AtomicReferenceFieldUpdater.newUpdater(
SingleThreadEventExecutor.class, ThreadProperties.class, "threadProperties");
private final Queue<Runnable> taskQueue;
private volatile Thread thread;
@SuppressWarnings("unused")
private volatile ThreadProperties threadProperties;
private final Executor executor;
private volatile boolean interrupted;
private final Semaphore threadLock = new Semaphore(0);
private final Set<Runnable> shutdownHooks = new LinkedHashSet<Runnable>();
private final boolean addTaskWakesUp;
private final int maxPendingTasks;
private final RejectedExecutionHandler rejectedExecutionHandler;
private long lastExecutionTime;
@SuppressWarnings({ "FieldMayBeFinal", "unused" })
Reduce the chance of RejectedExecutionException When a Netty application shuts down, a user often sees a REE (RejectedExecutionException). A REE is raised due to various reasons we don't have control over, such as: - A client connects to a server while the server is shutting down. - An event is triggered for a closed Channel while its event loop is also shutting down. Some of them are: - channelDeregistered (triggered after a channel is closed) - freeIn/OutboundBuffer (triggered after channelDeregistered) - userEventTriggered (triggered anytime) To address this issue, a new method called confirmShutdown() has been added to SingleThreadEventExecutor. After a user calls shutdown(), confirmShutdown() runs any remaining tasks in the task queue and ensures no events are triggered for last 2 seconds. If any task are added to the task queue before 2 seconds passes, confirmShutdown() prevents the event loop from terminating by returning false. Now that SingleThreadEventExecutor needs to accept tasks even after shutdown(), its execute() method only rejects the task after the event loop is terminated (i.e. isTerminated() returns true.) Except that, there's no change in semantics. SingleThreadEventExecutor also checks if its subclass called confirmShutdown() in its run() implementation, so that Netty developers can make sure they shut down their event loop impementation correctly. It also fixes a bug in AioSocketChannel, revealed by delayed shutdown, where an inboundBufferUpdated() event is triggered on a closed Channel with deallocated buffers. Caveats: Because SingleThreadEventExecutor.takeTask() does not have a notion of timeout, confirmShutdown() adds a dummy task (WAKEUP_TASK) to wake up takeTask() immediately and instead sleeps hard-coded 100ms. I'll address this issue later by modifying takeTask() times out dynamically. Miscellaneous changes: SingleThreadEventExecutor.wakeup() now has the default implementation. Instead of interrupting the current thread, it simply adds a dummy task (WAKEUP_TASK) to the task queue, which is more elegant and efficient. NioEventLoop is the only implementation that overrides it. All other implementations' wakeup()s were removed thanks to this change.
2012-11-22 12:45:49 +01:00
private volatile int state = ST_NOT_STARTED;
private volatile long gracefulShutdownQuietPeriod;
private volatile long gracefulShutdownTimeout;
private long gracefulShutdownStartTime;
private final Promise<?> terminationFuture = new DefaultPromise<Void>(GlobalEventExecutor.INSTANCE);
/**
* Create a new instance
*
* @param parent the {@link EventExecutorGroup} which is the parent of this instance and belongs to it
* @param threadFactory the {@link ThreadFactory} which will be used for the used {@link Thread}
* @param addTaskWakesUp {@code true} if and only if invocation of {@link #addTask(Runnable)} will wake up the
* executor thread
*/
protected SingleThreadEventExecutor(
EventExecutorGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp) {
this(parent, new ThreadPerTaskExecutor(threadFactory), addTaskWakesUp);
}
/**
* Create a new instance
*
* @param parent the {@link EventExecutorGroup} which is the parent of this instance and belongs to it
* @param threadFactory the {@link ThreadFactory} which will be used for the used {@link Thread}
* @param addTaskWakesUp {@code true} if and only if invocation of {@link #addTask(Runnable)} will wake up the
* executor thread
* @param maxPendingTasks the maximum number of pending tasks before new tasks will be rejected.
* @param rejectedHandler the {@link RejectedExecutionHandler} to use.
*/
protected SingleThreadEventExecutor(
EventExecutorGroup parent, ThreadFactory threadFactory,
boolean addTaskWakesUp, int maxPendingTasks, RejectedExecutionHandler rejectedHandler) {
this(parent, new ThreadPerTaskExecutor(threadFactory), addTaskWakesUp, maxPendingTasks, rejectedHandler);
}
/**
* Create a new instance
*
* @param parent the {@link EventExecutorGroup} which is the parent of this instance and belongs to it
* @param executor the {@link Executor} which will be used for executing
* @param addTaskWakesUp {@code true} if and only if invocation of {@link #addTask(Runnable)} will wake up the
* executor thread
*/
protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor, boolean addTaskWakesUp) {
this(parent, executor, addTaskWakesUp, DEFAULT_MAX_PENDING_EXECUTOR_TASKS, RejectedExecutionHandlers.reject());
}
/**
* Create a new instance
*
* @param parent the {@link EventExecutorGroup} which is the parent of this instance and belongs to it
* @param executor the {@link Executor} which will be used for executing
* @param addTaskWakesUp {@code true} if and only if invocation of {@link #addTask(Runnable)} will wake up the
* executor thread
* @param maxPendingTasks the maximum number of pending tasks before new tasks will be rejected.
* @param rejectedHandler the {@link RejectedExecutionHandler} to use.
*/
protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
boolean addTaskWakesUp, int maxPendingTasks,
RejectedExecutionHandler rejectedHandler) {
super(parent);
this.addTaskWakesUp = addTaskWakesUp;
this.maxPendingTasks = Math.max(16, maxPendingTasks);
this.executor = ObjectUtil.checkNotNull(executor, "executor");
taskQueue = newTaskQueue(this.maxPendingTasks);
rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
}
/**
* @deprecated Please use and override {@link #newTaskQueue(int)}.
*/
@Deprecated
protected Queue<Runnable> newTaskQueue() {
return newTaskQueue(maxPendingTasks);
}
2012-12-23 20:58:49 +01:00
/**
* Create a new {@link Queue} which will holds the tasks to execute. This default implementation will return a
* {@link LinkedBlockingQueue} but if your sub-class of {@link SingleThreadEventExecutor} will not do any blocking
* calls on the this {@link Queue} it may make sense to {@code @Override} this and return some more performant
* implementation that does not support blocking operations at all.
*/
protected Queue<Runnable> newTaskQueue(int maxPendingTasks) {
return new LinkedBlockingQueue<Runnable>(maxPendingTasks);
}
2012-12-23 20:58:49 +01:00
/**
* Interrupt the current running {@link Thread}.
*/
protected void interruptThread() {
Thread currentThread = thread;
if (currentThread == null) {
interrupted = true;
} else {
currentThread.interrupt();
}
}
2012-12-23 20:58:49 +01:00
/**
* @see Queue#poll()
2012-12-23 20:58:49 +01:00
*/
protected Runnable pollTask() {
assert inEventLoop();
return pollTaskFrom(taskQueue);
}
protected static Runnable pollTaskFrom(Queue<Runnable> taskQueue) {
for (;;) {
Runnable task = taskQueue.poll();
if (task == WAKEUP_TASK) {
continue;
}
return task;
}
}
2012-12-23 20:58:49 +01:00
/**
* Take the next {@link Runnable} from the task queue and so will block if no task is currently present.
* <p>
2012-12-23 20:58:49 +01:00
* Be aware that this method will throw an {@link UnsupportedOperationException} if the task queue, which was
* created via {@link #newTaskQueue()}, does not implement {@link BlockingQueue}.
* </p>
*
* @return {@code null} if the executor thread has been interrupted or waken up.
2012-12-23 20:58:49 +01:00
*/
protected Runnable takeTask() {
assert inEventLoop();
if (!(taskQueue instanceof BlockingQueue)) {
throw new UnsupportedOperationException();
}
BlockingQueue<Runnable> taskQueue = (BlockingQueue<Runnable>) this.taskQueue;
for (;;) {
ScheduledFutureTask<?> scheduledTask = peekScheduledTask();
if (scheduledTask == null) {
Runnable task = null;
try {
task = taskQueue.take();
if (task == WAKEUP_TASK) {
task = null;
}
} catch (InterruptedException e) {
// Ignore
}
return task;
} else {
long delayNanos = scheduledTask.delayNanos();
Runnable task = null;
if (delayNanos > 0) {
try {
task = taskQueue.poll(delayNanos, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
// Waken up.
return null;
}
}
if (task == null) {
// We need to fetch the scheduled tasks now as otherwise there may be a chance that
// scheduled tasks are never executed if there is always one task in the taskQueue.
// This is for example true for the read task of OIO Transport
// See https://github.com/netty/netty/issues/1614
fetchFromScheduledTaskQueue();
task = taskQueue.poll();
}
if (task != null) {
return task;
}
}
}
}
private boolean fetchFromScheduledTaskQueue() {
long nanoTime = AbstractScheduledEventExecutor.nanoTime();
Runnable scheduledTask = pollScheduledTask(nanoTime);
while (scheduledTask != null) {
if (!taskQueue.offer(scheduledTask)) {
// No space left in the task queue add it back to the scheduledTaskQueue so we pick it up again.
scheduledTaskQueue().add((ScheduledFutureTask<?>) scheduledTask);
return false;
}
scheduledTask = pollScheduledTask(nanoTime);
}
return true;
}
2012-12-23 20:58:49 +01:00
/**
* @see Queue#peek()
2012-12-23 20:58:49 +01:00
*/
protected Runnable peekTask() {
assert inEventLoop();
return taskQueue.peek();
}
2012-12-23 20:58:49 +01:00
/**
* @see Queue#isEmpty()
2012-12-23 20:58:49 +01:00
*/
protected boolean hasTasks() {
assert inEventLoop();
return !taskQueue.isEmpty();
}
/**
* Return the number of tasks that are pending for processing.
*
* <strong>Be aware that this operation may be expensive as it depends on the internal implementation of the
* SingleThreadEventExecutor. So use it with care!</strong>
*/
public int pendingTasks() {
return taskQueue.size();
}
2012-12-23 20:58:49 +01:00
/**
* Add a task to the task queue, or throws a {@link RejectedExecutionException} if this instance was shutdown
* before.
*/
protected void addTask(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
if (!offerTask(task)) {
reject(task);
}
}
final boolean offerTask(Runnable task) {
if (isShutdown()) {
reject();
}
return taskQueue.offer(task);
}
2012-12-23 20:58:49 +01:00
/**
* @see Queue#remove(Object)
2012-12-23 20:58:49 +01:00
*/
protected boolean removeTask(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
return taskQueue.remove(task);
}
2012-12-23 20:58:49 +01:00
/**
* Poll all tasks from the task queue and run them via {@link Runnable#run()} method.
*
* @return {@code true} if and only if at least one task was run
2012-12-23 20:58:49 +01:00
*/
protected boolean runAllTasks() {
assert inEventLoop();
boolean fetchedAll;
boolean ranAtLeastOne = false;
do {
fetchedAll = fetchFromScheduledTaskQueue();
if (runAllTasksFrom(taskQueue)) {
ranAtLeastOne = true;
}
} while (!fetchedAll); // keep on processing until we fetched all scheduled tasks.
if (ranAtLeastOne) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
}
afterRunningAllTasks();
return ranAtLeastOne;
}
/**
* Runs all tasks from the passed {@code taskQueue}.
*
* @param taskQueue To poll and execute all tasks.
*
2017-04-19 22:37:03 +02:00
* @return {@code true} if at least one task was executed.
*/
protected final boolean runAllTasksFrom(Queue<Runnable> taskQueue) {
Runnable task = pollTaskFrom(taskQueue);
if (task == null) {
return false;
}
for (;;) {
safeExecute(task);
task = pollTaskFrom(taskQueue);
if (task == null) {
return true;
Reduce the chance of RejectedExecutionException When a Netty application shuts down, a user often sees a REE (RejectedExecutionException). A REE is raised due to various reasons we don't have control over, such as: - A client connects to a server while the server is shutting down. - An event is triggered for a closed Channel while its event loop is also shutting down. Some of them are: - channelDeregistered (triggered after a channel is closed) - freeIn/OutboundBuffer (triggered after channelDeregistered) - userEventTriggered (triggered anytime) To address this issue, a new method called confirmShutdown() has been added to SingleThreadEventExecutor. After a user calls shutdown(), confirmShutdown() runs any remaining tasks in the task queue and ensures no events are triggered for last 2 seconds. If any task are added to the task queue before 2 seconds passes, confirmShutdown() prevents the event loop from terminating by returning false. Now that SingleThreadEventExecutor needs to accept tasks even after shutdown(), its execute() method only rejects the task after the event loop is terminated (i.e. isTerminated() returns true.) Except that, there's no change in semantics. SingleThreadEventExecutor also checks if its subclass called confirmShutdown() in its run() implementation, so that Netty developers can make sure they shut down their event loop impementation correctly. It also fixes a bug in AioSocketChannel, revealed by delayed shutdown, where an inboundBufferUpdated() event is triggered on a closed Channel with deallocated buffers. Caveats: Because SingleThreadEventExecutor.takeTask() does not have a notion of timeout, confirmShutdown() adds a dummy task (WAKEUP_TASK) to wake up takeTask() immediately and instead sleeps hard-coded 100ms. I'll address this issue later by modifying takeTask() times out dynamically. Miscellaneous changes: SingleThreadEventExecutor.wakeup() now has the default implementation. Instead of interrupting the current thread, it simply adds a dummy task (WAKEUP_TASK) to the task queue, which is more elegant and efficient. NioEventLoop is the only implementation that overrides it. All other implementations' wakeup()s were removed thanks to this change.
2012-11-22 12:45:49 +01:00
}
}
}
Reduce the chance of RejectedExecutionException When a Netty application shuts down, a user often sees a REE (RejectedExecutionException). A REE is raised due to various reasons we don't have control over, such as: - A client connects to a server while the server is shutting down. - An event is triggered for a closed Channel while its event loop is also shutting down. Some of them are: - channelDeregistered (triggered after a channel is closed) - freeIn/OutboundBuffer (triggered after channelDeregistered) - userEventTriggered (triggered anytime) To address this issue, a new method called confirmShutdown() has been added to SingleThreadEventExecutor. After a user calls shutdown(), confirmShutdown() runs any remaining tasks in the task queue and ensures no events are triggered for last 2 seconds. If any task are added to the task queue before 2 seconds passes, confirmShutdown() prevents the event loop from terminating by returning false. Now that SingleThreadEventExecutor needs to accept tasks even after shutdown(), its execute() method only rejects the task after the event loop is terminated (i.e. isTerminated() returns true.) Except that, there's no change in semantics. SingleThreadEventExecutor also checks if its subclass called confirmShutdown() in its run() implementation, so that Netty developers can make sure they shut down their event loop impementation correctly. It also fixes a bug in AioSocketChannel, revealed by delayed shutdown, where an inboundBufferUpdated() event is triggered on a closed Channel with deallocated buffers. Caveats: Because SingleThreadEventExecutor.takeTask() does not have a notion of timeout, confirmShutdown() adds a dummy task (WAKEUP_TASK) to wake up takeTask() immediately and instead sleeps hard-coded 100ms. I'll address this issue later by modifying takeTask() times out dynamically. Miscellaneous changes: SingleThreadEventExecutor.wakeup() now has the default implementation. Instead of interrupting the current thread, it simply adds a dummy task (WAKEUP_TASK) to the task queue, which is more elegant and efficient. NioEventLoop is the only implementation that overrides it. All other implementations' wakeup()s were removed thanks to this change.
2012-11-22 12:45:49 +01:00
/**
* Poll all tasks from the task queue and run them via {@link Runnable#run()} method. This method stops running
* the tasks in the task queue and returns if it ran longer than {@code timeoutNanos}.
*/
protected boolean runAllTasks(long timeoutNanos) {
fetchFromScheduledTaskQueue();
Runnable task = pollTask();
if (task == null) {
afterRunningAllTasks();
return false;
}
final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;
long runTasks = 0;
long lastExecutionTime;
for (;;) {
safeExecute(task);
runTasks ++;
// Check timeout every 64 tasks because nanoTime() is relatively expensive.
// XXX: Hard-coded value - will make it configurable if it is really a problem.
2013-03-14 05:17:07 +01:00
if ((runTasks & 0x3F) == 0) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
if (lastExecutionTime >= deadline) {
break;
}
}
task = pollTask();
if (task == null) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
break;
}
}
afterRunningAllTasks();
this.lastExecutionTime = lastExecutionTime;
return true;
}
/**
* Invoked before returning from {@link #runAllTasks()} and {@link #runAllTasks(long)}.
*/
@UnstableApi
protected void afterRunningAllTasks() { }
/**
2013-07-22 13:51:09 +02:00
* Returns the amount of time left until the scheduled task with the closest dead line is executed.
*/
protected long delayNanos(long currentTimeNanos) {
ScheduledFutureTask<?> scheduledTask = peekScheduledTask();
if (scheduledTask == null) {
return SCHEDULE_PURGE_INTERVAL;
}
return scheduledTask.delayNanos(currentTimeNanos);
}
Don't re-arm timerfd each epoll_wait (#7816) Motivation: The Epoll transport checks to see if there are any scheduled tasks before entering epoll_wait, and resets the timerfd just before. This causes an extra syscall to timerfd_settime before doing any actual work. When scheduled tasks aren't added frequently, or tasks are added with later deadlines, this is unnecessary. Modification: Check the *deadline* of the peeked task in EpollEventLoop, rather than the *delay*. If it hasn't changed since last time, don't re-arm the timer Result: About 2us faster on gRPC RTT 50pct latency benchmarks. Before (2 runs for 5 minutes, 1 minute of warmup): ``` 50.0%ile Latency (in nanos): 64267 90.0%ile Latency (in nanos): 72851 95.0%ile Latency (in nanos): 78903 99.0%ile Latency (in nanos): 92327 99.9%ile Latency (in nanos): 119691 100.0%ile Latency (in nanos): 13347327 QPS: 14933 50.0%ile Latency (in nanos): 63907 90.0%ile Latency (in nanos): 73055 95.0%ile Latency (in nanos): 79443 99.0%ile Latency (in nanos): 93739 99.9%ile Latency (in nanos): 123583 100.0%ile Latency (in nanos): 14028287 QPS: 14936 ``` After: ``` 50.0%ile Latency (in nanos): 62123 90.0%ile Latency (in nanos): 70795 95.0%ile Latency (in nanos): 76895 99.0%ile Latency (in nanos): 90887 99.9%ile Latency (in nanos): 117819 100.0%ile Latency (in nanos): 14126591 QPS: 15387 50.0%ile Latency (in nanos): 61021 90.0%ile Latency (in nanos): 70311 95.0%ile Latency (in nanos): 76687 99.0%ile Latency (in nanos): 90887 99.9%ile Latency (in nanos): 119527 100.0%ile Latency (in nanos): 6351615 QPS: 15571 ```
2018-09-11 13:38:38 +02:00
/**
* Returns the absolute point in time (relative to {@link #nanoTime()}) at which the the next
* closest scheduled task should run.
*/
@UnstableApi
protected long deadlineNanos() {
ScheduledFutureTask<?> scheduledTask = peekScheduledTask();
if (scheduledTask == null) {
return nanoTime() + SCHEDULE_PURGE_INTERVAL;
}
return scheduledTask.deadlineNanos();
}
/**
* Updates the internal timestamp that tells when a submitted task was executed most recently.
* {@link #runAllTasks()} and {@link #runAllTasks(long)} updates this timestamp automatically, and thus there's
* usually no need to call this method. However, if you take the tasks manually using {@link #takeTask()} or
* {@link #pollTask()}, you have to call this method at the end of task execution loop for accurate quiet period
* checks.
*/
protected void updateLastExecutionTime() {
lastExecutionTime = ScheduledFutureTask.nanoTime();
}
2012-12-23 20:58:49 +01:00
/**
*
*/
protected abstract void run();
2012-12-23 20:58:49 +01:00
/**
* Do nothing, sub-classes may override
*/
protected void cleanup() {
2012-12-23 20:58:49 +01:00
// NOOP
}
Reduce the chance of RejectedExecutionException When a Netty application shuts down, a user often sees a REE (RejectedExecutionException). A REE is raised due to various reasons we don't have control over, such as: - A client connects to a server while the server is shutting down. - An event is triggered for a closed Channel while its event loop is also shutting down. Some of them are: - channelDeregistered (triggered after a channel is closed) - freeIn/OutboundBuffer (triggered after channelDeregistered) - userEventTriggered (triggered anytime) To address this issue, a new method called confirmShutdown() has been added to SingleThreadEventExecutor. After a user calls shutdown(), confirmShutdown() runs any remaining tasks in the task queue and ensures no events are triggered for last 2 seconds. If any task are added to the task queue before 2 seconds passes, confirmShutdown() prevents the event loop from terminating by returning false. Now that SingleThreadEventExecutor needs to accept tasks even after shutdown(), its execute() method only rejects the task after the event loop is terminated (i.e. isTerminated() returns true.) Except that, there's no change in semantics. SingleThreadEventExecutor also checks if its subclass called confirmShutdown() in its run() implementation, so that Netty developers can make sure they shut down their event loop impementation correctly. It also fixes a bug in AioSocketChannel, revealed by delayed shutdown, where an inboundBufferUpdated() event is triggered on a closed Channel with deallocated buffers. Caveats: Because SingleThreadEventExecutor.takeTask() does not have a notion of timeout, confirmShutdown() adds a dummy task (WAKEUP_TASK) to wake up takeTask() immediately and instead sleeps hard-coded 100ms. I'll address this issue later by modifying takeTask() times out dynamically. Miscellaneous changes: SingleThreadEventExecutor.wakeup() now has the default implementation. Instead of interrupting the current thread, it simply adds a dummy task (WAKEUP_TASK) to the task queue, which is more elegant and efficient. NioEventLoop is the only implementation that overrides it. All other implementations' wakeup()s were removed thanks to this change.
2012-11-22 12:45:49 +01:00
protected void wakeup(boolean inEventLoop) {
if (!inEventLoop || state == ST_SHUTTING_DOWN) {
// Use offer as we actually only need this to unblock the thread and if offer fails we do not care as there
// is already something in the queue.
taskQueue.offer(WAKEUP_TASK);
Reduce the chance of RejectedExecutionException When a Netty application shuts down, a user often sees a REE (RejectedExecutionException). A REE is raised due to various reasons we don't have control over, such as: - A client connects to a server while the server is shutting down. - An event is triggered for a closed Channel while its event loop is also shutting down. Some of them are: - channelDeregistered (triggered after a channel is closed) - freeIn/OutboundBuffer (triggered after channelDeregistered) - userEventTriggered (triggered anytime) To address this issue, a new method called confirmShutdown() has been added to SingleThreadEventExecutor. After a user calls shutdown(), confirmShutdown() runs any remaining tasks in the task queue and ensures no events are triggered for last 2 seconds. If any task are added to the task queue before 2 seconds passes, confirmShutdown() prevents the event loop from terminating by returning false. Now that SingleThreadEventExecutor needs to accept tasks even after shutdown(), its execute() method only rejects the task after the event loop is terminated (i.e. isTerminated() returns true.) Except that, there's no change in semantics. SingleThreadEventExecutor also checks if its subclass called confirmShutdown() in its run() implementation, so that Netty developers can make sure they shut down their event loop impementation correctly. It also fixes a bug in AioSocketChannel, revealed by delayed shutdown, where an inboundBufferUpdated() event is triggered on a closed Channel with deallocated buffers. Caveats: Because SingleThreadEventExecutor.takeTask() does not have a notion of timeout, confirmShutdown() adds a dummy task (WAKEUP_TASK) to wake up takeTask() immediately and instead sleeps hard-coded 100ms. I'll address this issue later by modifying takeTask() times out dynamically. Miscellaneous changes: SingleThreadEventExecutor.wakeup() now has the default implementation. Instead of interrupting the current thread, it simply adds a dummy task (WAKEUP_TASK) to the task queue, which is more elegant and efficient. NioEventLoop is the only implementation that overrides it. All other implementations' wakeup()s were removed thanks to this change.
2012-11-22 12:45:49 +01:00
}
}
@Override
public boolean inEventLoop(Thread thread) {
return thread == this.thread;
}
2012-12-23 20:58:49 +01:00
/**
* Add a {@link Runnable} which will be executed on shutdown of this instance
*/
public void addShutdownHook(final Runnable task) {
if (inEventLoop()) {
shutdownHooks.add(task);
} else {
execute(new Runnable() {
@Override
public void run() {
shutdownHooks.add(task);
}
});
}
}
2012-12-23 20:58:49 +01:00
/**
* Remove a previous added {@link Runnable} as a shutdown hook
*/
public void removeShutdownHook(final Runnable task) {
if (inEventLoop()) {
shutdownHooks.remove(task);
} else {
execute(new Runnable() {
@Override
public void run() {
shutdownHooks.remove(task);
}
});
}
}
private boolean runShutdownHooks() {
boolean ran = false;
// Note shutdown hooks can add / remove shutdown hooks.
while (!shutdownHooks.isEmpty()) {
List<Runnable> copy = new ArrayList<Runnable>(shutdownHooks);
shutdownHooks.clear();
for (Runnable task: copy) {
try {
task.run();
} catch (Throwable t) {
logger.warn("Shutdown hook raised an exception.", t);
} finally {
ran = true;
}
}
}
if (ran) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
}
return ran;
}
@Override
public Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) {
if (quietPeriod < 0) {
throw new IllegalArgumentException("quietPeriod: " + quietPeriod + " (expected >= 0)");
}
if (timeout < quietPeriod) {
throw new IllegalArgumentException(
"timeout: " + timeout + " (expected >= quietPeriod (" + quietPeriod + "))");
}
if (unit == null) {
throw new NullPointerException("unit");
}
if (isShuttingDown()) {
return terminationFuture();
}
boolean inEventLoop = inEventLoop();
boolean wakeup;
int oldState;
for (;;) {
if (isShuttingDown()) {
return terminationFuture();
}
int newState;
wakeup = true;
oldState = state;
if (inEventLoop) {
newState = ST_SHUTTING_DOWN;
} else {
switch (oldState) {
case ST_NOT_STARTED:
case ST_STARTED:
newState = ST_SHUTTING_DOWN;
break;
default:
newState = oldState;
wakeup = false;
}
}
if (STATE_UPDATER.compareAndSet(this, oldState, newState)) {
break;
}
}
gracefulShutdownQuietPeriod = unit.toNanos(quietPeriod);
gracefulShutdownTimeout = unit.toNanos(timeout);
if (ensureThreadStarted(oldState)) {
return terminationFuture;
}
if (wakeup) {
wakeup(inEventLoop);
}
return terminationFuture();
}
@Override
public Future<?> terminationFuture() {
return terminationFuture;
}
@Override
@Deprecated
public void shutdown() {
Reduce the chance of RejectedExecutionException When a Netty application shuts down, a user often sees a REE (RejectedExecutionException). A REE is raised due to various reasons we don't have control over, such as: - A client connects to a server while the server is shutting down. - An event is triggered for a closed Channel while its event loop is also shutting down. Some of them are: - channelDeregistered (triggered after a channel is closed) - freeIn/OutboundBuffer (triggered after channelDeregistered) - userEventTriggered (triggered anytime) To address this issue, a new method called confirmShutdown() has been added to SingleThreadEventExecutor. After a user calls shutdown(), confirmShutdown() runs any remaining tasks in the task queue and ensures no events are triggered for last 2 seconds. If any task are added to the task queue before 2 seconds passes, confirmShutdown() prevents the event loop from terminating by returning false. Now that SingleThreadEventExecutor needs to accept tasks even after shutdown(), its execute() method only rejects the task after the event loop is terminated (i.e. isTerminated() returns true.) Except that, there's no change in semantics. SingleThreadEventExecutor also checks if its subclass called confirmShutdown() in its run() implementation, so that Netty developers can make sure they shut down their event loop impementation correctly. It also fixes a bug in AioSocketChannel, revealed by delayed shutdown, where an inboundBufferUpdated() event is triggered on a closed Channel with deallocated buffers. Caveats: Because SingleThreadEventExecutor.takeTask() does not have a notion of timeout, confirmShutdown() adds a dummy task (WAKEUP_TASK) to wake up takeTask() immediately and instead sleeps hard-coded 100ms. I'll address this issue later by modifying takeTask() times out dynamically. Miscellaneous changes: SingleThreadEventExecutor.wakeup() now has the default implementation. Instead of interrupting the current thread, it simply adds a dummy task (WAKEUP_TASK) to the task queue, which is more elegant and efficient. NioEventLoop is the only implementation that overrides it. All other implementations' wakeup()s were removed thanks to this change.
2012-11-22 12:45:49 +01:00
if (isShutdown()) {
return;
}
boolean inEventLoop = inEventLoop();
boolean wakeup;
int oldState;
for (;;) {
if (isShuttingDown()) {
return;
}
int newState;
wakeup = true;
oldState = state;
if (inEventLoop) {
newState = ST_SHUTDOWN;
} else {
switch (oldState) {
case ST_NOT_STARTED:
case ST_STARTED:
case ST_SHUTTING_DOWN:
newState = ST_SHUTDOWN;
break;
default:
newState = oldState;
wakeup = false;
}
}
if (STATE_UPDATER.compareAndSet(this, oldState, newState)) {
break;
}
}
if (ensureThreadStarted(oldState)) {
return;
}
if (wakeup) {
wakeup(inEventLoop);
}
}
@Override
public boolean isShuttingDown() {
return state >= ST_SHUTTING_DOWN;
}
@Override
public boolean isShutdown() {
return state >= ST_SHUTDOWN;
}
@Override
public boolean isTerminated() {
return state == ST_TERMINATED;
Reduce the chance of RejectedExecutionException When a Netty application shuts down, a user often sees a REE (RejectedExecutionException). A REE is raised due to various reasons we don't have control over, such as: - A client connects to a server while the server is shutting down. - An event is triggered for a closed Channel while its event loop is also shutting down. Some of them are: - channelDeregistered (triggered after a channel is closed) - freeIn/OutboundBuffer (triggered after channelDeregistered) - userEventTriggered (triggered anytime) To address this issue, a new method called confirmShutdown() has been added to SingleThreadEventExecutor. After a user calls shutdown(), confirmShutdown() runs any remaining tasks in the task queue and ensures no events are triggered for last 2 seconds. If any task are added to the task queue before 2 seconds passes, confirmShutdown() prevents the event loop from terminating by returning false. Now that SingleThreadEventExecutor needs to accept tasks even after shutdown(), its execute() method only rejects the task after the event loop is terminated (i.e. isTerminated() returns true.) Except that, there's no change in semantics. SingleThreadEventExecutor also checks if its subclass called confirmShutdown() in its run() implementation, so that Netty developers can make sure they shut down their event loop impementation correctly. It also fixes a bug in AioSocketChannel, revealed by delayed shutdown, where an inboundBufferUpdated() event is triggered on a closed Channel with deallocated buffers. Caveats: Because SingleThreadEventExecutor.takeTask() does not have a notion of timeout, confirmShutdown() adds a dummy task (WAKEUP_TASK) to wake up takeTask() immediately and instead sleeps hard-coded 100ms. I'll address this issue later by modifying takeTask() times out dynamically. Miscellaneous changes: SingleThreadEventExecutor.wakeup() now has the default implementation. Instead of interrupting the current thread, it simply adds a dummy task (WAKEUP_TASK) to the task queue, which is more elegant and efficient. NioEventLoop is the only implementation that overrides it. All other implementations' wakeup()s were removed thanks to this change.
2012-11-22 12:45:49 +01:00
}
2012-12-23 20:58:49 +01:00
/**
* Confirm that the shutdown if the instance should be done now!
*/
Reduce the chance of RejectedExecutionException When a Netty application shuts down, a user often sees a REE (RejectedExecutionException). A REE is raised due to various reasons we don't have control over, such as: - A client connects to a server while the server is shutting down. - An event is triggered for a closed Channel while its event loop is also shutting down. Some of them are: - channelDeregistered (triggered after a channel is closed) - freeIn/OutboundBuffer (triggered after channelDeregistered) - userEventTriggered (triggered anytime) To address this issue, a new method called confirmShutdown() has been added to SingleThreadEventExecutor. After a user calls shutdown(), confirmShutdown() runs any remaining tasks in the task queue and ensures no events are triggered for last 2 seconds. If any task are added to the task queue before 2 seconds passes, confirmShutdown() prevents the event loop from terminating by returning false. Now that SingleThreadEventExecutor needs to accept tasks even after shutdown(), its execute() method only rejects the task after the event loop is terminated (i.e. isTerminated() returns true.) Except that, there's no change in semantics. SingleThreadEventExecutor also checks if its subclass called confirmShutdown() in its run() implementation, so that Netty developers can make sure they shut down their event loop impementation correctly. It also fixes a bug in AioSocketChannel, revealed by delayed shutdown, where an inboundBufferUpdated() event is triggered on a closed Channel with deallocated buffers. Caveats: Because SingleThreadEventExecutor.takeTask() does not have a notion of timeout, confirmShutdown() adds a dummy task (WAKEUP_TASK) to wake up takeTask() immediately and instead sleeps hard-coded 100ms. I'll address this issue later by modifying takeTask() times out dynamically. Miscellaneous changes: SingleThreadEventExecutor.wakeup() now has the default implementation. Instead of interrupting the current thread, it simply adds a dummy task (WAKEUP_TASK) to the task queue, which is more elegant and efficient. NioEventLoop is the only implementation that overrides it. All other implementations' wakeup()s were removed thanks to this change.
2012-11-22 12:45:49 +01:00
protected boolean confirmShutdown() {
if (!isShuttingDown()) {
return false;
Reduce the chance of RejectedExecutionException When a Netty application shuts down, a user often sees a REE (RejectedExecutionException). A REE is raised due to various reasons we don't have control over, such as: - A client connects to a server while the server is shutting down. - An event is triggered for a closed Channel while its event loop is also shutting down. Some of them are: - channelDeregistered (triggered after a channel is closed) - freeIn/OutboundBuffer (triggered after channelDeregistered) - userEventTriggered (triggered anytime) To address this issue, a new method called confirmShutdown() has been added to SingleThreadEventExecutor. After a user calls shutdown(), confirmShutdown() runs any remaining tasks in the task queue and ensures no events are triggered for last 2 seconds. If any task are added to the task queue before 2 seconds passes, confirmShutdown() prevents the event loop from terminating by returning false. Now that SingleThreadEventExecutor needs to accept tasks even after shutdown(), its execute() method only rejects the task after the event loop is terminated (i.e. isTerminated() returns true.) Except that, there's no change in semantics. SingleThreadEventExecutor also checks if its subclass called confirmShutdown() in its run() implementation, so that Netty developers can make sure they shut down their event loop impementation correctly. It also fixes a bug in AioSocketChannel, revealed by delayed shutdown, where an inboundBufferUpdated() event is triggered on a closed Channel with deallocated buffers. Caveats: Because SingleThreadEventExecutor.takeTask() does not have a notion of timeout, confirmShutdown() adds a dummy task (WAKEUP_TASK) to wake up takeTask() immediately and instead sleeps hard-coded 100ms. I'll address this issue later by modifying takeTask() times out dynamically. Miscellaneous changes: SingleThreadEventExecutor.wakeup() now has the default implementation. Instead of interrupting the current thread, it simply adds a dummy task (WAKEUP_TASK) to the task queue, which is more elegant and efficient. NioEventLoop is the only implementation that overrides it. All other implementations' wakeup()s were removed thanks to this change.
2012-11-22 12:45:49 +01:00
}
Reduce the chance of RejectedExecutionException When a Netty application shuts down, a user often sees a REE (RejectedExecutionException). A REE is raised due to various reasons we don't have control over, such as: - A client connects to a server while the server is shutting down. - An event is triggered for a closed Channel while its event loop is also shutting down. Some of them are: - channelDeregistered (triggered after a channel is closed) - freeIn/OutboundBuffer (triggered after channelDeregistered) - userEventTriggered (triggered anytime) To address this issue, a new method called confirmShutdown() has been added to SingleThreadEventExecutor. After a user calls shutdown(), confirmShutdown() runs any remaining tasks in the task queue and ensures no events are triggered for last 2 seconds. If any task are added to the task queue before 2 seconds passes, confirmShutdown() prevents the event loop from terminating by returning false. Now that SingleThreadEventExecutor needs to accept tasks even after shutdown(), its execute() method only rejects the task after the event loop is terminated (i.e. isTerminated() returns true.) Except that, there's no change in semantics. SingleThreadEventExecutor also checks if its subclass called confirmShutdown() in its run() implementation, so that Netty developers can make sure they shut down their event loop impementation correctly. It also fixes a bug in AioSocketChannel, revealed by delayed shutdown, where an inboundBufferUpdated() event is triggered on a closed Channel with deallocated buffers. Caveats: Because SingleThreadEventExecutor.takeTask() does not have a notion of timeout, confirmShutdown() adds a dummy task (WAKEUP_TASK) to wake up takeTask() immediately and instead sleeps hard-coded 100ms. I'll address this issue later by modifying takeTask() times out dynamically. Miscellaneous changes: SingleThreadEventExecutor.wakeup() now has the default implementation. Instead of interrupting the current thread, it simply adds a dummy task (WAKEUP_TASK) to the task queue, which is more elegant and efficient. NioEventLoop is the only implementation that overrides it. All other implementations' wakeup()s were removed thanks to this change.
2012-11-22 12:45:49 +01:00
if (!inEventLoop()) {
throw new IllegalStateException("must be invoked from an event loop");
}
cancelScheduledTasks();
if (gracefulShutdownStartTime == 0) {
gracefulShutdownStartTime = ScheduledFutureTask.nanoTime();
}
Reduce the chance of RejectedExecutionException When a Netty application shuts down, a user often sees a REE (RejectedExecutionException). A REE is raised due to various reasons we don't have control over, such as: - A client connects to a server while the server is shutting down. - An event is triggered for a closed Channel while its event loop is also shutting down. Some of them are: - channelDeregistered (triggered after a channel is closed) - freeIn/OutboundBuffer (triggered after channelDeregistered) - userEventTriggered (triggered anytime) To address this issue, a new method called confirmShutdown() has been added to SingleThreadEventExecutor. After a user calls shutdown(), confirmShutdown() runs any remaining tasks in the task queue and ensures no events are triggered for last 2 seconds. If any task are added to the task queue before 2 seconds passes, confirmShutdown() prevents the event loop from terminating by returning false. Now that SingleThreadEventExecutor needs to accept tasks even after shutdown(), its execute() method only rejects the task after the event loop is terminated (i.e. isTerminated() returns true.) Except that, there's no change in semantics. SingleThreadEventExecutor also checks if its subclass called confirmShutdown() in its run() implementation, so that Netty developers can make sure they shut down their event loop impementation correctly. It also fixes a bug in AioSocketChannel, revealed by delayed shutdown, where an inboundBufferUpdated() event is triggered on a closed Channel with deallocated buffers. Caveats: Because SingleThreadEventExecutor.takeTask() does not have a notion of timeout, confirmShutdown() adds a dummy task (WAKEUP_TASK) to wake up takeTask() immediately and instead sleeps hard-coded 100ms. I'll address this issue later by modifying takeTask() times out dynamically. Miscellaneous changes: SingleThreadEventExecutor.wakeup() now has the default implementation. Instead of interrupting the current thread, it simply adds a dummy task (WAKEUP_TASK) to the task queue, which is more elegant and efficient. NioEventLoop is the only implementation that overrides it. All other implementations' wakeup()s were removed thanks to this change.
2012-11-22 12:45:49 +01:00
if (runAllTasks() || runShutdownHooks()) {
if (isShutdown()) {
// Executor shut down - no new tasks anymore.
return true;
}
// There were tasks in the queue. Wait a little bit more until no tasks are queued for the quiet period or
// terminate if the quiet period is 0.
// See https://github.com/netty/netty/issues/4241
if (gracefulShutdownQuietPeriod == 0) {
return true;
}
Reduce the chance of RejectedExecutionException When a Netty application shuts down, a user often sees a REE (RejectedExecutionException). A REE is raised due to various reasons we don't have control over, such as: - A client connects to a server while the server is shutting down. - An event is triggered for a closed Channel while its event loop is also shutting down. Some of them are: - channelDeregistered (triggered after a channel is closed) - freeIn/OutboundBuffer (triggered after channelDeregistered) - userEventTriggered (triggered anytime) To address this issue, a new method called confirmShutdown() has been added to SingleThreadEventExecutor. After a user calls shutdown(), confirmShutdown() runs any remaining tasks in the task queue and ensures no events are triggered for last 2 seconds. If any task are added to the task queue before 2 seconds passes, confirmShutdown() prevents the event loop from terminating by returning false. Now that SingleThreadEventExecutor needs to accept tasks even after shutdown(), its execute() method only rejects the task after the event loop is terminated (i.e. isTerminated() returns true.) Except that, there's no change in semantics. SingleThreadEventExecutor also checks if its subclass called confirmShutdown() in its run() implementation, so that Netty developers can make sure they shut down their event loop impementation correctly. It also fixes a bug in AioSocketChannel, revealed by delayed shutdown, where an inboundBufferUpdated() event is triggered on a closed Channel with deallocated buffers. Caveats: Because SingleThreadEventExecutor.takeTask() does not have a notion of timeout, confirmShutdown() adds a dummy task (WAKEUP_TASK) to wake up takeTask() immediately and instead sleeps hard-coded 100ms. I'll address this issue later by modifying takeTask() times out dynamically. Miscellaneous changes: SingleThreadEventExecutor.wakeup() now has the default implementation. Instead of interrupting the current thread, it simply adds a dummy task (WAKEUP_TASK) to the task queue, which is more elegant and efficient. NioEventLoop is the only implementation that overrides it. All other implementations' wakeup()s were removed thanks to this change.
2012-11-22 12:45:49 +01:00
wakeup(true);
return false;
}
final long nanoTime = ScheduledFutureTask.nanoTime();
Reduce the chance of RejectedExecutionException When a Netty application shuts down, a user often sees a REE (RejectedExecutionException). A REE is raised due to various reasons we don't have control over, such as: - A client connects to a server while the server is shutting down. - An event is triggered for a closed Channel while its event loop is also shutting down. Some of them are: - channelDeregistered (triggered after a channel is closed) - freeIn/OutboundBuffer (triggered after channelDeregistered) - userEventTriggered (triggered anytime) To address this issue, a new method called confirmShutdown() has been added to SingleThreadEventExecutor. After a user calls shutdown(), confirmShutdown() runs any remaining tasks in the task queue and ensures no events are triggered for last 2 seconds. If any task are added to the task queue before 2 seconds passes, confirmShutdown() prevents the event loop from terminating by returning false. Now that SingleThreadEventExecutor needs to accept tasks even after shutdown(), its execute() method only rejects the task after the event loop is terminated (i.e. isTerminated() returns true.) Except that, there's no change in semantics. SingleThreadEventExecutor also checks if its subclass called confirmShutdown() in its run() implementation, so that Netty developers can make sure they shut down their event loop impementation correctly. It also fixes a bug in AioSocketChannel, revealed by delayed shutdown, where an inboundBufferUpdated() event is triggered on a closed Channel with deallocated buffers. Caveats: Because SingleThreadEventExecutor.takeTask() does not have a notion of timeout, confirmShutdown() adds a dummy task (WAKEUP_TASK) to wake up takeTask() immediately and instead sleeps hard-coded 100ms. I'll address this issue later by modifying takeTask() times out dynamically. Miscellaneous changes: SingleThreadEventExecutor.wakeup() now has the default implementation. Instead of interrupting the current thread, it simply adds a dummy task (WAKEUP_TASK) to the task queue, which is more elegant and efficient. NioEventLoop is the only implementation that overrides it. All other implementations' wakeup()s were removed thanks to this change.
2012-11-22 12:45:49 +01:00
if (isShutdown() || nanoTime - gracefulShutdownStartTime > gracefulShutdownTimeout) {
return true;
}
if (nanoTime - lastExecutionTime <= gracefulShutdownQuietPeriod) {
Reduce the chance of RejectedExecutionException When a Netty application shuts down, a user often sees a REE (RejectedExecutionException). A REE is raised due to various reasons we don't have control over, such as: - A client connects to a server while the server is shutting down. - An event is triggered for a closed Channel while its event loop is also shutting down. Some of them are: - channelDeregistered (triggered after a channel is closed) - freeIn/OutboundBuffer (triggered after channelDeregistered) - userEventTriggered (triggered anytime) To address this issue, a new method called confirmShutdown() has been added to SingleThreadEventExecutor. After a user calls shutdown(), confirmShutdown() runs any remaining tasks in the task queue and ensures no events are triggered for last 2 seconds. If any task are added to the task queue before 2 seconds passes, confirmShutdown() prevents the event loop from terminating by returning false. Now that SingleThreadEventExecutor needs to accept tasks even after shutdown(), its execute() method only rejects the task after the event loop is terminated (i.e. isTerminated() returns true.) Except that, there's no change in semantics. SingleThreadEventExecutor also checks if its subclass called confirmShutdown() in its run() implementation, so that Netty developers can make sure they shut down their event loop impementation correctly. It also fixes a bug in AioSocketChannel, revealed by delayed shutdown, where an inboundBufferUpdated() event is triggered on a closed Channel with deallocated buffers. Caveats: Because SingleThreadEventExecutor.takeTask() does not have a notion of timeout, confirmShutdown() adds a dummy task (WAKEUP_TASK) to wake up takeTask() immediately and instead sleeps hard-coded 100ms. I'll address this issue later by modifying takeTask() times out dynamically. Miscellaneous changes: SingleThreadEventExecutor.wakeup() now has the default implementation. Instead of interrupting the current thread, it simply adds a dummy task (WAKEUP_TASK) to the task queue, which is more elegant and efficient. NioEventLoop is the only implementation that overrides it. All other implementations' wakeup()s were removed thanks to this change.
2012-11-22 12:45:49 +01:00
// Check if any tasks were added to the queue every 100ms.
// TODO: Change the behavior of takeTask() so that it returns on timeout.
wakeup(true);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// Ignore
}
return false;
}
// No tasks were added for last quiet period - hopefully safe to shut down.
Reduce the chance of RejectedExecutionException When a Netty application shuts down, a user often sees a REE (RejectedExecutionException). A REE is raised due to various reasons we don't have control over, such as: - A client connects to a server while the server is shutting down. - An event is triggered for a closed Channel while its event loop is also shutting down. Some of them are: - channelDeregistered (triggered after a channel is closed) - freeIn/OutboundBuffer (triggered after channelDeregistered) - userEventTriggered (triggered anytime) To address this issue, a new method called confirmShutdown() has been added to SingleThreadEventExecutor. After a user calls shutdown(), confirmShutdown() runs any remaining tasks in the task queue and ensures no events are triggered for last 2 seconds. If any task are added to the task queue before 2 seconds passes, confirmShutdown() prevents the event loop from terminating by returning false. Now that SingleThreadEventExecutor needs to accept tasks even after shutdown(), its execute() method only rejects the task after the event loop is terminated (i.e. isTerminated() returns true.) Except that, there's no change in semantics. SingleThreadEventExecutor also checks if its subclass called confirmShutdown() in its run() implementation, so that Netty developers can make sure they shut down their event loop impementation correctly. It also fixes a bug in AioSocketChannel, revealed by delayed shutdown, where an inboundBufferUpdated() event is triggered on a closed Channel with deallocated buffers. Caveats: Because SingleThreadEventExecutor.takeTask() does not have a notion of timeout, confirmShutdown() adds a dummy task (WAKEUP_TASK) to wake up takeTask() immediately and instead sleeps hard-coded 100ms. I'll address this issue later by modifying takeTask() times out dynamically. Miscellaneous changes: SingleThreadEventExecutor.wakeup() now has the default implementation. Instead of interrupting the current thread, it simply adds a dummy task (WAKEUP_TASK) to the task queue, which is more elegant and efficient. NioEventLoop is the only implementation that overrides it. All other implementations' wakeup()s were removed thanks to this change.
2012-11-22 12:45:49 +01:00
// (Hopefully because we really cannot make a guarantee that there will be no execute() calls by a user.)
return true;
}
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
if (unit == null) {
throw new NullPointerException("unit");
}
if (inEventLoop()) {
throw new IllegalStateException("cannot await termination of the current thread");
}
if (threadLock.tryAcquire(timeout, unit)) {
threadLock.release();
}
return isTerminated();
}
@Override
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
boolean inEventLoop = inEventLoop();
addTask(task);
if (!inEventLoop) {
startThread();
if (isShutdown()) {
boolean reject = false;
try {
if (removeTask(task)) {
reject = true;
}
} catch (UnsupportedOperationException e) {
// The task queue does not support removal so the best thing we can do is to just move on and
// hope we will be able to pick-up the task before its completely terminated.
// In worst case we will log on termination.
}
if (reject) {
reject();
}
}
}
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
throwIfInEventLoop("invokeAny");
return super.invokeAny(tasks);
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
throwIfInEventLoop("invokeAny");
return super.invokeAny(tasks, timeout, unit);
}
@Override
public <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
throwIfInEventLoop("invokeAll");
return super.invokeAll(tasks);
}
@Override
public <T> List<java.util.concurrent.Future<T>> invokeAll(
Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException {
throwIfInEventLoop("invokeAll");
return super.invokeAll(tasks, timeout, unit);
}
private void throwIfInEventLoop(String method) {
if (inEventLoop()) {
throw new RejectedExecutionException("Calling " + method + " from within the EventLoop is not allowed");
}
}
/**
* Returns the {@link ThreadProperties} of the {@link Thread} that powers the {@link SingleThreadEventExecutor}.
* If the {@link SingleThreadEventExecutor} is not started yet, this operation will start it and block until the
* it is fully started.
*/
public final ThreadProperties threadProperties() {
ThreadProperties threadProperties = this.threadProperties;
if (threadProperties == null) {
Thread thread = this.thread;
if (thread == null) {
assert !inEventLoop();
submit(NOOP_TASK).syncUninterruptibly();
thread = this.thread;
assert thread != null;
}
threadProperties = new DefaultThreadProperties(thread);
if (!PROPERTIES_UPDATER.compareAndSet(this, null, threadProperties)) {
threadProperties = this.threadProperties;
}
}
return threadProperties;
}
@SuppressWarnings("unused")
protected boolean wakesUpForTask(Runnable task) {
return true;
}
protected static void reject() {
Reduce the chance of RejectedExecutionException When a Netty application shuts down, a user often sees a REE (RejectedExecutionException). A REE is raised due to various reasons we don't have control over, such as: - A client connects to a server while the server is shutting down. - An event is triggered for a closed Channel while its event loop is also shutting down. Some of them are: - channelDeregistered (triggered after a channel is closed) - freeIn/OutboundBuffer (triggered after channelDeregistered) - userEventTriggered (triggered anytime) To address this issue, a new method called confirmShutdown() has been added to SingleThreadEventExecutor. After a user calls shutdown(), confirmShutdown() runs any remaining tasks in the task queue and ensures no events are triggered for last 2 seconds. If any task are added to the task queue before 2 seconds passes, confirmShutdown() prevents the event loop from terminating by returning false. Now that SingleThreadEventExecutor needs to accept tasks even after shutdown(), its execute() method only rejects the task after the event loop is terminated (i.e. isTerminated() returns true.) Except that, there's no change in semantics. SingleThreadEventExecutor also checks if its subclass called confirmShutdown() in its run() implementation, so that Netty developers can make sure they shut down their event loop impementation correctly. It also fixes a bug in AioSocketChannel, revealed by delayed shutdown, where an inboundBufferUpdated() event is triggered on a closed Channel with deallocated buffers. Caveats: Because SingleThreadEventExecutor.takeTask() does not have a notion of timeout, confirmShutdown() adds a dummy task (WAKEUP_TASK) to wake up takeTask() immediately and instead sleeps hard-coded 100ms. I'll address this issue later by modifying takeTask() times out dynamically. Miscellaneous changes: SingleThreadEventExecutor.wakeup() now has the default implementation. Instead of interrupting the current thread, it simply adds a dummy task (WAKEUP_TASK) to the task queue, which is more elegant and efficient. NioEventLoop is the only implementation that overrides it. All other implementations' wakeup()s were removed thanks to this change.
2012-11-22 12:45:49 +01:00
throw new RejectedExecutionException("event executor terminated");
}
/**
* Offers the task to the associated {@link RejectedExecutionHandler}.
*
* @param task to reject.
*/
protected final void reject(Runnable task) {
rejectedExecutionHandler.rejected(task, this);
}
// ScheduledExecutorService implementation
private static final long SCHEDULE_PURGE_INTERVAL = TimeUnit.SECONDS.toNanos(1);
private void startThread() {
if (state == ST_NOT_STARTED) {
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
try {
doStartThread();
} catch (Throwable cause) {
STATE_UPDATER.set(this, ST_NOT_STARTED);
PlatformDependent.throwException(cause);
}
}
}
}
private boolean ensureThreadStarted(int oldState) {
if (oldState == ST_NOT_STARTED) {
try {
doStartThread();
} catch (Throwable cause) {
STATE_UPDATER.set(this, ST_TERMINATED);
terminationFuture.tryFailure(cause);
if (!(cause instanceof Exception)) {
// Also rethrow as it may be an OOME for example
PlatformDependent.throwException(cause);
}
return true;
}
}
return false;
}
private void doStartThread() {
assert thread == null;
executor.execute(new Runnable() {
@Override
public void run() {
thread = Thread.currentThread();
if (interrupted) {
thread.interrupt();
}
boolean success = false;
updateLastExecutionTime();
try {
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
for (;;) {
int oldState = state;
if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet(
SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) {
break;
}
}
// Check if confirmShutdown() was called at the end of the loop.
if (success && gracefulShutdownStartTime == 0) {
if (logger.isErrorEnabled()) {
logger.error("Buggy " + EventExecutor.class.getSimpleName() + " implementation; " +
SingleThreadEventExecutor.class.getSimpleName() + ".confirmShutdown() must " +
"be called before run() implementation terminates.");
}
}
try {
// Run all remaining tasks and shutdown hooks.
for (;;) {
if (confirmShutdown()) {
break;
}
}
} finally {
try {
cleanup();
} finally {
// Lets remove all FastThreadLocals for the Thread as we are about to terminate and notify
// the future. The user may block on the future and once it unblocks the JVM may terminate
// and start unloading classes.
// See https://github.com/netty/netty/issues/6596.
FastThreadLocal.removeAll();
STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED);
threadLock.release();
if (!taskQueue.isEmpty()) {
if (logger.isWarnEnabled()) {
logger.warn("An event executor terminated with " +
"non-empty task queue (" + taskQueue.size() + ')');
}
}
terminationFuture.setSuccess(null);
}
}
}
}
});
}
private static final class DefaultThreadProperties implements ThreadProperties {
private final Thread t;
DefaultThreadProperties(Thread t) {
this.t = t;
}
@Override
public State state() {
return t.getState();
}
@Override
public int priority() {
return t.getPriority();
}
@Override
public boolean isInterrupted() {
return t.isInterrupted();
}
@Override
public boolean isDaemon() {
return t.isDaemon();
}
@Override
public String name() {
return t.getName();
}
@Override
public long id() {
return t.getId();
}
@Override
public StackTraceElement[] stackTrace() {
return t.getStackTrace();
}
@Override
public boolean isAlive() {
return t.isAlive();
}
}
}