Skip to content

Commit 4433e73

Browse files
authored
Add MacOS Workflow and IO_URING Transport (#1834)
* Add MacOS Workflow and IO_URING Transport
1 parent ddeade9 commit 4433e73

14 files changed

+236
-115
lines changed

.github/workflows/maven.yml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ on:
1313
default: 'GitHub Actions'
1414

1515
jobs:
16-
Build:
16+
RunOnLinux:
1717
runs-on: ubuntu-latest
1818
steps:
1919
- uses: actions/checkout@v3
@@ -25,3 +25,16 @@ jobs:
2525
java-version: '11'
2626
- name: Run Tests
2727
run: ./mvnw -B -ntp clean test
28+
29+
RunOnMacOs:
30+
runs-on: macos-latest
31+
steps:
32+
- uses: actions/checkout@v3
33+
- name: Grant Permission
34+
run: sudo chmod +x ./mvnw
35+
- uses: actions/setup-java@v3
36+
with:
37+
distribution: 'corretto'
38+
java-version: '11'
39+
- name: Run Tests
40+
run: ./mvnw -B -ntp clean test

client/pom.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,5 +153,13 @@
153153
<version>${hamcrest.version}</version>
154154
<scope>test</scope>
155155
</dependency>
156+
157+
<!-- https://mvnrepository.com/artifact/io.github.artsok/rerunner-jupiter -->
158+
<dependency>
159+
<groupId>io.github.artsok</groupId>
160+
<artifactId>rerunner-jupiter</artifactId>
161+
<version>2.1.6</version>
162+
<scope>test</scope>
163+
</dependency>
156164
</dependencies>
157165
</project>

client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,8 @@ public interface AsyncHttpClientConfig {
303303

304304
boolean isUseNativeTransport();
305305

306+
boolean isUseOnlyEpollNativeTransport();
307+
306308
Consumer<Channel> getHttpAdditionalChannelInitializer();
307309

308310
Consumer<Channel> getWsAdditionalChannelInitializer();

client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ public DefaultAsyncHttpClient(AsyncHttpClientConfig config) {
106106
}
107107
}
108108

109+
// Visible for testing
110+
ChannelManager channelManager() {
111+
return channelManager;
112+
}
113+
109114
private static Timer newNettyTimer(AsyncHttpClientConfig config) {
110115
ThreadFactory threadFactory = config.getThreadFactory() != null ? config.getThreadFactory() : new DefaultThreadFactory(config.getThreadPoolName() + "-timer");
111116
HashedWheelTimer timer = new HashedWheelTimer(threadFactory, config.getHashedWheelTimerTickDuration(), TimeUnit.MILLISECONDS, config.getHashedWheelTimerSize());

client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultUseInsecureTrustManager;
9292
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultUseLaxCookieEncoder;
9393
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultUseNativeTransport;
94+
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultUseOnlyEpollNativeTransport;
9495
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultUseOpenSsl;
9596
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultUseProxyProperties;
9697
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultUseProxySelector;
@@ -179,6 +180,7 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {
179180
private final Map<ChannelOption<Object>, Object> channelOptions;
180181
private final EventLoopGroup eventLoopGroup;
181182
private final boolean useNativeTransport;
183+
private final boolean useOnlyEpollNativeTransport;
182184
private final ByteBufAllocator allocator;
183185
private final boolean tcpNoDelay;
184186
private final boolean soReuseAddress;
@@ -273,6 +275,7 @@ private DefaultAsyncHttpClientConfig(// http
273275
Map<ChannelOption<Object>, Object> channelOptions,
274276
EventLoopGroup eventLoopGroup,
275277
boolean useNativeTransport,
278+
boolean useOnlyEpollNativeTransport,
276279
ByteBufAllocator allocator,
277280
Timer nettyTimer,
278281
ThreadFactory threadFactory,
@@ -363,6 +366,12 @@ private DefaultAsyncHttpClientConfig(// http
363366
this.channelOptions = channelOptions;
364367
this.eventLoopGroup = eventLoopGroup;
365368
this.useNativeTransport = useNativeTransport;
369+
this.useOnlyEpollNativeTransport = useOnlyEpollNativeTransport;
370+
371+
if (useOnlyEpollNativeTransport && !useNativeTransport) {
372+
throw new IllegalArgumentException("Native Transport must be enabled to use Epoll Native Transport only");
373+
}
374+
366375
this.allocator = allocator;
367376
this.nettyTimer = nettyTimer;
368377
this.threadFactory = threadFactory;
@@ -703,6 +712,11 @@ public boolean isUseNativeTransport() {
703712
return useNativeTransport;
704713
}
705714

715+
@Override
716+
public boolean isUseOnlyEpollNativeTransport() {
717+
return useOnlyEpollNativeTransport;
718+
}
719+
706720
@Override
707721
public ByteBufAllocator getAllocator() {
708722
return allocator;
@@ -832,6 +846,7 @@ public static class Builder {
832846
private int httpClientCodecInitialBufferSize = defaultHttpClientCodecInitialBufferSize();
833847
private int chunkedFileChunkSize = defaultChunkedFileChunkSize();
834848
private boolean useNativeTransport = defaultUseNativeTransport();
849+
private boolean useOnlyEpollNativeTransport = defaultUseOnlyEpollNativeTransport();
835850
private ByteBufAllocator allocator;
836851
private final Map<ChannelOption<Object>, Object> channelOptions = new HashMap<>();
837852
private EventLoopGroup eventLoopGroup;
@@ -918,6 +933,8 @@ public Builder(AsyncHttpClientConfig config) {
918933
channelOptions.putAll(config.getChannelOptions());
919934
eventLoopGroup = config.getEventLoopGroup();
920935
useNativeTransport = config.isUseNativeTransport();
936+
useOnlyEpollNativeTransport = config.isUseOnlyEpollNativeTransport();
937+
921938
allocator = config.getAllocator();
922939
nettyTimer = config.getNettyTimer();
923940
threadFactory = config.getThreadFactory();
@@ -1309,6 +1326,11 @@ public Builder setUseNativeTransport(boolean useNativeTransport) {
13091326
return this;
13101327
}
13111328

1329+
public Builder setUseOnlyEpollNativeTransport(boolean useOnlyEpollNativeTransport) {
1330+
this.useOnlyEpollNativeTransport = useOnlyEpollNativeTransport;
1331+
return this;
1332+
}
1333+
13121334
public Builder setAllocator(ByteBufAllocator allocator) {
13131335
this.allocator = allocator;
13141336
return this;
@@ -1426,6 +1448,7 @@ public DefaultAsyncHttpClientConfig build() {
14261448
channelOptions.isEmpty() ? Collections.emptyMap() : Collections.unmodifiableMap(channelOptions),
14271449
eventLoopGroup,
14281450
useNativeTransport,
1451+
useOnlyEpollNativeTransport,
14291452
allocator,
14301453
nettyTimer,
14311454
threadFactory,

client/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigDefaults.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public final class AsyncHttpClientConfigDefaults {
7070
public static final String SHUTDOWN_QUIET_PERIOD_CONFIG = "shutdownQuietPeriod";
7171
public static final String SHUTDOWN_TIMEOUT_CONFIG = "shutdownTimeout";
7272
public static final String USE_NATIVE_TRANSPORT_CONFIG = "useNativeTransport";
73+
public static final String USE_ONLY_EPOLL_NATIVE_TRANSPORT = "useOnlyEpollNativeTransport";
7374
public static final String IO_THREADS_COUNT_CONFIG = "ioThreadsCount";
7475
public static final String HASHED_WHEEL_TIMER_TICK_DURATION = "hashedWheelTimerTickDuration";
7576
public static final String HASHED_WHEEL_TIMER_SIZE = "hashedWheelTimerSize";
@@ -294,6 +295,10 @@ public static boolean defaultUseNativeTransport() {
294295
return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + USE_NATIVE_TRANSPORT_CONFIG);
295296
}
296297

298+
public static boolean defaultUseOnlyEpollNativeTransport() {
299+
return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + USE_ONLY_EPOLL_NATIVE_TRANSPORT);
300+
}
301+
297302
public static int defaultIoThreadsCount() {
298303
return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + IO_THREADS_COUNT_CONFIG);
299304
}

client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import io.netty.handler.proxy.Socks5ProxyHandler;
4343
import io.netty.handler.ssl.SslHandler;
4444
import io.netty.handler.stream.ChunkedWriteHandler;
45+
import io.netty.incubator.channel.uring.IOUringEventLoopGroup;
4546
import io.netty.resolver.NameResolver;
4647
import io.netty.util.Timer;
4748
import io.netty.util.concurrent.DefaultThreadFactory;
@@ -140,12 +141,11 @@ public ChannelManager(final AsyncHttpClientConfig config, Timer nettyTimer) {
140141

141142
if (allowReleaseEventLoopGroup) {
142143
if (config.isUseNativeTransport()) {
143-
transportFactory = getNativeTransportFactory();
144+
transportFactory = getNativeTransportFactory(config);
144145
} else {
145146
transportFactory = NioTransportFactory.INSTANCE;
146147
}
147148
eventLoopGroup = transportFactory.newEventLoopGroup(config.getIoThreadsCount(), threadFactory);
148-
149149
} else {
150150
eventLoopGroup = config.getEventLoopGroup();
151151

@@ -155,6 +155,8 @@ public ChannelManager(final AsyncHttpClientConfig config, Timer nettyTimer) {
155155
transportFactory = new EpollTransportFactory();
156156
} else if (eventLoopGroup instanceof KQueueEventLoopGroup) {
157157
transportFactory = new KQueueTransportFactory();
158+
} else if (eventLoopGroup instanceof IOUringEventLoopGroup) {
159+
transportFactory = new IoUringIncubatorTransportFactory();
158160
} else {
159161
throw new IllegalArgumentException("Unknown event loop group " + eventLoopGroup.getClass().getSimpleName());
160162
}
@@ -167,6 +169,30 @@ public ChannelManager(final AsyncHttpClientConfig config, Timer nettyTimer) {
167169
httpBootstrap.option(ChannelOption.AUTO_READ, false);
168170
}
169171

172+
private static TransportFactory<? extends Channel, ? extends EventLoopGroup> getNativeTransportFactory(AsyncHttpClientConfig config) {
173+
// If we are running on macOS then use KQueue
174+
if (PlatformDependent.isOsx()) {
175+
if (KQueueTransportFactory.isAvailable()) {
176+
return new KQueueTransportFactory();
177+
}
178+
}
179+
180+
// If we're not running on Windows then we're probably running on Linux.
181+
// We will check if Io_Uring is available or not. If available, return IoUringIncubatorTransportFactory.
182+
// Else
183+
// We will check if Epoll is available or not. If available, return EpollTransportFactory.
184+
// If none of the condition matches then no native transport is available, and we will throw an exception.
185+
if (!PlatformDependent.isWindows()) {
186+
if (IoUringIncubatorTransportFactory.isAvailable() && !config.isUseOnlyEpollNativeTransport()) {
187+
return new IoUringIncubatorTransportFactory();
188+
} else if (EpollTransportFactory.isAvailable()) {
189+
return new EpollTransportFactory();
190+
}
191+
}
192+
193+
throw new IllegalArgumentException("No suitable native transport (Epoll, Io_Uring or KQueue) available");
194+
}
195+
170196
public static boolean isSslHandlerConfigured(ChannelPipeline pipeline) {
171197
return pipeline.get(SSL_HANDLER) != null;
172198
}
@@ -202,24 +228,6 @@ private static Bootstrap newBootstrap(ChannelFactory<? extends Channel> channelF
202228
return bootstrap;
203229
}
204230

205-
private static TransportFactory<? extends Channel, ? extends EventLoopGroup> getNativeTransportFactory() {
206-
String nativeTransportFactoryClassName = null;
207-
if (PlatformDependent.isOsx()) {
208-
nativeTransportFactoryClassName = "org.asynchttpclient.netty.channel.KQueueTransportFactory";
209-
} else if (!PlatformDependent.isWindows()) {
210-
nativeTransportFactoryClassName = "org.asynchttpclient.netty.channel.EpollTransportFactory";
211-
}
212-
213-
try {
214-
if (nativeTransportFactoryClassName != null) {
215-
return (TransportFactory<? extends Channel, ? extends EventLoopGroup>) Class.forName(nativeTransportFactoryClassName).newInstance();
216-
}
217-
} catch (Exception ignored) {
218-
// Ignore
219-
}
220-
throw new IllegalArgumentException("No suitable native transport (epoll or kqueue) available");
221-
}
222-
223231
public void configureBootstraps(NettyRequestSender requestSender) {
224232
final AsyncHttpClientHandler httpHandler = new HttpHandler(config, this, requestSender);
225233
wsHandler = new WebSocketHandler(config, this, requestSender);

client/src/main/java/org/asynchttpclient/netty/channel/EpollTransportFactory.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,13 @@
2121

2222
class EpollTransportFactory implements TransportFactory<EpollSocketChannel, EpollEventLoopGroup> {
2323

24-
EpollTransportFactory() {
24+
static boolean isAvailable() {
2525
try {
2626
Class.forName("io.netty.channel.epoll.Epoll");
2727
} catch (ClassNotFoundException e) {
28-
throw new IllegalStateException("The epoll transport is not available");
29-
}
30-
if (!Epoll.isAvailable()) {
31-
throw new IllegalStateException("The epoll transport is not supported");
28+
return false;
3229
}
30+
return Epoll.isAvailable();
3331
}
3432

3533
@Override
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright (c) 2022 AsyncHttpClient Project. All rights reserved.
3+
*
4+
* This program is licensed to you under the Apache License Version 2.0,
5+
* and you may not use this file except in compliance with the Apache License Version 2.0.
6+
* You may obtain a copy of the Apache License Version 2.0 at
7+
* http://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* Unless required by applicable law or agreed to in writing,
10+
* software distributed under the Apache License Version 2.0 is distributed on an
11+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
13+
*/
14+
package org.asynchttpclient.netty.channel;
15+
16+
import io.netty.incubator.channel.uring.IOUring;
17+
import io.netty.incubator.channel.uring.IOUringEventLoopGroup;
18+
import io.netty.incubator.channel.uring.IOUringSocketChannel;
19+
20+
import java.util.concurrent.ThreadFactory;
21+
22+
class IoUringIncubatorTransportFactory implements TransportFactory<IOUringSocketChannel, IOUringEventLoopGroup> {
23+
24+
static boolean isAvailable() {
25+
try {
26+
Class.forName("io.netty.incubator.channel.uring.IOUring");
27+
} catch (ClassNotFoundException e) {
28+
return false;
29+
}
30+
return IOUring.isAvailable();
31+
}
32+
33+
@Override
34+
public IOUringSocketChannel newChannel() {
35+
return new IOUringSocketChannel();
36+
}
37+
38+
@Override
39+
public IOUringEventLoopGroup newEventLoopGroup(int ioThreadsCount, ThreadFactory threadFactory) {
40+
return new IOUringEventLoopGroup(ioThreadsCount, threadFactory);
41+
}
42+
}

client/src/main/java/org/asynchttpclient/netty/channel/KQueueTransportFactory.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,13 @@
2121

2222
class KQueueTransportFactory implements TransportFactory<KQueueSocketChannel, KQueueEventLoopGroup> {
2323

24-
KQueueTransportFactory() {
24+
static boolean isAvailable() {
2525
try {
2626
Class.forName("io.netty.channel.kqueue.KQueue");
2727
} catch (ClassNotFoundException e) {
28-
throw new IllegalStateException("The kqueue transport is not available");
29-
}
30-
if (!KQueue.isAvailable()) {
31-
throw new IllegalStateException("The kqueue transport is not supported");
28+
return false;
3229
}
30+
return KQueue.isAvailable();
3331
}
3432

3533
@Override

client/src/test/java/org/asynchttpclient/AsyncHttpClientDefaultsTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@
2727

2828
public class AsyncHttpClientDefaultsTest {
2929

30+
@Test
31+
public void testDefaultUseOnlyEpollNativeTransport() {
32+
assertFalse(AsyncHttpClientConfigDefaults.defaultUseOnlyEpollNativeTransport());
33+
testBooleanSystemProperty("useOnlyEpollNativeTransport", "defaultUseOnlyEpollNativeTransport", "false");
34+
}
35+
3036
@Test
3137
public void testDefaultMaxTotalConnections() {
3238
assertEquals(AsyncHttpClientConfigDefaults.defaultMaxConnections(), -1);

0 commit comments

Comments
 (0)