Repository: netty/netty
Issue: UnorderedThreadPoolEventExecutor#scheduleAtFixedRate not working as expected

### Expected behavior
The runnable passed to scheduleAtFixedRate() should run multiple times, not just once.

### Actual behavior
using UnorderedThreadPoolEventExecutor the runnable is executed just once, using directly the ScheduledThreadPoolExecutor or DefaultEventExecutorGroup the task is executed at fixed rate as expected

### Minimal yet complete reproducer code 
```java
public static void main(final String[] args) throws Exception {
  final UnorderedThreadPoolEventExecutor executor = new UnorderedThreadPoolEventExecutor(2);
  //final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2); // THIS WORKS
  //final DefaultEventExecutorGroup executor = new DefaultEventExecutorGroup(2); // THIS WORKS
  executor.scheduleAtFixedRate(() -> System.out.println("XXX"), 1, 1, TimeUnit.SECONDS);
  executor.awaitTermination(10, TimeUnit.SECONDS); // let it run...
}
```

### Netty version
4.1.52.Final 

### JVM version
latest OpenJDK 11 and 15


Issue comments:
hi~  I not sure UnorderedThreadPoolEventExecutor#scheduleAtFixedRate should work as you expected ,but as you expected ,you can change the code as I already commit. 

Pull request review comments:
After this unblocks we should cancel the scheduled task via `future.cancel()`. Also we should shutdown the `executor`.
nit: shutdownGracefully() ?
Alright, done.

Patch that resolved the issue:
diff --git a/common/src/main/java/io/netty/util/concurrent/UnorderedThreadPoolEventExecutor.java b/common/src/main/java/io/netty/util/concurrent/UnorderedThreadPoolEventExecutor.java
index 277c90322ad..dbf3f25fda6 100644
--- a/common/src/main/java/io/netty/util/concurrent/UnorderedThreadPoolEventExecutor.java
+++ b/common/src/main/java/io/netty/util/concurrent/UnorderedThreadPoolEventExecutor.java
@@ -161,12 +161,12 @@ public Iterator<EventExecutor> iterator() {
     @Override
     protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) {
         return runnable instanceof NonNotifyRunnable ?
-                task : new RunnableScheduledFutureTask<V>(this, runnable, task);
+                task : new RunnableScheduledFutureTask<V>(this, task);
     }
 
     @Override
     protected <V> RunnableScheduledFuture<V> decorateTask(Callable<V> callable, RunnableScheduledFuture<V> task) {
-        return new RunnableScheduledFutureTask<V>(this, callable, task);
+        return new RunnableScheduledFutureTask<V>(this, task);
     }
 
     @Override
@@ -213,15 +213,8 @@ private static final class RunnableScheduledFutureTask<V> extends PromiseTask<V>
             implements RunnableScheduledFuture<V>, ScheduledFuture<V> {
         private final RunnableScheduledFuture<V> future;
 
-        RunnableScheduledFutureTask(EventExecutor executor, Runnable runnable,
-                                           RunnableScheduledFuture<V> future) {
-            super(executor, runnable);
-            this.future = future;
-        }
-
-        RunnableScheduledFutureTask(EventExecutor executor, Callable<V> callable,
-                                           RunnableScheduledFuture<V> future) {
-            super(executor, callable);
+        RunnableScheduledFutureTask(EventExecutor executor, RunnableScheduledFuture<V> future) {
+            super(executor, future);
             this.future = future;
         }
 


Test patch:
diff --git a/common/src/test/java/io/netty/util/concurrent/UnorderedThreadPoolEventExecutorTest.java b/common/src/test/java/io/netty/util/concurrent/UnorderedThreadPoolEventExecutorTest.java
index d96db3fcb07..2f4e02dc0ba 100644
--- a/common/src/test/java/io/netty/util/concurrent/UnorderedThreadPoolEventExecutorTest.java
+++ b/common/src/test/java/io/netty/util/concurrent/UnorderedThreadPoolEventExecutorTest.java
@@ -19,6 +19,7 @@
 import org.junit.Test;
 
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 public class UnorderedThreadPoolEventExecutorTest {
 
@@ -54,4 +55,22 @@ public void operationComplete(Future<Object> future) throws Exception {
             executor.shutdownGracefully();
         }
     }
+
+    @Test(timeout = 10000)
+    public void scheduledAtFixedRateMustRunTaskRepeatedly() throws InterruptedException {
+        UnorderedThreadPoolEventExecutor executor = new UnorderedThreadPoolEventExecutor(1);
+        final CountDownLatch latch = new CountDownLatch(3);
+        Future<?> future = executor.scheduleAtFixedRate(new Runnable() {
+            @Override
+            public void run() {
+                latch.countDown();
+            }
+        }, 1, 1, TimeUnit.MILLISECONDS);
+        try {
+            latch.await();
+        } finally {
+            future.cancel(true);
+            executor.shutdownGracefully();
+        }
+    }
 }
diff --git a/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSocketChannelConfigTest.java b/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSocketChannelConfigTest.java
index 58cf5af646c..c2f200a771a 100644
--- a/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSocketChannelConfigTest.java
+++ b/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSocketChannelConfigTest.java
@@ -152,7 +152,12 @@ public void testGetOptionWhenClosed() {
             ch.config().getSoLinger();
             fail();
         } catch (ChannelException e) {
-            assertTrue(e.getCause() instanceof ClosedChannelException);
+            if (!(e.getCause() instanceof ClosedChannelException)) {
+                AssertionError error = new AssertionError(
+                        "Expected the suppressed exception to be an instance of ClosedChannelException.");
+                error.addSuppressed(e.getCause());
+                throw error;
+            }
         }
     }