Remove use of PlatformDependent.throwsException in SingleThreadEventExecutor (#10827)

Motivation:
We should avoid lying with throws declarations whenever possible.

Modification:
Changed the code to instead directly throw Error, which seems to have been the intent.
Also, while we're here, convert its associated test to JUnit 5 and clean it up a bit.

Result:
Cleaner code.
This commit is contained in:
Chris Vest 2020-11-26 11:37:47 +01:00 committed by GitHub
parent 2dae6665f4
commit db4f85a479
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 84 additions and 95 deletions

View File

@ -831,9 +831,9 @@ public class SingleThreadEventExecutor extends AbstractScheduledEventExecutor im
STATE_UPDATER.set(this, ST_TERMINATED); STATE_UPDATER.set(this, ST_TERMINATED);
terminationFuture.tryFailure(cause); terminationFuture.tryFailure(cause);
if (!(cause instanceof Exception)) { if (cause instanceof Error) {
// Also rethrow as it may be an OOME for example // Rethrow errors so they can't be ignored.
PlatformDependent.throwException(cause); throw cause;
} }
return true; return true;
} }

View File

@ -15,9 +15,7 @@
*/ */
package io.netty.util.concurrent; package io.netty.util.concurrent;
import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test;
import org.junit.Assert;
import org.junit.Test;
import java.util.Collections; import java.util.Collections;
import java.util.Queue; import java.util.Queue;
@ -34,11 +32,16 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import static org.hamcrest.CoreMatchers.*; import static java.time.Duration.ofSeconds;
import static org.hamcrest.MatcherAssert.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.*; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class SingleThreadEventExecutorTest { public class SingleThreadEventExecutorTest {
public static final Runnable DUMMY_TASK = () -> {
};
@Test @Test
public void testWrappedExecutorIsShutdown() { public void testWrappedExecutorIsShutdown() {
@ -59,25 +62,14 @@ public class SingleThreadEventExecutorTest {
executorService.shutdownNow(); executorService.shutdownNow();
executeShouldFail(executor); executeShouldFail(executor);
executeShouldFail(executor); executeShouldFail(executor);
try { var exception = assertThrows(
executor.shutdownGracefully().syncUninterruptibly(); CompletionException.class, () -> executor.shutdownGracefully().syncUninterruptibly());
Assert.fail(); assertThat(exception).hasCauseInstanceOf(RejectedExecutionException.class);
} catch (CompletionException expected) { assertTrue(executor.isShutdown());
// expected
Assert.assertThat(expected.getCause(), CoreMatchers.instanceOf(RejectedExecutionException.class));
}
Assert.assertTrue(executor.isShutdown());
} }
private static void executeShouldFail(Executor executor) { private static void executeShouldFail(Executor executor) {
try { assertThrows(RejectedExecutionException.class, () -> executor.execute(DUMMY_TASK));
executor.execute(() -> {
// Noop.
});
Assert.fail();
} catch (RejectedExecutionException expected) {
// expected
}
} }
@Test @Test
@ -98,49 +90,45 @@ public class SingleThreadEventExecutorTest {
ThreadProperties threadProperties = executor.threadProperties(); ThreadProperties threadProperties = executor.threadProperties();
Thread thread = threadRef.get(); Thread thread = threadRef.get();
Assert.assertEquals(thread.getId(), threadProperties.id()); assertEquals(thread.getId(), threadProperties.id());
Assert.assertEquals(thread.getName(), threadProperties.name()); assertEquals(thread.getName(), threadProperties.name());
Assert.assertEquals(thread.getPriority(), threadProperties.priority()); assertEquals(thread.getPriority(), threadProperties.priority());
Assert.assertEquals(thread.isAlive(), threadProperties.isAlive()); assertEquals(thread.isAlive(), threadProperties.isAlive());
Assert.assertEquals(thread.isDaemon(), threadProperties.isDaemon()); assertEquals(thread.isDaemon(), threadProperties.isDaemon());
Assert.assertTrue(threadProperties.stackTrace().length > 0); assertTrue(threadProperties.stackTrace().length > 0);
executor.shutdownGracefully(); executor.shutdownGracefully();
} }
@Test(expected = RejectedExecutionException.class, timeout = 3000) @Test
public void testInvokeAnyInEventLoop() throws Throwable { public void testInvokeAnyInEventLoop() throws Throwable {
try { assertTimeoutPreemptively(ofSeconds(3), () -> {
testInvokeInEventLoop(true, false); var exception = assertThrows(CompletionException.class, () -> testInvokeInEventLoop(true, false));
} catch (CompletionException e) { assertThat(exception).hasCauseInstanceOf(RejectedExecutionException.class);
throw e.getCause(); });
}
} }
@Test(expected = RejectedExecutionException.class, timeout = 3000) @Test
public void testInvokeAnyInEventLoopWithTimeout() throws Throwable { public void testInvokeAnyInEventLoopWithTimeout() throws Throwable {
try { assertTimeoutPreemptively(ofSeconds(3), () -> {
testInvokeInEventLoop(true, true); var exception = assertThrows(CompletionException.class, () -> testInvokeInEventLoop(true, true));
} catch (CompletionException e) { assertThat(exception).hasCauseInstanceOf(RejectedExecutionException.class);
throw e.getCause(); });
}
} }
@Test(expected = RejectedExecutionException.class, timeout = 3000) @Test
public void testInvokeAllInEventLoop() throws Throwable { public void testInvokeAllInEventLoop() throws Throwable {
try { assertTimeoutPreemptively(ofSeconds(3), () -> {
testInvokeInEventLoop(false, false); var exception = assertThrows(CompletionException.class, () -> testInvokeInEventLoop(false, false));
} catch (CompletionException e) { assertThat(exception).hasCauseInstanceOf(RejectedExecutionException.class);
throw e.getCause(); });
}
} }
@Test(expected = RejectedExecutionException.class, timeout = 3000) @Test
public void testInvokeAllInEventLoopWithTimeout() throws Throwable { public void testInvokeAllInEventLoopWithTimeout() throws Throwable {
try { assertTimeoutPreemptively(ofSeconds(3), () -> {
testInvokeInEventLoop(false, true); var exception = assertThrows(CompletionException.class, () -> testInvokeInEventLoop(false, true));
} catch (CompletionException e) { assertThat(exception).hasCauseInstanceOf(RejectedExecutionException.class);
throw e.getCause(); });
}
} }
private static void testInvokeInEventLoop(final boolean any, final boolean timeout) { private static void testInvokeInEventLoop(final boolean any, final boolean timeout) {
@ -198,9 +186,6 @@ public class SingleThreadEventExecutorTest {
} }
}; };
final Runnable dummyTask = () -> {
};
final LinkedBlockingQueue<Future<?>> submittedTasks = new LinkedBlockingQueue<Future<?>>(); final LinkedBlockingQueue<Future<?>> submittedTasks = new LinkedBlockingQueue<Future<?>>();
final AtomicInteger attempts = new AtomicInteger(); final AtomicInteger attempts = new AtomicInteger();
final AtomicInteger rejects = new AtomicInteger(); final AtomicInteger rejects = new AtomicInteger();
@ -231,7 +216,7 @@ public class SingleThreadEventExecutorTest {
if (result) { if (result) {
attempts.incrementAndGet(); attempts.incrementAndGet();
try { try {
submittedTasks.add(submit(dummyTask)); submittedTasks.add(submit(DUMMY_TASK));
} catch (RejectedExecutionException e) { } catch (RejectedExecutionException e) {
// ignore, tasks are either accepted or rejected // ignore, tasks are either accepted or rejected
rejects.incrementAndGet(); rejects.incrementAndGet();
@ -242,24 +227,24 @@ public class SingleThreadEventExecutorTest {
}; };
// Start the loop // Start the loop
executor.submit(dummyTask).sync(); executor.submit(DUMMY_TASK).sync();
// Shutdown without any quiet period // Shutdown without any quiet period
executor.shutdownGracefully(0, 100, TimeUnit.MILLISECONDS).sync(); executor.shutdownGracefully(0, 100, TimeUnit.MILLISECONDS).sync();
// Ensure there are no user-tasks left. // Ensure there are no user-tasks left.
Assert.assertEquals(0, executor.drainTasks()); assertEquals(0, executor.drainTasks());
// Verify that queue is empty and all attempts either succeeded or were rejected // Verify that queue is empty and all attempts either succeeded or were rejected
Assert.assertTrue(taskQueue.isEmpty()); assertTrue(taskQueue.isEmpty());
Assert.assertTrue(attempts.get() > 0); assertTrue(attempts.get() > 0);
Assert.assertEquals(attempts.get(), submittedTasks.size() + rejects.get()); assertEquals(attempts.get(), submittedTasks.size() + rejects.get());
for (Future<?> f : submittedTasks) { for (Future<?> f : submittedTasks) {
Assert.assertTrue(f.isSuccess()); assertTrue(f.isSuccess());
} }
} }
@Test(timeout = 5000) @Test
public void testTakeTask() throws Exception { public void testTakeTask() throws Exception {
final SingleThreadEventExecutor executor = final SingleThreadEventExecutor executor =
new SingleThreadEventExecutor(Executors.defaultThreadFactory()) { new SingleThreadEventExecutor(Executors.defaultThreadFactory()) {
@ -274,26 +259,28 @@ public class SingleThreadEventExecutorTest {
} }
}; };
//add task assertTimeoutPreemptively(ofSeconds(5), () -> {
TestRunnable beforeTask = new TestRunnable(); //add task
executor.execute(beforeTask); TestRunnable beforeTask = new TestRunnable();
executor.execute(beforeTask);
//add scheduled task //add scheduled task
TestRunnable scheduledTask = new TestRunnable(); TestRunnable scheduledTask = new TestRunnable();
ScheduledFuture<?> f = executor.schedule(scheduledTask , 1500, TimeUnit.MILLISECONDS); ScheduledFuture<?> f = executor.schedule(scheduledTask , 1500, TimeUnit.MILLISECONDS);
//add task //add task
TestRunnable afterTask = new TestRunnable(); TestRunnable afterTask = new TestRunnable();
executor.execute(afterTask); executor.execute(afterTask);
f.sync(); f.sync();
assertThat(beforeTask.ran.get(), is(true)); assertThat(beforeTask.ran.get()).isTrue();
assertThat(scheduledTask.ran.get(), is(true)); assertThat(scheduledTask.ran.get()).isTrue();
assertThat(afterTask.ran.get(), is(true)); assertThat(afterTask.ran.get()).isTrue();
});
} }
@Test(timeout = 5000) @Test
public void testTakeTaskAlwaysHasTask() throws Exception { public void testTakeTaskAlwaysHasTask() throws Exception {
//for https://github.com/netty/netty/issues/1614 //for https://github.com/netty/netty/issues/1614
@ -310,24 +297,26 @@ public class SingleThreadEventExecutorTest {
} }
}; };
//add scheduled task assertTimeoutPreemptively(ofSeconds(5), () -> {
TestRunnable t = new TestRunnable(); //add scheduled task
final ScheduledFuture<?> f = executor.schedule(t, 1500, TimeUnit.MILLISECONDS); TestRunnable t = new TestRunnable();
final ScheduledFuture<?> f = executor.schedule(t, 1500, TimeUnit.MILLISECONDS);
//ensure always has at least one task in taskQueue //ensure always has at least one task in taskQueue
//check if scheduled tasks are triggered //check if scheduled tasks are triggered
executor.execute(new Runnable() { executor.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
if (!f.isDone()) { if (!f.isDone()) {
executor.execute(this); executor.execute(this);
}
} }
} });
f.sync();
assertThat(t.ran.get()).isTrue();
}); });
f.sync();
assertThat(t.ran.get(), is(true));
} }
private static final class TestRunnable implements Runnable { private static final class TestRunnable implements Runnable {