Motivation: When kevent(...) returns with EINTR we do not correctly decrement the timespec structure contents to account for the time duration. This may lead to negative values for tv_nsec which will result in an EINVAL and raise an IOException to the event loop selection loop. Modifications: Correctly calculate new timeoutTs when EINTR is detected Result: Fixes #9013.
430 lines
15 KiB
C
430 lines
15 KiB
C
/*
|
|
* Copyright 2016 The Netty Project
|
|
*
|
|
* The Netty Project licenses this file to you 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.
|
|
*/
|
|
#include <errno.h>
|
|
#include <netdb.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <dlfcn.h>
|
|
#include <stddef.h>
|
|
#include <time.h>
|
|
#include <sys/event.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/time.h>
|
|
|
|
#include "netty_kqueue_bsdsocket.h"
|
|
#include "netty_kqueue_eventarray.h"
|
|
#include "netty_unix_buffer.h"
|
|
#include "netty_unix_errors.h"
|
|
#include "netty_unix_filedescriptor.h"
|
|
#include "netty_unix_jni.h"
|
|
#include "netty_unix_limits.h"
|
|
#include "netty_unix_socket.h"
|
|
#include "netty_unix_util.h"
|
|
|
|
// Currently only macOS supports EVFILT_SOCK, and it is currently only available in internal APIs.
|
|
// To make compiling easier we redefine the values here if they are not present.
|
|
#ifdef __APPLE__
|
|
#ifndef EVFILT_SOCK
|
|
#define EVFILT_SOCK -13
|
|
#endif /* EVFILT_SOCK */
|
|
#ifndef NOTE_CONNRESET
|
|
#define NOTE_CONNRESET 0x00000001
|
|
#endif /* NOTE_CONNRESET */
|
|
#ifndef NOTE_READCLOSED
|
|
#define NOTE_READCLOSED 0x00000002
|
|
#endif /* NOTE_READCLOSED */
|
|
#ifndef NOTE_DISCONNECTED
|
|
#define NOTE_DISCONNECTED 0x00001000
|
|
#endif /* NOTE_DISCONNECTED */
|
|
#else
|
|
#ifndef EVFILT_SOCK
|
|
#define EVFILT_SOCK 0 // Disabled
|
|
#endif /* EVFILT_SOCK */
|
|
#ifndef NOTE_CONNRESET
|
|
#define NOTE_CONNRESET 0
|
|
#endif /* NOTE_CONNRESET */
|
|
#ifndef NOTE_READCLOSED
|
|
#define NOTE_READCLOSED 0
|
|
#endif /* NOTE_READCLOSED */
|
|
#ifndef NOTE_DISCONNECTED
|
|
#define NOTE_DISCONNECTED 0
|
|
#endif /* NOTE_DISCONNECTED */
|
|
#endif /* __APPLE__ */
|
|
|
|
static clockid_t waitClockId = 0; // initialized by netty_unix_util_initialize_wait_clock
|
|
|
|
static jint netty_kqueue_native_kqueueCreate(JNIEnv* env, jclass clazz) {
|
|
jint kq = kqueue();
|
|
if (kq < 0) {
|
|
netty_unix_errors_throwChannelExceptionErrorNo(env, "kqueue() failed: ", errno);
|
|
}
|
|
return kq;
|
|
}
|
|
|
|
static jint netty_kqueue_native_keventChangeSingleUserEvent(jint kqueueFd, struct kevent* userEvent) {
|
|
int result, err;
|
|
result = kevent(kqueueFd, userEvent, 1, NULL, 0, NULL);
|
|
if (result < 0) {
|
|
// https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2
|
|
// When kevent() call fails with EINTR error, all changes in the changelist have been applied.
|
|
return (err = errno) == EINTR ? 0 : -err;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static jint netty_kqueue_native_keventTriggerUserEvent(JNIEnv* env, jclass clazz, jint kqueueFd, jint ident) {
|
|
struct kevent userEvent;
|
|
EV_SET(&userEvent, ident, EVFILT_USER, 0, NOTE_TRIGGER | NOTE_FFNOP, 0, NULL);
|
|
return netty_kqueue_native_keventChangeSingleUserEvent(kqueueFd, &userEvent);
|
|
}
|
|
|
|
static jint netty_kqueue_native_keventAddUserEvent(JNIEnv* env, jclass clazz, jint kqueueFd, jint ident) {
|
|
struct kevent userEvent;
|
|
EV_SET(&userEvent, ident, EVFILT_USER, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_FFNOP, 0, NULL);
|
|
return netty_kqueue_native_keventChangeSingleUserEvent(kqueueFd, &userEvent);
|
|
}
|
|
|
|
static jint netty_kqueue_native_keventWait(JNIEnv* env, jclass clazz, jint kqueueFd, jlong changeListAddress, jint changeListLength,
|
|
jlong eventListAddress, jint eventListLength, jint tvSec, jint tvNsec) {
|
|
struct kevent* changeList = (struct kevent*) changeListAddress;
|
|
struct kevent* eventList = (struct kevent*) eventListAddress;
|
|
struct timespec beforeTs, nowTs, timeoutTs;
|
|
int result, err;
|
|
|
|
timeoutTs.tv_sec = tvSec;
|
|
timeoutTs.tv_nsec = tvNsec;
|
|
|
|
if (tvSec == 0 && tvNsec == 0) {
|
|
// Zeros = poll (aka return immediately).
|
|
for (;;) {
|
|
result = kevent(kqueueFd, changeList, changeListLength, eventList, eventListLength, &timeoutTs);
|
|
if (result >= 0) {
|
|
return result;
|
|
}
|
|
if ((err = errno) != EINTR) {
|
|
return -err;
|
|
}
|
|
|
|
// https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2
|
|
// When kevent() call fails with EINTR error, all changes in the changelist have been applied.
|
|
changeListLength = 0;
|
|
}
|
|
}
|
|
|
|
// Wait with a timeout value.
|
|
netty_unix_util_clock_gettime(waitClockId, &beforeTs);
|
|
for (;;) {
|
|
result = kevent(kqueueFd, changeList, changeListLength, eventList, eventListLength, &timeoutTs);
|
|
if (result >= 0) {
|
|
return result;
|
|
}
|
|
if ((err = errno) != EINTR) {
|
|
return -err;
|
|
}
|
|
|
|
netty_unix_util_clock_gettime(waitClockId, &nowTs);
|
|
if (netty_unix_util_timespec_subtract_ns(&timeoutTs,
|
|
netty_unix_util_timespec_elapsed_ns(&beforeTs, &nowTs))) {
|
|
return 0;
|
|
}
|
|
|
|
beforeTs = nowTs;
|
|
// https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2
|
|
// When kevent() call fails with EINTR error, all changes in the changelist have been applied.
|
|
changeListLength = 0;
|
|
}
|
|
}
|
|
|
|
static jint netty_kqueue_native_sizeofKEvent(JNIEnv* env, jclass clazz) {
|
|
return sizeof(struct kevent);
|
|
}
|
|
|
|
static jint netty_kqueue_native_offsetofKEventIdent(JNIEnv* env, jclass clazz) {
|
|
return offsetof(struct kevent, ident);
|
|
}
|
|
|
|
static jint netty_kqueue_native_offsetofKEventFlags(JNIEnv* env, jclass clazz) {
|
|
return offsetof(struct kevent, flags);
|
|
}
|
|
|
|
static jint netty_kqueue_native_offsetofKEventFilter(JNIEnv* env, jclass clazz) {
|
|
return offsetof(struct kevent, filter);
|
|
}
|
|
|
|
static jint netty_kqueue_native_offsetofKEventFFlags(JNIEnv* env, jclass clazz) {
|
|
return offsetof(struct kevent, fflags);
|
|
}
|
|
|
|
static jint netty_kqueue_native_offsetofKeventData(JNIEnv* env, jclass clazz) {
|
|
return offsetof(struct kevent, data);
|
|
}
|
|
|
|
static jshort netty_kqueue_native_evfiltRead(JNIEnv* env, jclass clazz) {
|
|
return EVFILT_READ;
|
|
}
|
|
|
|
static jshort netty_kqueue_native_evfiltWrite(JNIEnv* env, jclass clazz) {
|
|
return EVFILT_WRITE;
|
|
}
|
|
|
|
static jshort netty_kqueue_native_evfiltUser(JNIEnv* env, jclass clazz) {
|
|
return EVFILT_USER;
|
|
}
|
|
|
|
static jshort netty_kqueue_native_evfiltSock(JNIEnv* env, jclass clazz) {
|
|
return EVFILT_SOCK;
|
|
}
|
|
|
|
static jshort netty_kqueue_native_evAdd(JNIEnv* env, jclass clazz) {
|
|
return EV_ADD;
|
|
}
|
|
|
|
static jshort netty_kqueue_native_evEnable(JNIEnv* env, jclass clazz) {
|
|
return EV_ENABLE;
|
|
}
|
|
|
|
static jshort netty_kqueue_native_evDisable(JNIEnv* env, jclass clazz) {
|
|
return EV_DISABLE;
|
|
}
|
|
|
|
static jshort netty_kqueue_native_evDelete(JNIEnv* env, jclass clazz) {
|
|
return EV_DELETE;
|
|
}
|
|
|
|
static jshort netty_kqueue_native_evClear(JNIEnv* env, jclass clazz) {
|
|
return EV_CLEAR;
|
|
}
|
|
|
|
static jshort netty_kqueue_native_evEOF(JNIEnv* env, jclass clazz) {
|
|
return EV_EOF;
|
|
}
|
|
|
|
static jshort netty_kqueue_native_evError(JNIEnv* env, jclass clazz) {
|
|
return EV_ERROR;
|
|
}
|
|
|
|
static jshort netty_kqueue_native_noteConnReset(JNIEnv* env, jclass clazz) {
|
|
return NOTE_CONNRESET;
|
|
}
|
|
|
|
static jshort netty_kqueue_native_noteReadClosed(JNIEnv* env, jclass clazz) {
|
|
return NOTE_READCLOSED;
|
|
}
|
|
|
|
static jshort netty_kqueue_native_noteDisconnected(JNIEnv* env, jclass clazz) {
|
|
return NOTE_DISCONNECTED;
|
|
}
|
|
|
|
// JNI Method Registration Table Begin
|
|
static const JNINativeMethod statically_referenced_fixed_method_table[] = {
|
|
{ "evfiltRead", "()S", (void *) netty_kqueue_native_evfiltRead },
|
|
{ "evfiltWrite", "()S", (void *) netty_kqueue_native_evfiltWrite },
|
|
{ "evfiltUser", "()S", (void *) netty_kqueue_native_evfiltUser },
|
|
{ "evfiltSock", "()S", (void *) netty_kqueue_native_evfiltSock },
|
|
{ "evAdd", "()S", (void *) netty_kqueue_native_evAdd },
|
|
{ "evEnable", "()S", (void *) netty_kqueue_native_evEnable },
|
|
{ "evDisable", "()S", (void *) netty_kqueue_native_evDisable },
|
|
{ "evDelete", "()S", (void *) netty_kqueue_native_evDelete },
|
|
{ "evClear", "()S", (void *) netty_kqueue_native_evClear },
|
|
{ "evEOF", "()S", (void *) netty_kqueue_native_evEOF },
|
|
{ "evError", "()S", (void *) netty_kqueue_native_evError },
|
|
{ "noteReadClosed", "()S", (void *) netty_kqueue_native_noteReadClosed },
|
|
{ "noteConnReset", "()S", (void *) netty_kqueue_native_noteConnReset },
|
|
{ "noteDisconnected", "()S", (void *) netty_kqueue_native_noteDisconnected }
|
|
};
|
|
static const jint statically_referenced_fixed_method_table_size = sizeof(statically_referenced_fixed_method_table) / sizeof(statically_referenced_fixed_method_table[0]);
|
|
static const JNINativeMethod fixed_method_table[] = {
|
|
{ "kqueueCreate", "()I", (void *) netty_kqueue_native_kqueueCreate },
|
|
{ "keventTriggerUserEvent", "(II)I", (void *) netty_kqueue_native_keventTriggerUserEvent },
|
|
{ "keventAddUserEvent", "(II)I", (void *) netty_kqueue_native_keventAddUserEvent },
|
|
{ "keventWait", "(IJIJIII)I", (void *) netty_kqueue_native_keventWait },
|
|
{ "sizeofKEvent", "()I", (void *) netty_kqueue_native_sizeofKEvent },
|
|
{ "offsetofKEventIdent", "()I", (void *) netty_kqueue_native_offsetofKEventIdent },
|
|
{ "offsetofKEventFlags", "()I", (void *) netty_kqueue_native_offsetofKEventFlags },
|
|
{ "offsetofKEventFFlags", "()I", (void *) netty_kqueue_native_offsetofKEventFFlags },
|
|
{ "offsetofKEventFilter", "()I", (void *) netty_kqueue_native_offsetofKEventFilter },
|
|
{ "offsetofKeventData", "()I", (void *) netty_kqueue_native_offsetofKeventData }
|
|
};
|
|
static const jint fixed_method_table_size = sizeof(fixed_method_table) / sizeof(fixed_method_table[0]);
|
|
// JNI Method Registration Table End
|
|
|
|
static jint netty_kqueue_native_JNI_OnLoad(JNIEnv* env, const char* packagePrefix) {
|
|
int limitsOnLoadCalled = 0;
|
|
int errorsOnLoadCalled = 0;
|
|
int filedescriptorOnLoadCalled = 0;
|
|
int socketOnLoadCalled = 0;
|
|
int bufferOnLoadCalled = 0;
|
|
int bsdsocketOnLoadCalled = 0;
|
|
int eventarrayOnLoadCalled = 0;
|
|
|
|
// We must register the statically referenced methods first!
|
|
if (netty_unix_util_register_natives(env,
|
|
packagePrefix,
|
|
"io/netty/channel/kqueue/KQueueStaticallyReferencedJniMethods",
|
|
statically_referenced_fixed_method_table,
|
|
statically_referenced_fixed_method_table_size) != 0) {
|
|
goto error;
|
|
}
|
|
// Register the methods which are not referenced by static member variables
|
|
if (netty_unix_util_register_natives(env, packagePrefix, "io/netty/channel/kqueue/Native", fixed_method_table, fixed_method_table_size) != 0) {
|
|
goto error;
|
|
}
|
|
// Load all c modules that we depend upon
|
|
if (netty_unix_limits_JNI_OnLoad(env, packagePrefix) == JNI_ERR) {
|
|
goto error;
|
|
}
|
|
limitsOnLoadCalled = 1;
|
|
|
|
if (netty_unix_errors_JNI_OnLoad(env, packagePrefix) == JNI_ERR) {
|
|
goto error;
|
|
}
|
|
errorsOnLoadCalled = 1;
|
|
|
|
if (netty_unix_filedescriptor_JNI_OnLoad(env, packagePrefix) == JNI_ERR) {
|
|
goto error;
|
|
}
|
|
filedescriptorOnLoadCalled = 1;
|
|
|
|
if (netty_unix_socket_JNI_OnLoad(env, packagePrefix) == JNI_ERR) {
|
|
goto error;
|
|
}
|
|
socketOnLoadCalled = 1;
|
|
|
|
if (netty_unix_buffer_JNI_OnLoad(env, packagePrefix) == JNI_ERR) {
|
|
goto error;
|
|
}
|
|
bufferOnLoadCalled = 1;
|
|
|
|
if (netty_kqueue_bsdsocket_JNI_OnLoad(env, packagePrefix) == JNI_ERR) {
|
|
goto error;
|
|
}
|
|
bsdsocketOnLoadCalled = 1;
|
|
|
|
if (netty_kqueue_eventarray_JNI_OnLoad(env, packagePrefix) == JNI_ERR) {
|
|
goto error;
|
|
}
|
|
eventarrayOnLoadCalled = 1;
|
|
|
|
// Initialize this module
|
|
|
|
if (!netty_unix_util_initialize_wait_clock(&waitClockId)) {
|
|
fprintf(stderr, "FATAL: could not find a clock for clock_gettime!\n");
|
|
goto error;
|
|
}
|
|
|
|
return NETTY_JNI_VERSION;
|
|
error:
|
|
if (limitsOnLoadCalled == 1) {
|
|
netty_unix_limits_JNI_OnUnLoad(env);
|
|
}
|
|
if (errorsOnLoadCalled == 1) {
|
|
netty_unix_errors_JNI_OnUnLoad(env);
|
|
}
|
|
if (filedescriptorOnLoadCalled == 1) {
|
|
netty_unix_filedescriptor_JNI_OnUnLoad(env);
|
|
}
|
|
if (socketOnLoadCalled == 1) {
|
|
netty_unix_socket_JNI_OnUnLoad(env);
|
|
}
|
|
if (bufferOnLoadCalled == 1) {
|
|
netty_unix_buffer_JNI_OnUnLoad(env);
|
|
}
|
|
if (bsdsocketOnLoadCalled == 1) {
|
|
netty_kqueue_bsdsocket_JNI_OnUnLoad(env);
|
|
}
|
|
if (eventarrayOnLoadCalled == 1) {
|
|
netty_kqueue_eventarray_JNI_OnUnLoad(env);
|
|
}
|
|
return JNI_ERR;
|
|
}
|
|
|
|
static void netty_kqueue_native_JNI_OnUnLoad(JNIEnv* env) {
|
|
netty_unix_limits_JNI_OnUnLoad(env);
|
|
netty_unix_errors_JNI_OnUnLoad(env);
|
|
netty_unix_filedescriptor_JNI_OnUnLoad(env);
|
|
netty_unix_socket_JNI_OnUnLoad(env);
|
|
netty_unix_buffer_JNI_OnUnLoad(env);
|
|
netty_kqueue_bsdsocket_JNI_OnUnLoad(env);
|
|
netty_kqueue_eventarray_JNI_OnUnLoad(env);
|
|
}
|
|
|
|
static jint JNI_OnLoad_netty_transport_native_kqueue0(JavaVM* vm, void* reserved) {
|
|
JNIEnv* env;
|
|
if ((*vm)->GetEnv(vm, (void**) &env, NETTY_JNI_VERSION) != JNI_OK) {
|
|
return JNI_ERR;
|
|
}
|
|
|
|
char* packagePrefix = NULL;
|
|
#ifndef NETTY_BUILD_STATIC
|
|
Dl_info dlinfo;
|
|
jint status = 0;
|
|
// We need to use an address of a function that is uniquely part of this library, so choose a static
|
|
// function. See https://github.com/netty/netty/issues/4840.
|
|
if (!dladdr((void*) netty_kqueue_native_JNI_OnUnLoad, &dlinfo)) {
|
|
fprintf(stderr, "FATAL: transport-native-kqueue JNI call to dladdr failed!\n");
|
|
return JNI_ERR;
|
|
}
|
|
packagePrefix = netty_unix_util_parse_package_prefix(dlinfo.dli_fname, "netty_transport_native_kqueue", &status);
|
|
if (status == JNI_ERR) {
|
|
fprintf(stderr, "FATAL: transport-native-kqueue JNI encountered unexpected dlinfo.dli_fname: %s\n", dlinfo.dli_fname);
|
|
return JNI_ERR;
|
|
}
|
|
#endif /* NETTY_BUILD_STATIC */
|
|
jint ret = netty_kqueue_native_JNI_OnLoad(env, packagePrefix);
|
|
|
|
if (packagePrefix != NULL) {
|
|
free(packagePrefix);
|
|
packagePrefix = NULL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void JNI_OnUnload_netty_transport_native_kqueue0(JavaVM* vm, void* reserved) {
|
|
JNIEnv* env;
|
|
if ((*vm)->GetEnv(vm, (void**) &env, NETTY_JNI_VERSION) != JNI_OK) {
|
|
// Something is wrong but nothing we can do about this :(
|
|
return;
|
|
}
|
|
netty_kqueue_native_JNI_OnUnLoad(env);
|
|
}
|
|
|
|
// We build with -fvisibility=hidden so ensure we mark everything that needs to be visible with JNIEXPORT
|
|
// http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-February/014549.html
|
|
|
|
// Invoked by the JVM when statically linked
|
|
JNIEXPORT jint JNI_OnLoad_netty_transport_native_kqueue(JavaVM* vm, void* reserved) {
|
|
return JNI_OnLoad_netty_transport_native_kqueue0(vm, reserved);
|
|
}
|
|
|
|
// Invoked by the JVM when statically linked
|
|
JNIEXPORT void JNI_OnUnload_netty_transport_native_kqueue(JavaVM* vm, void* reserved) {
|
|
JNI_OnUnload_netty_transport_native_kqueue0(vm, reserved);
|
|
}
|
|
|
|
#ifndef NETTY_BUILD_STATIC
|
|
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
|
|
return JNI_OnLoad_netty_transport_native_kqueue0(vm, reserved);
|
|
}
|
|
|
|
JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved) {
|
|
return JNI_OnUnload_netty_transport_native_kqueue0(vm, reserved);
|
|
}
|
|
#endif /* NETTY_BUILD_STATIC */
|