Skip to content

Commit 0c74810

Browse files
committed
Support restore with -XX:CRaCRestoreFrom=PATH
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]>
1 parent 88124bc commit 0c74810

File tree

1 file changed

+360
-1
lines changed
  • src/java.base/share/native/libjli

1 file changed

+360
-1
lines changed

src/java.base/share/native/libjli/java.c

Lines changed: 360 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
/*
2727
* ===========================================================================
28-
* (c) Copyright IBM Corp. 2022, 2023 All Rights Reserved
28+
* (c) Copyright IBM Corp. 2022, 2024 All Rights Reserved
2929
* ===========================================================================
3030
*/
3131

@@ -61,6 +61,11 @@
6161

6262
#include "java.h"
6363
#include "jni.h"
64+
#include "j9cfg.h"
65+
#if defined(J9VM_OPT_CRAC_SUPPORT)
66+
#include <ctype.h>
67+
#include <sys/wait.h>
68+
#endif /* defined(J9VM_OPT_CRAC_SUPPORT) */
6469

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

7378
#define USE_STDERR JNI_TRUE /* we usually print to stderr */
7479
#define USE_STDOUT JNI_FALSE
80+
#if defined(J9VM_OPT_CRAC_SUPPORT)
81+
#define EQUALS_SIGN '='
82+
#define NULL_TERMINATOR '\0'
83+
#define OPTION_VALUE_MEMORY_ALLOCATION_ERROR_MESSAGE "Failed to allocate memory for the value of the command line option %s."
84+
#define OPTION_VALUE_NOT_FOUND_ERROR_MESSAGE "The value of the command line option %s was not found."
85+
#define LOG_LEVEL_LENGTH_CALCULATATION_ERROR_MESSAGE "Failed to calculate the length of the CRIU log level string."
86+
#define LOG_LEVEL_MEMORY_ALLOCATION_ERROR_MESSAGE "Failed to allocate memory for the CRIU log level."
87+
#define OPTION_VALUE_NOT_VALID_ERROR_MESSAGE "The value of the command line option is not valid, option=%s, value=%s."
88+
#define OPTION_VALUE_NOT_EXPECTED_ERROR_MESSAGE "The value of the command line option is not expected, option=%s, value=%s."
89+
#define LOG_FILE_LENGTH_CALCULATION_ERROR_MESSAGE "Failed to calculate the length of the CRIU log file string."
90+
#define LOG_FILE_MEMORY_ALLOCATION_ERROR_MESSAGE "Failed to allocate memory for the CRIU log file option."
91+
#define CHECKPOINT_DIRECTORY_ERROR_MESSAGE "Failed to get the CRIU checkpoint directory, error=%d."
92+
#define LOG_LEVEL_ERROR_MESSAGE "Failed to get the CRIU log level, error=%d."
93+
#define UNPRIVILEGED_MODE_ERROR_MESSAGE "Failed to get the CRIU unprivileged mode, error=%d."
94+
#define LOG_FILE_ERROR_MESSAGE "Failed to get the CRIU log file, error=%d."
95+
#define RESTORE_FROM_CHECKPOINT_ERROR_MESSAGE "Failed to restore from checkpoint."
96+
#define RESTORE_CHILD_PROCESS_FAILED_ERROR_MESSAGE "The CRIU restore child process failed."
97+
#define MEMORY_ALLOCATION_ERROR 1
98+
#define OPTION_NAME_NOT_FOUND_ERROR 2
99+
#define OPTION_VALUE_NOT_FOUND_ERROR 3
100+
#define OPTION_VALUE_NOT_VALID_ERROR 4
101+
#define OPTION_VALUE_NOT_EXPECTED_ERROR 5
102+
#define CRAC_RESTORE_FROM_OPTION_NAME "-XX:CRaCRestoreFrom"
103+
#define LOG_LEVEL_OPTION_NAME "-Dopenj9.internal.criu.logLevel"
104+
#define LOG_LEVEL_OPTION_FORMAT "-v%d"
105+
#define UNPRIVILEGED_MODE_OPTION_NAME "-Dopenj9.internal.criu.unprivilegedMode"
106+
#define LOG_FILE_OPTION_NAME "-Dopenj9.internal.criu.logFile"
107+
#define LOG_FILE_OPTION_FORMAT "--log-file=%s"
108+
#define CRIU_RESTORE_MIN_ARGS 6
109+
#define CRIU_RESTORE_MAX_ARGS 9
110+
#define CRIU_COMMAND "criu"
111+
#define CRIU_RESTORE_OPTION "restore"
112+
#define CRIU_CHECKPOINT_DIRECTORY_OPTION "-D"
113+
#define CRIU_DEFAULT_LOG_LEVEL_OPTION "-v2"
114+
#define CRIU_UNPRIVILEGED_MODE_OPTION "--unprivileged"
115+
#define CRIU_SHELL_JOB_OPTION "--shell-job"
116+
#define EXECVP_ERROR -1
117+
#define EXECVP_SUCCESS 0
118+
#define WAIT_INDEFINITELY 0
119+
#endif /* defined(J9VM_OPT_CRAC_SUPPORT) */
75120

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

582+
#if defined(J9VM_OPT_CRAC_SUPPORT)
583+
/**
584+
* Get the value of a command line option in the array of command line arguments.
585+
* @param[in] optionName The name of the command line option to search for
586+
* @param[in] argc The number of command line arguments
587+
* @param[in] argv The array of command line arguments
588+
* @param[out] error A pointer to an integer for the error code
589+
* @return A pointer to the value of the command line option if found, NULL otherwise
590+
*/
591+
static char *getCommandLineOptionValue(const char *optionName, int argc, char **argv, int *error) {
592+
char *arg = NULL;
593+
unsigned int optionNameLength = strlen(optionName);
594+
char *equals = NULL;
595+
jboolean optionNameFound = JNI_FALSE;
596+
char *value = NULL;
597+
*error = 0;
598+
for (int i = 0; i < argc; i++) {
599+
arg = argv[i];
600+
if (0 == strncmp(arg, optionName, optionNameLength)) {
601+
equals = arg + optionNameLength;
602+
if ((EQUALS_SIGN == *equals) || (NULL_TERMINATOR == *equals)) {
603+
optionNameFound = JNI_TRUE;
604+
if (EQUALS_SIGN == *equals) {
605+
equals++;
606+
}
607+
if (NULL_TERMINATOR != *equals) {
608+
value = strdup(equals);
609+
if (NULL == value) {
610+
JLI_ReportErrorMessage(OPTION_VALUE_MEMORY_ALLOCATION_ERROR_MESSAGE, optionName);
611+
*error = MEMORY_ALLOCATION_ERROR;
612+
}
613+
}
614+
break;
615+
}
616+
}
617+
}
618+
if (!optionNameFound) {
619+
*error = OPTION_NAME_NOT_FOUND_ERROR;
620+
}
621+
return value;
622+
}
623+
624+
/**
625+
* Free the memory allocated for a command line option value.
626+
* @param[in/out] value A pointer to a pointer to the command line option value
627+
*/
628+
static void freeCommandLineOptionValue(char **value) {
629+
if ((NULL != value) && (NULL != *value)) {
630+
JLI_MemFree(*value);
631+
*value = NULL;
632+
}
633+
}
634+
635+
/**
636+
* Check if an error occurred while retrieving a command line option value.
637+
* @param[in] error The error code of the result of obtaining the command line option value
638+
* @return true if an error occurred, false otherwise
639+
*/
640+
static jboolean hasGetCommandLineOptionValueFoundError(int error) {
641+
return 0 != error;
642+
}
643+
644+
/**
645+
* Get the checkpoint directory from command line options.
646+
* @param[in] argc The number of command line arguments
647+
* @param[in] argv The array of command line arguments
648+
* @param[in/out] error A pointer to an integer for the error code
649+
* @return A pointer to the checkpoint directory if found, NULL otherwise
650+
*/
651+
static char *getCheckpointDirectory(int argc, char **argv, int *error) {
652+
char *checkpointDirectory = NULL;
653+
char *checkpointDirectoryPropertyValue = getCommandLineOptionValue(CRAC_RESTORE_FROM_OPTION_NAME, argc, argv, error);
654+
if (!hasGetCommandLineOptionValueFoundError(*error)) {
655+
if (NULL != checkpointDirectoryPropertyValue) {
656+
checkpointDirectory = checkpointDirectoryPropertyValue;
657+
} else {
658+
JLI_ReportErrorMessage(OPTION_VALUE_NOT_FOUND_ERROR_MESSAGE, CRAC_RESTORE_FROM_OPTION_NAME);
659+
*error = OPTION_VALUE_NOT_FOUND_ERROR;
660+
}
661+
}
662+
return checkpointDirectory;
663+
}
664+
665+
/**
666+
* Check if a command line option is found with an error condition.
667+
* @param[in] error The error code of the result of obtaining the command line option
668+
* @return true if the command line option is found and an error occurred, false otherwise
669+
*/
670+
static jboolean isCommandLineOptionFoundWithError(int error) {
671+
return (OPTION_NAME_NOT_FOUND_ERROR != error) && hasGetCommandLineOptionValueFoundError(error);
672+
}
673+
674+
/**
675+
* Get the log level specified in the command line arguments.
676+
* @param[in] argc The number of command line arguments
677+
* @param[in] argv The array of command line arguments
678+
* @param[in/out] error A pointer to an integer for the error code
679+
* @return A pointer to the log level string if successful, NULL otherwise
680+
*/
681+
static char *getLogLevel(int argc, char **argv, int *error) {
682+
char *logLevel = NULL;
683+
char *logLevelPropertyValue = NULL;
684+
int logLevelValue = 0;
685+
int logLevelLength = 0;
686+
logLevelPropertyValue = getCommandLineOptionValue(LOG_LEVEL_OPTION_NAME, argc, argv, error);
687+
if (!isCommandLineOptionFoundWithError(*error)) {
688+
if (NULL != logLevelPropertyValue) {
689+
for (const char *c = logLevelPropertyValue; NULL_TERMINATOR != *c; c++) {
690+
if (!isdigit(*c)) {
691+
goto setLogLevelOptionValueNotValidError;
692+
}
693+
}
694+
logLevelValue = atoi(logLevelPropertyValue);
695+
if ((logLevelValue >= 0) && (logLevelValue <= 4)) {
696+
logLevelLength = snprintf(NULL, 0, LOG_LEVEL_OPTION_FORMAT, logLevelValue);
697+
if (logLevelLength < 0) {
698+
JLI_ReportErrorMessage(LOG_LEVEL_LENGTH_CALCULATATION_ERROR_MESSAGE);
699+
*error = MEMORY_ALLOCATION_ERROR;
700+
goto freeLogLevelPropertyValue;
701+
}
702+
logLevel = (char *)JLI_MemAlloc(logLevelLength + 1);
703+
if (NULL == logLevel) {
704+
JLI_ReportErrorMessage(LOG_LEVEL_MEMORY_ALLOCATION_ERROR_MESSAGE);
705+
*error = MEMORY_ALLOCATION_ERROR;
706+
goto freeLogLevelPropertyValue;
707+
}
708+
snprintf(logLevel, logLevelLength + 1, LOG_LEVEL_OPTION_FORMAT, logLevelValue);
709+
goto freeLogLevelPropertyValue;
710+
} else {
711+
goto setLogLevelOptionValueNotValidError;
712+
}
713+
} else {
714+
goto returnGetLogLevel;
715+
}
716+
}
717+
setLogLevelOptionValueNotValidError:
718+
JLI_ReportErrorMessage(OPTION_VALUE_NOT_VALID_ERROR_MESSAGE, LOG_LEVEL_OPTION_NAME, logLevelPropertyValue);
719+
*error = OPTION_VALUE_NOT_VALID_ERROR;
720+
freeLogLevelPropertyValue:
721+
freeCommandLineOptionValue(&logLevelPropertyValue);
722+
returnGetLogLevel:
723+
return logLevel;
724+
}
725+
726+
/**
727+
* Check if the unprivileged mode option is specified in the command line arguments.
728+
* @param[in] argc The number of command line arguments
729+
* @param[in] argv The array of command line arguments
730+
* @param[in/out] error A pointer to an integer for the error code
731+
* @return true if the unprivileged mode option is specified in the command line arguments, false otherwise
732+
*/
733+
static jboolean isUnprivilegedModeOn(int argc, char **argv, int *error) {
734+
jboolean isUnprivilegedModeOn = JNI_FALSE;
735+
char *unprivilegedModePropertyValue = NULL;
736+
unprivilegedModePropertyValue = getCommandLineOptionValue(UNPRIVILEGED_MODE_OPTION_NAME, argc, argv, error);
737+
if (!hasGetCommandLineOptionValueFoundError(*error)) {
738+
if (NULL == unprivilegedModePropertyValue) {
739+
isUnprivilegedModeOn = JNI_TRUE;
740+
} else {
741+
JLI_ReportErrorMessage(OPTION_VALUE_NOT_EXPECTED_ERROR_MESSAGE, UNPRIVILEGED_MODE_OPTION_NAME, unprivilegedModePropertyValue);
742+
*error = OPTION_VALUE_NOT_EXPECTED_ERROR;
743+
freeCommandLineOptionValue(&unprivilegedModePropertyValue);
744+
}
745+
}
746+
return isUnprivilegedModeOn;
747+
}
748+
749+
/**
750+
* Get the log file option specified in the command line arguments.
751+
* @param[in] argc The number of command line arguments
752+
* @param[in] argv The array of command line arguments
753+
* @param[in/out] error A pointer to an integer for the error code
754+
* @return A pointer to the log file string if successful, NULL otherwise
755+
*/
756+
static char *getLogFile(int argc, char **argv, int *error) {
757+
char *logFile = NULL;
758+
char *logFilePropertyValue = NULL;
759+
int logFileLength = 0;
760+
logFilePropertyValue = getCommandLineOptionValue(LOG_FILE_OPTION_NAME, argc, argv, error);
761+
if (!hasGetCommandLineOptionValueFoundError(*error)) {
762+
if (NULL != logFilePropertyValue) {
763+
logFileLength = snprintf(NULL, 0, LOG_FILE_OPTION_FORMAT, logFilePropertyValue) + 1;
764+
if (logFileLength < 0) {
765+
JLI_ReportErrorMessage(LOG_FILE_LENGTH_CALCULATION_ERROR_MESSAGE);
766+
*error = MEMORY_ALLOCATION_ERROR;
767+
goto freeLogFilePropertyValue;
768+
}
769+
logFile = (char *)JLI_MemAlloc(logFileLength);
770+
if (NULL == logFile) {
771+
JLI_ReportErrorMessage(LOG_FILE_MEMORY_ALLOCATION_ERROR_MESSAGE);
772+
*error = MEMORY_ALLOCATION_ERROR;
773+
} else {
774+
snprintf(logFile, logFileLength, LOG_FILE_OPTION_FORMAT, logFilePropertyValue);
775+
}
776+
goto freeLogFilePropertyValue;
777+
} else {
778+
JLI_ReportErrorMessage(OPTION_VALUE_NOT_FOUND_ERROR_MESSAGE, LOG_FILE_OPTION_NAME);
779+
*error = OPTION_VALUE_NOT_FOUND_ERROR;
780+
goto returnGetLogFile;
781+
}
782+
}
783+
freeLogFilePropertyValue:
784+
freeCommandLineOptionValue(&logFilePropertyValue);
785+
returnGetLogFile:
786+
return logFile;
787+
}
788+
789+
/**
790+
* Restore the system state from a checkpoint using the CRIU tool.
791+
* @param[in] checkpointDirectory The directory containing the checkpoint data
792+
* @param[in] logLevel The log level for CRIU logging
793+
* @param[in] unprivilegedModeOn Indicates whether the unprivileged mode option is on
794+
* @param[in] logFile The log file option for CRIU
795+
* @return EXECVP_SUCCESS if the execution of the 'criu restore' command succeeds, EXECVP_ERROR otherwise
796+
*/
797+
static int restoreFromCheckpoint(char *checkpointDirectory, char *logLevel, jboolean unprivilegedModeOn, char *logFile) {
798+
int argc = CRIU_RESTORE_MIN_ARGS;
799+
char *argv[CRIU_RESTORE_MAX_ARGS] = { NULL };
800+
argv[0] = CRIU_COMMAND;
801+
argv[1] = CRIU_RESTORE_OPTION;
802+
argv[2] = CRIU_CHECKPOINT_DIRECTORY_OPTION;
803+
argv[3] = checkpointDirectory;
804+
argv[4] = (NULL == logLevel) ? CRIU_DEFAULT_LOG_LEVEL_OPTION : logLevel;
805+
argv[5] = CRIU_SHELL_JOB_OPTION;
806+
if (unprivilegedModeOn) {
807+
argv[argc++] = CRIU_UNPRIVILEGED_MODE_OPTION;
808+
}
809+
if (NULL != logFile) {
810+
argv[argc++] = logFile;
811+
}
812+
argv[argc] = NULL;
813+
if (EXECVP_ERROR == execvp(argv[0], argv)) {
814+
return EXECVP_ERROR;
815+
}
816+
return EXECVP_SUCCESS;
817+
}
818+
819+
/**
820+
* Handle the restoration of the system state from a checkpoint.
821+
* @param[in] argc The number of command line arguments
822+
* @param[in] argv The array of command line arguments
823+
*/
824+
static void handleCRaCRestore(int argc, char **argv) {
825+
char *checkpointDirectory = NULL;
826+
int error = 0;
827+
int parentProcessExitStatus = EXIT_SUCCESS;
828+
pid_t childProcessPid = 0;
829+
char *logLevel = NULL;
830+
int childProcessExitStatus = EXIT_SUCCESS;
831+
jboolean unprivilegedModeOn = JNI_FALSE;
832+
char *logFile = NULL;
833+
int childProcessPidStatus = 0;
834+
checkpointDirectory = getCheckpointDirectory(argc, argv, &error);
835+
if (OPTION_NAME_NOT_FOUND_ERROR == error) {
836+
return;
837+
}
838+
if (hasGetCommandLineOptionValueFoundError(error)) {
839+
JLI_ReportErrorMessage(CHECKPOINT_DIRECTORY_ERROR_MESSAGE, error);
840+
parentProcessExitStatus = EXIT_FAILURE;
841+
goto exitParentProcessHandleCRaCRestore;
842+
}
843+
/*
844+
* The if block will be invoked by the child process,
845+
* and the else block will be invoked by the parent process.
846+
*/
847+
childProcessPid = fork();
848+
if (0 == childProcessPid) {
849+
logLevel = getLogLevel(argc, argv, &error);
850+
if (isCommandLineOptionFoundWithError(error)) {
851+
JLI_ReportErrorMessage(LOG_LEVEL_ERROR_MESSAGE, error);
852+
childProcessExitStatus = EXIT_FAILURE;
853+
goto exitChildProcessHandleCRaCRestore;
854+
}
855+
unprivilegedModeOn = isUnprivilegedModeOn(argc, argv, &error);
856+
if (isCommandLineOptionFoundWithError(error)) {
857+
JLI_ReportErrorMessage(UNPRIVILEGED_MODE_ERROR_MESSAGE, error);
858+
childProcessExitStatus = EXIT_FAILURE;
859+
goto freeLogLevel;
860+
}
861+
logFile = getLogFile(argc, argv, &error);
862+
if (isCommandLineOptionFoundWithError(error)) {
863+
JLI_ReportErrorMessage(LOG_FILE_ERROR_MESSAGE, error);
864+
childProcessExitStatus = EXIT_FAILURE;
865+
goto freeLogLevel;
866+
}
867+
childProcessExitStatus = restoreFromCheckpoint(checkpointDirectory, logLevel, unprivilegedModeOn, logFile);
868+
freeLogFile:
869+
freeCommandLineOptionValue(&logFile);
870+
freeLogLevel:
871+
freeCommandLineOptionValue(&logLevel);
872+
exitChildProcessHandleCRaCRestore:
873+
exit(childProcessExitStatus);
874+
} else {
875+
waitpid(childProcessPid, &childProcessPidStatus, WAIT_INDEFINITELY);
876+
if (WIFEXITED(childProcessPidStatus)) {
877+
if (EXIT_SUCCESS != WEXITSTATUS(childProcessPidStatus)) {
878+
JLI_ReportErrorMessage(RESTORE_FROM_CHECKPOINT_ERROR_MESSAGE);
879+
parentProcessExitStatus = EXIT_FAILURE;
880+
}
881+
} else {
882+
JLI_ReportErrorMessage(RESTORE_CHILD_PROCESS_FAILED_ERROR_MESSAGE);
883+
parentProcessExitStatus = EXIT_FAILURE;
884+
}
885+
freeCommandLineOptionValue(&checkpointDirectory);
886+
}
887+
exitParentProcessHandleCRaCRestore:
888+
exit(parentProcessExitStatus);
889+
}
890+
#endif /* defined(J9VM_OPT_CRAC_SUPPORT) */
891+
537892
int
538893
JavaMain(void* _args)
539894
{
@@ -554,6 +909,10 @@ JavaMain(void* _args)
554909

555910
RegisterThread();
556911

912+
#if defined(J9VM_OPT_CRAC_SUPPORT)
913+
handleCRaCRestore(argc, argv);
914+
#endif /* defined(J9VM_OPT_CRAC_SUPPORT) */
915+
557916
/* Initialize the virtual machine */
558917
start = CurrentTimeMicros();
559918
if (!InitializeJVM(&vm, &env, &ifn)) {

0 commit comments

Comments
 (0)