blob: 22b9003a4e72aded687a3991f15d8a9d5461dc79 [file] [log] [blame]
// **********************************************************************
// @@@ START COPYRIGHT @@@
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF 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.
//
// @@@ END COPYRIGHT @@@
// **********************************************************************
#include "JavaObjectInterface.h"
#include "QRLogger.h"
#include "Globals.h"
#include "Context.h"
#include "ComUser.h"
#include "LmJavaOptions.h"
#include "ex_ex.h"
#define DEFAULT_JVM_MAX_HEAP_SIZE 256
#define DEFAULT_COMPRESSED_CLASSSPACE_SIZE 128
#define DEFAULT_MAX_METASPACE_SIZE 128
#define TRAF_DEFAULT_JNIHANDLE_CAPACITY 32
// ===========================================================================
// ===== Class JavaObjectInterface
// ===========================================================================
JavaVM* JavaObjectInterface::jvm_ = NULL;
jint JavaObjectInterface::jniHandleCapacity_ = 0;
int JavaObjectInterface::debugPort_ = 0;
int JavaObjectInterface::debugTimeout_ = 0;
__thread JNIEnv* jenv_ = NULL;
__thread NAString *tsRecentJMFromJNI = NULL;
__thread NAString *tsSqlJniErrorStr = NULL;
jclass JavaObjectInterface::gThrowableClass = NULL;
jclass JavaObjectInterface::gStackTraceClass = NULL;
jmethodID JavaObjectInterface::gGetStackTraceMethodID = NULL;
jmethodID JavaObjectInterface::gThrowableToStringMethodID = NULL;
jmethodID JavaObjectInterface::gStackFrameToStringMethodID = NULL;
jmethodID JavaObjectInterface::gGetCauseMethodID = NULL;
void setSqlJniErrorStr(NAString &errorMsg)
{
if (tsSqlJniErrorStr != NULL)
delete tsSqlJniErrorStr;
tsSqlJniErrorStr = new NAString(errorMsg);
}
void setSqlJniErrorStr(const char *errorMsg)
{
if (tsSqlJniErrorStr != NULL)
delete tsSqlJniErrorStr;
tsSqlJniErrorStr = new NAString(errorMsg);
}
const char *getSqlJniErrorStr()
{
if (tsSqlJniErrorStr == NULL)
return "";
else
return tsSqlJniErrorStr->data();
}
static const char* const joiErrorEnumStr[] =
{
"All is well."
,"Error checking for existing JVMs"
,"Error attaching to a JVM of the wrong version"
,"Error attaching to an existing JVM"
,"Error creating a new JVM"
,"JNI FindClass() failed"
,"JNI GetMethodID() failed"
,"JNI NewObject() failed"
,"initJNIEnv() failed"
};
//////////////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////////////
char* JavaObjectInterface::getErrorText(JOI_RetCode errEnum)
{
return (char*)joiErrorEnumStr[errEnum];
}
//////////////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////////////
JavaObjectInterface::~JavaObjectInterface()
{
if (jenv_ == NULL)
return;
if ((long)javaObj_ != -1)
jenv_->DeleteGlobalRef(javaObj_);
javaObj_ = NULL;
isInitialized_ = FALSE;
}
void JavaObjectInterface::setJavaObject(jobject jobj) {
if ((long)javaObj_ != -1)
jenv_->DeleteGlobalRef(javaObj_);
javaObj_ = jenv_->NewGlobalRef(jobj);
}
//////////////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////////////
char* JavaObjectInterface::buildClassPath()
{
char* classPath = getenv("CLASSPATH");
Int32 size = strlen(classPath) + 128;
char* classPathBuffer = (char*)malloc(size);
strcpy(classPathBuffer, "-Djava.class.path=");
strcat(classPathBuffer, classPath);
return classPathBuffer;
}
// helper method for JavaObjectInterface::createJVM() below
static NABoolean isDefinedInOptions(LmJavaOptions *options,
const char *prefix)
{
return (options &&
options->entries() &&
options->findByPrefix(prefix) != NULL_COLL_INDEX);
}
#define MAX_NO_JVM_OPTIONS 12
//////////////////////////////////////////////////////////////////////////////
// Create a new JVM instance.
//////////////////////////////////////////////////////////////////////////////
int JavaObjectInterface::createJVM(LmJavaOptions *options)
{
JavaVMInitArgs jvm_args;
JavaVMOption jvm_options[
MAX_NO_JVM_OPTIONS + (options ? options->entries() : 0)];
int numJVMOptions = 0;
// variables for option strings used below
char* classPathArg = NULL;
char maxHeapOptions[64];
char compressedClassSpaceSizeOptions[64];
char maxMetaspaceSizeOptions[64];
char initHeapOptions[64];
char debugOptions[100];
const char *oomOption = "-XX:+HeapDumpOnOutOfMemoryError";
char *oomDumpDir = NULL;
// call the helper method defined above to check for already defined options
if (!isDefinedInOptions(options, "-Djava.class.path="))
{
classPathArg = buildClassPath();
jvm_options[numJVMOptions].optionString = classPathArg;
jvm_options[numJVMOptions].extraInfo = NULL;
QRLogger::log(CAT_SQL_HDFS_JNI_TOP, LL_DEBUG, "Using classpath: %s",
jvm_options[numJVMOptions].optionString);
numJVMOptions++;
}
if (!isDefinedInOptions(options, "-Xmx"))
{
int maxHeapEnvvarMB = DEFAULT_JVM_MAX_HEAP_SIZE;
const char *maxHeapSizeStr = getenv("JVM_MAX_HEAP_SIZE_MB");
if (maxHeapSizeStr != NULL)
{
maxHeapEnvvarMB = atoi(maxHeapSizeStr);
if (maxHeapEnvvarMB <= 0)
maxHeapEnvvarMB = DEFAULT_JVM_MAX_HEAP_SIZE;
}
snprintf(maxHeapOptions, sizeof(maxHeapOptions),
"-Xmx%dm", maxHeapEnvvarMB);
jvm_options[numJVMOptions].optionString = maxHeapOptions;
jvm_options[numJVMOptions].extraInfo = NULL;
QRLogger::log(CAT_SQL_HDFS_JNI_TOP, LL_DEBUG,
"Max heap option: %s",
jvm_options[numJVMOptions].optionString);
numJVMOptions++;
}
const char *jvmGC = getenv("JVM_GC_OPTION");
if (jvmGC != NULL)
{
jvm_options[numJVMOptions].optionString = (char *)jvmGC;
jvm_options[numJVMOptions].extraInfo = NULL;
QRLogger::log(CAT_SQL_HDFS_JNI_TOP, LL_DEBUG,
"GC Option: %s",
jvm_options[numJVMOptions].optionString);
numJVMOptions++;
}
if (!isDefinedInOptions(options, "-XX:CompressedClassSpaceSize="))
{
int compressedClassSpaceSize = 0;
const char *compressedClassSpaceSizeStr = getenv("JVM_COMPRESSED_CLASS_SPACE_SIZE");
if (compressedClassSpaceSizeStr != NULL)
compressedClassSpaceSize = atoi(compressedClassSpaceSizeStr);
if (compressedClassSpaceSize <= 0)
compressedClassSpaceSize = DEFAULT_COMPRESSED_CLASSSPACE_SIZE;
snprintf(compressedClassSpaceSizeOptions,
sizeof(compressedClassSpaceSizeOptions),
"-XX:CompressedClassSpaceSize=%dm", compressedClassSpaceSize);
jvm_options[numJVMOptions].optionString = compressedClassSpaceSizeOptions;
jvm_options[numJVMOptions].extraInfo = NULL;
QRLogger::log(CAT_SQL_HDFS_JNI_TOP, LL_DEBUG,
"CompressedClassSpaceSize: %s",
jvm_options[numJVMOptions].optionString);
numJVMOptions++;
}
if (!isDefinedInOptions(options, "-XX:MaxMetaspaceSize="))
{
int maxMetaspaceSize = 0;
const char *maxMetaspaceSizeStr = getenv("JVM_MAX_METASPACE_SIZE");
if (maxMetaspaceSizeStr != NULL)
maxMetaspaceSize = atoi(maxMetaspaceSizeStr);
if (maxMetaspaceSize <= 0)
maxMetaspaceSize = DEFAULT_MAX_METASPACE_SIZE;
snprintf(maxMetaspaceSizeOptions, sizeof(maxMetaspaceSizeOptions),
"-XX:MaxMetaspaceSize=%dm", maxMetaspaceSize);
jvm_options[numJVMOptions].optionString = maxMetaspaceSizeOptions;
jvm_options[numJVMOptions].extraInfo = NULL;
QRLogger::log(CAT_SQL_HDFS_JNI_TOP, LL_DEBUG,
"MaxMetaspaceSize: %s",
jvm_options[numJVMOptions].optionString);
numJVMOptions++;
}
if (!isDefinedInOptions(options, "-Xms"))
{
const char *initHeapSizeStr = getenv("JVM_INIT_HEAP_SIZE_MB");
if (initHeapSizeStr != NULL)
{
const int initHeapEnvvarMB = atoi(initHeapSizeStr);
if (initHeapEnvvarMB > 0)
{
snprintf(initHeapOptions, sizeof(initHeapOptions),
"-Xms%dm", initHeapEnvvarMB);
jvm_options[numJVMOptions].optionString = initHeapOptions;
jvm_options[numJVMOptions].extraInfo = NULL;
QRLogger::log(CAT_SQL_HDFS_JNI_TOP, LL_DEBUG,
"Init heap option: %s",
jvm_options[numJVMOptions].optionString);
numJVMOptions++;
}
}
}
if (!isDefinedInOptions(options, "-agentlib:jdwp="))
{
int debugPort = 0;
const char *debugPortStr = getenv("JVM_DEBUG_PORT");
if (debugPortStr != NULL)
debugPort = atoi(debugPortStr);
if (debugPort > 0)
{
const char *debugTimeoutStr = getenv("JVM_DEBUG_TIMEOUT");
if (debugTimeoutStr != NULL)
debugTimeout_ = atoi(debugTimeoutStr);
const char *suspendOnDebug = getenv("JVM_SUSPEND_ON_DEBUG");
debugPort_ = debugPort;
// to allow debugging multiple processes at the same time,
// specify a port that is a multiple of 1000 and the code will
// add pid mod 1000 to the port number to use
if (debugPort_ % 1000 == 0)
debugPort_ += (GetCliGlobals()->myPin() % 1000);
snprintf(debugOptions,
sizeof(debugOptions),
"-agentlib:jdwp=transport=dt_socket,address=%d,server=y,timeout=%d",
debugPort_, debugTimeout_);
if (suspendOnDebug != NULL)
strcat(debugOptions, ",suspend=y");
else
strcat(debugOptions, ",suspend=n");
jvm_options[numJVMOptions].optionString = debugOptions;
jvm_options[numJVMOptions].extraInfo = NULL;
QRLogger::log(CAT_SQL_HDFS_JNI_TOP, LL_WARN,
"Debugging JVM with options: %s",
jvm_options[numJVMOptions].optionString);
numJVMOptions++;
}
}
if (!isDefinedInOptions(options, oomOption))
{
jvm_options[numJVMOptions].optionString = (char *)oomOption;
jvm_options[numJVMOptions].extraInfo = NULL;
numJVMOptions++;
}
if (!isDefinedInOptions(options, "-XX:HeapDumpPath="))
{
char *mySqRoot = getenv("TRAF_HOME");
int len;
if (mySqRoot != NULL)
{
len = strlen(mySqRoot);
oomDumpDir = new char[len+50];
strcpy(oomDumpDir, "-XX:HeapDumpPath=");
strcat(oomDumpDir, mySqRoot);
strcat(oomDumpDir, "/logs");
jvm_options[numJVMOptions].optionString = (char *)oomDumpDir;
jvm_options[numJVMOptions].extraInfo = NULL;
numJVMOptions++;
}
}
ex_assert((numJVMOptions < MAX_NO_JVM_OPTIONS), "Buffer overflow in JVM options");
if (options)
for (CollIndex o=0; o<options->entries(); o++)
{
jvm_options[numJVMOptions].optionString = (char *) options->getOption(o);
jvm_options[numJVMOptions].extraInfo = NULL;
QRLogger::log(CAT_SQL_HDFS_JNI_TOP, LL_DEBUG,
"Option passed to JavaObjectInterface::createJVM(): %s",
jvm_options[numJVMOptions].optionString);
numJVMOptions++;
}
jvm_args.version = JNI_VERSION_1_6;
jvm_args.options = jvm_options;
jvm_args.nOptions = numJVMOptions;
jvm_args.ignoreUnrecognized = 1;
int ret = JNI_CreateJavaVM(&jvm_, (void**)&jenv_, &jvm_args);
if (classPathArg)
free(classPathArg);
if (oomDumpDir)
delete oomDumpDir;
return ret;
}
//////////////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////////////
JOI_RetCode JavaObjectInterface::initJVM(LmJavaOptions *options)
{
jint result;
if (jvm_ == NULL)
{
jsize jvm_count = 0;
// Is there an existing JVM already created?
result = JNI_GetCreatedJavaVMs (&jvm_, 1, &jvm_count);
if (result != JNI_OK)
{
GetCliGlobals()->setJniErrorStr(getErrorText(JOI_ERROR_CHECK_JVM));
return JOI_ERROR_CHECK_JVM;
}
if (jvm_count == 0)
{
// No - create a new one.
result = createJVM(options);
if (result != JNI_OK)
{
GetCliGlobals()->setJniErrorStr(getErrorText(JOI_ERROR_CHECK_JVM));
return JOI_ERROR_CREATE_JVM;
}
QRLogger::log(CAT_SQL_HDFS_JNI_TOP, LL_DEBUG, "Created a new JVM.");
}
char *jniHandleCapacityStr = getenv("TRAF_JNIHANDLE_CAPACITY");
if (jniHandleCapacityStr != NULL)
jniHandleCapacity_ = atoi(jniHandleCapacityStr);
if (jniHandleCapacity_ == 0)
jniHandleCapacity_ = TRAF_DEFAULT_JNIHANDLE_CAPACITY;
}
if (jenv_ == NULL)
{
// We found a JVM, can we use it?
result = jvm_->GetEnv((void**) &jenv_, JNI_VERSION_1_6);
switch (result)
{
case JNI_OK:
QRLogger::log(CAT_SQL_HDFS_JNI_TOP, LL_DEBUG, "Attached to an existing JVM.");
break;
case JNI_EDETACHED:
result = jvm_->AttachCurrentThread((void**) &jenv_, NULL);
if (result != JNI_OK)
return JOI_ERROR_ATTACH_JVM;
QRLogger::log(CAT_SQL_HDFS_JNI_TOP, LL_DEBUG, "Attached to an existing JVM from another thread.");
break;
case JNI_EVERSION:
QRLogger::log(CAT_SQL_HDFS_JNI_TOP, LL_DEBUG, "Attaching to a JVM of the wrong version.");
GetCliGlobals()->setJniErrorStr(getErrorText(JOI_ERROR_JVM_VERSION));
return JOI_ERROR_JVM_VERSION;
break;
default:
QRLogger::log(CAT_SQL_HDFS_JNI_TOP, LL_DEBUG, "Unknown error Attaching to an existing JVM.");
GetCliGlobals()->setJniErrorStr(getErrorText(JOI_ERROR_ATTACH_JVM));
return JOI_ERROR_ATTACH_JVM;
break;
}
}
jclass lJavaClass;
if (gThrowableClass == NULL)
{
lJavaClass = jenv_->FindClass("java/lang/Throwable");
if (lJavaClass != NULL)
{
gThrowableClass = (jclass)jenv_->NewGlobalRef(lJavaClass);
jenv_->DeleteLocalRef(lJavaClass);
gGetStackTraceMethodID = jenv_->GetMethodID(gThrowableClass,
"getStackTrace",
"()[Ljava/lang/StackTraceElement;");
gThrowableToStringMethodID = jenv_->GetMethodID(gThrowableClass,
"toString",
"()Ljava/lang/String;");
gGetCauseMethodID = jenv_->GetMethodID(gThrowableClass,
"getCause",
"()Ljava/lang/Throwable;");
}
}
if (gStackTraceClass == NULL)
{
lJavaClass = (jclass)jenv_->FindClass("java/lang/StackTraceElement");
if (lJavaClass != NULL)
{
gStackTraceClass = (jclass)jenv_->NewGlobalRef(lJavaClass);
jenv_->DeleteLocalRef(lJavaClass);
gStackFrameToStringMethodID = jenv_->GetMethodID(gStackTraceClass,
"toString",
"()Ljava/lang/String;");
}
}
return JOI_OK;
}
//////////////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////////////
JOI_RetCode JavaObjectInterface::init(char *className,
jclass &javaClass,
JavaMethodInit* JavaMethods,
Int32 howManyMethods,
bool methodsInitialized)
{
if (isInitialized_)
return JOI_OK;
JOI_RetCode retCode = JOI_OK;
// Make sure the JVM environment is set up correctly.
jclass lJavaClass;
retCode = initJVM();
if (retCode != JOI_OK)
return retCode;
if (methodsInitialized == FALSE || javaObj_ == NULL)
{
if (javaClass == 0)
{
lJavaClass = jenv_->FindClass(className);
if (jenv_->ExceptionCheck())
{
char errMsg[200];
snprintf(errMsg, sizeof(errMsg), "Exception in FindClass(%s)", className);
getExceptionDetails(__FILE__, __LINE__, errMsg);
return JOI_ERROR_FINDCLASS;
}
if (lJavaClass == 0)
{
QRLogger::log(CAT_SQL_HDFS_JNI_TOP, LL_ERROR, "Error in FindClass(%s).", className);
return JOI_ERROR_FINDCLASS;
}
javaClass = (jclass)jenv_->NewGlobalRef(lJavaClass);
jenv_->DeleteLocalRef(lJavaClass);
}
// Initialize the method pointers.
if (!methodsInitialized)
{
for (int i=0; i<howManyMethods; i++)
{
JavaMethods[i].jm_full_name = new (heap_) NAString(className, heap_);
JavaMethods[i].jm_full_name->append('.', 1);
JavaMethods[i].jm_full_name->append(JavaMethods[i].jm_name);
JavaMethods[i].methodID = jenv_->GetMethodID(javaClass,
JavaMethods[i].jm_name,
JavaMethods[i].jm_signature);
if (JavaMethods[i].methodID == 0 || jenv_->ExceptionCheck())
{
getExceptionDetails(__FILE__, __LINE__, "GetMethodId()");
JavaMethods[i].methodID = jenv_->GetStaticMethodID(javaClass,
JavaMethods[i].jm_name,
JavaMethods[i].jm_signature);
if (JavaMethods[i].methodID == 0 || jenv_->ExceptionCheck()) {
getExceptionDetails(__FILE__, __LINE__, "GetMethodId()");
return JOI_ERROR_GETMETHOD;
}
}
}
}
if (javaObj_ == NULL)
{
// Allocate an object of the Java class, and call its constructor.
// The constructor must be the first entry in the methods array.
jobject jObj = jenv_->NewObject(javaClass, JavaMethods[0].methodID);
if (jObj == 0 || jenv_->ExceptionCheck())
{
char errMsg[200];
snprintf(errMsg, sizeof(errMsg), "Error in NewObject() for class %s.", className);
getExceptionDetails(__FILE__, __LINE__, errMsg);
return JOI_ERROR_NEWOBJ;
}
javaObj_ = jenv_->NewGlobalRef(jObj);
jenv_->DeleteLocalRef(jObj);
}
}
isInitialized_ = true;
return JOI_OK;
}
//////////////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////////////
void JavaObjectInterface::logError(std::string &cat, const char* methodName, const char *result)
{
if (result == NULL)
QRLogger::log(cat, LL_ERROR, "Unknown Java error in %s.", methodName);
else
QRLogger::log(cat, LL_ERROR, "%s error: %s.", methodName, result);
}
//
//////////////////////////////////////////////////////////////////////////////
void JavaObjectInterface::logError(std::string &cat, const char* methodName, jstring jresult)
{
if (jresult == NULL)
QRLogger::log(cat, LL_ERROR, "Unknown Java error in %s.", methodName);
else
{
const char* char_result = jenv_->GetStringUTFChars(jresult, 0);
QRLogger::log(cat, LL_ERROR, "%s error: %s.", methodName, char_result);
jenv_->ReleaseStringUTFChars(jresult, char_result);
jenv_->DeleteLocalRef(jresult);
}
}
//////////////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////////////
void JavaObjectInterface::logError(std::string &cat, const char* file, int line)
{
QRLogger::log(cat, LL_ERROR, "Java exception in file %s, line %d.", file, line);
}
NABoolean JavaObjectInterface::getExceptionDetails(const char *fileName, int lineNo,
const char *methodName)
{
JNIEnv *jenv = jenv_;
CliGlobals *cliGlobals = GetCliGlobals();
NAString error_msg;
if (gThrowableClass == NULL)
{
jenv->ExceptionDescribe();
error_msg = "Internal Error - Unable to find Throwable class";
setSqlJniErrorStr(error_msg);
return FALSE;
}
jthrowable a_exception = jenv->ExceptionOccurred();
if (a_exception == NULL)
{
error_msg = "No java exception was thrown";
setSqlJniErrorStr(error_msg);
return FALSE;
}
appendExceptionMessages(a_exception, error_msg);
setSqlJniErrorStr(error_msg);
logError(CAT_SQL_EXE, fileName, lineNo);
logError(CAT_SQL_EXE, methodName, error_msg);
jenv->ExceptionClear();
return TRUE;
}
void JavaObjectInterface::appendExceptionMessages(jthrowable a_exception, NAString &error_msg)
{
jstring msg_obj =
(jstring) jenv_->CallObjectMethod(a_exception,
gThrowableToStringMethodID);
const char *msg_str;
if (msg_obj != NULL)
{
msg_str = jenv_->GetStringUTFChars(msg_obj, 0);
error_msg += msg_str;
jenv_->ReleaseStringUTFChars(msg_obj, msg_str);
jenv_->DeleteLocalRef(msg_obj);
}
else
msg_str = "Exception is thrown, but tostring is null";
// Get the stack trace
jobjectArray frames =
(jobjectArray) jenv_->CallObjectMethod(
a_exception,
gGetStackTraceMethodID);
if (frames == NULL)
return;
jsize frames_length = jenv_->GetArrayLength(frames);
jsize i = 0;
for (i = 0; i < frames_length; i++)
{
jobject frame = jenv_->GetObjectArrayElement(frames, i);
msg_obj = (jstring) jenv_->CallObjectMethod(frame,
gStackFrameToStringMethodID);
if (msg_obj != NULL)
{
msg_str = jenv_->GetStringUTFChars(msg_obj, 0);
error_msg += "\n";
error_msg += msg_str;
jenv_->ReleaseStringUTFChars(msg_obj, msg_str);
jenv_->DeleteLocalRef(msg_obj);
jenv_->DeleteLocalRef(frame);
}
}
jthrowable j_cause = (jthrowable)jenv_->CallObjectMethod(a_exception, gGetCauseMethodID);
if (j_cause != NULL) {
error_msg += " Caused by \n";
appendExceptionMessages(j_cause, error_msg);
}
jenv_->DeleteLocalRef(a_exception);
}
JOI_RetCode JavaObjectInterface::initJNIEnv()
{
JOI_RetCode retcode;
if (jenv_ == NULL) {
if ((retcode = initJVM()) != JOI_OK)
return retcode;
}
if (jenv_->PushLocalFrame(jniHandleCapacity_) != 0) {
return JOI_ERROR_INIT_JNI;
}
return JOI_OK;
}