netty5/transport-native-kqueue/src/main/c/netty_kqueue_native.c
Norman Maurer 15eef2425a Cleanup JNI code to always correctly free memory when loading fails and also correctly respect out of memory in all cases (#9596)
Motivation:

At the moment we not consistently (and also not correctly) free allocated native memory in all cases during loading the JNI library. This can lead to native memory leaks in the unlikely case of failure while trying to load the library.

Beside this we also not always correctly handle the case when a new java object can not be created in native code because of out of memory.

Modification:

- Copy some macros from netty-tcnative to be able to handle errors in a more easy fashion
- Correctly account for New* functions to return NULL
- Share code

Result:

More robust and clean JNI code
2019-09-24 07:23:50 +02:00

428 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);
// It's safe to call free(...) with a NULL argument as well.
free(packagePrefix);
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 */