Remove dependency on GLIBC 2.12 by using syscalls directly (#9797)

Motivation:

394a1b3485 introduced a hard dependency on GLIBC 2.12 which was not the case before. This had the effect of not be able to use the native epoll transports on platforms which ship with earlier versions of GLIBC.
To make things a backward compatible as possible we should not introduce such changes in a bugfix release.

Special thanks to @weissi with all the help to fix this.

Modifications:

- Use syscalls directly to remove dependency on GLIBC 2.12
- Make code consistent that needs newer GLIBC versions
- Adjust scattering read test to only run if recvmmsg syscall is supported
- Cleanup pom.xml as some stuff is not needed anymore after using syscalls.

Result:

Fixes https://github.com/netty/netty/issues/9758.
This commit is contained in:
Norman Maurer 2019-11-23 21:12:24 +01:00
parent b7b6156505
commit 3add35fe82
6 changed files with 49 additions and 109 deletions

View File

@ -34,6 +34,7 @@
<unix.common.lib.dir>${project.build.directory}/unix-common-lib</unix.common.lib.dir>
<unix.common.lib.unpacked.dir>${unix.common.lib.dir}/META-INF/native/lib</unix.common.lib.unpacked.dir>
<unix.common.include.unpacked.dir>${unix.common.lib.dir}/META-INF/native/include</unix.common.include.unpacked.dir>
<jni.compiler.args.cflags>CFLAGS=-O3 -Werror -fno-omit-frame-pointer -Wunused-variable -fvisibility=hidden -I${unix.common.include.unpacked.dir}</jni.compiler.args.cflags>
<jni.compiler.args.ldflags>LDFLAGS=-L${unix.common.lib.unpacked.dir} -Wl,--no-as-needed -lrt -Wl,--whole-archive -l${unix.common.lib.name} -Wl,--no-whole-archive</jni.compiler.args.ldflags>
<nativeSourceDirectory>${project.basedir}/src/main/c</nativeSourceDirectory>
<skipTests>true</skipTests>
@ -192,102 +193,6 @@
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<!-- Phase must be before regex-glibc-sendmmsg and regex-linux-sendmmsg -->
<phase>validate</phase>
<goals>
<goal>run</goal>
</goals>
<id>ant-get-systeminfo</id>
<configuration>
<exportAntProperties>true</exportAntProperties>
<tasks>
<exec executable="sh" outputproperty="ldd_version">
<arg value="-c" />
<arg value="ldd --version | tail | head -1" />
</exec>
<exec executable="uname" outputproperty="uname_os_version">
<arg value="-r" />
</exec>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<!-- Phase must be before regex-combined-sendmmsg -->
<phase>initialize</phase>
<id>regex-glibc-sendmmsg</id>
<goals>
<goal>regex-property</goal>
</goals>
<configuration>
<name>glibc.sendmmsg.support</name>
<value>${ldd_version}</value>
<!-- Version must be >= 2.14 - set to IO_NETTY_SENDMSSG_NOT_FOUND if this version is not satisfied -->
<regex>^((?!^[^)]+\)\s+(0*2\.1[4-9]|0*2\.[2-9][0-9]+|0*[3-9][0-9]*|0*[1-9]+[0-9]+).*).)*$</regex>
<replacement>IO_NETTY_SENDMSSG_NOT_FOUND</replacement>
<failIfNoMatch>false</failIfNoMatch>
</configuration>
</execution>
<execution>
<!-- Phase must be before regex-combined-sendmmsg -->
<phase>initialize</phase>
<id>regex-linux-sendmmsg</id>
<goals>
<goal>regex-property</goal>
</goals>
<configuration>
<name>linux.sendmmsg.support</name>
<value>${uname_os_version}</value>
<!-- Version must be >= 3 - set to IO_NETTY_SENDMSSG_NOT_FOUND if this version is not satisfied -->
<regex>^((?!^[0-9]*[3-9]\.?.*).)*$</regex>
<replacement>IO_NETTY_SENDMSSG_NOT_FOUND</replacement>
<failIfNoMatch>false</failIfNoMatch>
</configuration>
</execution>
<execution>
<!-- Phase must be before regex-unset-if-needed-sendmmsg -->
<phase>generate-sources</phase>
<id>regex-combined-sendmmsg</id>
<goals>
<goal>regex-property</goal>
</goals>
<configuration>
<name>jni.compiler.args.cflags</name>
<value>${linux.sendmmsg.support}${glibc.sendmmsg.support}</value>
<!-- If glibc and linux kernel are both not sufficient...then define the CFLAGS -->
<regex>.*IO_NETTY_SENDMSSG_NOT_FOUND.*</regex>
<replacement>CFLAGS=-O3 -DIO_NETTY_SENDMMSG_NOT_FOUND -Werror -fno-omit-frame-pointer -Wunused-variable -fvisibility=hidden -I${unix.common.include.unpacked.dir}</replacement>
<failIfNoMatch>false</failIfNoMatch>
</configuration>
</execution>
<execution>
<!-- Phase must be before build-native-lib -->
<phase>generate-sources</phase>
<id>regex-unset-if-needed-sendmmsg</id>
<goals>
<goal>regex-property</goal>
</goals>
<configuration>
<name>jni.compiler.args.cflags</name>
<value>${jni.compiler.args.cflags}</value>
<!-- If glibc and linux kernel are both not sufficient...then define the CFLAGS -->
<regex>^((?!CFLAGS=).)*$</regex>
<replacement>CFLAGS=-O3 -Werror -fno-omit-frame-pointer -Wunused-variable -fvisibility=hidden -I${unix.common.include.unpacked.dir}</replacement>
<failIfNoMatch>false</failIfNoMatch>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

View File

@ -36,6 +36,9 @@
#include <inttypes.h>
#include <link.h>
#include <time.h>
// Needed to be able to use syscalls directly and so not depend on newer GLIBC versions
#include <linux/net.h>
#include <sys/syscall.h>
#include "netty_epoll_linuxsocket.h"
#include "netty_unix_buffer.h"
@ -54,15 +57,20 @@
// optional
extern int epoll_create1(int flags) __attribute__((weak));
#ifdef IO_NETTY_SENDMMSG_NOT_FOUND
extern int sendmmsg(int sockfd, struct mmsghdr* msgvec, unsigned int vlen, unsigned int flags) __attribute__((weak));
#ifndef __USE_GNU
struct mmsghdr {
struct msghdr msg_hdr; /* Message header */
unsigned int msg_len; /* Number of bytes transmitted */
};
#endif
// All linux syscall numbers are stable so this is safe.
#ifndef SYS_recvmmsg
#define SYS_recvmmsg 299
#endif
#ifndef SYS_sendmmsg
#define SYS_sendmmsg 307
#endif
// Those are initialized in the init(...) method and cached for performance reasons
@ -305,7 +313,8 @@ static jint netty_epoll_native_sendmmsg0(JNIEnv* env, jclass clazz, jint fd, jbo
ssize_t res;
int err;
do {
res = sendmmsg(fd, msg, len, 0);
// We directly use the syscall to prevent depending on GLIBC 2.14.
res = syscall(SYS_sendmmsg, fd, msg, len, 0);
// keep on writing if it was interrupted
} while (res == -1 && ((err = errno) == EINTR));
@ -336,8 +345,10 @@ static jint netty_epoll_native_recvmmsg0(JNIEnv* env, jclass clazz, jint fd, jbo
ssize_t res;
int err;
do {
res = recvmmsg(fd, msg, len, 0, NULL);
// keep on reading if it was interrupted
// We directly use the syscall to prevent depending on GLIBC 2.12.
res = syscall(SYS_recvmmsg, fd, &msg, len, 0, NULL);
// keep on reading if it was interrupted
} while (res == -1 && ((err = errno) == EINTR));
if (res < 0) {
@ -388,14 +399,22 @@ static jstring netty_epoll_native_kernelVersion(JNIEnv* env, jclass clazz) {
netty_unix_errors_throwRuntimeExceptionErrorNo(env, "uname() failed: ", errno);
return NULL;
}
static jboolean netty_epoll_native_isSupportingSendmmsg(JNIEnv* env, jclass clazz) {
// Use & to avoid warnings with -Wtautological-pointer-compare when sendmmsg is
// not weakly defined.
if (&sendmmsg != NULL) {
return JNI_TRUE;
if (syscall(SYS_sendmmsg, -1, NULL, 0, 0) == -1) {
if (errno == ENOSYS) {
return JNI_FALSE;
}
}
return JNI_FALSE;
return JNI_TRUE;
}
static jboolean netty_epoll_native_isSupportingRecvmmsg(JNIEnv* env, jclass clazz) {
if (syscall(SYS_recvmmsg, -1, NULL, 0, 0, NULL) == -1) {
if (errno == ENOSYS) {
return JNI_FALSE;
}
}
return JNI_TRUE;
}
static jboolean netty_epoll_native_isSupportingTcpFastopen(JNIEnv* env, jclass clazz) {
@ -476,6 +495,7 @@ static const JNINativeMethod statically_referenced_fixed_method_table[] = {
{ "epollerr", "()I", (void *) netty_epoll_native_epollerr },
{ "tcpMd5SigMaxKeyLen", "()I", (void *) netty_epoll_native_tcpMd5SigMaxKeyLen },
{ "isSupportingSendmmsg", "()Z", (void *) netty_epoll_native_isSupportingSendmmsg },
{ "isSupportingRecvmmsg", "()Z", (void *) netty_epoll_native_isSupportingRecvmmsg },
{ "isSupportingTcpFastopen", "()Z", (void *) netty_epoll_native_isSupportingTcpFastopen },
{ "kernelVersion", "()Ljava/lang/String;", (void *) netty_epoll_native_kernelVersion }
};

View File

@ -483,7 +483,11 @@ public final class EpollDatagramChannel extends AbstractEpollChannel implements
ByteBuf byteBuf = allocHandle.allocate(allocator);
final boolean read;
int datagramSize = config().getMaxDatagramPayloadSize();
int numDatagram = datagramSize == 0 ? 1 : byteBuf.writableBytes() / datagramSize;
// Only try to use recvmmsg if its really supported by the running system.
int numDatagram = Native.IS_SUPPORTING_RECVMMSG ?
datagramSize == 0 ? 1 : byteBuf.writableBytes() / datagramSize :
0;
try {
if (numDatagram <= 1) {

View File

@ -32,6 +32,7 @@ import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.epolle
import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.epollin;
import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.epollout;
import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.epollrdhup;
import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.isSupportingRecvmmsg;
import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.isSupportingSendmmsg;
import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.isSupportingTcpFastopen;
import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.kernelVersion;
@ -67,6 +68,8 @@ public final class Native {
public static final int EPOLLERR = epollerr();
public static final boolean IS_SUPPORTING_SENDMMSG = isSupportingSendmmsg();
static final boolean IS_SUPPORTING_RECVMMSG = isSupportingRecvmmsg();
public static final boolean IS_SUPPORTING_TCP_FASTOPEN = isSupportingTcpFastopen();
public static final int TCP_MD5SIG_MAXKEYLEN = tcpMd5SigMaxKeyLen();
public static final String KERNEL_VERSION = kernelVersion();

View File

@ -40,6 +40,7 @@ final class NativeStaticallyReferencedJniMethods {
static native int iovMax();
static native int uioMaxIov();
static native boolean isSupportingSendmmsg();
static native boolean isSupportingRecvmmsg();
static native boolean isSupportingTcpFastopen();
static native String kernelVersion();
}

View File

@ -25,6 +25,8 @@ import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import io.netty.testsuite.transport.TestsuitePermutation;
import io.netty.testsuite.transport.socket.AbstractDatagramTest;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
import java.net.InetSocketAddress;
@ -43,6 +45,11 @@ import static org.junit.Assert.fail;
public class EpollDatagramScatteringReadTest extends AbstractDatagramTest {
@BeforeClass
public static void assumeRecvmmsgSupported() {
Assume.assumeTrue(Native.IS_SUPPORTING_RECVMMSG);
}
@Override
protected List<TestsuitePermutation.BootstrapComboFactory<Bootstrap, Bootstrap>> newFactories() {
return EpollSocketTestPermutation.INSTANCE.epollOnlyDatagram(internetProtocolFamily());