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:
parent
8d043cc4dd
commit
00f74b92fa
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user