8243099: SO_INCOMING_NAPI_ID support

Add support for the SO_INCOMING_NAPI_ID socket option to jdk.net.ExtendedSocketOptions

Co-authored-by: Dinesh Kumar <dinesh.kumar@intel.com>
Reviewed-by: alanb, chegar, dfuchs, vtewari, pconcannon
This commit is contained in:
Vladimir A Ivanov 2020-05-20 15:12:28 -07:00 committed by Sandhya Viswanathan
parent 822ec45b02
commit 93fcbec20a
6 changed files with 149 additions and 67 deletions

View File

@ -84,6 +84,16 @@ class LinuxSocketOptions extends PlatformSocketOptions {
return getTcpKeepAliveIntvl0(fd);
}
@Override
boolean incomingNapiIdSupported() {
return incomingNapiIdSupported0();
}
@Override
int getIncomingNapiId(int fd) throws SocketException {
return getIncomingNapiId0(fd);
}
private static native void setTcpkeepAliveProbes0(int fd, int value) throws SocketException;
private static native void setTcpKeepAliveTime0(int fd, int value) throws SocketException;
private static native void setTcpKeepAliveIntvl0(int fd, int value) throws SocketException;
@ -94,6 +104,8 @@ class LinuxSocketOptions extends PlatformSocketOptions {
private static native boolean getQuickAck0(int fd) throws SocketException;
private static native boolean keepAliveOptionsSupported0();
private static native boolean quickAckSupported0();
private static native boolean incomingNapiIdSupported0();
private static native int getIncomingNapiId0(int fd) throws SocketException;
static {
if (System.getSecurityManager() == null) {
System.loadLibrary("extnet");

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -33,6 +33,39 @@
#include "jni_util.h"
#include "jdk_net_LinuxSocketOptions.h"
#ifndef SO_INCOMING_NAPI_ID
#define SO_INCOMING_NAPI_ID 56
#endif
static void handleError(JNIEnv *env, jint rv, const char *errmsg) {
if (rv < 0) {
if (errno == ENOPROTOOPT) {
JNU_ThrowByName(env, "java/lang/UnsupportedOperationException",
"unsupported socket option");
} else {
JNU_ThrowByNameWithLastError(env, "java/net/SocketException", errmsg);
}
}
}
static jint socketOptionSupported(jint level, jint optname) {
jint one = 1;
jint rv, s;
socklen_t sz = sizeof (one);
s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s < 0) {
return 0;
}
rv = getsockopt(s, level, optname, (void *) &one, &sz);
if (rv != 0 && errno == ENOPROTOOPT) {
rv = 0;
} else {
rv = 1;
}
close(s);
return rv;
}
/*
* Declare library specific JNI_Onload entry if static build
*/
@ -49,15 +82,7 @@ JNIEXPORT void JNICALL Java_jdk_net_LinuxSocketOptions_setQuickAck0
int rv;
optval = (on ? 1 : 0);
rv = setsockopt(fd, SOL_SOCKET, TCP_QUICKACK, &optval, sizeof (optval));
if (rv < 0) {
if (errno == ENOPROTOOPT) {
JNU_ThrowByName(env, "java/lang/UnsupportedOperationException",
"unsupported socket option");
} else {
JNU_ThrowByNameWithLastError(env, "java/net/SocketException",
"set option TCP_QUICKACK failed");
}
}
handleError(env, rv, "set option TCP_QUICKACK failed");
}
/*
@ -70,15 +95,7 @@ JNIEXPORT jboolean JNICALL Java_jdk_net_LinuxSocketOptions_getQuickAck0
int on;
socklen_t sz = sizeof (on);
int rv = getsockopt(fd, SOL_SOCKET, TCP_QUICKACK, &on, &sz);
if (rv < 0) {
if (errno == ENOPROTOOPT) {
JNU_ThrowByName(env, "java/lang/UnsupportedOperationException",
"unsupported socket option");
} else {
JNU_ThrowByNameWithLastError(env, "java/net/SocketException",
"get option TCP_QUICKACK failed");
}
}
handleError(env, rv, "get option TCP_QUICKACK failed");
return on != 0;
}
@ -89,48 +106,7 @@ JNIEXPORT jboolean JNICALL Java_jdk_net_LinuxSocketOptions_getQuickAck0
*/
JNIEXPORT jboolean JNICALL Java_jdk_net_LinuxSocketOptions_quickAckSupported0
(JNIEnv *env, jobject unused) {
int one = 1;
int rv, s;
s = socket(PF_INET, SOCK_STREAM, 0);
if (s < 0) {
return JNI_FALSE;
}
rv = setsockopt(s, SOL_SOCKET, TCP_QUICKACK, (void *) &one, sizeof (one));
if (rv != 0 && errno == ENOPROTOOPT) {
rv = JNI_FALSE;
} else {
rv = JNI_TRUE;
}
close(s);
return rv;
}
static jint socketOptionSupported(jint sockopt) {
jint one = 1;
jint rv, s;
s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s < 0) {
return 0;
}
rv = setsockopt(s, SOL_TCP, sockopt, (void *) &one, sizeof (one));
if (rv != 0 && errno == ENOPROTOOPT) {
rv = 0;
} else {
rv = 1;
}
close(s);
return rv;
}
static void handleError(JNIEnv *env, jint rv, const char *errmsg) {
if (rv < 0) {
if (errno == ENOPROTOOPT) {
JNU_ThrowByName(env, "java/lang/UnsupportedOperationException",
"unsupported socket option");
} else {
JNU_ThrowByNameWithLastError(env, "java/net/SocketException", errmsg);
}
}
return socketOptionSupported(SOL_SOCKET, TCP_QUICKACK);
}
/*
@ -140,8 +116,8 @@ static void handleError(JNIEnv *env, jint rv, const char *errmsg) {
*/
JNIEXPORT jboolean JNICALL Java_jdk_net_LinuxSocketOptions_keepAliveOptionsSupported0
(JNIEnv *env, jobject unused) {
return socketOptionSupported(TCP_KEEPIDLE) && socketOptionSupported(TCP_KEEPCNT)
&& socketOptionSupported(TCP_KEEPINTVL);
return socketOptionSupported(SOL_TCP, TCP_KEEPIDLE) && socketOptionSupported(SOL_TCP, TCP_KEEPCNT)
&& socketOptionSupported(SOL_TCP, TCP_KEEPINTVL);
}
/*
@ -218,3 +194,27 @@ JNIEXPORT jint JNICALL Java_jdk_net_LinuxSocketOptions_getTcpKeepAliveIntvl0
handleError(env, rv, "get option TCP_KEEPINTVL failed");
return optval;
}
/*
* Class: jdk_net_LinuxSocketOptions
* Method: incomingNapiIdSupported0
* Signature: ()Z;
*/
JNIEXPORT jboolean JNICALL Java_jdk_net_LinuxSocketOptions_incomingNapiIdSupported0
(JNIEnv *env, jobject unused) {
return socketOptionSupported(SOL_SOCKET, SO_INCOMING_NAPI_ID);
}
/*
* Class: jdk_net_LinuxSocketOptions
* Method: getIncomingNapiId0
* Signature: (I)I;
*/
JNIEXPORT jint JNICALL Java_jdk_net_LinuxSocketOptions_getIncomingNapiId0
(JNIEnv *env, jobject unused, jint fd) {
jint optval, rv;
socklen_t sz = sizeof (optval);
rv = getsockopt(fd, SOL_SOCKET, SO_INCOMING_NAPI_ID, &optval, &sz);
handleError(env, rv, "get option SO_INCOMING_NAPI_ID failed");
return optval;
}

View File

@ -165,6 +165,34 @@ public final class ExtendedSocketOptions {
public static final SocketOption<Integer> TCP_KEEPCOUNT
= new ExtSocketOption<Integer>("TCP_KEEPCOUNT", Integer.class);
/**
* Identifies the receive queue that the last incoming packet for the socket
* was received on.
*
* <p> The value of this socket option is a positive {@code Integer} that
* identifies a receive queue that the application can use to split the
* incoming flows among threads based on the queue identifier. The value is
* {@code 0} when the socket is not bound, a packet has not been received,
* or more generally, when there is no receive queue to identify.
* The socket option is supported by both stream-oriented and datagram-oriented
* sockets.
*
* <p> The socket option is read-only and an attempt to set the socket option
* will throw {@code SocketException}.
*
* @apiNote
* Network devices may have multiple queues or channels to transmit and receive
* network packets. The {@code SO_INCOMING_NAPI_ID} socket option provides a hint
* to the application to indicate the receive queue on which an incoming socket
* connection or packets for that connection are directed to. An application may
* take advantage of this by handling all socket connections assigned to a
* specific queue on one thread.
*
* @since 15
*/
public static final SocketOption<Integer> SO_INCOMING_NAPI_ID
= new ExtSocketOption<Integer>("SO_INCOMING_NAPI_ID", Integer.class);
private static final PlatformSocketOptions platformSocketOptions =
PlatformSocketOptions.get();
@ -174,6 +202,8 @@ public final class ExtendedSocketOptions {
platformSocketOptions.quickAckSupported();
private static final boolean keepAliveOptSupported =
platformSocketOptions.keepAliveOptionsSupported();
private static final boolean incomingNapiIdOptSupported =
platformSocketOptions.incomingNapiIdSupported();
private static final Set<SocketOption<?>> extendedOptions = options();
static Set<SocketOption<?>> options() {
@ -184,6 +214,9 @@ public final class ExtendedSocketOptions {
if (quickAckSupported) {
options.add(TCP_QUICKACK);
}
if (incomingNapiIdOptSupported) {
options.add(SO_INCOMING_NAPI_ID);
}
if (keepAliveOptSupported) {
options.addAll(Set.of(TCP_KEEPCOUNT, TCP_KEEPIDLE, TCP_KEEPINTERVAL));
}
@ -221,6 +254,11 @@ public final class ExtendedSocketOptions {
setTcpKeepAliveTime(fd, (Integer) value);
} else if (option == TCP_KEEPINTERVAL) {
setTcpKeepAliveIntvl(fd, (Integer) value);
} else if (option == SO_INCOMING_NAPI_ID) {
if (!incomingNapiIdOptSupported)
throw new UnsupportedOperationException("Attempt to set unsupported option " + option);
else
throw new SocketException("Attempt to set read only option " + option);
} else {
throw new InternalError("Unexpected option " + option);
}
@ -252,6 +290,8 @@ public final class ExtendedSocketOptions {
return getTcpKeepAliveTime(fd);
} else if (option == TCP_KEEPINTERVAL) {
return getTcpKeepAliveIntvl(fd);
} else if (option == SO_INCOMING_NAPI_ID) {
return getIncomingNapiId(fd);
} else {
throw new InternalError("Unexpected option " + option);
}
@ -325,6 +365,10 @@ public final class ExtendedSocketOptions {
return platformSocketOptions.getTcpKeepAliveIntvl(fdAccess.get(fd));
}
private static int getIncomingNapiId(FileDescriptor fd) throws SocketException {
return platformSocketOptions.getIncomingNapiId(fdAccess.get(fd));
}
static class PlatformSocketOptions {
protected PlatformSocketOptions() {}
@ -418,5 +462,13 @@ public final class ExtendedSocketOptions {
int getTcpKeepAliveIntvl(int fd) throws SocketException {
throw new UnsupportedOperationException("unsupported TCP_KEEPINTVL option");
}
boolean incomingNapiIdSupported() {
return false;
}
int getIncomingNapiId(int fd) throws SocketException {
throw new UnsupportedOperationException("unsupported SO_INCOMING_NAPI_ID socket option");
}
}
}

View File

@ -263,6 +263,8 @@ public class Sockets {
private static Map<Class<?>,Set<SocketOption<?>>> optionSets() {
Map<Class<?>,Set<SocketOption<?>>> options = new HashMap<>();
boolean flowsupported = PlatformSocketOptions.get().flowSupported();
boolean incomingNapiIdsupported = PlatformSocketOptions.get().incomingNapiIdSupported();
boolean reuseportsupported = isReusePortAvailable();
// Socket
@ -288,6 +290,9 @@ public class Sockets {
ExtendedSocketOptions.TCP_KEEPIDLE,
ExtendedSocketOptions.TCP_KEEPINTERVAL));
}
if (incomingNapiIdsupported) {
set.add(ExtendedSocketOptions.SO_INCOMING_NAPI_ID);
}
set = Collections.unmodifiableSet(set);
options.put(Socket.class, set);
@ -308,6 +313,9 @@ public class Sockets {
ExtendedSocketOptions.TCP_KEEPINTERVAL));
}
set.add(StandardSocketOptions.IP_TOS);
if (incomingNapiIdsupported) {
set.add(ExtendedSocketOptions.SO_INCOMING_NAPI_ID);
}
set = Collections.unmodifiableSet(set);
options.put(ServerSocket.class, set);
@ -324,6 +332,9 @@ public class Sockets {
if (flowsupported) {
set.add(ExtendedSocketOptions.SO_FLOW_SLA);
}
if (incomingNapiIdsupported) {
set.add(ExtendedSocketOptions.SO_INCOMING_NAPI_ID);
}
set = Collections.unmodifiableSet(set);
options.put(DatagramSocket.class, set);

View File

@ -59,6 +59,7 @@ import static org.testng.Assert.expectThrows;
public class AfterClose {
static final Class<IOException> IOE = IOException.class;
static final String RO = "READ_ONLY";
static Map<SocketOption<?>,List<Object>> OPTION_VALUES_MAP = optionValueMap();
@ -106,6 +107,8 @@ public class AfterClose {
map.put((SocketOption<?>)field.get(null), listOf(10, 100));
field = c.getField("TCP_KEEPCOUNT");
map.put((SocketOption<?>)field.get(null), listOf(10, 100));
field = c.getField("SO_INCOMING_NAPI_ID");
map.put((SocketOption<?>)field.get(null), listOf(RO));
} catch (ClassNotFoundException e) {
// ignore, jdk.net module not present
} catch (ReflectiveOperationException e) {
@ -158,7 +161,7 @@ public class AfterClose {
Socket socket = createClosedSocketFromAdapter();
for (int i=0; i<3; i++); {
for (T value : values) {
expectThrows(IOE, () -> socket.setOption(option, value));
if (!RO.equals(value)) expectThrows(IOE, () -> socket.setOption(option, value));
expectThrows(IOE, () -> socket.getOption(option));
}
}
@ -211,7 +214,7 @@ public class AfterClose {
ServerSocket serverSocket = createClosedServerSocketFromAdapter();
for (int i=0; i<3; i++); {
for (T value : values) {
expectThrows(IOE, () -> serverSocket.setOption(option, value));
if (!RO.equals(value)) expectThrows(IOE, () -> serverSocket.setOption(option, value));
expectThrows(IOE, () -> serverSocket.getOption(option));
}
}

View File

@ -33,6 +33,7 @@
import java.io.IOException;
import java.net.SocketOption;
import java.nio.channels.*;
import java.util.*;
import jdk.test.lib.net.IPSupport;
@ -54,6 +55,8 @@ public class PrintSupportedOptions {
test(() -> AsynchronousServerSocketChannel.open());
}
static final Set<String> READ_ONLY_OPTS = Set.of("SO_INCOMING_NAPI_ID");
@SuppressWarnings("unchecked")
static <T extends NetworkChannel>
void test(NetworkChannelSupplier<T> supplier) throws IOException {
@ -62,8 +65,9 @@ public class PrintSupportedOptions {
for (SocketOption<?> opt : ch.supportedOptions()) {
Object value = ch.getOption(opt);
System.out.format(" %s -> %s%n", opt.name(), value);
if (value != null) {
ch.setOption((SocketOption<Object>) opt, value);
if (!READ_ONLY_OPTS.contains(opt.name())) {
if (value != null)
ch.setOption((SocketOption<Object>) opt, value);
}
}
}