Skip to content

Commit

Permalink
Support restore with -XX:CRaCRestoreFrom=PATH
Browse files Browse the repository at this point in the history
Support restore with -XX:CRaCRestoreFrom=PATH that
specifies the path to the checkpoint image.

Issue: eclipse-openj9/openj9#19261
Signed-off-by: Amarpreet Singh <[email protected]>
  • Loading branch information
singh264 committed May 30, 2024
1 parent 88124bc commit 0c74810
Showing 1 changed file with 360 additions and 1 deletion.
361 changes: 360 additions & 1 deletion src/java.base/share/native/libjli/java.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

/*
* ===========================================================================
* (c) Copyright IBM Corp. 2022, 2023 All Rights Reserved
* (c) Copyright IBM Corp. 2022, 2024 All Rights Reserved
* ===========================================================================
*/

Expand Down Expand Up @@ -61,6 +61,11 @@

#include "java.h"
#include "jni.h"
#include "j9cfg.h"
#if defined(J9VM_OPT_CRAC_SUPPORT)
#include <ctype.h>
#include <sys/wait.h>
#endif /* defined(J9VM_OPT_CRAC_SUPPORT) */

/*
* A NOTE TO DEVELOPERS: For performance reasons it is important that
Expand All @@ -72,6 +77,46 @@

#define USE_STDERR JNI_TRUE /* we usually print to stderr */
#define USE_STDOUT JNI_FALSE
#if defined(J9VM_OPT_CRAC_SUPPORT)
#define EQUALS_SIGN '='
#define NULL_TERMINATOR '\0'
#define OPTION_VALUE_MEMORY_ALLOCATION_ERROR_MESSAGE "Failed to allocate memory for the value of the command line option %s."
#define OPTION_VALUE_NOT_FOUND_ERROR_MESSAGE "The value of the command line option %s was not found."
#define LOG_LEVEL_LENGTH_CALCULATATION_ERROR_MESSAGE "Failed to calculate the length of the CRIU log level string."
#define LOG_LEVEL_MEMORY_ALLOCATION_ERROR_MESSAGE "Failed to allocate memory for the CRIU log level."
#define OPTION_VALUE_NOT_VALID_ERROR_MESSAGE "The value of the command line option is not valid, option=%s, value=%s."
#define OPTION_VALUE_NOT_EXPECTED_ERROR_MESSAGE "The value of the command line option is not expected, option=%s, value=%s."
#define LOG_FILE_LENGTH_CALCULATION_ERROR_MESSAGE "Failed to calculate the length of the CRIU log file string."
#define LOG_FILE_MEMORY_ALLOCATION_ERROR_MESSAGE "Failed to allocate memory for the CRIU log file option."
#define CHECKPOINT_DIRECTORY_ERROR_MESSAGE "Failed to get the CRIU checkpoint directory, error=%d."
#define LOG_LEVEL_ERROR_MESSAGE "Failed to get the CRIU log level, error=%d."
#define UNPRIVILEGED_MODE_ERROR_MESSAGE "Failed to get the CRIU unprivileged mode, error=%d."
#define LOG_FILE_ERROR_MESSAGE "Failed to get the CRIU log file, error=%d."
#define RESTORE_FROM_CHECKPOINT_ERROR_MESSAGE "Failed to restore from checkpoint."
#define RESTORE_CHILD_PROCESS_FAILED_ERROR_MESSAGE "The CRIU restore child process failed."
#define MEMORY_ALLOCATION_ERROR 1
#define OPTION_NAME_NOT_FOUND_ERROR 2
#define OPTION_VALUE_NOT_FOUND_ERROR 3
#define OPTION_VALUE_NOT_VALID_ERROR 4
#define OPTION_VALUE_NOT_EXPECTED_ERROR 5
#define CRAC_RESTORE_FROM_OPTION_NAME "-XX:CRaCRestoreFrom"
#define LOG_LEVEL_OPTION_NAME "-Dopenj9.internal.criu.logLevel"
#define LOG_LEVEL_OPTION_FORMAT "-v%d"
#define UNPRIVILEGED_MODE_OPTION_NAME "-Dopenj9.internal.criu.unprivilegedMode"
#define LOG_FILE_OPTION_NAME "-Dopenj9.internal.criu.logFile"
#define LOG_FILE_OPTION_FORMAT "--log-file=%s"
#define CRIU_RESTORE_MIN_ARGS 6
#define CRIU_RESTORE_MAX_ARGS 9
#define CRIU_COMMAND "criu"
#define CRIU_RESTORE_OPTION "restore"
#define CRIU_CHECKPOINT_DIRECTORY_OPTION "-D"
#define CRIU_DEFAULT_LOG_LEVEL_OPTION "-v2"
#define CRIU_UNPRIVILEGED_MODE_OPTION "--unprivileged"
#define CRIU_SHELL_JOB_OPTION "--shell-job"
#define EXECVP_ERROR -1
#define EXECVP_SUCCESS 0
#define WAIT_INDEFINITELY 0
#endif /* defined(J9VM_OPT_CRAC_SUPPORT) */

static jboolean printVersion = JNI_FALSE; /* print and exit */
static jboolean showVersion = JNI_FALSE; /* print but continue */
Expand Down Expand Up @@ -534,6 +579,316 @@ invokeInstanceMainWithoutArgs(JNIEnv *env, jclass mainClass) {
return 1;
}

#if defined(J9VM_OPT_CRAC_SUPPORT)
/**
* Get the value of a command line option in the array of command line arguments.
* @param[in] optionName The name of the command line option to search for
* @param[in] argc The number of command line arguments
* @param[in] argv The array of command line arguments
* @param[out] error A pointer to an integer for the error code
* @return A pointer to the value of the command line option if found, NULL otherwise
*/
static char *getCommandLineOptionValue(const char *optionName, int argc, char **argv, int *error) {
char *arg = NULL;
unsigned int optionNameLength = strlen(optionName);
char *equals = NULL;
jboolean optionNameFound = JNI_FALSE;
char *value = NULL;
*error = 0;
for (int i = 0; i < argc; i++) {
arg = argv[i];
if (0 == strncmp(arg, optionName, optionNameLength)) {
equals = arg + optionNameLength;
if ((EQUALS_SIGN == *equals) || (NULL_TERMINATOR == *equals)) {
optionNameFound = JNI_TRUE;
if (EQUALS_SIGN == *equals) {
equals++;
}
if (NULL_TERMINATOR != *equals) {
value = strdup(equals);
if (NULL == value) {
JLI_ReportErrorMessage(OPTION_VALUE_MEMORY_ALLOCATION_ERROR_MESSAGE, optionName);
*error = MEMORY_ALLOCATION_ERROR;
}
}
break;
}
}
}
if (!optionNameFound) {
*error = OPTION_NAME_NOT_FOUND_ERROR;
}
return value;
}

/**
* Free the memory allocated for a command line option value.
* @param[in/out] value A pointer to a pointer to the command line option value
*/
static void freeCommandLineOptionValue(char **value) {
if ((NULL != value) && (NULL != *value)) {
JLI_MemFree(*value);
*value = NULL;
}
}

/**
* Check if an error occurred while retrieving a command line option value.
* @param[in] error The error code of the result of obtaining the command line option value
* @return true if an error occurred, false otherwise
*/
static jboolean hasGetCommandLineOptionValueFoundError(int error) {
return 0 != error;
}

/**
* Get the checkpoint directory from command line options.
* @param[in] argc The number of command line arguments
* @param[in] argv The array of command line arguments
* @param[in/out] error A pointer to an integer for the error code
* @return A pointer to the checkpoint directory if found, NULL otherwise
*/
static char *getCheckpointDirectory(int argc, char **argv, int *error) {
char *checkpointDirectory = NULL;
char *checkpointDirectoryPropertyValue = getCommandLineOptionValue(CRAC_RESTORE_FROM_OPTION_NAME, argc, argv, error);
if (!hasGetCommandLineOptionValueFoundError(*error)) {
if (NULL != checkpointDirectoryPropertyValue) {
checkpointDirectory = checkpointDirectoryPropertyValue;
} else {
JLI_ReportErrorMessage(OPTION_VALUE_NOT_FOUND_ERROR_MESSAGE, CRAC_RESTORE_FROM_OPTION_NAME);
*error = OPTION_VALUE_NOT_FOUND_ERROR;
}
}
return checkpointDirectory;
}

/**
* Check if a command line option is found with an error condition.
* @param[in] error The error code of the result of obtaining the command line option
* @return true if the command line option is found and an error occurred, false otherwise
*/
static jboolean isCommandLineOptionFoundWithError(int error) {
return (OPTION_NAME_NOT_FOUND_ERROR != error) && hasGetCommandLineOptionValueFoundError(error);
}

/**
* Get the log level specified in the command line arguments.
* @param[in] argc The number of command line arguments
* @param[in] argv The array of command line arguments
* @param[in/out] error A pointer to an integer for the error code
* @return A pointer to the log level string if successful, NULL otherwise
*/
static char *getLogLevel(int argc, char **argv, int *error) {
char *logLevel = NULL;
char *logLevelPropertyValue = NULL;
int logLevelValue = 0;
int logLevelLength = 0;
logLevelPropertyValue = getCommandLineOptionValue(LOG_LEVEL_OPTION_NAME, argc, argv, error);
if (!isCommandLineOptionFoundWithError(*error)) {
if (NULL != logLevelPropertyValue) {
for (const char *c = logLevelPropertyValue; NULL_TERMINATOR != *c; c++) {
if (!isdigit(*c)) {
goto setLogLevelOptionValueNotValidError;
}
}
logLevelValue = atoi(logLevelPropertyValue);
if ((logLevelValue >= 0) && (logLevelValue <= 4)) {
logLevelLength = snprintf(NULL, 0, LOG_LEVEL_OPTION_FORMAT, logLevelValue);
if (logLevelLength < 0) {
JLI_ReportErrorMessage(LOG_LEVEL_LENGTH_CALCULATATION_ERROR_MESSAGE);
*error = MEMORY_ALLOCATION_ERROR;
goto freeLogLevelPropertyValue;
}
logLevel = (char *)JLI_MemAlloc(logLevelLength + 1);
if (NULL == logLevel) {
JLI_ReportErrorMessage(LOG_LEVEL_MEMORY_ALLOCATION_ERROR_MESSAGE);
*error = MEMORY_ALLOCATION_ERROR;
goto freeLogLevelPropertyValue;
}
snprintf(logLevel, logLevelLength + 1, LOG_LEVEL_OPTION_FORMAT, logLevelValue);
goto freeLogLevelPropertyValue;
} else {
goto setLogLevelOptionValueNotValidError;
}
} else {
goto returnGetLogLevel;
}
}
setLogLevelOptionValueNotValidError:
JLI_ReportErrorMessage(OPTION_VALUE_NOT_VALID_ERROR_MESSAGE, LOG_LEVEL_OPTION_NAME, logLevelPropertyValue);
*error = OPTION_VALUE_NOT_VALID_ERROR;
freeLogLevelPropertyValue:
freeCommandLineOptionValue(&logLevelPropertyValue);
returnGetLogLevel:
return logLevel;
}

/**
* Check if the unprivileged mode option is specified in the command line arguments.
* @param[in] argc The number of command line arguments
* @param[in] argv The array of command line arguments
* @param[in/out] error A pointer to an integer for the error code
* @return true if the unprivileged mode option is specified in the command line arguments, false otherwise
*/
static jboolean isUnprivilegedModeOn(int argc, char **argv, int *error) {
jboolean isUnprivilegedModeOn = JNI_FALSE;
char *unprivilegedModePropertyValue = NULL;
unprivilegedModePropertyValue = getCommandLineOptionValue(UNPRIVILEGED_MODE_OPTION_NAME, argc, argv, error);
if (!hasGetCommandLineOptionValueFoundError(*error)) {
if (NULL == unprivilegedModePropertyValue) {
isUnprivilegedModeOn = JNI_TRUE;
} else {
JLI_ReportErrorMessage(OPTION_VALUE_NOT_EXPECTED_ERROR_MESSAGE, UNPRIVILEGED_MODE_OPTION_NAME, unprivilegedModePropertyValue);
*error = OPTION_VALUE_NOT_EXPECTED_ERROR;
freeCommandLineOptionValue(&unprivilegedModePropertyValue);
}
}
return isUnprivilegedModeOn;
}

/**
* Get the log file option specified in the command line arguments.
* @param[in] argc The number of command line arguments
* @param[in] argv The array of command line arguments
* @param[in/out] error A pointer to an integer for the error code
* @return A pointer to the log file string if successful, NULL otherwise
*/
static char *getLogFile(int argc, char **argv, int *error) {
char *logFile = NULL;
char *logFilePropertyValue = NULL;
int logFileLength = 0;
logFilePropertyValue = getCommandLineOptionValue(LOG_FILE_OPTION_NAME, argc, argv, error);
if (!hasGetCommandLineOptionValueFoundError(*error)) {
if (NULL != logFilePropertyValue) {
logFileLength = snprintf(NULL, 0, LOG_FILE_OPTION_FORMAT, logFilePropertyValue) + 1;
if (logFileLength < 0) {
JLI_ReportErrorMessage(LOG_FILE_LENGTH_CALCULATION_ERROR_MESSAGE);
*error = MEMORY_ALLOCATION_ERROR;
goto freeLogFilePropertyValue;
}
logFile = (char *)JLI_MemAlloc(logFileLength);
if (NULL == logFile) {
JLI_ReportErrorMessage(LOG_FILE_MEMORY_ALLOCATION_ERROR_MESSAGE);
*error = MEMORY_ALLOCATION_ERROR;
} else {
snprintf(logFile, logFileLength, LOG_FILE_OPTION_FORMAT, logFilePropertyValue);
}
goto freeLogFilePropertyValue;
} else {
JLI_ReportErrorMessage(OPTION_VALUE_NOT_FOUND_ERROR_MESSAGE, LOG_FILE_OPTION_NAME);
*error = OPTION_VALUE_NOT_FOUND_ERROR;
goto returnGetLogFile;
}
}
freeLogFilePropertyValue:
freeCommandLineOptionValue(&logFilePropertyValue);
returnGetLogFile:
return logFile;
}

/**
* Restore the system state from a checkpoint using the CRIU tool.
* @param[in] checkpointDirectory The directory containing the checkpoint data
* @param[in] logLevel The log level for CRIU logging
* @param[in] unprivilegedModeOn Indicates whether the unprivileged mode option is on
* @param[in] logFile The log file option for CRIU
* @return EXECVP_SUCCESS if the execution of the 'criu restore' command succeeds, EXECVP_ERROR otherwise
*/
static int restoreFromCheckpoint(char *checkpointDirectory, char *logLevel, jboolean unprivilegedModeOn, char *logFile) {
int argc = CRIU_RESTORE_MIN_ARGS;
char *argv[CRIU_RESTORE_MAX_ARGS] = { NULL };
argv[0] = CRIU_COMMAND;
argv[1] = CRIU_RESTORE_OPTION;
argv[2] = CRIU_CHECKPOINT_DIRECTORY_OPTION;
argv[3] = checkpointDirectory;
argv[4] = (NULL == logLevel) ? CRIU_DEFAULT_LOG_LEVEL_OPTION : logLevel;
argv[5] = CRIU_SHELL_JOB_OPTION;
if (unprivilegedModeOn) {
argv[argc++] = CRIU_UNPRIVILEGED_MODE_OPTION;
}
if (NULL != logFile) {
argv[argc++] = logFile;
}
argv[argc] = NULL;
if (EXECVP_ERROR == execvp(argv[0], argv)) {
return EXECVP_ERROR;
}
return EXECVP_SUCCESS;
}

/**
* Handle the restoration of the system state from a checkpoint.
* @param[in] argc The number of command line arguments
* @param[in] argv The array of command line arguments
*/
static void handleCRaCRestore(int argc, char **argv) {
char *checkpointDirectory = NULL;
int error = 0;
int parentProcessExitStatus = EXIT_SUCCESS;
pid_t childProcessPid = 0;
char *logLevel = NULL;
int childProcessExitStatus = EXIT_SUCCESS;
jboolean unprivilegedModeOn = JNI_FALSE;
char *logFile = NULL;
int childProcessPidStatus = 0;
checkpointDirectory = getCheckpointDirectory(argc, argv, &error);
if (OPTION_NAME_NOT_FOUND_ERROR == error) {
return;
}
if (hasGetCommandLineOptionValueFoundError(error)) {
JLI_ReportErrorMessage(CHECKPOINT_DIRECTORY_ERROR_MESSAGE, error);
parentProcessExitStatus = EXIT_FAILURE;
goto exitParentProcessHandleCRaCRestore;
}
/*
* The if block will be invoked by the child process,
* and the else block will be invoked by the parent process.
*/
childProcessPid = fork();
if (0 == childProcessPid) {
logLevel = getLogLevel(argc, argv, &error);
if (isCommandLineOptionFoundWithError(error)) {
JLI_ReportErrorMessage(LOG_LEVEL_ERROR_MESSAGE, error);
childProcessExitStatus = EXIT_FAILURE;
goto exitChildProcessHandleCRaCRestore;
}
unprivilegedModeOn = isUnprivilegedModeOn(argc, argv, &error);
if (isCommandLineOptionFoundWithError(error)) {
JLI_ReportErrorMessage(UNPRIVILEGED_MODE_ERROR_MESSAGE, error);
childProcessExitStatus = EXIT_FAILURE;
goto freeLogLevel;
}
logFile = getLogFile(argc, argv, &error);
if (isCommandLineOptionFoundWithError(error)) {
JLI_ReportErrorMessage(LOG_FILE_ERROR_MESSAGE, error);
childProcessExitStatus = EXIT_FAILURE;
goto freeLogLevel;
}
childProcessExitStatus = restoreFromCheckpoint(checkpointDirectory, logLevel, unprivilegedModeOn, logFile);
freeLogFile:
freeCommandLineOptionValue(&logFile);
freeLogLevel:
freeCommandLineOptionValue(&logLevel);
exitChildProcessHandleCRaCRestore:
exit(childProcessExitStatus);
} else {
waitpid(childProcessPid, &childProcessPidStatus, WAIT_INDEFINITELY);
if (WIFEXITED(childProcessPidStatus)) {
if (EXIT_SUCCESS != WEXITSTATUS(childProcessPidStatus)) {
JLI_ReportErrorMessage(RESTORE_FROM_CHECKPOINT_ERROR_MESSAGE);
parentProcessExitStatus = EXIT_FAILURE;
}
} else {
JLI_ReportErrorMessage(RESTORE_CHILD_PROCESS_FAILED_ERROR_MESSAGE);
parentProcessExitStatus = EXIT_FAILURE;
}
freeCommandLineOptionValue(&checkpointDirectory);
}
exitParentProcessHandleCRaCRestore:
exit(parentProcessExitStatus);
}
#endif /* defined(J9VM_OPT_CRAC_SUPPORT) */

int
JavaMain(void* _args)
{
Expand All @@ -554,6 +909,10 @@ JavaMain(void* _args)

RegisterThread();

#if defined(J9VM_OPT_CRAC_SUPPORT)
handleCRaCRestore(argc, argv);
#endif /* defined(J9VM_OPT_CRAC_SUPPORT) */

/* Initialize the virtual machine */
start = CurrentTimeMicros();
if (!InitializeJVM(&vm, &env, &ifn)) {
Expand Down

0 comments on commit 0c74810

Please sign in to comment.