Nio|Epoll|KqueueEventLoop task execution might throw UnsupportedOperationException on shutdown. (#8476)

Motivation:

There is a racy UnsupportedOperationException instead because the task removal is delegated to MpscChunkedArrayQueue that does not support removal. This happens with SingleThreadEventExecutor that overrides the newTaskQueue to return an MPSC queue instead of the LinkedBlockingQueue returned by the base class such as NioEventLoop, EpollEventLoop and KQueueEventLoop.

Modifications:

- Catch the UnsupportedOperationException
- Add unit test.

Result:

Fix #8475
This commit is contained in:
Norman Maurer 2018-11-15 07:19:28 +01:00 committed by GitHub
parent 0d2e38d5d6
commit 845a65b31c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 54 additions and 2 deletions

View File

@ -778,10 +778,22 @@ public abstract class SingleThreadEventExecutor extends AbstractScheduledEventEx
addTask(task);
if (!inEventLoop) {
startThread();
if (isShutdown() && removeTask(task)) {
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);

View File

@ -22,6 +22,7 @@ import io.netty.channel.EventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.Future;
import org.hamcrest.core.IsInstanceOf;
import org.junit.Test;
import java.net.InetSocketAddress;
@ -29,7 +30,9 @@ import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.Assert.*;
@ -171,4 +174,41 @@ public class NioEventLoopTest extends AbstractEventLoopTest {
group.shutdownGracefully();
}
}
@SuppressWarnings("deprecation")
@Test
public void testTaskRemovalOnShutdownThrowsNoUnsupportedOperationException() throws Exception {
final AtomicReference<Throwable> error = new AtomicReference<Throwable>();
final Runnable task = new Runnable() {
@Override
public void run() {
// NOOP
}
};
// Just run often enough to trigger it normally.
for (int i = 0; i < 1000; i++) {
NioEventLoopGroup group = new NioEventLoopGroup(1);
final NioEventLoop loop = (NioEventLoop) group.next();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
for (;;) {
loop.execute(task);
}
} catch (Throwable cause) {
error.set(cause);
}
}
});
t.start();
group.shutdownNow();
t.join();
group.terminationFuture().syncUninterruptibly();
assertThat(error.get(), IsInstanceOf.instanceOf(RejectedExecutionException.class));
error.set(null);
}
}
}