Skip to content

Commit

Permalink
Don't load netty-transport-native classes when native lib isn't avail…
Browse files Browse the repository at this point in the history
…able (#1539)

Motivation:

1. Do not try to load native libraries when it's known that OS does not
support it.
2. Log when OS supports native transport, but native lib cannot be
loaded.
3. Prevent class loading of any classes related to native transport when
it's not available.

Modifications:

- Enhance `NativeTransportUtils` to protect class loading when it's
known that OS does not support a specific type of transport;
- Log when native transport can not be loaded;

Result:

1. Less unused classes are loaded in memory.
2. Users are notified when native transport can be used but is not
loaded properly.
  • Loading branch information
idelpivnitskiy authored May 11, 2021
1 parent 63f30f0 commit c525813
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2018, 2020 Apple Inc. and the ServiceTalk project authors
* Copyright © 2018, 2020-2021 Apple Inc. and the ServiceTalk project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,18 +19,15 @@
import io.servicetalk.transport.api.HostAndPort;

import io.netty.channel.Channel;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.ServerChannel;
import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollDomainSocketChannel;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerDomainSocketChannel;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.kqueue.KQueueDatagramChannel;
import io.netty.channel.kqueue.KQueueDomainSocketChannel;
import io.netty.channel.kqueue.KQueueEventLoopGroup;
import io.netty.channel.kqueue.KQueueServerDomainSocketChannel;
import io.netty.channel.kqueue.KQueueServerSocketChannel;
import io.netty.channel.kqueue.KQueueSocketChannel;
Expand All @@ -51,6 +48,8 @@
import javax.annotation.Nullable;

import static io.netty.util.NetUtil.createByteArrayFromIpAddressString;
import static io.servicetalk.transport.netty.internal.NativeTransportUtils.useEpoll;
import static io.servicetalk.transport.netty.internal.NativeTransportUtils.useKQueue;
import static java.net.InetAddress.getByAddress;

/**
Expand All @@ -61,32 +60,6 @@ private BuilderUtils() {
// Utility methods only
}

/**
* Returns {@code true} if native epoll transport should be used.
*
* @param group the used {@link EventLoopGroup}
* @return {@code true} if native transport should be used
*/
public static boolean useEpoll(EventLoopGroup group) {
// Check if we should use the epoll transport. This is true if either the EpollEventLoopGroup is used directly
// or if the passed group is a EventLoop and it's parent is an EpollEventLoopGroup.
return group instanceof EpollEventLoopGroup || (group instanceof EventLoop &&
((EventLoop) group).parent() instanceof EpollEventLoopGroup);
}

/**
* Returns {@code true} if native kqueue transport should be used.
*
* @param group the used {@link EventLoopGroup}
* @return {@code true} if native transport should be used
*/
public static boolean useKQueue(EventLoopGroup group) {
// Check if we should use the kqueue transport. This is true if either the KQueueEventLoopGroup is used directly
// or if the passed group is a EventLoop and it's parent is an KQueueEventLoopGroup.
return group instanceof KQueueEventLoopGroup || (group instanceof EventLoop &&
((EventLoop) group).parent() instanceof KQueueEventLoopGroup);
}

/**
* Returns the correct {@link Class} to use with the given {@link EventLoopGroup}.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2018 Apple Inc. and the ServiceTalk project authors
* Copyright © 2018, 2021 Apple Inc. and the ServiceTalk project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,31 +15,123 @@
*/
package io.servicetalk.transport.netty.internal;

import io.servicetalk.transport.api.DomainSocketAddress;
import io.servicetalk.transport.api.FileDescriptorSocketAddress;

import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.kqueue.KQueue;
import io.netty.channel.kqueue.KQueueEventLoopGroup;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.internal.PlatformDependent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static io.netty.util.internal.PlatformDependent.normalizedArch;

/**
* Utility to check availability of Netty <a href="https://netty.io/wiki/native-transports.html">native transports</a>.
* <p>
* It also prevents the load of classes and libraries when OS does not support it, and logs when OS supports but
* libraries are not available.
*/
final class NativeTransportUtils {

private static final Logger LOGGER = LoggerFactory.getLogger(NativeTransportUtils.class);

private static final boolean IS_LINUX;
private static final boolean IS_OSX_OR_BSD;

static {
final String os = PlatformDependent.normalizedOs();
IS_LINUX = "linux".equals(os);
IS_OSX_OR_BSD = "osx".equals(os) || os.contains("bsd");

if (IS_LINUX && !Epoll.isAvailable()) {
logUnavailability("epoll", os, Epoll.unavailabilityCause());
} else if (IS_OSX_OR_BSD && !KQueue.isAvailable()) {
logUnavailability("kqueue", "osx", KQueue.unavailabilityCause());
}
}

private NativeTransportUtils() {
// No instances
}

private static void logUnavailability(final String transport, final String os, final Throwable cause) {
LOGGER.warn("Can not load \"io.netty:netty-transport-native-{}:$nettyVersion:{}-{}\", it may impact " +
"performance of the application. See https://netty.io/wiki/native-transports.html",
transport, os, normalizedArch(), cause);
}

/**
* Determine if {@link Epoll} is available.
*
* @return {@code true} if {@link Epoll} is available
*/
static boolean isEpollAvailable() {
return IS_LINUX && Epoll.isAvailable();
}

/**
* Determine if {@link KQueue} is available.
*
* @return {@code true} if {@link KQueue} is available
*/
static boolean isKQueueAvailable() {
return IS_OSX_OR_BSD && KQueue.isAvailable();
}

/**
* Returns {@code true} if native {@link Epoll} transport could be used.
*
* @param group the used {@link EventLoopGroup}
* @return {@code true} if native {@link Epoll} transport could be used
*/
static boolean useEpoll(final EventLoopGroup group) {
if (!isEpollAvailable()) {
return false;
}
// Check if we should use the epoll transport. This is true if either the EpollEventLoopGroup is used directly
// or if the passed group is a EventLoop and it's parent is an EpollEventLoopGroup.
return group instanceof EpollEventLoopGroup || (group instanceof EventLoop &&
((EventLoop) group).parent() instanceof EpollEventLoopGroup);
}

/**
* Returns {@code true} if native {@link KQueue} transport could be used.
*
* @param group the used {@link EventLoopGroup}
* @return {@code true} if native {@link KQueue} transport could be used
*/
static boolean useKQueue(final EventLoopGroup group) {
if (!isKQueueAvailable()) {
return false;
}
// Check if we should use the kqueue transport. This is true if either the KQueueEventLoopGroup is used directly
// or if the passed group is a EventLoop and it's parent is an KQueueEventLoopGroup.
return group instanceof KQueueEventLoopGroup || (group instanceof EventLoop &&
((EventLoop) group).parent() instanceof KQueueEventLoopGroup);
}

/**
* Determine if the {@code group} supports UDS.
* Determine if {@link DomainSocketAddress} is supported.
*
* @param group the group to test.
* @return {@code true} if UDS are supported by {@code group}.
* @return {@code true} if {@link DomainSocketAddress} is supported by {@code group}
*/
static boolean isUnixDomainSocketSupported(EventExecutorGroup group) {
return group instanceof EpollEventLoopGroup || group instanceof KQueueEventLoopGroup;
static boolean isUnixDomainSocketSupported(final EventLoopGroup group) {
return useEpoll(group) || useKQueue(group);
}

/**
* Determine if {@code FileDescriptorSocketAddress} is supported.
* Determine if {@link FileDescriptorSocketAddress} is supported.
*
* @param group the group to test.
* @return {@code true} if {@code FileDescriptorSocketAddress} are supported by {@code group}.
* @return {@code true} if {@link FileDescriptorSocketAddress} is supported by {@code group}
*/
static boolean isFileDescriptorSocketAddressSupported(EventExecutorGroup group) {
return group instanceof EpollEventLoopGroup || group instanceof KQueueEventLoopGroup;
static boolean isFileDescriptorSocketAddressSupported(final EventLoopGroup group) {
return useEpoll(group) || useKQueue(group);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2018 Apple Inc. and the ServiceTalk project authors
* Copyright © 2018, 2021 Apple Inc. and the ServiceTalk project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,14 +19,14 @@

import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.kqueue.KQueue;
import io.netty.channel.kqueue.KQueueEventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;

import java.util.concurrent.ThreadFactory;

import static io.servicetalk.transport.netty.internal.NativeTransportUtils.isEpollAvailable;
import static io.servicetalk.transport.netty.internal.NativeTransportUtils.isKQueueAvailable;
import static java.lang.Runtime.getRuntime;
import static java.util.Objects.requireNonNull;

Expand Down Expand Up @@ -70,8 +70,8 @@ public static EventLoopAwareNettyIoExecutor createIoExecutor(int ioThreads, Thre
*/
public static EventLoopGroup createEventLoopGroup(int ioThreads, ThreadFactory threadFactory) {
validateIoThreads(ioThreads);
return Epoll.isAvailable() ? new EpollEventLoopGroup(ioThreads, threadFactory) :
KQueue.isAvailable() ? new KQueueEventLoopGroup(ioThreads, threadFactory) :
return isEpollAvailable() ? new EpollEventLoopGroup(ioThreads, threadFactory) :
isKQueueAvailable() ? new KQueueEventLoopGroup(ioThreads, threadFactory) :
new NioEventLoopGroup(ioThreads, threadFactory);
}

Expand Down

0 comments on commit c525813

Please sign in to comment.