diff --git a/make/autoconf/flags-cflags.m4 b/make/autoconf/flags-cflags.m4 index e80d9a98957..38e8f2fce32 100644 --- a/make/autoconf/flags-cflags.m4 +++ b/make/autoconf/flags-cflags.m4 @@ -683,6 +683,9 @@ AC_DEFUN([FLAGS_SETUP_CFLAGS_CPU_DEP], $1_DEFINES_CPU_JDK="${$1_DEFINES_CPU_JDK} -DARCH='\"$FLAGS_CPU_LEGACY\"' \ -D$FLAGS_CPU_LEGACY" + # setup arch name (the same as 'os.arch' system property) + $1_DEFINES_CPU_JVM="${$1_DEFINES_CPU_JVM} -DARCHPROPNAME='\"$OPENJDK_TARGET_CPU_OSARCH\"'" + if test "x$FLAGS_CPU_BITS" = x64; then $1_DEFINES_CPU_JDK="${$1_DEFINES_CPU_JDK} -D_LP64=1" $1_DEFINES_CPU_JVM="${$1_DEFINES_CPU_JVM} -D_LP64=1" diff --git a/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp b/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp index bca985e0051..6b61f1f3011 100644 --- a/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp @@ -64,7 +64,9 @@ class VM_Version : public Abstract_VM_Version { public: // Initialization static void initialize(); - struct VM_Features {}; + struct VM_Features { + inline int print_numbers(char *buf_orig, size_t buflen, bool hexonly = false) const { return 0; } + }; static bool cpu_features_binary(VM_Features *data) { return false; } static bool cpu_features_binary_check(const VM_Features *data) { return data == nullptr; } static bool ignore_cpu_features() { return true; } diff --git a/src/hotspot/cpu/arm/vm_version_arm.hpp b/src/hotspot/cpu/arm/vm_version_arm.hpp index 99e2f570a91..a88ec94ccc3 100644 --- a/src/hotspot/cpu/arm/vm_version_arm.hpp +++ b/src/hotspot/cpu/arm/vm_version_arm.hpp @@ -41,7 +41,9 @@ class VM_Version: public Abstract_VM_Version { public: static void initialize(); static bool is_initialized() { return _is_initialized; } - struct VM_Features {}; + struct VM_Features { + inline int print_numbers(char *buf_orig, size_t buflen, bool hexonly = false) const { return 0; } + }; static bool cpu_features_binary(VM_Features *data) { return false; } static bool cpu_features_binary_check(const VM_Features *data) { return data == nullptr; } static bool ignore_cpu_features() { return true; } diff --git a/src/hotspot/cpu/ppc/vm_version_ppc.hpp b/src/hotspot/cpu/ppc/vm_version_ppc.hpp index e69ad8a0e3f..64cda6c1f96 100644 --- a/src/hotspot/cpu/ppc/vm_version_ppc.hpp +++ b/src/hotspot/cpu/ppc/vm_version_ppc.hpp @@ -53,7 +53,9 @@ class VM_Version: public Abstract_VM_Version { // Initialization static void initialize(); static void check_virtualizations(); - struct VM_Features {}; + struct VM_Features { + inline int print_numbers(char *buf_orig, size_t buflen, bool hexonly = false) const { return 0; } + }; static bool cpu_features_binary(VM_Features *data) { return false; } static bool cpu_features_binary_check(const VM_Features *data) { return data == nullptr; } static bool ignore_cpu_features() { return true; } diff --git a/src/hotspot/cpu/riscv/vm_version_riscv.hpp b/src/hotspot/cpu/riscv/vm_version_riscv.hpp index 3f9b8390498..bc84d943c6f 100644 --- a/src/hotspot/cpu/riscv/vm_version_riscv.hpp +++ b/src/hotspot/cpu/riscv/vm_version_riscv.hpp @@ -307,7 +307,9 @@ class VM_Version : public Abstract_VM_Version { // Initialization static void initialize(); static void initialize_cpu_information(); - struct VM_Features {}; + struct VM_Features { + inline int print_numbers(char *buf_orig, size_t buflen, bool hexonly = false) const { return 0; } + }; static bool cpu_features_binary(VM_Features *data) { return false; } static bool cpu_features_binary_check(const VM_Features *data) { return data == nullptr; } static bool ignore_cpu_features() { return true; } diff --git a/src/hotspot/cpu/s390/vm_version_s390.hpp b/src/hotspot/cpu/s390/vm_version_s390.hpp index 3b5b4cf5898..501c94a1de8 100644 --- a/src/hotspot/cpu/s390/vm_version_s390.hpp +++ b/src/hotspot/cpu/s390/vm_version_s390.hpp @@ -414,7 +414,9 @@ class VM_Version: public Abstract_VM_Version { static void initialize(); static void print_features(); static bool is_determine_features_test_running() { return _is_determine_features_test_running; } - struct VM_Features {}; + struct VM_Features { + inline int print_numbers(char *buf_orig, size_t buflen, bool hexonly = false) const { return 0; } + }; static bool cpu_features_binary(VM_Features *data) { return false; } static bool cpu_features_binary_check(const VM_Features *data) { return data == nullptr; } static bool ignore_cpu_features() { return true; } diff --git a/src/hotspot/cpu/x86/vm_version_x86.cpp b/src/hotspot/cpu/x86/vm_version_x86.cpp index 1fcd28675b0..e875bab5bf6 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.cpp +++ b/src/hotspot/cpu/x86/vm_version_x86.cpp @@ -108,7 +108,7 @@ bool VM_Version::supports_clflush() { VM_Features flush; flush.set_feature(CPU_FLUSH); char buf[MAX_CPU_FEATURES]; - flush.print_numbers(buf, sizeof(buf)); + guarantee(flush.print_numbers(buf, sizeof(buf)) >= 0, "buffer too short"); vm_exit_during_initialization(err_msg("-XX:CPUFeatures option requires FLUSH flag to be set: %s", buf)); return false; } @@ -1261,9 +1261,9 @@ bool VM_Version::glibc_not_using() { all_features.set_all_features(); if (handled != all_features) { char buf_handled[MAX_CPU_FEATURES]; - handled.print_numbers(buf_handled, sizeof(buf_handled)); + guarantee(handled.print_numbers(buf_handled, sizeof(buf_handled)) >= 0, "buffer too short"); char buf_all_features[MAX_CPU_FEATURES]; - all_features.print_numbers(buf_all_features, sizeof(buf_all_features)); + guarantee(all_features.print_numbers(buf_all_features, sizeof(buf_all_features)) >= 0, "buffer too short"); vm_exit_during_initialization(err_msg("internal error: Unsupported disabling of some CPU_* %s != full %s", buf_handled, buf_all_features)); } #endif // ASSERT @@ -1282,7 +1282,7 @@ void VM_Version::print_using_features_cr() { tty->print_cr("CPU features are being kept intact as requested by -XX:CPUFeatures=ignore"); } else { char buf[MAX_CPU_FEATURES]; - _features.print_numbers(buf, sizeof(buf)); + guarantee(_features.print_numbers(buf, sizeof(buf)) >= 0, "buffer too short"); tty->print_cr("CPU features being used are: -XX:CPUFeatures=%s", buf); } } @@ -1322,7 +1322,7 @@ void VM_Version::get_processor_features_hardware() { if (ShowCPUFeatures) { char buf[MAX_CPU_FEATURES]; - _features.print_numbers(buf, sizeof(buf)); + guarantee(_features.print_numbers(buf, sizeof(buf)) >= 0, "buffer too short"); tty->print_cr("This machine's CPU features are: -XX:CPUFeatures=%s", buf); } } @@ -1334,7 +1334,7 @@ void VM_Version::get_processor_features_hotspot() { VM_Features sse2; sse2.set_feature(CPU_SSE2); char buf[MAX_CPU_FEATURES]; - sse2.print_numbers(buf, sizeof(buf)); + guarantee(sse2.print_numbers(buf, sizeof(buf)) >= 0, "buffer too short"); vm_exit_during_initialization(err_msg("-XX:CPUFeatures option requires SSE2 flag to be set: %s", buf)); } vm_exit_during_initialization("Unknown x64 processor: SSE2 not supported"); @@ -2581,9 +2581,9 @@ bool VM_Version::cpu_features_binary_check(const VM_Version::VM_Features *data_p if (!features_missing.empty()) { char buf_use[MAX_CPU_FEATURES]; - (data & _features).print_numbers(buf_use, sizeof(buf_use)); + guarantee((data & _features).print_numbers(buf_use, sizeof(buf_use)) >= 0, "buffer too short"); char buf_have[MAX_CPU_FEATURES]; - data.print_numbers(buf_have, sizeof(buf_have)); + guarantee(data.print_numbers(buf_have, sizeof(buf_have)) >= 0, "buffer too short"); tty->print("You have to specify -XX:CPUFeatures=%s together with -XX:CRaCCheckpointTo when making a checkpoint file" "; specified -XX:CRaCRestoreFrom file contains CPU features %s", buf_use, buf_have); @@ -2671,9 +2671,9 @@ void VM_Version::initialize() { if (!features_missing.empty()) { char buf_CPUFeatures_parsed[MAX_CPU_FEATURES]; - CPUFeatures_parsed.print_numbers(buf_CPUFeatures_parsed, sizeof(buf_CPUFeatures_parsed)); + guarantee(CPUFeatures_parsed.print_numbers(buf_CPUFeatures_parsed, sizeof(buf_CPUFeatures_parsed)) >= 0, "buffer too short"); char buf_features[MAX_CPU_FEATURES]; - _features.print_numbers(buf_features, sizeof(buf_features)); + guarantee(_features.print_numbers(buf_features, sizeof(buf_features)) >= 0, "buffer too short"); tty->print("Specified -XX:CPUFeatures=%s; this machine's CPU features are %s", buf_CPUFeatures_parsed, buf_features); features_missing.print_missing_features(); vm_exit_during_initialization(); diff --git a/src/hotspot/cpu/x86/vm_version_x86.hpp b/src/hotspot/cpu/x86/vm_version_x86.hpp index 1b3e99b37a7..3c0e0dd6b57 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.hpp +++ b/src/hotspot/cpu/x86/vm_version_x86.hpp @@ -596,24 +596,32 @@ class VM_Version : public Abstract_VM_Version { return *this == empty_features; } - int print_numbers(char *buf_orig, size_t buflen) const { + int print_numbers(char *buf_orig, size_t buflen, bool hexonly = false) const { char *buf = buf_orig; + const char *format = hexonly ? UINT64_FORMAT_0 : UINT64_FORMAT_X; apply_to_all_features([&](uint64_t u, int idx) { - int res = jio_snprintf(buf, buflen, UINT64_FORMAT_X, u); + int res = jio_snprintf(buf, buflen, format, u); + if (res < 0) { + buflen = 0; + return; + } buf += res; buflen -= res; - assert(res > 0 && buflen >= 1, "not enough temporary space allocated"); - if (idx + 1 < features_bitmap_element_count()) { + if (!hexonly && idx + 1 < features_bitmap_element_count() && buflen > 0) { *buf++ = ','; --buflen; } }); + if (buflen == 0) { + return -1; + } *buf = 0; return buf - buf_orig; } void print_numbers_and_names(char *buf, size_t buflen) const { int res = print_numbers(buf, buflen); + assert(res >= 0, "buffer too short"); buf += res; buflen -= res; assert(buflen >= 3, "not enough temporary space allocated"); diff --git a/src/hotspot/cpu/zero/vm_version_zero.hpp b/src/hotspot/cpu/zero/vm_version_zero.hpp index 3b840961e37..44ddd2ce5c2 100644 --- a/src/hotspot/cpu/zero/vm_version_zero.hpp +++ b/src/hotspot/cpu/zero/vm_version_zero.hpp @@ -32,7 +32,9 @@ class VM_Version : public Abstract_VM_Version { public: static void initialize(); - struct VM_Features {}; + struct VM_Features { + inline int print_numbers(char *buf_orig, size_t buflen, bool hexonly = false) const { return 0; } + }; static bool cpu_features_binary(VM_Features *data) { return false; } static bool cpu_features_binary_check(const VM_Features *data) { return data == nullptr; } static bool ignore_cpu_features() { return true; } diff --git a/src/hotspot/share/runtime/crac.cpp b/src/hotspot/share/runtime/crac.cpp index 13badc0d314..61ec4c96277 100644 --- a/src/hotspot/share/runtime/crac.cpp +++ b/src/hotspot/share/runtime/crac.cpp @@ -56,6 +56,8 @@ static jlong _restore_start_time; static jlong _restore_start_nanos; CracEngine *crac::_engine = nullptr; +unsigned int crac::_generation = 1; + char crac::_checkpoint_bootid[UUID_LENGTH]; jlong crac::_checkpoint_wallclock_seconds; jlong crac::_checkpoint_wallclock_nanos; @@ -99,17 +101,210 @@ static char * strchrnul(char * str, char c) { } #endif //__APPLE__ || _WINDOWS +static int append_time(char *buf, size_t buflen, bool iso8601, bool zero_pad, int width, jlong timeMillis) { + if (iso8601) { + if (width >= 0 || zero_pad) { + log_warning(crac)("Cannot use zero-padding or set width for ISO-8601 time in CRaCCheckpointTo=%s", CRaCCheckpointTo); + } + // os::iso8601_time formats with dashes and colons, we want the basic version + time_t time = timeMillis / 1000; + struct tm tms; + if (os::gmtime_pd(&time, &tms) == nullptr) { + log_warning(crac)("Cannot format time " JLONG_FORMAT, timeMillis); + return -1; + } + return (int) strftime(buf, buflen, "%Y%m%dT%H%M%SZ", &tms); + } else { + // width -1 works too (means 1 char left aligned => we always print at least 1 char) + return snprintf(buf, buflen, zero_pad ? "%0*" PRId64 : "%*" PRId64, width, (int64_t) (timeMillis / 1000)); + } +} + +static int append_size(char *buf, size_t buflen, bool zero_pad, int width, size_t size) { + if (zero_pad) { + return snprintf(buf, buflen, "%0*zu", width, size); + } else if (width >= 0) { + return snprintf(buf, buflen, "%*zu", width, size); + } else { + static constexpr const char *suffixes[] = { "k", "M", "G" }; + const char *suffix = ""; + for (size_t i = 0; i < ARRAY_SIZE(suffixes) && size != 0 && (size & 1023) == 0; ++i) { + suffix = suffixes[i]; + size = size >> 10; + } + return snprintf(buf, buflen, "%zu%s", size, suffix); + } +} + +#define check_no_width_padding() do { \ + if (width >= 0) { \ + log_warning(crac)("Cannot set width for %%%c in CRaCCheckpointTo=%s", c, CRaCCheckpointTo); \ + } \ + if (zero_pad) { \ + log_warning(crac)("Cannot use zero-padding for %%%c in CRaCCheckpointTo=%s", c, CRaCCheckpointTo); \ + } \ + } while (false) + +#define check_retval(statement) do { \ + int ret = statement; \ + if ((size_t) ret > buflen) { \ + log_error(crac)("Error interpolating CRaCCheckpointTo=%s (too long)", CRaCCheckpointTo); \ + return false; \ + } else if (ret < 0) { \ + log_error(crac)("Error interpolating CRaCCheckpointTo=%s", CRaCCheckpointTo); \ + return false; \ + } \ + buf += ret; \ + buflen -= ret; \ + } while (false) + +static inline jlong boot_time() { + // RuntimeMxBean.getStartTime() returns Management::vm_init_done_time() but this is not initialized + // when CRaC checks the boot time early in the initialization phase + return os::javaTimeMillis() - 1000 * (os::elapsed_counter() / os::elapsed_frequency()); +} + +bool crac::interpolate_checkpoint_location(char *buf, size_t buflen, bool *fixed) { + *fixed = true; + for (size_t si = 0; ; si++) { + if (buflen == 0) { + log_error(crac)("Error interpolating CRaCCheckpointTo=%s (too long)", CRaCCheckpointTo); + return false; + } + char c = CRaCCheckpointTo[si]; + if (c != '%') { + *(buf++) = c; + --buflen; + if (!c) { + break; + } else { + continue; + } + } + + si++; + c = CRaCCheckpointTo[si]; + bool zero_pad = false; + if (c == '0') { + zero_pad = true; + si++; + } + size_t width_start = si; + while (CRaCCheckpointTo[si] >= '0' && CRaCCheckpointTo[si] <= '9') { + ++si; + } + if (zero_pad && width_start == si) { + log_error(crac)("CRaCCheckpointTo=%s contains a pattern with zero padding but no length", CRaCCheckpointTo); + return false; + } + const int width = si > width_start ? atoi(&CRaCCheckpointTo[width_start]) : -1; + c = CRaCCheckpointTo[si]; + switch (c) { + case '%': + check_no_width_padding(); + *(buf++) = '%'; + --buflen; + break; + case 'a': // CPU architecture; matches system property "os.arch" + check_no_width_padding(); +#ifndef ARCHPROPNAME +# error "ARCHPROPNAME must be defined by build scripts" +#endif + check_retval(snprintf(buf, buflen, "%s", ARCHPROPNAME)); + break; + case 'f': { // CPU features + check_no_width_padding(); + VM_Version::VM_Features data; + if (VM_Version::cpu_features_binary(&data)) { + check_retval(data.print_numbers(buf, buflen, true)); + } // otherwise just empty string + } + break; + case 'u': { // Random UUID (v4) + check_no_width_padding(); + *fixed = false; // FIXME? + u4 time_mid_high = static_cast(os::random()); + u4 seq_and_node_low = static_cast(os::random()); + check_retval(snprintf(buf, buflen, "%08x-%04x-4%03x-%04x-%04x%08x", + static_cast(os::random()), time_mid_high >> 16, time_mid_high & 0xFFF, + 0x8000 | (seq_and_node_low & 0x3FFF), seq_and_node_low >> 16, static_cast(os::random()))); + } + break; + case 't': // checkpoint (current) time + case 'T': + *fixed = false; + check_retval(append_time(buf, buflen, c == 't', zero_pad, width, os::javaTimeMillis())); + break; + case 'b': // boot time + case 'B': + check_retval(append_time(buf, buflen, c == 'b', zero_pad, width, boot_time())); + break; + case 'r': // last restore time + case 'R': + check_retval(append_time(buf, buflen, c == 'r', zero_pad, width, _generation != 1 ? crac::restore_start_time() : boot_time())); + break; + case 'p': // PID + check_retval(snprintf(buf, buflen, zero_pad ? "%0*d" : "%*d", width, os::current_process_id())); + break; + case 'c': // Number of CPUs + check_retval(snprintf(buf, buflen, zero_pad ? "%0*d" : "%*d", width, os::active_processor_count())); + break; + case 'm': // Max heap size + *fixed = false; // Heap size is not yet resolved when this is called from prepare_checkpoint() + check_retval(append_size(buf, buflen, zero_pad, width, Universe::heap() != nullptr ? Universe::heap()->max_capacity() : 0)); + break; + case 'g': // CRaC generation + check_retval(snprintf(buf, buflen, zero_pad ? "%0*d" : "%*d", width, _generation)); + break; + default: /* incl. terminating '\0' */ + log_error(crac)("CRaCCheckpointTo=%s contains an invalid pattern", CRaCCheckpointTo); + return false; + } + } + return true; +} + +#undef check_no_width_padding +#undef check_retval + +static bool ensure_checkpoint_dir(const char *path, bool rm) { + struct stat st; + if (0 == os::stat(path, &st)) { + if ((st.st_mode & S_IFMT) != S_IFDIR) { + log_error(crac)("CRaCCheckpointTo=%s is not a directory", path); + return false; + } + } else { + if (-1 == os::mkdir(path)) { + log_error(crac)("Cannot create CRaCCheckpointTo=%s: %s", path, os::strerror(errno)); + return false; + } + if (rm && -1 == os::rmdir(path)) { + log_warning(crac)("Cannot cleanup after CRaCCheckpointTo check: %s", os::strerror(errno)); + // not fatal + } + } + return true; +} + +#ifndef PATH_MAX +# define PATH_MAX 1024 +#endif + int crac::checkpoint_restore(int *shmid) { guarantee(_engine != nullptr, "CRaC engine is not initialized"); crac::record_time_before_checkpoint(); - // CRaCCheckpointTo can be changed on restore so we need to update the conf - // to account for that. + // CRaCCheckpointTo can be changed on restore, and if this contains a pattern + // it might not have been configured => we need to update the conf. // Note that CRaCEngine and CRaCEngineOptions are not updated (as documented) // so we don't need to re-init the whole engine handle. - if (restore_start_time() != -1 && // A way to detect we've restored at least once - !_engine->configure_image_location(CRaCCheckpointTo)) { + char image_location[PATH_MAX]; + bool ignored; + if (!interpolate_checkpoint_location(image_location, sizeof(image_location), &ignored) || + !ensure_checkpoint_dir(image_location, false) || + !_engine->configure_image_location(image_location)) { return JVM_CHECKPOINT_ERROR; } @@ -134,7 +329,7 @@ int crac::checkpoint_restore(int *shmid) { const int ret = _engine->checkpoint(); if (ret != 0) { - log_error(crac)("CRaC engine failed to checkpoint to %s: error %i", CRaCCheckpointTo, ret); + log_error(crac)("CRaC engine failed to checkpoint to %s: error %i", image_location, ret); return JVM_CHECKPOINT_ERROR; } @@ -275,6 +470,7 @@ void VM_Crac::doit() { } } + crac::_generation++; Arguments::reset_for_crac_restore(); os::reset_cached_process_id(); @@ -351,34 +547,45 @@ void crac::print_engine_info_and_exit() { ShouldNotReachHere(); } +template class FutureRef { +private: + T *_t; +public: + FutureRef(T *t): _t(t) {} + ~FutureRef() { + delete _t; + } + T *operator->() { + return _t; + } + T *extract() { + T *tmp = _t; + _t = nullptr; + return tmp; + } +}; + bool crac::prepare_checkpoint() { precond(CRaCCheckpointTo != nullptr); - struct stat st; - if (0 == os::stat(CRaCCheckpointTo, &st)) { - if ((st.st_mode & S_IFMT) != S_IFDIR) { - log_error(crac)("CRaCCheckpointTo=%s is not a directory", CRaCCheckpointTo); - return false; - } - } else { - if (-1 == os::mkdir(CRaCCheckpointTo)) { - log_error(crac)("Cannot create CRaCCheckpointTo=%s: %s", CRaCCheckpointTo, os::strerror(errno)); - return false; - } - if (-1 == os::rmdir(CRaCCheckpointTo)) { - log_warning(crac)("Cannot cleanup after CRaCCheckpointTo check: %s", os::strerror(errno)); - // not fatal - } - } - // Initialize CRaC engine now to verify all the related VM options assert(_engine == nullptr, "CRaC engine should be initialized only once"); - _engine = new CracEngine(CRaCCheckpointTo); - if (!_engine->is_initialized()) { - delete _engine; - _engine = nullptr; + FutureRef engine(new CracEngine()); + if (!engine->is_initialized()) { + return false; } - return _engine != nullptr; + + char image_location[PATH_MAX]; + bool fixed_path; + if (!interpolate_checkpoint_location(image_location, PATH_MAX, &fixed_path)) { + return false; + } + if (fixed_path && (!ensure_checkpoint_dir(image_location, true) || !engine->configure_image_location(image_location))) { + return false; + } + + _engine = engine.extract(); + return true; } static Handle ret_cr(int ret, Handle new_args, Handle new_props, Handle err_codes, Handle err_msgs, TRAPS) { @@ -405,11 +612,6 @@ Handle crac::checkpoint(jarray fd_arr, jobjectArray obj_arr, bool dry_run, jlong return ret_cr(JVM_CHECKPOINT_NONE, Handle(), Handle(), Handle(), Handle(), THREAD); } - if (-1 == os::mkdir(CRaCCheckpointTo) && errno != EEXIST) { - log_error(crac)("Cannot create CRaCCheckpointTo=%s: %s", CRaCCheckpointTo, os::strerror(errno)); - return ret_cr(JVM_CHECKPOINT_NONE, Handle(), Handle(), Handle(), Handle(), THREAD); - } - #if INCLUDE_JVMTI JvmtiExport::post_crac_before_checkpoint(); #endif @@ -515,8 +717,8 @@ void crac::restore(crac_restore_data& restore_data) { } // Note that this is a local, i.e. the handle will be destroyed if we fail to restore - CracEngine engine(CRaCRestoreFrom); - if (!engine.is_initialized()) { + CracEngine engine; + if (!engine.is_initialized() || !engine.configure_image_location(CRaCRestoreFrom)) { return; } diff --git a/src/hotspot/share/runtime/crac.hpp b/src/hotspot/share/runtime/crac.hpp index 1c607f3a50e..ade2501edcb 100644 --- a/src/hotspot/share/runtime/crac.hpp +++ b/src/hotspot/share/runtime/crac.hpp @@ -59,6 +59,7 @@ class crac: AllStatic { private: static CracEngine *_engine; + static unsigned int _generation; static char _checkpoint_bootid[UUID_LENGTH]; // Timestamps recorded before checkpoint. @@ -75,6 +76,8 @@ class crac: AllStatic { static void update_javaTimeNanos_offset(); static int checkpoint_restore(int *shmid); + + static bool interpolate_checkpoint_location(char *buf, size_t buflen, bool *fixed); }; #endif //SHARE_RUNTIME_CRAC_HPP diff --git a/src/hotspot/share/runtime/crac_engine.cpp b/src/hotspot/share/runtime/crac_engine.cpp index 056e5607615..68785a9c9e7 100644 --- a/src/hotspot/share/runtime/crac_engine.cpp +++ b/src/hotspot/share/runtime/crac_engine.cpp @@ -175,7 +175,7 @@ class CStringUtils : public AllStatic { using CStringSet = ResourceHashtable; -static crlib_conf_t *create_conf(const crlib_api_t &api, const char *image_location, const char *exec_location) { +static crlib_conf_t *create_conf(const crlib_api_t &api, const char *exec_location) { crlib_conf_t * const conf = api.create_conf(); if (conf == nullptr) { log_error(crac)("CRaC engine failed to create its configuration"); @@ -186,11 +186,6 @@ static crlib_conf_t *create_conf(const crlib_api_t &api, const char *image_locat return conf; } - if (image_location != nullptr && !configure_image_location(api, conf, image_location)) { - api.destroy_conf(conf); - return nullptr; - } - if (exec_location != nullptr) { // Only passed when using crexec guarantee(api.can_configure(conf, engine_opt_exec_location), "crexec does not support expected option: %s", engine_opt_exec_location); @@ -242,7 +237,7 @@ static crlib_conf_t *create_conf(const crlib_api_t &api, const char *image_locat return conf; } -CracEngine::CracEngine(const char *image_location) { +CracEngine::CracEngine() { if (CRaCEngine == nullptr) { log_error(crac)("CRaCEngine must not be empty"); return; @@ -321,7 +316,7 @@ CracEngine::CracEngine(const char *image_location) { } const char *exec_location = exec_path[0] != '\0' ? exec_path : nullptr; - crlib_conf_t * const conf = create_conf(*api, image_location, exec_location); + crlib_conf_t * const conf = create_conf(*api, exec_location); if (conf == nullptr) { os::dll_unload(lib); return; diff --git a/src/hotspot/share/runtime/crac_engine.hpp b/src/hotspot/share/runtime/crac_engine.hpp index 750d4b328f9..7e58fa926e0 100644 --- a/src/hotspot/share/runtime/crac_engine.hpp +++ b/src/hotspot/share/runtime/crac_engine.hpp @@ -39,7 +39,7 @@ // CRaC engine library wrapper. class CracEngine : public CHeapObj { public: - explicit CracEngine(const char *image_location = nullptr); + CracEngine(); ~CracEngine(); CracEngine(const CracEngine &) = delete; diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index c6b2050b741..f9f9ac48c79 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -1962,7 +1962,8 @@ const int ObjectAlignmentInBytes = 8; "Path where the checkpoint image should be placed. Currently an " \ "image is a directory, the directory will be created if it does " \ "not exist (parent directories are not created) or overwritten " \ - "otherwise.") \ + "otherwise. The path can contain placeholders (e.g. %p for PID);" \ + " check documentation for pattern format.") \ \ product(ccstr, CRaCRestoreFrom, nullptr, RESTORE_SETTABLE, \ "Path to the image to restore from.") \ diff --git a/src/java.base/share/man/java.md b/src/java.base/share/man/java.md index 7627ac3f3f6..d7e178f5344 100644 --- a/src/java.base/share/man/java.md +++ b/src/java.base/share/man/java.md @@ -1082,6 +1082,28 @@ These `java` options control the runtime behavior of the Java HotSpot VM. directory will be created if it does not exist, but no parent directories are created. + It is possible to use a pattern with automatically inferred values, using + these placeholders: + - `%%`: single % character + - `%a`: architecture; the same value as system property 'os.arch' + - `%f`: CPU features hex string. Empty string if the architecture does not + use optional CPU features. + - `%u`: UUID (version 4 = random) + - `%g`: checkpoint generation (starting with 1, 0 is reserved for ‘unknown’) + - `%t`: checkpoint date & time in ISO-8601 in UTC, basic format (without + separators) with second precision, e.g. `20250909T141711Z` + - `%T`: checkpoint epoch time (second precision) + - `%b` and `%B`: process boot time (generation 1), same format as `%t` or `%T` + - `%r` and `%R`: last restore time, same format as `%t` or `%T`. In case + of generation 1 this is is the same as process boot time. + - `%p`: PID of checkpointed process + - `%c`: number of CPU cores + - `%m`: max heap size (`-Xmx`) in a user-friendly format - using G or M suffix + + Numeric placeholders (`%T`, `%B`, `%R`, `%p`, `%c`, `%m` and `%g`) support + an optional prefix with minimum width, padded with spaces or zeroes if the prefix + starts with zero, e.g. `%3g` -> ` 1`, `%03g` -> `001`. + `-XX:CRaCRestoreFrom=`*directory* : Restores from the specified checkpoint image. diff --git a/test/jdk/jdk/crac/PathPatternTest.java b/test/jdk/jdk/crac/PathPatternTest.java new file mode 100644 index 00000000000..f99286a28ba --- /dev/null +++ b/test/jdk/jdk/crac/PathPatternTest.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2025, Azul Systems, Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import jdk.crac.Core; +import jdk.test.lib.Platform; +import jdk.test.lib.Unit; +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracEngine; +import jdk.test.lib.crac.CracTest; +import jdk.test.lib.util.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.text.SimpleDateFormat; +import java.util.Comparator; +import java.util.Date; +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static jdk.test.lib.Asserts.*; + +/** + * @test + * @summary Checks that we can use patterns in CRaCCheckpointTo + * @library /test/lib + * @build PathPatternTest + * @run driver jdk.test.lib.crac.CracTest + */ +public class PathPatternTest implements CracTest { + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HHmmss"); + private static final int MAX_DURATION_SECONDS = 300; + + static { + DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + private static long runCheckpoints(String pattern, boolean pause) throws Exception { + CracBuilder builder = new CracBuilder().engine(CracEngine.SIMULATE) + .vmOption("-Xmx1G") + .imageDir(pattern); + if (pause) { + builder.vmOption("-Dtest.pause=true"); + } + return builder.startCheckpoint().waitForSuccess().pid(); + } + + @Override + public void test() throws Exception { + Path foo = Path.of("foo"); + //noinspection ResultOfMethodCallIgnored + foo.toFile().mkdirs(); + + // Fixed fields only, no-timestamps + long pid = runCheckpoints("foo/cr_%%_%a_%p_%05c_%3m_%m_%g", false); + File f = new File(String.format("foo/cr_%%_%s_%d_%05d_%d_1G_1", + Platform.getOsArch(), pid, Runtime.getRuntime().availableProcessors(), Unit.G.size())); + assertTrue(f.exists(), "Expect " + f); + assertTrue(f.isDirectory()); + + FileUtils.deleteFileTreeWithRetry(foo); + //noinspection ResultOfMethodCallIgnored + foo.toFile().mkdirs(); + + // Pattern-based checks + runCheckpoints("foo/%u_%f_", false); + try (var stream = Files.list(foo)) { + AtomicInteger count = new AtomicInteger(0); + String featuresPattern = Platform.isX64() ? "\\p{XDigit}{32}" : ""; + assertTrue(stream.allMatch(d -> { + count.incrementAndGet(); + if (!d.getFileName().toString().matches("\\p{XDigit}{8}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{12}_" + featuresPattern + "_")) { + System.err.printf("Unexpected file: %s%nFull path: %s%n", d.getFileName(), d); + return false; + } + return d.toFile().isDirectory(); + })); + assertEquals(count.intValue(), 2); + } + FileUtils.deleteFileTreeWithRetry(foo); + //noinspection ResultOfMethodCallIgnored + foo.toFile().mkdirs(); + + // Timestamps and generation + runCheckpoints("foo/%T_%t_%015B_%b_%15R_%r_%02g", true); + Pattern pattern = Pattern.compile("(\\d+)_(\\d{8}T\\d{6})Z_0*(\\d+)_(\\d{8}T\\d{6})Z_ *(\\d+)_(\\d{8}T\\d{6})Z_0(\\d)"); + try (var stream = Files.list(foo)) { + Path[] paths = stream.sorted().toArray(Path[]::new); + assertEquals(paths.length, 2); + System.err.println("IMAGE1: " + paths[0]); + System.err.println("IMAGE2: " + paths[1]); + + Matcher matcher1 = pattern.matcher(paths[0].getFileName().toString()); + assertTrue(matcher1.matches()); + Matcher matcher2 = pattern.matcher(paths[1].getFileName().toString()); + assertTrue(matcher2.matches()); + + long t1 = Long.parseLong(matcher1.group(1)); + long t2 = Long.parseLong(matcher2.group(1)); + assertLT(t1, t2); + long now = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()); + assertLTE(t2, now); + assertGT(t1 + MAX_DURATION_SECONDS, now); + checkDateEquals(matcher1.group(2), t1); + checkDateEquals(matcher2.group(2), t2); + + // boot time is constant + long b1 = Long.parseLong(matcher1.group(3)); + long b2 = Long.parseLong(matcher2.group(3)); + assertEquals(b1, b2); + assertLTE(b1, t1); + assertGT(b1 + MAX_DURATION_SECONDS, now); + checkDateEquals(matcher1.group(4), b1); + checkDateEquals(matcher2.group(4), b2); + + long r1 = Long.parseLong(matcher1.group(5)); + long r2 = Long.parseLong(matcher2.group(5)); + assertEquals(r1, b1); + assertLTE(t1, r2); + assertLTE(r2, now); + checkDateEquals(matcher1.group(6), b1); + checkDateEquals(matcher2.group(6), b2); + + assertEquals("1", matcher1.group(7)); + assertEquals("2", matcher2.group(7)); + } + } + + private static void checkDateEquals(String str, long ts) { + assertEquals(str, DATE_FORMAT.format(new Date(TimeUnit.SECONDS.toMillis(ts)))); + } + + @Override + public void exec() throws Exception { + // Do two checkpoint-restores to ensure code behaves correctly after repeated execution + Core.checkpointRestore(); + if (Boolean.getBoolean("test.pause")) { + Thread.sleep(1000); + } + Core.checkpointRestore(); + } +}