Skip to content

Commit 8f392bf

Browse files
committed
Fix incorrect channel factory selection for custom EventLoopGroups
`LoopResources.onChannel()` and `onChannelClass()` incorrectly selected channel factories when users provided custom `EventLoopGroups` via `runOn()`. The previous logic only checked if `DefaultLoopNativeDetector.INSTANCE` (the highest-priority available transport) supported the group, falling back to `NIO` otherwise. This caused issues when, for example, `io_uring` was the default transport but a user explicitly provided an `Epoll` `EventLoopGroup` - the code would incorrectly return `NIO` channel factories instead of `Epoll` ones. Changes: - Add forGroup(EventLoopGroup) method to `DefaultLoopNativeDetector` that checks all available transports (`io_uring`, `epoll`, `kqueue`, `nio`) to find the correct channel factory for any given `EventLoopGroup` - Fix `DefaultLoopNIO.supportGroup()` to properly detect `NIO` event loops using `isCompatible(NioIoHandle.class)`, matching the pattern used by other transports - Update `onChannel()` and `onChannelClass()` to use `forGroup()` instead of the previous INSTANCE-or-NIO logic Signed-off-by: Jeff Bahr <[email protected]>
1 parent eb23743 commit 8f392bf

File tree

5 files changed

+312
-25
lines changed

5 files changed

+312
-25
lines changed

reactor-netty-core/src/main/java/reactor/netty/resources/DefaultLoopNIO.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020-2025 VMware, Inc. or its affiliates, All Rights Reserved.
2+
* Copyright (c) 2020-2026 VMware, Inc. or its affiliates, All Rights Reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,8 @@
1919

2020
import io.netty.channel.Channel;
2121
import io.netty.channel.EventLoopGroup;
22+
import io.netty.channel.IoEventLoopGroup;
23+
import io.netty.channel.nio.NioIoHandle;
2224
import io.netty.channel.socket.DatagramChannel;
2325
import io.netty.channel.socket.ServerSocketChannel;
2426
import io.netty.channel.socket.SocketChannel;
@@ -77,6 +79,9 @@ public EventLoopGroup newEventLoopGroup(int threads, ThreadFactory factory) {
7779

7880
@Override
7981
public boolean supportGroup(EventLoopGroup group) {
80-
return false;
82+
if (group instanceof ColocatedEventLoopGroup) {
83+
group = ((ColocatedEventLoopGroup) group).get();
84+
}
85+
return group instanceof IoEventLoopGroup && ((IoEventLoopGroup) group).isCompatible(NioIoHandle.class);
8186
}
8287
}

reactor-netty-core/src/main/java/reactor/netty/resources/DefaultLoopNativeDetector.java

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018-2022 VMware, Inc. or its affiliates, All Rights Reserved.
2+
* Copyright (c) 2018-2026 VMware, Inc. or its affiliates, All Rights Reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,6 +15,9 @@
1515
*/
1616
package reactor.netty.resources;
1717

18+
import io.netty.channel.EventLoopGroup;
19+
import org.jspecify.annotations.Nullable;
20+
1821
/**
1922
* Provides an {@link DefaultLoop} instance based on the available transport.
2023
*
@@ -26,20 +29,58 @@ final class DefaultLoopNativeDetector {
2629

2730
static final DefaultLoop NIO;
2831

32+
@Nullable
33+
static final DefaultLoop IO_URING;
34+
35+
@Nullable
36+
static final DefaultLoop EPOLL;
37+
38+
@Nullable
39+
static final DefaultLoop KQUEUE;
40+
2941
static {
3042
NIO = new DefaultLoopNIO();
43+
IO_URING = DefaultLoopIOUring.isIoUringAvailable ? new DefaultLoopIOUring() : null;
44+
EPOLL = DefaultLoopEpoll.isEpollAvailable ? new DefaultLoopEpoll() : null;
45+
KQUEUE = DefaultLoopKQueue.isKqueueAvailable ? new DefaultLoopKQueue() : null;
3146

32-
if (DefaultLoopIOUring.isIoUringAvailable) {
33-
INSTANCE = new DefaultLoopIOUring();
47+
if (IO_URING != null) {
48+
INSTANCE = IO_URING;
3449
}
35-
else if (DefaultLoopEpoll.isEpollAvailable) {
36-
INSTANCE = new DefaultLoopEpoll();
50+
else if (EPOLL != null) {
51+
INSTANCE = EPOLL;
3752
}
38-
else if (DefaultLoopKQueue.isKqueueAvailable) {
39-
INSTANCE = new DefaultLoopKQueue();
53+
else if (KQUEUE != null) {
54+
INSTANCE = KQUEUE;
4055
}
4156
else {
4257
INSTANCE = NIO;
4358
}
4459
}
60+
61+
/**
62+
* Finds the correct {@link DefaultLoop} for the given {@link EventLoopGroup}.
63+
* This method checks all available native transports to find one that supports
64+
* the provided group, falling back to NIO if none match.
65+
*
66+
* @param group the event loop group to find a matching channel factory for
67+
* @return the appropriate {@link DefaultLoop} for the group
68+
*/
69+
static DefaultLoop forGroup(EventLoopGroup group) {
70+
// Check each available native transport in priority order
71+
if (IO_URING != null && IO_URING.supportGroup(group)) {
72+
return IO_URING;
73+
}
74+
if (EPOLL != null && EPOLL.supportGroup(group)) {
75+
return EPOLL;
76+
}
77+
if (KQUEUE != null && KQUEUE.supportGroup(group)) {
78+
return KQUEUE;
79+
}
80+
if (NIO.supportGroup(group)) {
81+
return NIO;
82+
}
83+
// Fallback to NIO for unknown group types
84+
return NIO;
85+
}
4586
}

reactor-netty-core/src/main/java/reactor/netty/resources/LoopResources.java

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2011-2025 VMware, Inc. or its affiliates, All Rights Reserved.
2+
* Copyright (c) 2011-2026 VMware, Inc. or its affiliates, All Rights Reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -235,12 +235,7 @@ default Mono<Void> disposeLater(Duration quietPeriod, Duration timeout) {
235235
* @return a {@link Channel} instance
236236
*/
237237
default <CHANNEL extends Channel> CHANNEL onChannel(Class<CHANNEL> channelType, EventLoopGroup group) {
238-
DefaultLoop channelFactory =
239-
DefaultLoopNativeDetector.INSTANCE.supportGroup(group) ?
240-
DefaultLoopNativeDetector.INSTANCE :
241-
DefaultLoopNativeDetector.NIO;
242-
243-
return channelFactory.getChannel(channelType);
238+
return DefaultLoopNativeDetector.forGroup(group).getChannel(channelType);
244239
}
245240

246241
/**
@@ -252,12 +247,7 @@ default <CHANNEL extends Channel> CHANNEL onChannel(Class<CHANNEL> channelType,
252247
* @return a {@link Channel} class
253248
*/
254249
default <CHANNEL extends Channel> Class<? extends CHANNEL> onChannelClass(Class<CHANNEL> channelType, EventLoopGroup group) {
255-
DefaultLoop channelFactory =
256-
DefaultLoopNativeDetector.INSTANCE.supportGroup(group) ?
257-
DefaultLoopNativeDetector.INSTANCE :
258-
DefaultLoopNativeDetector.NIO;
259-
260-
return channelFactory.getChannelClass(channelType);
250+
return DefaultLoopNativeDetector.forGroup(group).getChannelClass(channelType);
261251
}
262252

263253
/**

reactor-netty-core/src/main/java17/reactor/netty/resources/DefaultLoopNIO.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020-2025 VMware, Inc. or its affiliates, All Rights Reserved.
2+
* Copyright (c) 2020-2026 VMware, Inc. or its affiliates, All Rights Reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,8 @@
1919

2020
import io.netty.channel.Channel;
2121
import io.netty.channel.EventLoopGroup;
22+
import io.netty.channel.IoEventLoopGroup;
23+
import io.netty.channel.nio.NioIoHandle;
2224
import io.netty.channel.socket.DatagramChannel;
2325
import io.netty.channel.socket.ServerSocketChannel;
2426
import io.netty.channel.socket.SocketChannel;
@@ -87,6 +89,9 @@ public EventLoopGroup newEventLoopGroup(int threads, ThreadFactory factory) {
8789

8890
@Override
8991
public boolean supportGroup(EventLoopGroup group) {
90-
return false;
92+
if (group instanceof ColocatedEventLoopGroup) {
93+
group = ((ColocatedEventLoopGroup) group).get();
94+
}
95+
return group instanceof IoEventLoopGroup && ((IoEventLoopGroup) group).isCompatible(NioIoHandle.class);
9196
}
9297
}

0 commit comments

Comments
 (0)