Fix the tcnative lib loading problem in OSGi

Motivation:

As the issue #5539 say, the OpenSsl.class will throw `java.lang.UnsatisfiedLinkError: org.apache.tomcat.jni.Library.version(I)I` when it is invoked. This path try to resolve the problem by modifying the native library loading logic of OpenSsl.class.

Modifications:

The OpenSsl.class loads the tcnative lib by `NativeLibraryLoader.loadFirstAvailable()`. The native library will be loaded in the bundle `netty-common`'s ClassLoader, which is diff with the native class's ClassLoader. That is the root cause of throws `UnsatisfiedLinkError` when the native method is invoked.
So, it should load the native library by the its bundle classloader firstly, then the embedded resources if failed.

Result:

First of all, the error threw by native method problem will be resolved.
Secondly, the native library should work as normal in non-OSGi env. But, this is hard. The loading logic of `Library.class` in `netty-tcnative` bundle is simple: try to load the library in PATH env. If not found, it falls back to the originally logic `NativeLibraryLoader.loadFirstAvailable()`.

Signed-off-by: XU JINCHUAN <xsir@msn.com>
This commit is contained in:
XU JINCHUAN 2016-07-15 22:36:44 +08:00 committed by Norman Maurer
parent 8d043cc4dd
commit 00f74b92fa
2 changed files with 159 additions and 9 deletions

View File

@ -19,12 +19,16 @@ import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.io.Closeable;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Locale;
@ -159,7 +163,7 @@ public final class NativeLibraryLoader {
public static void loadFirstAvailable(ClassLoader loader, String... names) {
for (String name : names) {
try {
NativeLibraryLoader.load(name, loader);
load(name, loader);
return;
} catch (Throwable t) {
logger.debug("Unable to load the library: " + name + '.', t);
@ -170,7 +174,7 @@ public final class NativeLibraryLoader {
}
/**
* Load the given library with the specified {@link java.lang.ClassLoader}
* Load the given library with the specified {@link ClassLoader}
*/
public static void load(String name, ClassLoader loader) {
String libname = System.mapLibraryName(name);
@ -187,7 +191,7 @@ public final class NativeLibraryLoader {
if (url == null) {
// Fall back to normal loading of JNI stuff
System.loadLibrary(name);
loadLibrary(loader, name, false);
return;
}
@ -209,13 +213,13 @@ public final class NativeLibraryLoader {
}
out.flush();
System.load(tmpFile.getPath());
loadLibrary(loader, tmpFile.getPath(), true);
} catch (Exception e) {
throw (UnsatisfiedLinkError) new UnsatisfiedLinkError(
"could not load a native library: " + name).initCause(e);
} finally {
safeClose(in);
safeClose(out);
closeQuietly(in);
closeQuietly(out);
// After we load the library it is safe to delete the file.
// We delete the file immediately to free up resources as soon as possible,
// and if this fails fallback to deleting on JVM exit.
@ -225,10 +229,111 @@ public final class NativeLibraryLoader {
}
}
private static void safeClose(Closeable closeable) {
if (closeable != null) {
/**
* Loading the native library into the specified {@link ClassLoader}.
* @param loader - The {@link ClassLoader} where the native library will be loaded into
* @param name - The native library path or name
* @param absolute - Whether the native library will be loaded by path or by name
*/
private static void loadLibrary(final ClassLoader loader, final String name, final boolean absolute) {
try {
// Make sure the helper is belong to the target ClassLoader.
final Class<?> newHelper = tryToLoadClass(loader, NativeLibraryUtil.class);
loadLibraryByHelper(newHelper, name, absolute);
} catch (Exception e) { // Should by pass the UnsatisfiedLinkError here!
logger.debug("Unable to load the library: " + name + '.', e);
NativeLibraryUtil.loadLibrary(name, absolute); // Fallback to local helper class.
}
}
private static void loadLibraryByHelper(final Class<?> helper, final String name, final boolean absolute) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
// Invoke the helper to load the native library, if succeed, then the native
// library belong to the specified ClassLoader.
Method method = helper.getMethod("loadLibrary",
new Class<?>[] { String.class, boolean.class });
method.setAccessible(true);
return method.invoke(null, name, absolute);
} catch (Exception e) {
throw new IllegalStateException("Load library failed!", e);
}
}
});
}
/**
* Try to load the helper {@link Class} into specified {@link ClassLoader}.
* @param loader - The {@link ClassLoader} where to load the helper {@link Class}
* @param helper - The helper {@link Class}
* @return A new helper Class defined in the specified ClassLoader.
* @throws ClassNotFoundException Helper class not found or loading failed
*/
private static Class<?> tryToLoadClass(final ClassLoader loader, final Class<?> helper)
throws ClassNotFoundException {
try {
return loader.loadClass(helper.getName());
} catch (ClassNotFoundException e) {
// The helper class is NOT found in target ClassLoader, we have to define the helper class.
final byte[] classBinary = classToByteArray(helper);
return AccessController.doPrivileged(new PrivilegedAction<Class<?>>() {
@Override
public Class<?> run() {
try {
// Define the helper class in the target ClassLoader,
// then we can call the helper to load the native library.
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class,
byte[].class, int.class, int.class);
defineClass.setAccessible(true);
return (Class<?>) defineClass.invoke(loader, helper.getName(), classBinary, 0,
classBinary.length);
} catch (Exception e) {
throw new IllegalStateException("Define class failed!", e);
}
}
});
}
}
/**
* Load the helper {@link Class} as a byte array, to be redefined in specified {@link ClassLoader}.
* @param clazz - The helper {@link Class} provided by this bundle
* @return The binary content of helper {@link Class}.
* @throws ClassNotFoundException Helper class not found or loading failed
*/
private static byte[] classToByteArray(Class<?> clazz) throws ClassNotFoundException {
String fileName = clazz.getName();
int lastDot = fileName.lastIndexOf('.');
if (lastDot > 0) {
fileName = fileName.substring(lastDot + 1);
}
URL classUrl = clazz.getResource(fileName + ".class");
if (classUrl == null) {
throw new ClassNotFoundException(clazz.getName());
}
byte[] buf = new byte[1024];
ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
InputStream in = null;
try {
in = classUrl.openStream();
for (int r; (r = in.read(buf)) != -1;) {
out.write(buf, 0, r);
}
return out.toByteArray();
} catch (IOException ex) {
throw new ClassNotFoundException(clazz.getName(), ex);
} finally {
closeQuietly(in);
closeQuietly(out);
}
}
private static void closeQuietly(Closeable c) {
if (c != null) {
try {
closeable.close();
c.close();
} catch (IOException ignore) {
// ignore
}

View File

@ -0,0 +1,45 @@
/*
* 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.
*/
package io.netty.util.internal;
/**
* A Utility to Call the {@link System#load(String)} or {@link System#loadLibrary(String)}.
* Because the {@link System#load(String)} and {@link System#loadLibrary(String)} are both
* CallerSensitive, it will load the native library into its caller's {@link ClassLoader}.
* In OSGi environment, we need this helper to delegate the calling to {@link System#load(String)}
* and it should be as simple as possible. It will be injected into the native library's
* ClassLoader when it is undefined. And therefore, when the defined new helper is invoked,
* the native library would be loaded into the native library's ClassLoader, not the
* caller's ClassLoader.
*/
final class NativeLibraryUtil {
/**
* Delegate the calling to {@link System#load(String)} or {@link System#loadLibrary(String)}.
* @param libName - The native library path or name
* @param absolute - Whether the native library will be loaded by path or by name
*/
public static void loadLibrary(String libName, boolean absolute) {
if (absolute) {
System.load(libName);
} else {
System.loadLibrary(libName);
}
}
private NativeLibraryUtil() {
// Utility
}
}