diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java b/client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java index 3db64e64ad..6bfa3f7fe6 100755 --- a/client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java +++ b/client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java @@ -26,6 +26,8 @@ import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; +import io.netty.channel.MultiThreadIoEventLoopGroup; +import io.netty.channel.MultithreadEventLoopGroup; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.ChannelGroupFuture; import io.netty.channel.group.DefaultChannelGroup; @@ -168,7 +170,9 @@ public ChannelManager(final AsyncHttpClientConfig config, Timer nettyTimer) { handshakeTimeout = config.getHandshakeTimeout(); // check if external EventLoopGroup is defined - ThreadFactory threadFactory = config.getThreadFactory() != null ? config.getThreadFactory() : new DefaultThreadFactory(config.getThreadPoolName()); + ThreadFactory threadFactory = config.getThreadFactory() != null + ? config.getThreadFactory() + : new DefaultThreadFactory(config.getThreadPoolName()); allowReleaseEventLoopGroup = config.getEventLoopGroup() == null; TransportFactory transportFactory; @@ -182,7 +186,17 @@ public ChannelManager(final AsyncHttpClientConfig config, Timer nettyTimer) { } else { eventLoopGroup = config.getEventLoopGroup(); - if (eventLoopGroup instanceof NioEventLoopGroup) { + if (eventLoopGroup instanceof MultiThreadIoEventLoopGroup) { + if (IoUringTransportFactory.isAvailable()) { + transportFactory = new IoUringTransportFactory(); + } else if (EpollTransportFactory.isAvailable()) { + transportFactory = new EpollTransportFactory(); + } else if (KQueueTransportFactory.isAvailable()) { + transportFactory = new KQueueTransportFactory(); + } else { + transportFactory = NioTransportFactory.INSTANCE; + } + } else if (eventLoopGroup instanceof NioEventLoopGroup) { transportFactory = NioTransportFactory.INSTANCE; } else if (isInstanceof(eventLoopGroup, "io.netty.channel.epoll.EpollEventLoopGroup")) { transportFactory = new EpollTransportFactory(); diff --git a/client/src/test/java/org/asynchttpclient/netty/channel/MultiThreadIoEventLoopGroupTest.java b/client/src/test/java/org/asynchttpclient/netty/channel/MultiThreadIoEventLoopGroupTest.java new file mode 100644 index 0000000000..8d68f14af8 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/netty/channel/MultiThreadIoEventLoopGroupTest.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2024 AsyncHttpClient Project. All rights reserved. + * + * Licensed 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 org.asynchttpclient.netty.channel; + +import io.netty.channel.MultiThreadIoEventLoopGroup; +import io.netty.channel.nio.NioIoHandler; +import io.netty.channel.uring.IoUring; +import io.netty.channel.uring.IoUringIoHandler; +import io.netty.util.concurrent.DefaultThreadFactory; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.DefaultAsyncHttpClientConfig; +import org.asynchttpclient.Dsl; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Tests for Netty 4.2+ MultiThreadIoEventLoopGroup support. + */ +class MultiThreadIoEventLoopGroupTest { + + @Test + void testMultiThreadIoEventLoopGroupWithNioHandler() { + // Create a Netty 4.2 style event loop group with NIO handler + MultiThreadIoEventLoopGroup eventLoopGroup = new MultiThreadIoEventLoopGroup( + 2, + new DefaultThreadFactory("test-nio"), + NioIoHandler.newFactory() + ); + + try { + // Should not throw IllegalArgumentException for unknown event loop group + DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder() + .setEventLoopGroup(eventLoopGroup) + .build(); + + AsyncHttpClient client = assertDoesNotThrow( + () -> Dsl.asyncHttpClient(config), + "Should accept MultiThreadIoEventLoopGroup with NIO handler" + ); + + assertNotNull(client, "Client should be created successfully"); + + try { + client.close(); + } catch (Exception e) { + fail("Failed to close client: " + e.getMessage()); + } + } finally { + eventLoopGroup.shutdownGracefully(); + } + } + + @Test + void testMultiThreadIoEventLoopGroupWithIoUringHandler() { + // Skip test if io_uring is not available on this platform + if (!IoUring.isAvailable()) { + return; + } + + // Create a Netty 4.2 style event loop group with io_uring handler + MultiThreadIoEventLoopGroup eventLoopGroup = new MultiThreadIoEventLoopGroup( + 2, + new DefaultThreadFactory("test-iouring"), + IoUringIoHandler.newFactory() + ); + + try { + // Should not throw IllegalArgumentException for unknown event loop group + DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder() + .setEventLoopGroup(eventLoopGroup) + .build(); + + AsyncHttpClient client = assertDoesNotThrow( + () -> Dsl.asyncHttpClient(config), + "Should accept MultiThreadIoEventLoopGroup with io_uring handler" + ); + + assertNotNull(client, "Client should be created successfully"); + + try { + client.close(); + } catch (Exception e) { + fail("Failed to close client: " + e.getMessage()); + } + } finally { + eventLoopGroup.shutdownGracefully(); + } + } + + @Test + void testMultiThreadIoEventLoopGroupWithUseNativeTransportFalse() { + // Create event loop group with NIO handler + MultiThreadIoEventLoopGroup eventLoopGroup = new MultiThreadIoEventLoopGroup( + 2, + new DefaultThreadFactory("test-nio-nonative"), + NioIoHandler.newFactory() + ); + + try { + // Explicitly disable native transport + DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder() + .setEventLoopGroup(eventLoopGroup) + .setUseNativeTransport(false) + .build(); + + AsyncHttpClient client = assertDoesNotThrow( + () -> Dsl.asyncHttpClient(config), + "Should accept MultiThreadIoEventLoopGroup even with useNativeTransport=false" + ); + + assertNotNull(client, "Client should be created successfully"); + + try { + client.close(); + } catch (Exception e) { + fail("Failed to close client: " + e.getMessage()); + } + } finally { + eventLoopGroup.shutdownGracefully(); + } + } + + @Test + void testSharedMultiThreadIoEventLoopGroupWithIoUring() { + // Skip test if io_uring is not available on this platform + if (!IoUring.isAvailable()) { + return; + } + + MultiThreadIoEventLoopGroup sharedEventLoopGroup = new MultiThreadIoEventLoopGroup( + Runtime.getRuntime().availableProcessors() * 2, + new DefaultThreadFactory("shared-iouring"), + IoUringIoHandler.newFactory() + ); + + try { + DefaultAsyncHttpClientConfig config1 = new DefaultAsyncHttpClientConfig.Builder() + .setEventLoopGroup(sharedEventLoopGroup) + .setThreadPoolName("Client1-Thread-Pool") + .build(); + + DefaultAsyncHttpClientConfig config2 = new DefaultAsyncHttpClientConfig.Builder() + .setEventLoopGroup(sharedEventLoopGroup) + .setThreadPoolName("Client2-Thread-Pool") + .build(); + + DefaultAsyncHttpClientConfig config3 = new DefaultAsyncHttpClientConfig.Builder() + .setEventLoopGroup(sharedEventLoopGroup) + .setThreadPoolName("Client3-Thread-Pool") + .build(); + + // All clients should be created successfully without IllegalArgumentException + AsyncHttpClient client1 = assertDoesNotThrow( + () -> Dsl.asyncHttpClient(config1), + "First client should accept shared MultiThreadIoEventLoopGroup with io_uring" + ); + + AsyncHttpClient client2 = assertDoesNotThrow( + () -> Dsl.asyncHttpClient(config2), + "Second client should accept shared MultiThreadIoEventLoopGroup with io_uring" + ); + + AsyncHttpClient client3 = assertDoesNotThrow( + () -> Dsl.asyncHttpClient(config3), + "Third client should accept shared MultiThreadIoEventLoopGroup with io_uring" + ); + + assertNotNull(client1, "Client 1 should be created successfully"); + assertNotNull(client2, "Client 2 should be created successfully"); + assertNotNull(client3, "Client 3 should be created successfully"); + + try { + client1.close(); + client2.close(); + client3.close(); + } catch (Exception e) { + fail("Failed to close clients: " + e.getMessage()); + } + } finally { + sharedEventLoopGroup.shutdownGracefully(); + } + } +}