Skip to content

Commit

Permalink
make JNA optional for tests and move classes to bootstrap package
Browse files Browse the repository at this point in the history
Today, JNA is a optional dependency in the build but when running tests or running
with mlockall set to true, JNA must be on the classpath for Windows systems since
we always try to load JNA classes when using mlockall.

The old Natives class was renamed to JNANatives, and a new Natives class is
introduced without any direct imports on JNA classes. The Natives class checks to
see if JNA classes are available at startup. If the classes are available the Natives
class will delegate to the JNANatives class. If the classes are not available the
Natives class will not use the JNANatives class, which results in no additional attempts
to load JNA classes.

Additionally, all of the JNA classes were moved to the bootstrap package and made
package private as this is the only place they should be called from.

Closes elastic#11360
  • Loading branch information
jaymode committed May 27, 2015
1 parent 5384e47 commit 07c4b16
Show file tree
Hide file tree
Showing 14 changed files with 174 additions and 95 deletions.
9 changes: 5 additions & 4 deletions src/main/java/org/elasticsearch/bootstrap/Bootstrap.java
Expand Up @@ -28,8 +28,6 @@
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.inject.CreationException;
import org.elasticsearch.common.inject.spi.Message;
import org.elasticsearch.common.jna.Kernel32Library;
import org.elasticsearch.common.jna.Natives;
import org.elasticsearch.common.lease.Releasables;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
Expand All @@ -48,7 +46,6 @@
import java.util.concurrent.CountDownLatch;

import static com.google.common.collect.Sets.newHashSet;
import static org.elasticsearch.common.jna.Kernel32Library.ConsoleCtrlHandler;
import static org.elasticsearch.common.settings.Settings.Builder.EMPTY_SETTINGS;

/**
Expand Down Expand Up @@ -122,7 +119,7 @@ public boolean handle(int code) {

// force remainder of JNA to be loaded (if available).
try {
Kernel32Library.getInstance();
JNAKernel32Library.getInstance();
} catch (Throwable ignored) {
// we've already logged this.
}
Expand All @@ -143,6 +140,10 @@ public boolean handle(int code) {
StringHelper.randomId();
}

public static boolean isMemoryLocked() {
return Natives.isMemoryLocked();
}

private void setup(boolean addShutdownHook, Settings settings, Environment environment) throws Exception {
initializeNatives(settings.getAsBoolean("bootstrap.mlockall", false),
settings.getAsBoolean("bootstrap.ctrlhandler", true),
Expand Down
Expand Up @@ -17,19 +17,17 @@
* under the License.
*/

package org.elasticsearch.common.jna;
package org.elasticsearch.bootstrap;

import com.sun.jna.IntegerType;
import com.sun.jna.Native;
public interface ConsoleCtrlHandler {

public class SizeT extends IntegerType {

public SizeT() {
this(0);
}

public SizeT(long value) {
super(Native.SIZE_T_SIZE, value);
}
int CTRL_CLOSE_EVENT = 2;

/**
* Handles the Ctrl event.
*
* @param code the code corresponding to the Ctrl sent.
* @return true if the handler processed the event, false otherwise. If false, the next handler will be called.
*/
boolean handle(int code);
}
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

package org.elasticsearch.common.jna;
package org.elasticsearch.bootstrap;

import com.sun.jna.Native;
import org.elasticsearch.common.logging.ESLogger;
Expand All @@ -27,9 +27,9 @@
/**
*
*/
public class CLibrary {
class JNACLibrary {

private static ESLogger logger = Loggers.getLogger(CLibrary.class);
private static final ESLogger logger = Loggers.getLogger(JNACLibrary.class);

public static final int MCL_CURRENT = 1;
public static final int MCL_FUTURE = 2;
Expand All @@ -39,17 +39,15 @@ public class CLibrary {
static {
try {
Native.register("c");
} catch (NoClassDefFoundError e) {
logger.warn("JNA not found. native methods (mlockall) will be disabled.");
} catch (UnsatisfiedLinkError e) {
logger.warn("unable to link C library. native methods (mlockall) will be disabled.");
}
}

public static native int mlockall(int flags);
static native int mlockall(int flags);

public static native int geteuid();
static native int geteuid();

private CLibrary() {
private JNACLibrary() {
}
}
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

package org.elasticsearch.common.jna;
package org.elasticsearch.bootstrap;

import com.google.common.collect.ImmutableList;
import com.sun.jna.*;
Expand All @@ -35,20 +35,20 @@
/**
* Library for Windows/Kernel32
*/
public class Kernel32Library {
class JNAKernel32Library {

private static ESLogger logger = Loggers.getLogger(Kernel32Library.class);
private static final ESLogger logger = Loggers.getLogger(JNAKernel32Library.class);

// Callbacks must be kept around in order to be able to be called later,
// when the Windows ConsoleCtrlHandler sends an event.
private List<NativeHandlerCallback> callbacks = new ArrayList<>();

// Native library instance must be kept around for the same reason.
private final static class Holder {
private final static Kernel32Library instance = new Kernel32Library();
private final static JNAKernel32Library instance = new JNAKernel32Library();
}

private Kernel32Library() {
private JNAKernel32Library() {
if (Constants.WINDOWS) {
try {
Native.register("kernel32");
Expand All @@ -61,7 +61,7 @@ private Kernel32Library() {
}
}

public static Kernel32Library getInstance() {
static JNAKernel32Library getInstance() {
return Holder.instance;
}

Expand All @@ -73,7 +73,7 @@ public static Kernel32Library getInstance() {
* @throws java.lang.UnsatisfiedLinkError if the Kernel32 library is not loaded or if the native function is not found
* @throws java.lang.NoClassDefFoundError if the library for native calls is missing
*/
public boolean addConsoleCtrlHandler(ConsoleCtrlHandler handler) {
boolean addConsoleCtrlHandler(ConsoleCtrlHandler handler) {
boolean result = false;
if (handler != null) {
NativeHandlerCallback callback = new NativeHandlerCallback(handler);
Expand All @@ -85,7 +85,7 @@ public boolean addConsoleCtrlHandler(ConsoleCtrlHandler handler) {
return result;
}

public ImmutableList<Object> getCallbacks() {
ImmutableList<Object> getCallbacks() {
return ImmutableList.builder().addAll(callbacks).build();
}

Expand All @@ -98,7 +98,7 @@ public ImmutableList<Object> getCallbacks() {
* @throws java.lang.UnsatisfiedLinkError if the Kernel32 library is not loaded or if the native function is not found
* @throws java.lang.NoClassDefFoundError if the library for native calls is missing
*/
public native boolean SetConsoleCtrlHandler(StdCallLibrary.StdCallCallback handler, boolean add);
native boolean SetConsoleCtrlHandler(StdCallLibrary.StdCallCallback handler, boolean add);

/**
* Handles consoles event with WIN API
Expand All @@ -123,20 +123,6 @@ public boolean callback(long dwCtrlType) {
}
}

public interface ConsoleCtrlHandler {

public static final int CTRL_CLOSE_EVENT = 2;

/**
* Handles the Ctrl event.
*
* @param code the code corresponding to the Ctrl sent.
* @return true if the handler processed the event, false otherwise. If false, the next handler will be called.
*/
boolean handle(int code);
}


/**
* Memory protection constraints
*
Expand Down Expand Up @@ -167,6 +153,18 @@ protected List getFieldOrder() {
}
}

public static class SizeT extends IntegerType {

public SizeT() {
this(0);
}

public SizeT(long value) {
super(Native.SIZE_T_SIZE, value);
}

}

/**
* Locks the specified region of the process's virtual address space into physical
* memory, ensuring that subsequent access to the region will not incur a page fault.
Expand All @@ -177,7 +175,7 @@ protected List getFieldOrder() {
* @param size The size of the region to be locked, in bytes.
* @return true if the function succeeds
*/
public native boolean VirtualLock(Pointer address, SizeT size);
native boolean VirtualLock(Pointer address, SizeT size);

/**
* Retrieves information about a range of pages within the virtual address space of a specified process.
Expand All @@ -190,7 +188,7 @@ protected List getFieldOrder() {
* @param length The size of the buffer pointed to by the memoryInfo parameter, in bytes.
* @return the actual number of bytes returned in the information buffer.
*/
public native int VirtualQueryEx(Pointer handle, Pointer address, MemoryBasicInformation memoryInfo, int length);
native int VirtualQueryEx(Pointer handle, Pointer address, MemoryBasicInformation memoryInfo, int length);

/**
* Sets the minimum and maximum working set sizes for the specified process.
Expand All @@ -202,7 +200,7 @@ protected List getFieldOrder() {
* @param maxSize The maximum working set size for the process, in bytes.
* @return true if the function succeeds.
*/
public native boolean SetProcessWorkingSetSize(Pointer handle, SizeT minSize, SizeT maxSize);
native boolean SetProcessWorkingSetSize(Pointer handle, SizeT minSize, SizeT maxSize);

/**
* Retrieves a pseudo handle for the current process.
Expand All @@ -211,7 +209,7 @@ protected List getFieldOrder() {
*
* @return a pseudo handle to the current process.
*/
public native Pointer GetCurrentProcess();
native Pointer GetCurrentProcess();

/**
* Closes an open object handle.
Expand All @@ -221,5 +219,5 @@ protected List getFieldOrder() {
* @param handle A valid handle to an open object.
* @return true if the function succeeds.
*/
public native boolean CloseHandle(Pointer handle);
native boolean CloseHandle(Pointer handle);
}
Expand Up @@ -17,32 +17,34 @@
* under the License.
*/

package org.elasticsearch.common.jna;
package org.elasticsearch.bootstrap;

import com.sun.jna.Native;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;
import org.apache.lucene.util.Constants;
import org.elasticsearch.common.jna.Kernel32Library.ConsoleCtrlHandler;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.monitor.jvm.JvmInfo;

import java.util.Locale;

import static org.elasticsearch.bootstrap.JNAKernel32Library.SizeT;

/**
*
* This class performs the actual work with JNA and library bindings to call native methods. It should only be used after
* we are sure that the JNA classes are available to the JVM
*/
public class Natives {
class JNANatives {

private static final ESLogger logger = Loggers.getLogger(JNANatives.class);

private static ESLogger logger = Loggers.getLogger(Natives.class);
// Set to true, in case native mlockall call was successful
public static boolean LOCAL_MLOCKALL = false;

public static void tryMlockall() {
static void tryMlockall() {
int errno = Integer.MIN_VALUE;
try {
int result = CLibrary.mlockall(CLibrary.MCL_CURRENT);
int result = JNACLibrary.mlockall(JNACLibrary.MCL_CURRENT);
if (result != 0) {
errno = Native.getLastError();
} else {
Expand All @@ -54,7 +56,7 @@ public static void tryMlockall() {
}

if (errno != Integer.MIN_VALUE) {
if (errno == CLibrary.ENOMEM && System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("linux")) {
if (errno == JNACLibrary.ENOMEM && System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("linux")) {
logger.warn("Unable to lock JVM memory (ENOMEM)."
+ " This can result in part of the JVM being swapped out."
+ " Increase RLIMIT_MEMLOCK (ulimit).");
Expand All @@ -66,21 +68,20 @@ public static void tryMlockall() {
}

/** Returns true if user is root, false if not, or if we don't know */
public static boolean definitelyRunningAsRoot() {
static boolean definitelyRunningAsRoot() {
if (Constants.WINDOWS) {
return false; // don't know
}
try {
return CLibrary.geteuid() == 0;
return JNACLibrary.geteuid() == 0;
} catch (UnsatisfiedLinkError e) {
// this will have already been logged by Kernel32Library, no need to repeat it
return false;
}
}

public static void tryVirtualLock()
{
Kernel32Library kernel = Kernel32Library.getInstance();
static void tryVirtualLock() {
JNAKernel32Library kernel = JNAKernel32Library.getInstance();
Pointer process = null;
try {
process = kernel.GetCurrentProcess();
Expand All @@ -91,12 +92,12 @@ public static void tryVirtualLock()
if (!kernel.SetProcessWorkingSetSize(process, size, size)) {
logger.warn("Unable to lock JVM memory. Failed to set working set size. Error code " + Native.getLastError());
} else {
Kernel32Library.MemoryBasicInformation memInfo = new Kernel32Library.MemoryBasicInformation();
JNAKernel32Library.MemoryBasicInformation memInfo = new JNAKernel32Library.MemoryBasicInformation();
long address = 0;
while (kernel.VirtualQueryEx(process, new Pointer(address), memInfo, memInfo.size()) != 0) {
boolean lockable = memInfo.State.longValue() == Kernel32Library.MEM_COMMIT
&& (memInfo.Protect.longValue() & Kernel32Library.PAGE_NOACCESS) != Kernel32Library.PAGE_NOACCESS
&& (memInfo.Protect.longValue() & Kernel32Library.PAGE_GUARD) != Kernel32Library.PAGE_GUARD;
boolean lockable = memInfo.State.longValue() == JNAKernel32Library.MEM_COMMIT
&& (memInfo.Protect.longValue() & JNAKernel32Library.PAGE_NOACCESS) != JNAKernel32Library.PAGE_NOACCESS
&& (memInfo.Protect.longValue() & JNAKernel32Library.PAGE_GUARD) != JNAKernel32Library.PAGE_GUARD;
if (lockable) {
kernel.VirtualLock(memInfo.BaseAddress, new SizeT(memInfo.RegionSize.longValue()));
}
Expand All @@ -114,18 +115,16 @@ public static void tryVirtualLock()
}
}

public static void addConsoleCtrlHandler(ConsoleCtrlHandler handler) {
static void addConsoleCtrlHandler(ConsoleCtrlHandler handler) {
// The console Ctrl handler is necessary on Windows platforms only.
if (Constants.WINDOWS) {
try {
boolean result = Kernel32Library.getInstance().addConsoleCtrlHandler(handler);
boolean result = JNAKernel32Library.getInstance().addConsoleCtrlHandler(handler);
if (result) {
logger.debug("console ctrl handler correctly set");
} else {
logger.warn("unknown error " + Native.getLastError() + " when adding console ctrl handler:");
}
} catch (NoClassDefFoundError e) {
logger.warn("JNA not found: native methods and handlers will be disabled.");
} catch (UnsatisfiedLinkError e) {
// this will have already been logged by Kernel32Library, no need to repeat it
}
Expand Down

0 comments on commit 07c4b16

Please sign in to comment.