diff --git a/make/CompileDemos.gmk b/make/CompileDemos.gmk index 74c54ce0cf2..73d66aee67c 100644 --- a/make/CompileDemos.gmk +++ b/make/CompileDemos.gmk @@ -219,6 +219,10 @@ $(eval $(call SetupBuildDemo, TransparentRuler, \ MAIN_CLASS := transparentruler.Ruler, \ )) +$(eval $(call SetupBuildDemo, JavaCompilerCRaC, \ + DEMO_SUBDIR := crac, \ +)) + ################################################################################ # Copy html and README files. diff --git a/make/autoconf/libraries.m4 b/make/autoconf/libraries.m4 index 5c49fd9285d..823a0632470 100644 --- a/make/autoconf/libraries.m4 +++ b/make/autoconf/libraries.m4 @@ -130,6 +130,15 @@ AC_DEFUN_ONCE([LIB_SETUP_LIBRARIES], BASIC_JVM_LIBS="$BASIC_JVM_LIBS -lthread" fi + # librt for legacy clock_gettime + if test "x$OPENJDK_TARGET_OS" = xlinux; then + # Hotspot needs to link librt to get the clock_* functions. + # But once our supported minimum build and runtime platform + # has glibc 2.17, this can be removed as the functions are + # in libc. + BASIC_JVM_LIBS="$BASIC_JVM_LIBS -lrt" + fi + # Because RISC-V only has word-sized atomics, it requries libatomic where # other common architectures do not. So link libatomic by default. if test "x$OPENJDK_TARGET_OS" = xlinux && test "x$OPENJDK_TARGET_CPU" = xriscv64; then diff --git a/make/hotspot/symbols/symbols-shared b/make/hotspot/symbols/symbols-shared index 5d26d1028c7..a12fc903bcd 100644 --- a/make/hotspot/symbols/symbols-shared +++ b/make/hotspot/symbols/symbols-shared @@ -33,3 +33,8 @@ JNI_GetDefaultJavaVMInitArgs JVM_FindClassFromBootLoader JVM_GetVersionInfo JVM_InitAgentProperties +JVM_Checkpoint +JVM_RegisterPersistent +JVM_DeregisterPersistent +JVM_RegisterPseudoPersistent +JVM_UnregisterPseudoPersistent diff --git a/make/hotspot/symbols/symbols-unix b/make/hotspot/symbols/symbols-unix index 39afdfdc9b5..d15947946dd 100644 --- a/make/hotspot/symbols/symbols-unix +++ b/make/hotspot/symbols/symbols-unix @@ -199,3 +199,5 @@ JVM_AddModuleExportsToAllUnnamed JVM_AddReadsModule JVM_DefineModule JVM_SetBootLoaderUnnamedModule + +JVM_CheckpointEnabled diff --git a/make/launcher/Launcher-java.base.gmk b/make/launcher/Launcher-java.base.gmk index a8990dd0efc..bd7b8b31c78 100644 --- a/make/launcher/Launcher-java.base.gmk +++ b/make/launcher/Launcher-java.base.gmk @@ -90,3 +90,41 @@ ifeq ($(call isTargetOs, macosx solaris aix linux), true) endif ################################################################################ + +ifeq ($(OPENJDK_TARGET_OS), linux) + $(eval $(call SetupJdkExecutable, BUILD_CRIUENGINE, \ + NAME := criuengine, \ + SRC := $(TOPDIR)/src/$(MODULE)/unix/native/criuengine, \ + INCLUDE_FILES := criuengine.c, \ + OPTIMIZATION := HIGH, \ + CFLAGS := $(CFLAGS_JDKEXE), \ + LDFLAGS := $(LDFLAGS), \ + OUTPUT_DIR := $(SUPPORT_OUTPUTDIR)/modules_libs/$(MODULE), \ + )) + TARGETS += $(BUILD_CRIUENGINE) + + $(eval $(call SetupJdkExecutable, BUILD_PAUSEENGINE, \ + NAME := pauseengine, \ + SRC := $(TOPDIR)/src/$(MODULE)/unix/native/pauseengine, \ + INCLUDE_FILES := pauseengine.c, \ + OPTIMIZATION := LOW, \ + CFLAGS := $(CFLAGS_JDKEXE), \ + LDFLAGS := $(LDFLAGS), \ + OUTPUT_DIR := $(SUPPORT_OUTPUTDIR)/modules_libs/$(MODULE), \ + )) + TARGETS += $(BUILD_PAUSEENGINE) + + $(eval $(call SetupJdkExecutable, BUILD_SIMENGINE, \ + NAME := simengine, \ + SRC := $(TOPDIR)/src/$(MODULE)/unix/native/simengine, \ + INCLUDE_FILES := simengine.c, \ + OPTIMIZATION := LOW, \ + CFLAGS := $(CFLAGS_JDKEXE), \ + LDFLAGS := $(LDFLAGS), \ + OUTPUT_DIR := $(SUPPORT_OUTPUTDIR)/modules_libs/$(MODULE), \ + )) + TARGETS += $(BUILD_SIMENGINE) + +endif + +################################################################################ diff --git a/src/demo/share/crac/JavaCompilerCRaC/Compile.java b/src/demo/share/crac/JavaCompilerCRaC/Compile.java new file mode 100644 index 00000000000..2bfeeb17683 --- /dev/null +++ b/src/demo/share/crac/JavaCompilerCRaC/Compile.java @@ -0,0 +1,5 @@ +public class Compile { + public static void main(String... args) throws Exception { + JavaCompilerCRaC.runJavac(args); + } +} diff --git a/src/demo/share/crac/JavaCompilerCRaC/JavaCompilerCRaC.java b/src/demo/share/crac/JavaCompilerCRaC/JavaCompilerCRaC.java new file mode 100644 index 00000000000..fbad36c4310 --- /dev/null +++ b/src/demo/share/crac/JavaCompilerCRaC/JavaCompilerCRaC.java @@ -0,0 +1,29 @@ +import java.util.Arrays; +import jdk.crac.Core; + +public class JavaCompilerCRaC { + + static void runJavac(String... args) { + System.out.println("javac " + String.join(" ", args)); + int status = com.sun.tools.javac.Main.compile(args); + if (status != 0) { + System.exit(status); + } + } + + public static void main(String... args) throws Exception { + int startIdx = 0; + for (int endIdx = 1; endIdx < args.length; ++endIdx) { + if (args[endIdx].equals("--")) { + runJavac(Arrays.copyOfRange(args, startIdx, endIdx)); + startIdx = endIdx + 1; + } + } + + if (startIdx < args.length) { + runJavac(Arrays.copyOfRange(args, startIdx, args.length)); + } + + Core.checkpointRestore(); + } +} diff --git a/src/hotspot/os/linux/attachListener_linux.cpp b/src/hotspot/os/linux/attachListener_linux.cpp index aa114d91232..12869072d40 100644 --- a/src/hotspot/os/linux/attachListener_linux.cpp +++ b/src/hotspot/os/linux/attachListener_linux.cpp @@ -28,7 +28,10 @@ #include "runtime/interfaceSupport.inline.hpp" #include "runtime/os.inline.hpp" #include "services/attachListener.hpp" +#include "attachListener_linux.hpp" #include "services/dtraceAttacher.hpp" +#include "linuxAttachOperation.hpp" +#include "memory/resourceArea.hpp" #include #include @@ -37,10 +40,6 @@ #include #include -#ifndef UNIX_PATH_MAX -#define UNIX_PATH_MAX sizeof(((struct sockaddr_un *)0)->sun_path) -#endif - // The attach mechanism on Linux uses a UNIX domain socket. An attach listener // thread is created at startup or is created on-demand via a signal from // the client tool. The attach listener creates a socket and binds it to a file @@ -57,78 +56,12 @@ // obtain the credentials of client. We check that the effective uid // of the client matches this process. -// forward reference -class LinuxAttachOperation; - -class LinuxAttachListener: AllStatic { - private: - // the path to which we bind the UNIX domain socket - static char _path[UNIX_PATH_MAX]; - static bool _has_path; - - // the file descriptor for the listening socket - static volatile int _listener; - - static bool _atexit_registered; - - // reads a request from the given connected socket - static LinuxAttachOperation* read_request(int s); - - public: - enum { - ATTACH_PROTOCOL_VER = 1 // protocol version - }; - enum { - ATTACH_ERROR_BADVERSION = 101 // error codes - }; - - static void set_path(char* path) { - if (path == NULL) { - _path[0] = '\0'; - _has_path = false; - } else { - strncpy(_path, path, UNIX_PATH_MAX); - _path[UNIX_PATH_MAX-1] = '\0'; - _has_path = true; - } - } - - static void set_listener(int s) { _listener = s; } - - // initialize the listener, returns 0 if okay - static int init(); - - static char* path() { return _path; } - static bool has_path() { return _has_path; } - static int listener() { return _listener; } - - // write the given buffer to a socket - static int write_fully(int s, char* buf, int len); - - static LinuxAttachOperation* dequeue(); -}; - -class LinuxAttachOperation: public AttachOperation { - private: - // the connection to the client - int _socket; - - public: - void complete(jint res, bufferedStream* st); - - void set_socket(int s) { _socket = s; } - int socket() const { return _socket; } - - LinuxAttachOperation(char* name) : AttachOperation(name) { - set_socket(-1); - } -}; - // statics char LinuxAttachListener::_path[UNIX_PATH_MAX]; bool LinuxAttachListener::_has_path; volatile int LinuxAttachListener::_listener = -1; bool LinuxAttachListener::_atexit_registered = false; +LinuxAttachOperation* LinuxAttachListener::_current_op = NULL; // Supporting class to help split a buffer into individual components class ArgumentIterator : public StackObj { @@ -377,6 +310,7 @@ LinuxAttachOperation* LinuxAttachListener::dequeue() { ::close(s); continue; } else { + _current_op = op; return op; } } @@ -397,6 +331,18 @@ int LinuxAttachListener::write_fully(int s, char* buf, int len) { return 0; } +// An operation completion is splitted into two parts. +// For proper handling the jcmd connection at CRaC checkpoint action. +// An effectively_complete_raw is called in checkpoint processing, before criu engine calls, for properly closing the socket. +// The complete() gets called after restore for proper deletion the leftover object. + +void LinuxAttachOperation::complete(jint result, bufferedStream* st) { + LinuxAttachOperation::effectively_complete_raw(result, st); + // reset the current op as late as possible, this happens on attach listener thread. + LinuxAttachListener::reset_current_op(); + delete this; +} + // Complete an operation by sending the operation result and any result // output to the client. At this time the socket is in blocking mode so // potentially we can block if there is a lot of data and the client is @@ -405,15 +351,33 @@ int LinuxAttachListener::write_fully(int s, char* buf, int len) { // if there are operations that involves a very big reply then it the // socket could be made non-blocking and a timeout could be used. -void LinuxAttachOperation::complete(jint result, bufferedStream* st) { - JavaThread* thread = JavaThread::current(); - ThreadBlockInVM tbivm(thread); +void LinuxAttachOperation::effectively_complete_raw(jint result, bufferedStream* st) { - thread->set_suspend_equivalent(); - // cleared by handle_special_suspend_equivalent_condition() or - // java_suspend_self() via check_and_wait_while_suspended() + if (_effectively_completed) { + assert(st->size() == 0, "no lost output"); + return; + } // write operation result + Thread* thread = Thread::current(); + if (thread->is_Java_thread()) { + JavaThread* jt = (JavaThread* )thread; + ThreadBlockInVM tbivm(jt); + jt->set_suspend_equivalent(); + // cleared by handle_special_suspend_equivalent_condition() or + // java_suspend_self() via check_and_wait_while_suspended() + + write_operation_result(result, st); + + // were we externally suspended while we were waiting? + jt->check_and_wait_while_suspended(); + } else { + write_operation_result(result, st); + } + _effectively_completed = true; +} + +void LinuxAttachOperation::write_operation_result(jint result, bufferedStream* st) { char msg[32]; sprintf(msg, "%d\n", result); int rc = LinuxAttachListener::write_fully(this->socket(), msg, strlen(msg)); @@ -421,18 +385,30 @@ void LinuxAttachOperation::complete(jint result, bufferedStream* st) { // write any result data if (rc == 0) { LinuxAttachListener::write_fully(this->socket(), (char*) st->base(), st->size()); - ::shutdown(this->socket(), 2); + ::shutdown(this->socket(), SHUT_RDWR); } // done ::close(this->socket()); + st->reset(); +} - // were we externally suspended while we were waiting? - thread->check_and_wait_while_suspended(); +static void assert_listener_thread() { +#ifdef ASSERT + ResourceMark rm; // For retrieving the thread names + assert(strcmp("Attach Listener", Thread::current()->name()) == 0, "should gets called from Attach Listener thread"); +#endif +} - delete this; +LinuxAttachOperation* LinuxAttachListener::get_current_op() { + assert_listener_thread(); + return LinuxAttachListener::_current_op; } +void LinuxAttachListener::reset_current_op() { + assert_listener_thread(); + LinuxAttachListener::_current_op = NULL; +} // AttachListener functions diff --git a/src/hotspot/os/linux/attachListener_linux.hpp b/src/hotspot/os/linux/attachListener_linux.hpp new file mode 100644 index 00000000000..c2213560de9 --- /dev/null +++ b/src/hotspot/os/linux/attachListener_linux.hpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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. + * + */ + +#ifndef OS_LINUX_ATTACHLISTENER_LINUX_HPP +#define OS_LINUX_ATTACHLISTENER_LINUX_HPP + +#include "linuxAttachOperation.hpp" +#include "services/attachListener.hpp" + +#include + +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX sizeof(((struct sockaddr_un *)0)->sun_path) +#endif + +class LinuxAttachListener: AllStatic { + private: + // the path to which we bind the UNIX domain socket + static char _path[UNIX_PATH_MAX]; + static bool _has_path; + + // the file descriptor for the listening socket + static volatile int _listener; + + static bool _atexit_registered; + + // this is for proper reporting JDK.Chekpoint processing to jcmd peer + static LinuxAttachOperation* _current_op; + + // reads a request from the given connected socket + static LinuxAttachOperation* read_request(int s); + + public: + + enum { + ATTACH_PROTOCOL_VER = 1 // protocol version + }; + enum { + ATTACH_ERROR_BADVERSION = 101 // error codes + }; + + static void set_path(char* path) { + if (path == NULL) { + _path[0] = '\0'; + _has_path = false; + } else { + strncpy(_path, path, UNIX_PATH_MAX); + _path[UNIX_PATH_MAX-1] = '\0'; + _has_path = true; + } + } + + static void set_listener(int s) { _listener = s; } + + // initialize the listener, returns 0 if okay + static int init(); + + static char* path() { return _path; } + static bool has_path() { return _has_path; } + static int listener() { return _listener; } + + // write the given buffer to a socket + static int write_fully(int s, char* buf, int len); + + static LinuxAttachOperation* dequeue(); + static LinuxAttachOperation* get_current_op(); + static void reset_current_op(); +}; + +#endif // OS_LINUX_ATTACHLISTENERLINUX_HPP diff --git a/src/hotspot/os/linux/crac_linux.cpp b/src/hotspot/os/linux/crac_linux.cpp new file mode 100644 index 00000000000..61d493aac42 --- /dev/null +++ b/src/hotspot/os/linux/crac_linux.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2024 Alibaba Group Holding Limited. 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. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + * + */ + +#include +#include +#include +#include + +#include "crac_linux.hpp" +#include "runtime/globals.hpp" + +//the value should equal to PseudoPersistentMode.SKIP_LOG_FILES in criuengine.c +#define SKIP_LOG_FILES_MODE 0x20 + +PseudoPersistent::PseudoPersistent(GrowableArray + *ppfd, const char *config) : + _ppfd(ppfd), _append_files(NULL), _append_file_configs(NULL) { + if (config == NULL) { + return; + } + + _append_file_configs = new(ResourceObj::C_HEAP, mtInternal) GrowableArray(1, true); + if (!strcmp(config, "*")) { + AppendFileConfig *cfg = new AppendFileConfig(all, NULL, NULL); + _append_file_configs->append(cfg); + return; + } + + const char *p = config, *s = config; + while (*p) { + if (*(p + 1) == ',' || *(p + 1) == '\0') { + if (p - s > 2 && *s == '*' && *(s + 1) == '.') { + _append_file_configs->append(new AppendFileConfig(by_extension, s + 2, p + 1)); + } else { + _append_file_configs->append(new AppendFileConfig(by_full_path, s, p + 1)); + } + if (*(p + 1) == ',') { + p += 2; + s = p; + } else { + break; + } + } else { + p++; + } + } +} + + +bool PseudoPersistent::write_marked(const char* image_dir) { + char *path; + if (-1 == asprintf(&path, "%s/pseudopersistent", image_dir)) { + return false; + } + FILE *f = fopen(path, "w"); + if (f == NULL) { + fprintf(stderr, "open file: %s for write failed, error: %s\n", + path, strerror(errno)); + free(path); + return false; + } + + if (_ppfd) { + for(int i = 0 ; i < _ppfd->length();i++) { + PseudoPersistentFileDesc *ppfd = _ppfd->adr_at(i); + if (ppfd->_mark) { + fprintf(f, "%d,%s\n", ppfd->_mode, ppfd->_path); + } + } + } + + if (_append_files) { + for (int i = 0; i < _append_files->length(); i++) { + fprintf(f, "%d,%s\n", SKIP_LOG_FILES_MODE, _append_files->at(i)); + } + } + + fclose(f); + free(path); + return true; +} + +bool PseudoPersistent::in_registered_list(const char *path) { + if (!_ppfd) { + return false; + } + int j = 0; + while (j < _ppfd->length()) { + PseudoPersistentFileDesc *ppfd = _ppfd->adr_at(j); + int r = strcmp(ppfd->_path, path); + if (r == 0) { + ppfd->_mark = true; + return true; + } else if (r > 0) { + return false; + } + ++j; + } + return false; +} + +bool PseudoPersistent::in_configured_list(const char* path, int fd) { + if (_append_file_configs == NULL) { + return false; + } + + int ret = fcntl(fd, F_GETFL); + if (ret != -1 && ((ret & O_ACCMODE) == O_WRONLY) && (ret & O_APPEND)) { + for (int i = 0; i < _append_file_configs->length(); i++) { + if (_append_file_configs->at(i)->match(path)) { + if (_append_files == NULL) { + _append_files = new(ResourceObj::C_HEAP, mtInternal) GrowableArray(2, true); + } + char *copy_path = NEW_C_HEAP_ARRAY(char, strlen(path) + 1, mtInternal); + strcpy(copy_path, path); + _append_files->append(copy_path); + return true; + } + } + } + return false; +} + +bool PseudoPersistent::AppendFileConfig::match(const char *file) { + if (_type == all) { + return true; + } else if (_type == by_extension) { + const char *p = strrchr(file, '.'); + return p != NULL && strlen(p + 1) == _size && !strncmp(p + 1, _start_ext_or_file, _size); + } else if (_type == by_full_path) { + return strlen(file) == _size && !strncmp(file, _start_ext_or_file, _size); + } + return false; +} + +PseudoPersistent::~PseudoPersistent() { + if (_append_files != NULL) { + for (int i = 0; i < _append_files->length(); i++) { + FREE_C_HEAP_ARRAY(char, _append_files->at(i)); + } + delete _append_files; + _append_files = NULL; + } + + if (_append_file_configs != NULL) { + for (int i = 0; i < _append_file_configs->length(); i++) { + delete _append_file_configs->at(i); + } + delete _append_file_configs; + _append_file_configs = NULL; + } +} diff --git a/src/hotspot/os/linux/crac_linux.hpp b/src/hotspot/os/linux/crac_linux.hpp new file mode 100644 index 00000000000..61f71b73278 --- /dev/null +++ b/src/hotspot/os/linux/crac_linux.hpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024 Alibaba Group Holding Limited. 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. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + * + */ + +#ifndef OS_LINUX_CRAC_LINUX_HPP +#define OS_LINUX_CRAC_LINUX_HPP +#include "utilities/growableArray.hpp" + +struct PseudoPersistentFileDesc { + int _mode; + bool _mark; + const char* _path; + PseudoPersistentFileDesc(int mode, const char *path) : + _mode(mode), + _path(path), + _mark(false) + {} + + PseudoPersistentFileDesc(): + _mode(0), + _path(NULL), + _mark(false) + {} +}; + +class PseudoPersistent { +private: + enum AppendFileConfigType { + all, + by_extension, + by_full_path + }; + + class AppendFileConfig : public CHeapObj { + AppendFileConfigType _type; + size_t _size; + const char *_start_ext_or_file; + public: + AppendFileConfig(AppendFileConfigType type, const char *start_ext_or_file, const char *end_ext_or_file) + : _type(type), _start_ext_or_file(start_ext_or_file), _size(0) { + if (start_ext_or_file && end_ext_or_file) { + _size = end_ext_or_file - start_ext_or_file; + } + } + bool match(const char *file); + }; + +private: + GrowableArray *_ppfd; + GrowableArray *_append_files; + GrowableArray *_append_file_configs; +public: + PseudoPersistent(GrowableArray* ppfd, const char* config); + ~PseudoPersistent(); + + bool test_and_mark(const char *path, int fd) { + return in_registered_list(path) || in_configured_list(path, fd); + } + + bool write_marked(const char* image_dir); +private: + bool in_configured_list(const char* path, int fd); + bool in_registered_list(const char *path); +}; + +#endif //OS_LINUX_CRAC_LINUX_HPP diff --git a/src/hotspot/os/linux/linuxAttachOperation.hpp b/src/hotspot/os/linux/linuxAttachOperation.hpp new file mode 100644 index 00000000000..cd68abc8ffe --- /dev/null +++ b/src/hotspot/os/linux/linuxAttachOperation.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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. + * + */ +#ifndef OS_LINUX_LINUXATTACHOPERATION_HPP +#define OS_LINUX_LINUXATTACHOPERATION_HPP +#include "services/attachListener.hpp" + +class LinuxAttachOperation: public AttachOperation { + private: + // the connection to the client + int _socket; + bool _effectively_completed; + void write_operation_result(jint result, bufferedStream* st); + + public: + void complete(jint res, bufferedStream* st); + void effectively_complete_raw(jint res, bufferedStream* st); + bool is_effectively_completed() { return _effectively_completed; } + + void set_socket(int s) { _socket = s; } + int socket() const { return _socket; } + + LinuxAttachOperation(char* name) : AttachOperation(name) { + set_socket(-1); + _effectively_completed = false; + } +}; +#endif // OS_LINUX_LINUXATTACHOPERATION_HPP diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index c60ce65f481..e91fe1faaad 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -32,15 +32,19 @@ #include "code/vtableStubs.hpp" #include "compiler/compileBroker.hpp" #include "compiler/disassembler.hpp" +#include "crac_linux.hpp" #include "interpreter/interpreter.hpp" #include "logging/log.hpp" #include "logging/logStream.hpp" #include "memory/allocation.inline.hpp" #include "memory/filemap.hpp" +#include "memory/oopFactory.hpp" #include "oops/oop.inline.hpp" +#include "oops/typeArrayOop.inline.hpp" #include "os_linux.inline.hpp" #include "os_share_linux.hpp" #include "osContainer_linux.hpp" +#include "perfMemory_linux.hpp" #include "prims/jniFastGetField.hpp" #include "prims/jvm_misc.hpp" #include "runtime/arguments.hpp" @@ -66,8 +70,10 @@ #include "runtime/vm_version.hpp" #include "semaphore_posix.hpp" #include "services/attachListener.hpp" +#include "services/heapDumper.hpp" #include "services/memTracker.hpp" #include "services/runtimeService.hpp" +#include "linuxAttachOperation.hpp" #include "utilities/align.hpp" #include "utilities/decoder.hpp" #include "utilities/defaultStream.hpp" @@ -76,13 +82,16 @@ #include "utilities/growableArray.hpp" #include "utilities/macros.hpp" #include "utilities/vmError.hpp" +#include "attachListener_linux.hpp" // put OS-includes here +# include # include # include # include # include # include +# include # include # include # include @@ -112,6 +121,7 @@ #ifdef __GLIBC__ # include #endif +# include #ifndef _GNU_SOURCE #define _GNU_SOURCE @@ -152,6 +162,267 @@ enum CoredumpFilterBit { DAX_SHARED_BIT = 1 << 8 }; +class FdsInfo { +public: + + enum state_t { + INVALID = -3, + CLOSED = -2, + ROOT = -1, + DUP_OF_0 = 0, + // ... + }; + + enum mark_t { + M_ZIP_CACHE = 1 << 0, + M_CANT_RESTORE = 1 << 1, + M_CLASSPATH = 1 << 2, + M_PERSISTENT = 1 << 3, + }; + +private: + struct fdinfo { + struct stat stat; + state_t state; + unsigned mark; + + int flags; + }; + + bool same_fd(int fd1, int fd2); + + fdinfo *_fdinfos; + int _len; + + void assert_mark(int i) { + assert(inited(), ""); + assert(i < len(), ""); + assert(_fdinfos[i].state != CLOSED, ""); + } + +public: + void initialize(); + + bool inited() { return _fdinfos != NULL; } + int len() { return _len; } + + state_t get_state(int i, state_t orstate = INVALID) { + assert(inited(), ""); + if (i < len()) { + return _fdinfos[i].state; + } + guarantee(orstate != INVALID, "can't use default orstate"); + return orstate; + } + + void set_state(int i, state_t newst) { + assert(inited(), ""); + assert(i < len(), ""); + _fdinfos[i].state = newst; + } + + void mark(int i, mark_t m) { + assert_mark(i); + _fdinfos[i].mark |= (unsigned)m; + } + void clear(int i, mark_t m) { + assert_mark(i); + _fdinfos[i].mark &= ~(unsigned)m; + } + bool check(int i, mark_t m) { + assert_mark(i); + return 0 != (_fdinfos[i].mark & (unsigned)m); + } + + struct stat* get_stat(int i) { + assert(inited(), ""); + assert(i < len(), ""); + return &_fdinfos[i].stat; + } + + FdsInfo(bool do_init = true) : + _fdinfos(NULL), + _len(-1) + { + if (do_init) { + initialize(); + } + } + + ~FdsInfo() { + if (_fdinfos) { + FREE_C_HEAP_ARRAY(fdinfo, _fdinfos); + } + } +}; + +struct PersistentResourceDesc { + int _fd; + dev_t _st_dev; + ino_t _st_ino; + PersistentResourceDesc(int fd, int st_dev, int st_ino) : + _fd(fd), + _st_dev((dev_t)st_dev), + _st_ino((ino_t)st_ino) + {} + + PersistentResourceDesc() : + _fd(INT_MAX) + {} +}; + + +struct CracFailDep { + int _type; + char* _msg; + CracFailDep(int type, char* msg) : + _type(type), + _msg(msg) + { } + CracFailDep() : + _type(JVM_CR_FAIL), + _msg(NULL) + { } +}; + +class CracRestoreParameters : public CHeapObj { + char* _raw_content; + GrowableArray* _properties; + const char* _args; + + struct header { + jlong _restore_time; + jlong _restore_counter; + int _nprops; + int _env_memory_size; + }; + + static bool write_check_error(int fd, const void *buf, int count) { + int wret = write(fd, buf, count); + if (wret != count) { + if (wret < 0) { + perror("shm error"); + } else { + fprintf(stderr, "write shm truncated"); + } + return false; + } + return true; + } + + static int system_props_length(const SystemProperty* props) { + int len = 0; + while (props != NULL) { + ++len; + props = props->next(); + } + return len; + } + + static int env_vars_size(const char* const * env) { + int len = 0; + for (; *env; ++env) { + len += strlen(*env) + 1; + } + return len; + } + + public: + const char *args() const { return _args; } + GrowableArray* properties() const { return _properties; } + + CracRestoreParameters() : + _raw_content(NULL), + _properties(new (ResourceObj::C_HEAP, mtInternal) GrowableArray(0, true)), + _args(NULL) + {} + + ~CracRestoreParameters() { + if (_raw_content) { + FREE_C_HEAP_ARRAY(char, _raw_content); + } + delete _properties; + } + + static bool write_to(int fd, + const SystemProperty* props, + const char *args, + jlong restore_time, + jlong restore_counter) { + header hdr = { + restore_time, + restore_counter, + system_props_length(props), + env_vars_size(environ) + }; + + if (!write_check_error(fd, (void *)&hdr, sizeof(header))) { + return false; + } + + const SystemProperty* p = props; + while (p != NULL) { + char prop[4096]; + int len = snprintf(prop, sizeof(prop), "%s=%s", p->key(), p->value()); + guarantee((0 < len) && ((unsigned)len < sizeof(prop)), "property does not fit temp buffer"); + if (!write_check_error(fd, prop, len+1)) { + return false; + } + p = p->next(); + } + + // Write env vars + for (char** env = environ; *env; ++env) { + if (!write_check_error(fd, *env, strlen(*env) + 1)) { + return false; + } + } + + return write_check_error(fd, args, strlen(args)+1); // +1 for null char + } + + bool read_from(int fd); + +}; + +class VM_Crac: public VM_Operation { + const bool _dry_run; + bool _ok; + GrowableArray* _failures; + CracRestoreParameters _restore_parameters; + outputStream* _ostream; + LinuxAttachOperation* _attach_op; + +public: + VM_Crac(bool dry_run, bufferedStream* jcmd_stream) : + _dry_run(dry_run), + _ok(false), + _failures(new (ResourceObj::C_HEAP, mtInternal) GrowableArray(0, true)), + _restore_parameters(), + _ostream(jcmd_stream ? jcmd_stream : tty), + _attach_op(jcmd_stream ? LinuxAttachListener::get_current_op() : NULL) + { } + + ~VM_Crac() { + delete _failures; + } + + GrowableArray* failures() { return _failures; } + bool ok() { return _ok; } + const char* new_args() { return _restore_parameters.args(); } + GrowableArray* new_properties() { return _restore_parameters.properties(); } + virtual bool allow_nested_vm_operations() const { return true; } + VMOp_Type type() const { return VMOp_VM_Crac; } + void doit(); + bool read_shm(int shmid); + +private: + bool is_socket_from_jcmd(int sock_fd); + void report_ok_to_jcmd_if_any(); + void print_resources(const char* msg, ...); + void trace_cr(const char* msg, ...); +}; + //////////////////////////////////////////////////////////////////////////////// // global variables julong os::Linux::_physical_memory = 0; @@ -179,6 +450,14 @@ static jlong initial_time_count=0; static int clock_tics_per_sec = 100; +// CRaC +static const char* _crengine = NULL; +static jlong _restore_start_time; +static jlong _restore_start_counter; +static FdsInfo _vm_inited_fds(false); +static GrowableArray* _persistent_resources = NULL; +static GrowableArray* _pseudo_persistent = NULL; + // If the VM might have been created on the primordial thread, we need to resolve the // primordial thread stack bounds and check if the current thread might be the // primordial thread in places. If we know that the primordial thread is never used, @@ -536,7 +815,7 @@ extern "C" void breakpoint() { // signal support debug_only(static bool signal_sets_initialized = false); -static sigset_t unblocked_sigs, vm_sigs; +static sigset_t unblocked_sigs, blocked_sigs, vm_sigs; void os::Linux::signal_sets_init() { // Should also have an assertion stating we are still single-threaded. @@ -580,6 +859,10 @@ void os::Linux::signal_sets_init() { if (!ReduceSignalUsage) { sigaddset(&vm_sigs, BREAK_SIGNAL); } + + sigemptyset(&blocked_sigs); + sigaddset(&blocked_sigs, RESTORE_SIGNAL); + debug_only(signal_sets_initialized = true); } @@ -608,6 +891,7 @@ void os::Linux::hotspot_sigmask(Thread* thread) { osthread->set_caller_sigmask(caller_sigmask); pthread_sigmask(SIG_UNBLOCK, os::Linux::unblocked_signals(), NULL); + pthread_sigmask(SIG_BLOCK, &blocked_sigs, NULL); if (!ReduceSignalUsage) { if (thread->is_VM_thread()) { @@ -6652,6 +6936,967 @@ int os::compare_file_modified_times(const char* file1, const char* file2) { return diff; } +// CRaC + +jlong os::Linux::restore_start_time() { + if (!_restore_start_time) { + return -1; + } + return _restore_start_time; +} + +jlong os::Linux::uptime_since_restore() { + if (!_restore_start_counter) { + return -1; + } + return javaTimeNanos() - _restore_start_counter; +} + +void VM_Crac::trace_cr(const char* msg, ...) { + if (CRTrace) { + va_list ap; + va_start(ap, msg); + _ostream->print("CR: "); + _ostream->vprint_cr(msg, ap); + va_end(ap); + } +} + +void VM_Crac::print_resources(const char* msg, ...) { + if (CRPrintResourcesOnCheckpoint) { + va_list ap; + va_start(ap, msg); + _ostream->vprint(msg, ap); + va_end(ap); + } +} + +void os::Linux::vm_create_start() { + if (!CRaCCheckpointTo) { + return; + } + close_extra_descriptors(); + _vm_inited_fds.initialize(); +} + +/* taken from criu, that took this from kernel */ +#define NFS_PREF ".nfs" +#define NFS_PREF_LEN ((unsigned)sizeof(NFS_PREF) - 1) +#define NFS_FILEID_LEN ((unsigned)sizeof(uint64_t) << 1) +#define NFS_COUNTER_LEN ((unsigned)sizeof(unsigned int) << 1) +#define NFS_LEN (NFS_PREF_LEN + NFS_FILEID_LEN + NFS_COUNTER_LEN) +static bool nfs_silly_rename(char* path) { + char *sep = strrchr(path, '/'); + char *base = sep ? sep + 1 : path; + if (strncmp(base, NFS_PREF, NFS_PREF_LEN)) { + return false; + } + for (unsigned i = NFS_PREF_LEN; i < NFS_LEN; ++i) { + if (!isxdigit(base[i])) { + return false; + } + } + return true; +} + +static int readfdlink(int fd, char *link, size_t len) { + char fdpath[64]; + snprintf(fdpath, sizeof(fdpath), "/proc/self/fd/%d", fd); + int ret = readlink(fdpath, link, len); + if (ret == -1) { + return ret; + } + link[(unsigned)ret < len ? ret : len - 1] = '\0'; + return ret; +} + +static bool same_stat(struct stat* st1, struct stat* st2) { + return st1->st_dev == st2->st_dev && + st1->st_ino == st2->st_ino; +} + +bool FdsInfo::same_fd(int fd1, int fd2) { + if (!same_stat(get_stat(fd1), get_stat(fd2))) { + return false; + } + + int flags1 = fcntl(fd1, F_GETFL); + int flags2 = fcntl(fd2, F_GETFL); + if (flags1 != flags2) { + return false; + } + + const int test_flag = O_NONBLOCK; + const int new_flags1 = flags1 ^ test_flag; + fcntl(fd1, F_SETFL, new_flags1); + if (fcntl(fd1, F_GETFL) != new_flags1) { + // flag write ignored or handled differently, + // don't know what to do + return false; + } + + const int new_flags2 = fcntl(fd2, F_GETFL); + const bool are_same = new_flags1 == new_flags2; + + fcntl(fd1, flags1); + + return are_same; +} + +void FdsInfo::initialize() { + assert(!inited(), "should be called only once"); + + const int max_fd = sysconf(_SC_OPEN_MAX); + _fdinfos = NEW_C_HEAP_ARRAY(fdinfo, max_fd, mtInternal); + int last_fd = -1; + + for (int i = 0; i < max_fd; ++i) { + fdinfo* info = _fdinfos + i; + int r = fstat(i, &info->stat); + if (r == -1) { + info->state = CLOSED; + continue; + } + info->state = ROOT; // can be changed to DUP_OF_0 + N below + info->mark = 0; + last_fd = i; + } + _len = last_fd + 1; + _fdinfos = REALLOC_C_HEAP_ARRAY(fdinfo, _fdinfos, _len, mtInternal); + + for (int i = 0; i < _len; ++i) { + for (int j = 0; j < i; ++j) { + if (get_state(j) == ROOT && same_fd(i, j)) { + _fdinfos[i].state = (state_t)(DUP_OF_0 + j); + break; + } + } + + if (get_state(i) == ROOT) { + char fdpath[PATH_MAX]; + int r = readfdlink(i, fdpath, sizeof(fdpath)); + guarantee(-1 != r, "can't stat fd"); + if (get_stat(i)->st_nlink == 0 || + strstr(fdpath, "(deleted)") || + nfs_silly_rename(fdpath)) { + mark(i, FdsInfo::M_CANT_RESTORE); + } + } + } +} + +static void mark_classpath_entry(FdsInfo *fds, char* cp) { + struct stat st; + if (-1 == stat(cp, &st)) { + return; + } + for (int i = 0; i < fds->len(); ++i) { + if (same_stat(&st, fds->get_stat(i))) { + fds->mark(i, FdsInfo::M_CLASSPATH); + } + } +} + +static void do_classpaths(void (*fn)(FdsInfo*, char*), FdsInfo *fds, char* classpath) { + assert(SafepointSynchronize::is_at_safepoint(), + "can't do nasty things with sysclasspath"); + char *cp = classpath; + char *n; + while ((n = strchr(cp, ':'))) { + *n = '\0'; + fn(fds, cp); + *n = ':'; + cp = n + 1; + } + mark_classpath_entry(fds, cp); +} + + +static void mark_all_in(FdsInfo *fds, char* dirpath) { + DIR *dir = os::opendir(dirpath); + if (!dir) { + return; + } + + struct dirent* dent; + while ((dent = os::readdir(dir))) { + for (int i = 0; i < fds->len(); ++i) { + if (fds->get_state(i) != FdsInfo::ROOT) { + continue; + } + struct stat* fstat = fds->get_stat(i); + if (dent->d_ino == fstat->st_ino) { + fds->mark(i, FdsInfo::M_CLASSPATH); + } + } + } + + os::closedir(dir); +} + +static void mark_persistent(FdsInfo *fds) { + if (!_persistent_resources) { + return; + } + + for (int i = 0; i < _persistent_resources->length(); ++i) { + PersistentResourceDesc* pr = _persistent_resources->adr_at(i); + int fd = pr->_fd; + if (fds->len() <= fd) { + break; + } + if (fds->get_state(fd) != FdsInfo::ROOT) { + continue; + } + struct stat* st = fds->get_stat(fd); + if (st->st_dev == pr->_st_dev && st->st_ino == pr->_st_ino) { + fds->mark(fd, FdsInfo::M_PERSISTENT); + } + } + + delete _persistent_resources; + _persistent_resources = NULL; +} + +static int cr_util_path(char* path, int len) { + os::jvm_path(path, len); + // path is ".../lib/server/libjvm.so" + char *after_elem = NULL; + for (int i = 0; i < 2; ++i) { + after_elem = strrchr(path, '/'); + *after_elem = '\0'; + } + return after_elem - path; +} + +static bool compute_crengine() { + if (!CREngine) { + return true; + } + + if (CREngine[0] == '/') { + _crengine = CREngine; + return true; + } + + char path[JVM_MAXPATHLEN]; + int pathlen = cr_util_path(path, sizeof(path)); + strcat(path + pathlen, "/"); + strcat(path + pathlen, CREngine); + + struct stat st; + if (0 != stat(path, &st)) { + warning("Could not find %s: %s", path, strerror(errno)); + return false; + } + + _crengine = os::strdup(path); + return true; +} + +static int call_crengine() { + if (!_crengine) { + return -1; + } + + pid_t pid = fork(); + if (pid == -1) { + perror("cannot fork for crengine"); + return -1; + } + if (pid == 0) { + execl(_crengine, _crengine, "checkpoint", CRaCCheckpointTo, + CRaCValidateBeforeRestore ? "true" : "false", + VM_Version::internal_vm_info_string(), + CRaCUnprivileged ? "true" : "false", + CRaCRestoreInheritPipeFds != NULL ? CRaCRestoreInheritPipeFds : "", NULL); + perror("execl"); + exit(1); + } + + int status; + int ret; + do { + ret = waitpid(pid, &status, 0); + } while (ret == -1 && errno == EINTR); + + if (ret == -1 || !WIFEXITED(status)) { + return -1; + } + return WEXITSTATUS(status) == 0 ? 0 : -1; +} + +class CracSHM { + char _path[128]; +public: + CracSHM(int id) { + int shmpathlen = snprintf(_path, sizeof(_path), "/crac_%d", id); + if (shmpathlen < 0 || sizeof(_path) <= (size_t)shmpathlen) { + fprintf(stderr, "shmpath is too long: %d\n", shmpathlen); + } + } + + int open(int mode) { + int shmfd = shm_open(_path, mode, 0600); + if (-1 == shmfd) { + perror("shm_open"); + } + return shmfd; + } + + void unlink() { + shm_unlink(_path); + } +}; + +static int checkpoint_restore(int *shmid) { + int cres = call_crengine(); + if (cres < 0) { + return JVM_CHECKPOINT_ERROR; + } + + sigset_t waitmask; + sigemptyset(&waitmask); + sigaddset(&waitmask, RESTORE_SIGNAL); + + siginfo_t info; + int sig; + do { + sig = sigwaitinfo(&waitmask, &info); + } while (sig == -1 && errno == EINTR); + assert(sig == RESTORE_SIGNAL, "got what requested"); + + if (CRTraceStartupTime) { + tty->print_cr("STARTUPTIME " JLONG_FORMAT " restore-native", os::javaTimeNanos()); + tty->print_cr("STARTUPTIME " JLONG_FORMAT " restore-native-time", os::javaTimeMillis()); + } + + if (info.si_code != SI_QUEUE || info.si_int < 0) { + tty->print("JVM: invalid info for restore provided: %s", info.si_code == SI_QUEUE ? "queued" : "not queued"); + if (info.si_code == SI_QUEUE) { + tty->print(" code %d", info.si_int); + } + tty->cr(); + return JVM_CHECKPOINT_ERROR; + } + + if (0 < info.si_int) { + *shmid = info.si_int; + } + + return JVM_CHECKPOINT_OK; +} + +static const char* stat2strtype(mode_t mode) { + switch (mode & S_IFMT) { + case S_IFSOCK: return "socket"; + case S_IFLNK: return "symlink"; + case S_IFREG: return "regular"; + case S_IFBLK: return "block"; + case S_IFDIR: return "directory"; + case S_IFCHR: return "character"; + case S_IFIFO: return "fifo"; + default: break; + } + return "unknown"; +} + +static int stat2stfail(mode_t mode) { + switch (mode & S_IFMT) { + case S_IFSOCK: + return JVM_CR_FAIL_SOCK; + case S_IFLNK: + case S_IFREG: + case S_IFBLK: + case S_IFDIR: + case S_IFCHR: + return JVM_CR_FAIL_FILE; + case S_IFIFO: + return JVM_CR_FAIL_PIPE; + default: + break; + } + return JVM_CR_FAIL; +} + +static bool find_sock_details(int sockino, const char* base, bool v6, char* buf, size_t sz) { + char filename[16]; + snprintf(filename, sizeof(filename), "/proc/net/%s", base); + FILE* f = fopen(filename, "r"); + if (!f) { + return false; + } + int r = fscanf(f, "%*[^\n]"); + if (r) {} // suppress warn unused gcc diagnostic + + char la[33], ra[33]; + int lp, rp; + int ino; + // sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode + // 0: 0100007F:08AE 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 2988639 + // %4d: %08X%08X%08X%08X:%04X %08X%08X%08X%08X:%04X %02X %08X:%08X %02X:%08lX %08X %5u %8d %d + bool eof; + do { + eof = EOF == fscanf(f, "%*d: %[^:]:%X %[^:]:%X %*X %*X:%*X %*X:%*X %*X %*d %*d %d%*[^\n]\n", + la, &lp, ra, &rp, &ino); + } while (ino != sockino && !eof); + fclose(f); + + if (ino != sockino) { + return false; + } + + struct in6_addr a6l, a6r; + struct in_addr a4l, a4r; + if (v6) { + for (int i = 0; i < 4; ++i) { + sscanf(la + i * 8, "%8" PRIX32, a6l.s6_addr32 + i); + sscanf(ra + i * 8, "%8" PRIX32, a6r.s6_addr32 + i); + } + } else { + sscanf(la, "%" PRIX32, &a4l.s_addr); + sscanf(ra, "%" PRIX32, &a4r.s_addr); + } + + int const af = v6 ? AF_INET6 : AF_INET; + void* const laddr = v6 ? (void*)&a6l : (void*)&a4l; + void* const raddr = v6 ? (void*)&a6r : (void*)&a4r; + char lstrb[48], rstrb[48]; + const char* const lstr = ::inet_ntop(af, laddr, lstrb, sizeof(lstrb)) ? lstrb : "NONE"; + const char* const rstr = ::inet_ntop(af, raddr, rstrb, sizeof(rstrb)) ? rstrb : "NONE"; + int msgsz = snprintf(buf, sz, "%s localAddr %s localPort %d remoteAddr %s remotePort %d", + base, lstr, lp, rstr, rp); + return msgsz < (int)sz; +} + +static const char* sock_details(const char* details, char* buf, size_t sz) { + int sockino; + if (sscanf(details, "socket:[%d]", &sockino) <= 0) { + return details; + } + + const char* bases[] = { "tcp", "udp", "tcp6", "udp6", NULL }; + for (const char** b = bases; *b; ++b) { + if (find_sock_details(sockino, *b, 2 <= b - bases, buf, sz)) { + return buf; + } + } + + return details; +} + +bool VM_Crac::read_shm(int shmid) { + CracSHM shm(shmid); + int shmfd = shm.open(O_RDONLY); + shm.unlink(); + if (shmfd < 0) { + return false; + } + bool ret = _restore_parameters.read_from(shmfd); + close(shmfd); + return ret; +} + +// If checkpoint is called throught the API, jcmd operation and jcmd output doesn't exist. +bool VM_Crac::is_socket_from_jcmd(int sock) { + if (_attach_op == NULL) + return false; + int sock_fd = _attach_op->socket(); + return sock == sock_fd; +} + +void VM_Crac::report_ok_to_jcmd_if_any() { + if (_attach_op == NULL) + return; + bufferedStream* buf = static_cast(_ostream); + _attach_op->effectively_complete_raw(JNI_OK, buf); + // redirect any further output to console + _ostream = tty; +} + +void VM_Crac::doit() { + + AttachListener::abort(); + + FdsInfo fds; + do_classpaths(mark_classpath_entry, &fds, Arguments::get_sysclasspath()); + do_classpaths(mark_classpath_entry, &fds, Arguments::get_appclasspath()); + do_classpaths(mark_all_in, &fds, Arguments::get_ext_dirs()); + mark_persistent(&fds); + + PseudoPersistent pp(_pseudo_persistent, CRaCAppendOnlyLogFiles); + int markcnt = 0; + + // dry-run fails checkpoint + bool ok = !_dry_run; + + for (int i = 0; i < fds.len(); ++i) { + if (fds.get_state(i) == FdsInfo::CLOSED) { + continue; + } + + char detailsbuf[PATH_MAX]; + int linkret = readfdlink(i, detailsbuf, sizeof(detailsbuf)); + const char* details = 0 < linkret ? detailsbuf : ""; + print_resources("JVM: FD fd=%d type=%s: details1=\"%s\" ", + i, stat2strtype(fds.get_stat(i)->st_mode), details); + struct stat* st = fds.get_stat(i); + + if (S_ISREG(st->st_mode) && pp.test_and_mark(detailsbuf, i)) { + markcnt++; + print_resources("OK: user registered pseudo persistent file \n"); + continue; + } + + if (_vm_inited_fds.get_state(i, FdsInfo::CLOSED) != FdsInfo::CLOSED) { + print_resources("OK: inherited from process env\n"); + continue; + } + + if (S_ISCHR(st->st_mode)) { + const int mjr = major(st->st_rdev); + const int mnr = minor(st->st_rdev); + if (mjr == 1 && (mnr == 8 || mnr == 9)) { + print_resources("OK: always available, random or urandom\n"); + continue; + } + } + + if (fds.check(i, FdsInfo::M_CLASSPATH) && !fds.check(i, FdsInfo::M_CANT_RESTORE)) { + print_resources("OK: in classpath\n"); + continue; + } + + if (fds.check(i, FdsInfo::M_PERSISTENT)) { + print_resources("OK: assured persistent\n"); + continue; + } + + if (S_ISSOCK(st->st_mode)) { + if (is_socket_from_jcmd(i)){ + print_resources("OK: jcmd socket\n"); + continue; + } + details = sock_details(details, detailsbuf, sizeof(detailsbuf)); + print_resources(" details2=\"%s\" ", details); + } + + print_resources("BAD: opened by application\n"); + ok = false; + + char* msg = NEW_C_HEAP_ARRAY(char, strlen(details) + 1, mtInternal); + strcpy(msg, details); + _failures->append(CracFailDep(stat2stfail(st->st_mode & S_IFMT), msg)); + } + + if (ok && markcnt) { + ok = pp.write_marked(CRaCCheckpointTo); + } + + // _pseudo_persistent initialize and free in each checkpoint operation. + if (_pseudo_persistent) { + int j = 0; + while (j < _pseudo_persistent->length()) { + FREE_C_HEAP_ARRAY(char, _pseudo_persistent->adr_at(j)->_path); + ++j; + } + delete _pseudo_persistent; + _pseudo_persistent = NULL; + } + + if (!ok && CRHeapDumpOnCheckpointException) { + HeapDumper::dump_heap(); + } + + if (!ok && CRDoThrowCheckpointException) { + return; + } + + if (!PerfMemoryLinux::checkpoint(CRaCCheckpointTo)) { + return; + } + + int shmid = 0; + if (CRAllowToSkipCheckpoint) { + trace_cr("Skip Checkpoint"); + } else { + trace_cr("Checkpoint ..."); + report_ok_to_jcmd_if_any(); + int ret = checkpoint_restore(&shmid); + if (ret == JVM_CHECKPOINT_ERROR) { + PerfMemoryLinux::restore(); + return; + } + } + + if (shmid <= 0 || !VM_Crac::read_shm(shmid)) { + _restore_start_time = os::javaTimeMillis(); + _restore_start_counter = os::javaTimeNanos(); + } + PerfMemoryLinux::restore(); + + if (CRTraceStartupTime) { + tty->print_cr("STARTUPTIME " JLONG_FORMAT " restore-start-time", _restore_start_time); + } + + _ok = true; +} + +void os::Linux::register_persistent_fd(int fd, int st_dev, int st_ino) { + if (!CRaCCheckpointTo) { + return; + } + if (!_persistent_resources) { + _persistent_resources = new (ResourceObj::C_HEAP, mtInternal) + GrowableArray(0, true/*C_heap*/); + } + int dup = -1; + int i = 0; + while (i < _persistent_resources->length()) { + int pfd = _persistent_resources->adr_at(i)->_fd; + if (pfd == fd) { + dup = i; + break; + } else if (fd < pfd) { + break; + } + ++i; + } + + if (0 <= dup) { + _persistent_resources->at_put(dup, PersistentResourceDesc(fd, st_dev, st_ino)); + } else { + _persistent_resources->insert_before(i, PersistentResourceDesc(fd, st_dev, st_ino)); + } +} + +void os::Linux::deregister_persistent_fd(int fd, int st_dev, int st_ino) { + if (!CRaCCheckpointTo) { + return; + } + if (!_persistent_resources) { + return; + } + int i = 0; + while (i < _persistent_resources->length()) { + PersistentResourceDesc* pr = _persistent_resources->adr_at(i); + if (pr->_fd == fd && pr->_st_dev == (dev_t)st_dev && pr->_st_ino == (ino_t)st_ino) { + break; + } + } + if (i < _persistent_resources->length()) { + _persistent_resources->remove_at(i); + } +} + +void os::Linux::register_pseudo_persistent(const char* absolute_file_path, int mode) { + if (!CRaCCheckpointTo) { + return; + } + if (!_pseudo_persistent) { + _pseudo_persistent = new (ResourceObj::C_HEAP, mtInternal) + GrowableArray(0, true/*C_heap*/); + } + int i = 0 ; + while (i < _pseudo_persistent->length()) { + int r = strcmp(_pseudo_persistent->adr_at(i)->_path, absolute_file_path); + if (r == 0 ) { + return; + } else if (r > 0) { + break; + } + ++i; + } + char* path = NEW_C_HEAP_ARRAY(char, strlen(absolute_file_path) + 1, mtInternal); + strcpy(path, absolute_file_path); + _pseudo_persistent->insert_before(i, PseudoPersistentFileDesc(mode, path)); +} + +void os::Linux::unregister_pseudo_persistent(const char *absolute_file_path) { + if (!CRaCCheckpointTo) { + return; + } + if (!_pseudo_persistent) { + return; + } + int i = 0; + while (i < _pseudo_persistent->length()) { + int r = strcmp(_pseudo_persistent->adr_at(i)->_path, absolute_file_path); + if (r == 0 ) { + break; + } else if (r > 0) { + return; + } + ++i; + } + if (i < _pseudo_persistent->length()) { + _pseudo_persistent->remove_at(i); + } +} + + +bool os::Linux::prepare_checkpoint() { + struct stat st; + + if (0 == stat(CRaCCheckpointTo, &st)) { + if ((st.st_mode & S_IFMT) != S_IFDIR) { + warning("%s: not a directory", CRaCCheckpointTo); + return false; + } + } else { + if (-1 == mkdir(CRaCCheckpointTo, 0700)) { + warning("cannot create %s: %s", CRaCCheckpointTo, strerror(errno)); + return false; + } + if (-1 == rmdir(CRaCCheckpointTo)) { + warning("cannot cleanup after check: %s", strerror(errno)); + // not fatal + } + } + + if (!compute_crengine()) { + return false; + } + + return true; +} + +static Handle ret_cr(int ret, Handle new_args, Handle new_props, Handle err_codes, Handle err_msgs, TRAPS) { + objArrayOop bundleObj = oopFactory::new_objectArray(5, CHECK_NH); + objArrayHandle bundle(THREAD, bundleObj); + jvalue jval = { .i = ret }; + oop retObj = java_lang_boxing_object::create(T_INT, &jval, CHECK_NH); + bundle->obj_at_put(0, retObj); + bundle->obj_at_put(1, new_args()); + bundle->obj_at_put(2, new_props()); + bundle->obj_at_put(3, err_codes()); + bundle->obj_at_put(4, err_msgs()); + return bundle; +} + +/** Checkpoint main entry. + */ +Handle os::Linux::checkpoint(bool dry_run, jlong jcmd_stream, TRAPS) { + if (!CRaCCheckpointTo) { + return ret_cr(JVM_CHECKPOINT_NONE, Handle(), Handle(), Handle(), Handle(), THREAD); + } + + if (-1 == mkdir(CRaCCheckpointTo, 0700) && errno != EEXIST) { + warning("cannot create %s: %s", CRaCCheckpointTo, strerror(errno)); + return ret_cr(JVM_CHECKPOINT_NONE, Handle(), Handle(), Handle(), Handle(), THREAD); + } + + Universe::heap()->set_cleanup_unused(true); + Universe::heap()->collect(GCCause::_full_gc_alot); + Universe::heap()->set_cleanup_unused(false); + + VM_Crac cr(dry_run, (bufferedStream*)jcmd_stream); + { + MutexLocker ml(Heap_lock); + VMThread::execute(&cr); + } + if (cr.ok()) { + oop new_args = NULL; + if (cr.new_args()) { + new_args = java_lang_String::create_oop_from_str(cr.new_args(), CHECK_NH); + } + GrowableArray* new_properties = cr.new_properties(); + objArrayOop propsObj = oopFactory::new_objArray(SystemDictionary::String_klass(), new_properties->length(), CHECK_NH); + objArrayHandle props(THREAD, propsObj); + + for (int i = 0; i < new_properties->length(); i++) { + oop propObj = java_lang_String::create_oop_from_str(new_properties->at(i), CHECK_NH); + props->obj_at_put(i, propObj); + } + return ret_cr(JVM_CHECKPOINT_OK, Handle(THREAD, new_args), props, Handle(), Handle(), THREAD); + } + + GrowableArray* failures = cr.failures(); + + typeArrayOop codesObj = oopFactory::new_intArray(failures->length(), CHECK_NH); + typeArrayHandle codes(THREAD, codesObj); + objArrayOop msgsObj = oopFactory::new_objArray(SystemDictionary::String_klass(), failures->length(), CHECK_NH); + objArrayHandle msgs(THREAD, msgsObj); + + for (int i = 0; i < failures->length(); ++i) { + codes->int_at_put(i, failures->at(i)._type); + oop msgObj = java_lang_String::create_oop_from_str(failures->at(i)._msg, CHECK_NH); + FREE_C_HEAP_ARRAY(char, failures->at(i)._msg); + msgs->obj_at_put(i, msgObj); + } + + return ret_cr(JVM_CHECKPOINT_ERROR, Handle(), Handle(), codes, msgs, THREAD); +} + +void os::Linux::restore() { + struct stat st; + + jlong restore_time = javaTimeMillis(); + jlong restore_counter = javaTimeNanos(); + + compute_crengine(); + + if (CRaCValidateBeforeRestore) { + pid_t pid = fork(); + if (pid == -1) { + perror("cannot fork for criuengine"); + return; + } + if (pid == 0) { + execl(_crengine, _crengine, "restorevalidate", CRaCRestoreFrom, + VM_Version::internal_vm_info_string(), + CRaCUnprivileged ? "true" : "false", NULL); + perror("execl"); + exit(1); + } + + int status; + int ret; + do { + ret = waitpid(pid, &status, 0); + } while (ret == -1 && errno == EINTR); + + if (ret == -1 || !WIFEXITED(status)) { + return; + } + if (WEXITSTATUS(status) != 0) { + return; + } + } + + int id = getpid(); + CracSHM shm(id); + int shmfd = shm.open(O_RDWR | O_CREAT); + if (0 <= shmfd) { + if (CracRestoreParameters::write_to( + shmfd, + Arguments::system_properties(), + Arguments::java_command() ? Arguments::java_command() : "", + restore_time, + restore_counter)) { + char strid[32]; + snprintf(strid, sizeof(strid), "%d", id); + setenv("CRAC_NEW_ARGS_ID", strid, true); + } + close(shmfd); + } + + + if (_crengine) { + execl(_crengine, _crengine, "restore", CRaCRestoreFrom, CRaCUnprivileged ? "true" : "false", NULL); + warning("cannot execute \"%s restore ...\" (%s)", _crengine, strerror(errno)); + } +} + +static char modules_path[JVM_MAXPATHLEN] = { '\0' }; + +static bool is_fd_ignored(int fd, const char *path) { + if (!strcmp(modules_path, path)) { + // Path to the modules directory is opened early when JVM is booted up and won't be closed. + // We can ignore this for purposes of CRaC. + return true; + } + + const char *list = CRaCIgnoredFileDescriptors; + while (list && *list) { + const char *end = strchr(list, ','); + if (!end) { + end = list + strlen(list); + } + char *invalid; + int ignored_fd = strtol(list, &invalid, 10); + if (invalid == end) { // entry was integer -> file descriptor + if (fd == ignored_fd) { + log_trace(os)("CRaC not closing file descriptor %d (%s) as it is marked as ignored.", fd, path); + return true; + } + } else { // interpret entry as path + int path_len = path ? strlen(path) : -1; + if (path_len != -1 && path_len == end - list && !strncmp(path, list, end - list)) { + log_trace(os)("CRaC not closing file descriptor %d (%s) as it is marked as ignored.", fd, path); + return true; + } + } + if (*end) { + list = end + 1; + } else { + break; + } + } + return false; +} + +void os::Linux::close_extra_descriptors() { + // Path to the modules directory is opened early when JVM is booted up and won't be closed. + // We can ignore this for purposes of CRaC. + if (modules_path[0] == '\0') { + const char* fileSep = os::file_separator(); + jio_snprintf(modules_path, JVM_MAXPATHLEN, "%s%slib%smodules", Arguments::get_java_home(), fileSep, fileSep); + } + + char path[PATH_MAX]; + struct dirent *dp; + + DIR *dir = opendir("/proc/self/fd"); + while (dp = readdir(dir)) { + int fd = atoi(dp->d_name); + if (fd > 2 && fd != dirfd(dir)) { + int r = readfdlink(fd, path, sizeof(path)); + if (!is_fd_ignored(fd, r != -1 ? path : NULL)) { + log_warning(os)("CRaC closing file descriptor %d: %s\n", fd, path); + close(fd); + } + } + } + closedir(dir); +} + +bool CracRestoreParameters::read_from(int fd) { + struct stat st; + if (fstat(fd, &st)) { + perror("fstat (ignoring restore parameters)"); + return false; + } + + char *contents = NEW_C_HEAP_ARRAY(char, st.st_size, mtInternal); + if (read(fd, contents, st.st_size) < 0) { + perror("read (ignoring restore parameters)"); + FREE_C_HEAP_ARRAY(char, contents); + return false; + } + + _raw_content = contents; + + // parse the contents to read new system properties and arguments + header* hdr = (header*)_raw_content; + char* cursor = _raw_content + sizeof(header); + + ::_restore_start_time = hdr->_restore_time; + ::_restore_start_counter = hdr->_restore_counter; + + for (int i = 0; i < hdr->_nprops; i++) { + assert((cursor + strlen(cursor) <= contents + st.st_size), "property length exceeds shared memory size"); + int idx = _properties->append(cursor); + int prop_len = strlen(cursor) + 1; + cursor = cursor + prop_len; + } + + char* env_mem = NEW_C_HEAP_ARRAY(char, hdr->_env_memory_size, mtArguments); // left this pointer unowned, it is freed when process dies + memcpy(env_mem, cursor, hdr->_env_memory_size); + + const char* env_end = env_mem + hdr->_env_memory_size; + while (env_mem < env_end) { + const size_t s = strlen(env_mem) + 1; + assert(env_mem + s <= env_end, "env vars exceed memory buffer, maybe ending 0 is lost"); + putenv(env_mem); + env_mem += s; + } + cursor += hdr->_env_memory_size; + + _args = cursor; + return true; +} + /////////////// Unit tests /////////////// #ifndef PRODUCT @@ -6709,9 +7954,9 @@ class TestReserveMemorySpecial : AllStatic { // sizes to test const size_t sizes[] = { - lp, lp + ag, lp + lp / 2, lp * 2, - lp * 2 + ag, lp * 2 - ag, lp * 2 + lp / 2, - lp * 10, lp * 10 + lp / 2 + lp, lp + ag, lp + lp / 2, lp * 2, + lp * 2 + ag, lp * 2 - ag, lp * 2 + lp / 2, + lp * 10, lp * 10 + lp / 2 }; const int num_sizes = sizeof(sizes) / sizeof(size_t); @@ -6724,14 +7969,14 @@ class TestReserveMemorySpecial : AllStatic { // Pre-allocate two areas; they shall be as large as the largest allocation // and aligned to the largest alignment we will be testing. const size_t mapping_size = sizes[num_sizes - 1] * 2; - char* const mapping1 = (char*) ::mmap(NULL, mapping_size, - PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, - -1, 0); + char *const mapping1 = (char *) ::mmap(NULL, mapping_size, + PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, + -1, 0); assert(mapping1 != MAP_FAILED, "should work"); - char* const mapping2 = (char*) ::mmap(NULL, mapping_size, - PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, - -1, 0); + char *const mapping2 = (char *) ::mmap(NULL, mapping_size, + PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, + -1, 0); assert(mapping2 != MAP_FAILED, "should work"); // Unmap the first mapping, but leave the second mapping intact: the first @@ -6746,7 +7991,7 @@ class TestReserveMemorySpecial : AllStatic { for (int i = 0; i < num_sizes; i++) { const size_t size = sizes[i]; for (size_t alignment = ag; is_aligned(size, alignment); alignment *= 2) { - char* p = os::Linux::reserve_memory_special_huge_tlbfs_mixed(size, alignment, NULL, false); + char *p = os::Linux::reserve_memory_special_huge_tlbfs_mixed(size, alignment, NULL, false); test_log(SIZE_FORMAT_HEX " " SIZE_FORMAT_HEX " -> " PTR_FORMAT " %s", size, alignment, p2i(p), (p != NULL ? "" : "(failed)")); if (p != NULL) { @@ -6764,8 +8009,8 @@ class TestReserveMemorySpecial : AllStatic { for (int i = 0; i < num_sizes; i++) { const size_t size = sizes[i]; for (size_t alignment = ag; is_aligned(size, alignment); alignment *= 2) { - char* const req_addr = align_up(mapping1, alignment); - char* p = os::Linux::reserve_memory_special_huge_tlbfs_mixed(size, alignment, req_addr, false); + char *const req_addr = align_up(mapping1, alignment); + char *p = os::Linux::reserve_memory_special_huge_tlbfs_mixed(size, alignment, req_addr, false); test_log(SIZE_FORMAT_HEX " " SIZE_FORMAT_HEX " " PTR_FORMAT " -> " PTR_FORMAT " %s", size, alignment, p2i(req_addr), p2i(p), ((p != NULL ? (p == req_addr ? "(exact match)" : "") : "(failed)"))); @@ -6784,8 +8029,8 @@ class TestReserveMemorySpecial : AllStatic { for (int i = 0; i < num_sizes; i++) { const size_t size = sizes[i]; for (size_t alignment = ag; is_aligned(size, alignment); alignment *= 2) { - char* const req_addr = align_up(mapping2, alignment); - char* p = os::Linux::reserve_memory_special_huge_tlbfs_mixed(size, alignment, req_addr, false); + char *const req_addr = align_up(mapping2, alignment); + char *p = os::Linux::reserve_memory_special_huge_tlbfs_mixed(size, alignment, req_addr, false); test_log(SIZE_FORMAT_HEX " " SIZE_FORMAT_HEX " " PTR_FORMAT " -> " PTR_FORMAT " %s", size, alignment, p2i(req_addr), p2i(p), ((p != NULL ? "" : "(failed)"))); // as the area around req_addr contains already existing mappings, the API should always @@ -6795,8 +8040,7 @@ class TestReserveMemorySpecial : AllStatic { } ::munmap(mapping2, mapping_size); - - } +} static void test_reserve_memory_special_huge_tlbfs() { if (!UseHugeTLBFS) { diff --git a/src/hotspot/os/linux/os_linux.hpp b/src/hotspot/os/linux/os_linux.hpp index ed53c686dc5..9da0c46659c 100644 --- a/src/hotspot/os/linux/os_linux.hpp +++ b/src/hotspot/os/linux/os_linux.hpp @@ -248,6 +248,22 @@ class Linux { // May fail (returns false) or succeed (returns true) but not all output fields are available; unavailable // fields will contain -1. static bool query_process_memory_info(meminfo_t* info); + static void vm_create_start(); + static bool prepare_checkpoint(); + static Handle checkpoint(bool dry_run, jlong jcmd_stream, TRAPS); + static void restore(); + static void close_extra_descriptors(); + static void register_persistent_fd(int fd, int st_dev, int st_ino); + static void deregister_persistent_fd(int fd, int st_dev, int st_ino); + static void register_pseudo_persistent(const char *absolute_file_path, int mode); + static void unregister_pseudo_persistent(const char *absolute_file_path); + + static jlong restore_start_time(); + static jlong uptime_since_restore(); + + // Determine if the vmid is the parent pid for a child in a PID namespace. + // Return the namespace pid if so, otherwise -1. + static int get_namespace_pid(int vmid); // Stack repair handling diff --git a/src/hotspot/os/linux/perfMemory_linux.cpp b/src/hotspot/os/linux/perfMemory_linux.cpp index b3f637811fb..34601f6b424 100644 --- a/src/hotspot/os/linux/perfMemory_linux.cpp +++ b/src/hotspot/os/linux/perfMemory_linux.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2001, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -29,6 +30,7 @@ #include "memory/resourceArea.hpp" #include "oops/oop.inline.hpp" #include "os_linux.inline.hpp" +#include "perfMemory_linux.hpp" #include "runtime/handles.inline.hpp" #include "runtime/os.hpp" #include "runtime/perfMemory.hpp" @@ -48,6 +50,7 @@ static char* backing_store_file_name = NULL; // name of the backing store // file, if successfully created. +static int checkpoint_fd = -1; // Standard Memory Implementation Details @@ -1405,3 +1408,101 @@ void PerfMemory::detach(char* addr, size_t bytes, TRAPS) { unmap_shared(addr, bytes); } + +bool PerfMemoryLinux::checkpoint(const char* checkpoint_path) { + assert(checkpoint_path, "should be set"); + + if (!backing_store_file_name) { + return true; + } + + char path[JVM_MAXPATHLEN]; + int pathlen = snprintf(path, sizeof(path),"%s/%s", checkpoint_path, perfdata_name()); + + RESTARTABLE(::open(path, O_RDWR|O_CREAT|O_NOFOLLOW, S_IRUSR|S_IWUSR), checkpoint_fd); + if (checkpoint_fd < 0) { + tty->print_cr("cannot open checkpoint perfdata: %s", os::strerror(errno)); + return false; + } + + char* p = PerfMemory::start(); + size_t len = PerfMemory::capacity(); + do { + int result; + RESTARTABLE(::write(checkpoint_fd, p, len), result); + if (result == OS_ERR) { + tty->print_cr("cannot write data to checkpoint perfdata file: %s", os::strerror(errno)); + ::close(checkpoint_fd); + checkpoint_fd = -1; + return false; + } + p += result; + len -= (size_t)result; + } while (0 < len); + + void* mmapret = ::mmap(PerfMemory::start(), PerfMemory::capacity(), + PROT_READ|PROT_WRITE, MAP_FIXED|MAP_SHARED, checkpoint_fd, 0); + if (MAP_FAILED == mmapret) { + tty->print_cr("cannot mmap checkpoint perfdata file: %s", os::strerror(errno)); + ::close(checkpoint_fd); + checkpoint_fd = -1; + return false; + } + + remove_file(backing_store_file_name); + + return true; +} + +bool PerfMemoryLinux::restore() { + if (checkpoint_fd < 0) { + return true; + } + + int vmid = os::current_process_id(); + char* user_name = get_user_name(geteuid()); + char* dirname = get_user_tmp_dir(user_name, vmid, -1); + if (!make_user_tmp_dir(dirname)) { + return false; + } + + int fd; + RESTARTABLE(::open(backing_store_file_name, O_RDWR|O_CREAT|O_NOFOLLOW, S_IRUSR|S_IWUSR), fd); + if (fd == OS_ERR) { + tty->print_cr("cannot open restore perfdata file: %s", os::strerror(errno)); + + void* mmapret = ::mmap(PerfMemory::start(), PerfMemory::capacity(), + PROT_READ|PROT_WRITE, MAP_FIXED|MAP_PRIVATE, checkpoint_fd, 0); + if (MAP_FAILED == mmapret) { + tty->print_cr("cannot remap checkpoint perfdata file: %s", os::strerror(errno)); + } + return false; + } + + char* p = PerfMemory::start(); + size_t len = PerfMemory::capacity(); + do { + int result; + RESTARTABLE(::write(fd, p, len), result); + if (result == OS_ERR) { + tty->print_cr("cannot write data to restore perfdata file: %s", os::strerror(errno)); + ::close(fd); + return false; + } + p += result; + len -= (size_t)result; + } while (0 < len); + + void* mmapret = ::mmap(PerfMemory::start(), PerfMemory::capacity(), + PROT_READ|PROT_WRITE, MAP_FIXED|MAP_SHARED, fd, 0); + if (MAP_FAILED == mmapret) { + tty->print_cr("cannot mmap restore perfdata file: %s", os::strerror(errno)); + ::close(fd); + return false; + } + + ::close(fd); + ::close(checkpoint_fd); + checkpoint_fd = -1; + return true; +} diff --git a/src/hotspot/os/linux/perfMemory_linux.hpp b/src/hotspot/os/linux/perfMemory_linux.hpp new file mode 100644 index 00000000000..07a3f611b57 --- /dev/null +++ b/src/hotspot/os/linux/perfMemory_linux.hpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020, 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. + * + */ + +#ifndef OS_LINUX_PERFMEMORY_LINUX_HPP +#define OS_LINUX_PERFMEMORY_LINUX_HPP + +#include "memory/allocation.hpp" + +class PerfMemoryLinux : AllStatic { + +public: + static inline const char* perfdata_name() { + return "perfdata"; + } + + static bool checkpoint(const char* checkpoint_path); + static bool restore(); +}; + +#endif // OS_LINUX_PERFMEMORY_LINUX_HPP diff --git a/src/hotspot/os/posix/include/jvm_md.h b/src/hotspot/os/posix/include/jvm_md.h index 13d778fba8f..16dc6114361 100644 --- a/src/hotspot/os/posix/include/jvm_md.h +++ b/src/hotspot/os/posix/include/jvm_md.h @@ -94,6 +94,10 @@ #define SHUTDOWN2_SIGNAL SIGINT #define SHUTDOWN3_SIGNAL SIGTERM +#ifdef LINUX +#define RESTORE_SIGNAL (SIGRTMIN + 2) +#endif + /* With 1.4.1 libjsig added versioning: used in os_solaris.cpp and jsig.c */ #define JSIG_VERSION_1_4_1 0x30140100 diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 32cb352c3b7..87b1437507c 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -748,6 +748,10 @@ template(toFileURL_signature, "(Ljava/lang/String;)Ljava/net/URL;") \ template(url_void_signature, "(Ljava/net/URL;)V") \ \ + template(jdk_crac_Core, "jdk/crac/Core") \ + template(checkpointRestoreInternal_name, "checkpointRestoreInternal") \ + template(checkpointRestoreInternal_signature, "(J)Ljava/lang/String;") \ + \ /*end*/ // Here are all the intrinsics known to the runtime and the CI. diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index f08e92112e1..4dbfdb95153 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -1208,6 +1208,12 @@ void G1CollectedHeap::resize_heap_if_necessary() { // we'll try to make the capacity smaller than it, not greater). maximum_desired_capacity = MAX2(maximum_desired_capacity, min_heap_size); + if (Universe::heap()->do_cleanup_unused()) { + // HeapRegionManager::shrink_by do not allow remove all regions. + // Make sure at least one is there. + maximum_desired_capacity = HeapRegion::GrainBytes; + } + if (capacity_after_gc < minimum_desired_capacity) { // Don't expand unless it's significant size_t expand_bytes = minimum_desired_capacity - capacity_after_gc; diff --git a/src/hotspot/share/gc/parallel/psMarkSweep.cpp b/src/hotspot/share/gc/parallel/psMarkSweep.cpp index e97f3b00bd4..e25d3fb30ee 100644 --- a/src/hotspot/share/gc/parallel/psMarkSweep.cpp +++ b/src/hotspot/share/gc/parallel/psMarkSweep.cpp @@ -111,6 +111,13 @@ void PSMarkSweep::invoke(bool maximum_heap_compaction) { PSMarkSweep::invoke_no_policy(clear_all_soft_refs || maximum_heap_compaction); } +static void zero_cap(MutableSpace* ms) { + os::cleanup_memory((char*)ms->top(), (char*)ms->end() - (char*)ms->top()); +} +static void zero_all(MutableSpace* ms) { + os::cleanup_memory((char*)ms->bottom(), (char*)ms->end() - (char*)ms->bottom()); +} + // This method contains no policy. You should probably // be calling invoke() instead. bool PSMarkSweep::invoke_no_policy(bool clear_all_softrefs) { @@ -330,6 +337,13 @@ bool PSMarkSweep::invoke_no_policy(bool clear_all_softrefs) { log_debug(gc, ergo)("AdaptiveSizeStop: collection: %d ", heap->total_collections()); } + if (heap->do_cleanup_unused()) { + zero_cap(young_gen->eden_space()); + zero_cap(young_gen->from_space()); + zero_all(young_gen->to_space()); + zero_cap(old_gen->object_space()); + } + if (UsePerfData) { heap->gc_policy_counters()->update_counters(); heap->gc_policy_counters()->update_old_capacity( diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp index 33b4ea9c5e4..a4bf5a7f897 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp @@ -1714,6 +1714,13 @@ void PSParallelCompact::invoke(bool maximum_heap_compaction) { maximum_heap_compaction); } +static void zero_cap(MutableSpace* ms) { + os::cleanup_memory((char*)ms->top(), (char*)ms->end() - (char*)ms->top()); +} +static void zero_all(MutableSpace* ms) { + os::cleanup_memory((char*)ms->bottom(), (char*)ms->end() - (char*)ms->bottom()); +} + // This method contains no policy. You should probably // be calling invoke() instead. bool PSParallelCompact::invoke_no_policy(bool maximum_heap_compaction) { @@ -1884,6 +1891,13 @@ bool PSParallelCompact::invoke_no_policy(bool maximum_heap_compaction) { log_debug(gc, ergo)("AdaptiveSizeStop: collection: %d ", heap->total_collections()); } + if (heap->do_cleanup_unused()) { + zero_cap(young_gen->eden_space()); + zero_cap(young_gen->from_space()); + zero_all(young_gen->to_space()); + zero_cap(old_gen->object_space()); + } + if (UsePerfData) { PSGCAdaptivePolicyCounters* const counters = heap->gc_policy_counters(); counters->update_counters(); diff --git a/src/hotspot/share/gc/serial/defNewGeneration.cpp b/src/hotspot/share/gc/serial/defNewGeneration.cpp index 93cda5f6161..69c27636a8e 100644 --- a/src/hotspot/share/gc/serial/defNewGeneration.cpp +++ b/src/hotspot/share/gc/serial/defNewGeneration.cpp @@ -366,6 +366,12 @@ size_t DefNewGeneration::adjust_for_thread_increase(size_t new_size_candidate, } void DefNewGeneration::compute_new_size() { + if (Universe::heap()->do_cleanup_unused()) { + os::cleanup_memory((char*)eden()->top(), (char*)eden()->end() - (char*)eden()->top()); + os::cleanup_memory((char*)from()->top(), (char*)from()->end() - (char*)from()->top()); + os::cleanup_memory((char*)to()->top(), (char*)to()->end() - (char*)to()->top()); + } + // This is called after a GC that includes the old generation, so from-space // will normally be empty. // Note that we check both spaces, since if scavenge failed they revert roles. diff --git a/src/hotspot/share/gc/shared/cardGeneration.cpp b/src/hotspot/share/gc/shared/cardGeneration.cpp index fac2fe92279..2a31b635bc6 100644 --- a/src/hotspot/share/gc/shared/cardGeneration.cpp +++ b/src/hotspot/share/gc/shared/cardGeneration.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -296,6 +297,9 @@ void CardGeneration::compute_new_size() { expansion_for_promotion / (double) K, shrink_bytes / (double) K); } + if (Universe::heap()->do_cleanup_unused()) { + shrink_bytes = capacity_after_gc - used_after_gc; + } // Don't shrink unless it's significant if (shrink_bytes >= _min_heap_delta_bytes) { shrink(shrink_bytes); diff --git a/src/hotspot/share/gc/shared/collectedHeap.cpp b/src/hotspot/share/gc/shared/collectedHeap.cpp index f2681e186cc..d246242fc5b 100644 --- a/src/hotspot/share/gc/shared/collectedHeap.cpp +++ b/src/hotspot/share/gc/shared/collectedHeap.cpp @@ -202,6 +202,7 @@ bool CollectedHeap::is_oop(oop object) const { CollectedHeap::CollectedHeap() : _is_gc_active(false), + _cleanup_unused(false), _total_collections(0), _total_full_collections(0), _gc_cause(GCCause::_no_gc), diff --git a/src/hotspot/share/gc/shared/collectedHeap.hpp b/src/hotspot/share/gc/shared/collectedHeap.hpp index dd77aefa0cb..8be8d36b37f 100644 --- a/src/hotspot/share/gc/shared/collectedHeap.hpp +++ b/src/hotspot/share/gc/shared/collectedHeap.hpp @@ -123,6 +123,8 @@ class CollectedHeap : public CHeapObj { // Used for filler objects (static, but initialized in ctor). static size_t _filler_array_max_size; + bool _cleanup_unused; + unsigned int _total_collections; // ... started unsigned int _total_full_collections; // ... started NOT_PRODUCT(volatile size_t _promotion_failure_alot_count;) @@ -432,6 +434,9 @@ class CollectedHeap : public CHeapObj { // collector -- dld). bool is_gc_active() const { return _is_gc_active; } + void set_cleanup_unused(bool value) { _cleanup_unused = value; } + bool do_cleanup_unused() const { return _cleanup_unused; } + // Total number of GC collections (started) unsigned int total_collections() const { return _total_collections; } unsigned int total_full_collections() const { return _total_full_collections;} diff --git a/src/hotspot/share/include/jmm.h b/src/hotspot/share/include/jmm.h index cc1ef9a9333..0993bc37f9e 100644 --- a/src/hotspot/share/include/jmm.h +++ b/src/hotspot/share/include/jmm.h @@ -79,6 +79,8 @@ typedef enum { JMM_GC_TIME_MS = 9, /* Total accumulated time spent in collection */ JMM_GC_COUNT = 10, /* Total number of collections */ JMM_JVM_UPTIME_MS = 11, /* The JVM uptime in milliseconds */ + JMM_JVM_RESTORE_START_TIME_MS = 12, /* Time when the JVM started restore operation */ + JMM_JVM_UPTIME_SINCE_RESTORE_MS = 13, /* The JVM uptime since restore */ JMM_INTERNAL_ATTRIBUTE_INDEX = 100, JMM_CLASS_LOADED_BYTES = 101, /* Number of bytes loaded instance classes */ diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h index 1f543ffbaef..920802a2ae1 100644 --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -1231,6 +1231,8 @@ JVM_GetEnclosingMethodInfo(JNIEnv* env, jclass ofClass); JNIEXPORT void JNICALL JVM_NotifyDump(JNIEnv *env, jclass ignored); +JNIEXPORT jboolean JNICALL +JVM_CheckpointEnabled(JNIEnv *env, jclass ignored); /* ========================================================================= * The following defines a private JVM interface that the JDK can query * for the JVM version and capabilities. sun.misc.Version defines @@ -1344,6 +1346,33 @@ typedef struct JDK1_1InitArgs { JNIEXPORT void JNICALL JVM_SetWispTask(JNIEnv* env, jclass clz, jlong coroutinePtr, jint task_id, jobject task, jobject engine); +enum { + JVM_CHECKPOINT_OK, + JVM_CHECKPOINT_ERROR, + JVM_CHECKPOINT_NONE, +}; + +enum cr_fail_type { + JVM_CR_FAIL = 0, + JVM_CR_FAIL_FILE = 1, + JVM_CR_FAIL_SOCK = 2, + JVM_CR_FAIL_PIPE = 3, +}; + +JNIEXPORT jobjectArray JNICALL +JVM_Checkpoint(JNIEnv *env, jboolean dry_run, jlong jcmd_stream); + +JNIEXPORT void JNICALL +JVM_RegisterPersistent(JNIEnv *env, int fd, int st_dev, int st_ino); + +JNIEXPORT void JNICALL +JVM_DeregisterPersistent(JNIEnv *env, int fd, int st_dev, int st_ino); + +JNIEXPORT void JNICALL +JVM_RegisterPseudoPersistent(JNIEnv *env, jstring absolute_file_path, int mode); + +JNIEXPORT void JNICALL +JVM_UnregisterPseudoPersistent(JNIEnv *env, jstring absolute_file_path); JNIEXPORT jint JNICALL JVM_GetProxyUnpark(JNIEnv* env, jclass clz, jintArray res); diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index bc0b7e43e1a..55f43a3fe6b 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -3935,4 +3935,40 @@ JVM_END JVM_ENTRY(void, JVM_NotifyDump(JNIEnv *env, jclass ignored)) JVMWrapper("JVM_NotifyDump"); QuickStart::notify_dump(); +JVM_END + +JVM_ENTRY(jboolean, JVM_CheckpointEnabled(JNIEnv *env, jclass ignored)) + JVMWrapper("JVM_CheckpointEnabled"); + return CRaCCheckpointTo ? JNI_TRUE : JNI_FALSE; +JVM_END + +JVM_ENTRY(jobjectArray, JVM_Checkpoint(JNIEnv *env, jboolean dry_run, jlong jcmd_stream)) + Handle ret = os::Linux::checkpoint(dry_run, jcmd_stream, CHECK_NULL); + return (jobjectArray) JNIHandles::make_local(THREAD, ret()); +JVM_END + +JVM_ENTRY(void, JVM_RegisterPersistent(JNIEnv *env, int fd, int st_dev, int st_ino)) + os::Linux::register_persistent_fd(fd, st_dev, st_ino); +JVM_END + +JVM_ENTRY(void, JVM_DeregisterPersistent(JNIEnv *env, int fd, int st_dev, int st_ino)) + os::Linux::deregister_persistent_fd(fd, st_dev, st_ino); +JVM_END + +JVM_ENTRY(void, JVM_RegisterPseudoPersistent(JNIEnv *env, jstring absolute_file_path, int mode)) + JVMWrapper("JVM_RegisterPseudoPersistent"); + ResourceMark rm; + const char* path = java_lang_String::as_utf8_string(JNIHandles::resolve_non_null(absolute_file_path)); + if (path != NULL) { + os::Linux::register_pseudo_persistent(path, mode); + } +JVM_END + +JVM_ENTRY(void, JVM_UnregisterPseudoPersistent(JNIEnv *env, jstring absolute_file_path)) + JVMWrapper("JVM_UnregisterPseudoPersistent"); + ResourceMark rm; + const char* path = java_lang_String::as_utf8_string(JNIHandles::resolve_non_null(absolute_file_path)); + if (path != NULL) { + os::Linux::unregister_pseudo_persistent(path); + } JVM_END \ No newline at end of file diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 028d96520cb..a655c55b130 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -1409,14 +1409,15 @@ const char* Arguments::get_property(const char* key) { return PropertyList_get_value(system_properties(), key); } -bool Arguments::add_property(const char* prop, PropertyWriteable writeable, PropertyInternal internal) { +void Arguments::get_key_value(const char* prop, const char** key, const char** value) { + assert(key != NULL, "key should not be NULL"); + assert(value != NULL, "value should not be NULL"); const char* eq = strchr(prop, '='); - const char* key; - const char* value = ""; if (eq == NULL) { // property doesn't have a value, thus use passed string - key = prop; + *key = prop; + *value = ""; } else { // property have a value, thus extract it and save to the // allocated string @@ -1424,10 +1425,17 @@ bool Arguments::add_property(const char* prop, PropertyWriteable writeable, Prop char* tmp_key = AllocateHeap(key_len + 1, mtArguments); jio_snprintf(tmp_key, key_len + 1, "%s", prop); - key = tmp_key; + *key = tmp_key; - value = &prop[key_len + 1]; + *value = &prop[key_len + 1]; } +} + +bool Arguments::add_property(const char* prop, PropertyWriteable writeable, PropertyInternal internal) { + const char* key = NULL; + const char* value = NULL; + + get_key_value(prop, &key, &value); if (strcmp(key, "java.compiler") == 0) { process_java_compiler_argument(value); @@ -2420,6 +2428,56 @@ jint Arguments::parse_xss(const JavaVMOption* option, const char* tail, intx* ou return JNI_OK; } +bool Arguments::is_restore_option_set(const JavaVMInitArgs* args) { + const char* tail; + // iterate over arguments + for (int index = 0; index < args->nOptions; index++) { + const JavaVMOption* option = args->options + index; + if (match_option(option, "-XX:CRaCRestoreFrom", &tail)) { + return true; + } + } + return false; +} + +bool Arguments::parse_options_for_restore(const JavaVMInitArgs* args) { + const char *tail = NULL; + + // iterate over arguments + for (int index = 0; index < args->nOptions; index++) { + bool is_absolute_path = false; // for -agentpath vs -agentlib + + const JavaVMOption* option = args->options + index; + + if (!match_option(option, "-Djava.class.path", &tail) && + !match_option(option, "-Dsun.java.launcher", &tail)) { + if (match_option(option, "-D", &tail)) { + const char* key = NULL; + const char* value = NULL; + + get_key_value(tail, &key, &value); + + if (strcmp(key, "sun.java.command") == 0) { + char *old_java_command = _java_command; + _java_command = os::strdup_check_oom(value, mtArguments); + if (old_java_command != NULL) { + os::free(old_java_command); + } + } else { + add_property(tail); + } + } else if (match_option(option, "-XX:", &tail)) { // -XX:xxxx + // Skip -XX:Flags= and -XX:VMOptionsFile= since those cases have + // already been handled + if (!process_argument(tail, args->ignoreUnrecognized, JVMFlag::COMMAND_LINE)) { + return false; + } + } + } + } + return true; +} + jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, bool* patch_mod_javabase, JVMFlag::Flags origin) { // For match_option to return remaining or value part of option string const char* tail; @@ -3266,6 +3324,10 @@ jint Arguments::finalize_vm_init_args(bool patch_mod_javabase) { UNSUPPORTED_OPTION(ShowRegistersOnAssert); #endif // CAN_SHOW_REGISTERS_ON_ASSERT + if (CRaCCheckpointTo && !os::Linux::prepare_checkpoint()) { + return JNI_ERR; + } + return JNI_OK; } diff --git a/src/hotspot/share/runtime/arguments.hpp b/src/hotspot/share/runtime/arguments.hpp index a22fe1878d1..611b1501bae 100644 --- a/src/hotspot/share/runtime/arguments.hpp +++ b/src/hotspot/share/runtime/arguments.hpp @@ -398,6 +398,10 @@ class Arguments : AllStatic { static exit_hook_t _exit_hook; static vfprintf_hook_t _vfprintf_hook; + // prop points to a string of the form key=value + // Parse the string to extract key and the value + static void get_key_value(const char* prop, const char** key, const char** value); + // System properties static bool add_property(const char* prop, PropertyWriteable writeable=WriteableProperty, PropertyInternal internal=ExternalProperty); @@ -664,6 +668,10 @@ class Arguments : AllStatic { static bool check_unsupported_cds_runtime_properties() NOT_CDS_RETURN0; static bool atojulong(const char *s, julong* result); + + static bool is_restore_option_set(const JavaVMInitArgs* args); + + static bool parse_options_for_restore(const JavaVMInitArgs* args); }; // Disable options not supported in this release, with a warning if they diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index 42ba54439c7..db06b9a263a 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -2701,6 +2701,40 @@ define_pd_global(uint64_t,MaxRAM, 1ULL*G); \ product(bool, UseCompactObjectHeaders, false, \ "Use compact 64-bit object headers in 64-bit VM.") \ + \ + product(ccstr, CRaCCheckpointTo, NULL, "Path to checkpoint image") \ + \ + product(ccstr, CRaCRestoreFrom, NULL, "Path to image for restore, " \ + "replaces the initializing VM on success") \ + \ + product(ccstr, CREngine, "criuengine", "Path or name of a program " \ + "implementing checkpoint/restore") \ + \ + product(bool, CRaCIgnoreRestoreIfUnavailable, true, "Ignore " \ + "-XX:CRaCRestoreFrom and continue initialization if restore is " \ + "unavailable") \ + \ + product(ccstr, CRaCIgnoredFileDescriptors, NULL, "Comma-separated list " \ + "of file descriptor numbers or paths. All file descriptors greater " \ + "than 2 (stdin, stdout and stderr are excluded automatically) not " \ + "in this list are closed when the VM is started.") \ + \ + product(bool, CRAllowToSkipCheckpoint, false, \ + "Allow implementation to not call Checkpoint if helper not found")\ + \ + diagnostic(bool, CRHeapDumpOnCheckpointException, false, "Dump heap on " \ + "CheckpointException thrown because of C/RaC precondition failed") \ + \ + diagnostic(bool, CRPrintResourcesOnCheckpoint, false, "Print resources " \ + "to decide CheckpointException") \ + \ + diagnostic(bool, CRTraceStartupTime, false, "Trace startup time") \ + \ + experimental(bool, CRDoThrowCheckpointException, true, "Throw " \ + "CheckpointException if uncheckpointable resource handle found") \ + \ + product(bool, CRTrace, true, "Minimal C/R tracing") \ + \ #define VM_FLAGS(develop, \ develop_pd, \ diff --git a/src/hotspot/share/runtime/globals_ext.hpp b/src/hotspot/share/runtime/globals_ext.hpp index 1a54881c4f5..6adf0a8cd76 100644 --- a/src/hotspot/share/runtime/globals_ext.hpp +++ b/src/hotspot/share/runtime/globals_ext.hpp @@ -96,6 +96,21 @@ product(bool, UseBigDecimalOpt, true, \ "use binary search in zero stripping of BigDecimal") \ \ + product(bool, CRaCValidateBeforeRestore, true, \ + "Validate OS,JVM,CPU before restore") \ + \ + product(bool, CRaCUnprivileged, false, \ + "Run criuengine as unprivileged") \ + product(ccstr, CRaCRestoreInheritPipeFds, NULL, \ + "Specify the pipe fds that inherit from parent process that need" \ + "to restore.If there are multiple fds, separate them with comma.") \ + \ + product(ccstr, CRaCAppendOnlyLogFiles, NULL, \ + "Files that no need to be closed when do checkpointing." \ + "These files must open with write and append mode first." \ + "*: all matched files." \ + "*.log,*.txt: match given extension files." \ + "/x/a.log,/y/b.log: match by full file path") \ //add new AJDK specific flags here diff --git a/src/hotspot/share/runtime/os.cpp b/src/hotspot/share/runtime/os.cpp index 46dc373fc70..b6fb35d5f6f 100644 --- a/src/hotspot/share/runtime/os.cpp +++ b/src/hotspot/share/runtime/os.cpp @@ -1877,6 +1877,13 @@ bool os::release_memory(char* addr, size_t bytes) { return res; } +void os::cleanup_memory(char* addr, size_t bytes) { + char* start = (char*)align_up(addr, os::vm_page_size()); + char* end = (char*)align_down(addr + bytes, os::vm_page_size()); + os::uncommit_memory(start, end - start); + os::commit_memory(start, end - start, false); +} + void os::pretouch_memory(void* start, void* end, size_t page_size) { for (volatile char *p = (char*)start; p < (char*)end; p += page_size) { *p = 0; diff --git a/src/hotspot/share/runtime/os.hpp b/src/hotspot/share/runtime/os.hpp index 7d55f9e2aba..7554653e256 100644 --- a/src/hotspot/share/runtime/os.hpp +++ b/src/hotspot/share/runtime/os.hpp @@ -371,6 +371,8 @@ class os: AllStatic { NOT_MACOS(static bool uncommit_memory(char* addr, size_t bytes);) static bool release_memory(char* addr, size_t bytes); + static void cleanup_memory(char* addr, size_t bytes); + // Touch memory pages that cover the memory range from start to end (exclusive) // to make the OS back the memory range with actual memory. // Current implementation may not touch the last page if unaligned addresses @@ -1073,7 +1075,6 @@ class os: AllStatic { char fileSep, char pathSep); static bool set_boot_path(char fileSep, char pathSep); - }; #ifndef _WINDOWS diff --git a/src/hotspot/share/runtime/thread.cpp b/src/hotspot/share/runtime/thread.cpp index 49c36d91942..3c141fc914d 100644 --- a/src/hotspot/share/runtime/thread.cpp +++ b/src/hotspot/share/runtime/thread.cpp @@ -3854,9 +3854,24 @@ void Threads::initialize_jsr292_core_classes(TRAPS) { initialize_class(vmSymbols::java_lang_invoke_MethodHandleNatives(), CHECK); } +jint Threads::check_for_restore(JavaVMInitArgs* args) { + if (Arguments::is_restore_option_set(args)) { + Arguments::parse_options_for_restore(args); + os::Linux::restore(); + if (!CRaCIgnoreRestoreIfUnavailable) { + // FIXME switch to unified hotspot logging + warning("cannot restore"); + return JNI_ERR; + } + } + return JNI_OK; +} + jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { extern void JDK_Version_init(); + if (check_for_restore(args) != JNI_OK) return JNI_ERR; + // Preinitialize version info. VM_Version::early_initialize(); @@ -3920,6 +3935,8 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { os::pause(); } + os::Linux::vm_create_start(); + HOTSPOT_VM_INIT_BEGIN(); // Timing (must come after argument parsing) diff --git a/src/hotspot/share/runtime/thread.hpp b/src/hotspot/share/runtime/thread.hpp index 1e0d1245d89..f73b44b001e 100644 --- a/src/hotspot/share/runtime/thread.hpp +++ b/src/hotspot/share/runtime/thread.hpp @@ -2282,6 +2282,8 @@ class Threads: AllStatic { static void threads_do(ThreadClosure* tc); static void possibly_parallel_threads_do(bool is_par, ThreadClosure* tc); + static jint check_for_restore(JavaVMInitArgs* args); + // Initializes the vm and creates the vm thread static jint create_vm(JavaVMInitArgs* args, bool* canTryAgain); static void convert_vm_init_libraries_to_agents(); diff --git a/src/hotspot/share/runtime/vmOperations.hpp b/src/hotspot/share/runtime/vmOperations.hpp index 012bea21b96..a0f71de2841 100644 --- a/src/hotspot/share/runtime/vmOperations.hpp +++ b/src/hotspot/share/runtime/vmOperations.hpp @@ -135,6 +135,7 @@ template(PrintMetadata) \ template(GTestExecuteAtSafepoint) \ template(JFROldObject) \ + template(VM_Crac) \ class VM_Operation: public CHeapObj { public: diff --git a/src/hotspot/share/services/diagnosticCommand.cpp b/src/hotspot/share/services/diagnosticCommand.cpp index ebcace1a214..9c5ed008c74 100644 --- a/src/hotspot/share/services/diagnosticCommand.cpp +++ b/src/hotspot/share/services/diagnosticCommand.cpp @@ -48,6 +48,8 @@ #include "services/heapDumper.hpp" #include "services/management.hpp" #include "services/writeableFlags.hpp" +#include "attachListener_linux.hpp" +#include "linuxAttachOperation.hpp" #include "utilities/debug.hpp" #include "utilities/formatBuffer.hpp" #include "utilities/macros.hpp" @@ -127,6 +129,8 @@ void DCmdRegistrant::register_dcmds(){ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true,false)); + // Enhanced JMX Agent Support // These commands won't be exported via the DiagnosticCommandMBean until an // appropriate permission is created for them @@ -948,7 +952,6 @@ JMXStatusDCmd::JMXStatusDCmd(outputStream *output, bool heap_allocated) : void JMXStatusDCmd::execute(DCmdSource source, TRAPS) { ResourceMark rm(THREAD); HandleMark hm(THREAD); - // Load and initialize the jdk.internal.agent.Agent class // invoke getManagementAgentStatus() method to generate the status info // throw java.lang.NoSuchMethodError if method doesn't exist @@ -960,8 +963,8 @@ void JMXStatusDCmd::execute(DCmdSource source, TRAPS) { JavaValue result(T_OBJECT); JavaCalls::call_static(&result, k, vmSymbols::getAgentStatus_name(), vmSymbols::void_string_signature(), CHECK); - jvalue* jv = (jvalue*) result.get_value_addr(); - oop str = (oop) jv->l; + jvalue* jv = (jvalue*)result.get_value_addr(); + oop str = cast_to_oop(jv->l); if (str != NULL) { char* out = java_lang_String::as_utf8_string(str); if (out) { @@ -1187,3 +1190,25 @@ void QuickStartDumpDCMD::execute(DCmdSource source, TRAPS) { long ms = TimeHelper::counter_to_millis(duration.value()); output()->print_cr("It took %lu ms to execute Quickstart.dump .", ms); } + +void CheckpointDCmd::execute(DCmdSource source, TRAPS) { + Klass* k = SystemDictionary::resolve_or_fail(vmSymbols::jdk_crac_Core(), + true, CHECK); + JavaValue result(T_OBJECT); + JavaCallArguments args; + args.push_long((jlong)output()); + LinuxAttachOperation* current_op; + JavaCalls::call_static(&result, k, + vmSymbols::checkpointRestoreInternal_name(), + vmSymbols::checkpointRestoreInternal_signature(), &args, CHECK); + oop str = (oop)result.get_jobject(); + if (str != NULL) { + char* out = java_lang_String::as_utf8_string(str); + if (out[0] != '\0') { + current_op = LinuxAttachListener::get_current_op(); + outputStream* stream = current_op->is_effectively_completed() ? tty : output(); + stream->print_cr("An exception during a checkpoint operation:"); + stream->print("%s", out); + } + } +} \ No newline at end of file diff --git a/src/hotspot/share/services/diagnosticCommand.hpp b/src/hotspot/share/services/diagnosticCommand.hpp index a0ecc89ef7d..c620d1a76bd 100644 --- a/src/hotspot/share/services/diagnosticCommand.hpp +++ b/src/hotspot/share/services/diagnosticCommand.hpp @@ -930,4 +930,18 @@ class QuickStartDumpDCMD : public DCmd { }; -#endif // SHARE_VM_SERVICES_DIAGNOSTICCOMMAND_HPP +class CheckpointDCmd : public DCmd { +public: + CheckpointDCmd(outputStream* output, bool heap) : DCmd(output, heap) { } + static const char* name() { return "JDK.checkpoint"; } + static const char* description() { + return "Checkpoint via jdk.crac.checkpointRestore()."; + } + static const char* impact() { + return "High: JVM terminates"; + } + static int num_arguments() { return 0; } + virtual void execute(DCmdSource source, TRAPS); +}; + +#endif // SHARE_SERVICES_DIAGNOSTICCOMMAND_HPP diff --git a/src/hotspot/share/services/management.cpp b/src/hotspot/share/services/management.cpp index f70dc1825e4..a6919327879 100644 --- a/src/hotspot/share/services/management.cpp +++ b/src/hotspot/share/services/management.cpp @@ -942,6 +942,18 @@ static jlong get_long_attribute(jmmLongAttribute att) { case JMM_OS_MEM_TOTAL_PHYSICAL_BYTES: return os::physical_memory(); + case JMM_JVM_RESTORE_START_TIME_MS: + return os::Linux::restore_start_time(); + + case JMM_JVM_UPTIME_SINCE_RESTORE_MS: + { + jlong ticks = os::Linux::uptime_since_restore(); + if (ticks == -1) { + return -1; + } + return Management::ticks_to_ms(ticks); + } + default: return -1; } diff --git a/src/java.base/linux/classes/sun/nio/ch/EPollSelectorImpl.java b/src/java.base/linux/classes/sun/nio/ch/EPollSelectorImpl.java index ba144abec7c..b5fd1312697 100644 --- a/src/java.base/linux/classes/sun/nio/ch/EPollSelectorImpl.java +++ b/src/java.base/linux/classes/sun/nio/ch/EPollSelectorImpl.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -28,9 +29,14 @@ import com.alibaba.wisp.engine.WispEngine; import jdk.internal.misc.SharedSecrets; import jdk.internal.misc.WispEngineAccess; +import jdk.crac.Context; +import jdk.crac.Resource; +import jdk.internal.crac.JDKResource; +import jdk.internal.crac.JDKResource.Priority; import java.io.IOException; import java.nio.channels.ClosedSelectorException; +import java.nio.channels.IllegalSelectorException; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.spi.SelectorProvider; @@ -52,22 +58,46 @@ * Linux epoll based Selector implementation */ -class EPollSelectorImpl extends SelectorImpl { +class EPollSelectorImpl extends SelectorImpl implements JDKResource { private static final WispEngineAccess WEA = SharedSecrets.getWispEngineAccess(); // maximum number of events to poll in one call to epoll_wait private static final int NUM_EPOLLEVENTS = Math.min(IOUtil.fdLimit(), 1024); + private enum CheckpointRestoreState { + NORMAL_OPERATION, + CHECKPOINT_TRANSITION, + CHECKPOINTED, + CHECKPOINT_ERROR, + RESTORE_TRANSITION, + } + + private static class MoveToCheckpointThread extends Thread { + private Selector selector; + + MoveToCheckpointThread(Selector selector) { + this.selector = selector; + } + + @Override + public void run() { + try { + selector.select(1); + } catch (IOException e) { + } + } + } + // epoll file descriptor - private final int epfd; + private int epfd; // address of poll array when polling with epoll_wait - private final long pollArrayAddress; + private long pollArrayAddress; // file descriptors used for interrupt - private final int fd0; - private final int fd1; + private int fd0; + private int fd1; // maps file descriptor to selection key, synchronize on selector private final Map fdToKey = new HashMap<>(); @@ -80,16 +110,15 @@ class EPollSelectorImpl extends SelectorImpl { private final Object interruptLock = new Object(); private boolean interruptTriggered; - EPollSelectorImpl(SelectorProvider sp) throws IOException { - super(sp); + private volatile CheckpointRestoreState checkpointState = CheckpointRestoreState.NORMAL_OPERATION;; - this.epfd = EPoll.create(); - this.pollArrayAddress = EPoll.allocatePollArray(NUM_EPOLLEVENTS); + private void initFDs() throws IOException { + epfd = EPoll.create(); try { long fds = IOUtil.makePipe(false); - this.fd0 = (int) (fds >>> 32); - this.fd1 = (int) fds; + fd0 = (int) (fds >>> 32); + fd1 = (int) fds; } catch (IOException ioe) { EPoll.freePollArray(pollArrayAddress); FileDispatcherImpl.closeIntFD(epfd); @@ -100,11 +129,70 @@ class EPollSelectorImpl extends SelectorImpl { EPoll.ctl(epfd, EPOLL_CTL_ADD, fd0, EPOLLIN); } + EPollSelectorImpl(SelectorProvider sp) throws IOException { + super(sp); + pollArrayAddress = EPoll.allocatePollArray(NUM_EPOLLEVENTS); + initFDs(); + // trigger FileDispatcherImpl initialization + new FileDispatcherImpl(); + if (jdk.crac.Configuration.checkpointEnabled()) { + jdk.internal.crac.Core.getJDKContext().register(this); + } + } + private void ensureOpen() { if (!isOpen()) throw new ClosedSelectorException(); } + private boolean processCheckpointRestore() throws IOException { + assert Thread.holdsLock(this); + + if (checkpointState != CheckpointRestoreState.CHECKPOINT_TRANSITION) { + return false; + } + + synchronized (interruptLock) { + IOUtil.drain(fd0); + + CheckpointRestoreState thisState; + if (fdToKey.size() == 0) { + FileDispatcherImpl.closeIntFD(epfd); + FileDispatcherImpl.closeIntFD(fd0); + FileDispatcherImpl.closeIntFD(fd1); + thisState = CheckpointRestoreState.CHECKPOINTED; + } else { + thisState = CheckpointRestoreState.CHECKPOINT_ERROR; + } + + checkpointState = thisState; + interruptLock.notifyAll(); + while (checkpointState == thisState) { + try { + interruptLock.wait(); + } catch (InterruptedException e) { + } + } + + assert checkpointState == CheckpointRestoreState.RESTORE_TRANSITION; + if (thisState == CheckpointRestoreState.CHECKPOINTED) { + initFDs(); + } + checkpointState = CheckpointRestoreState.NORMAL_OPERATION; + interruptLock.notifyAll(); + + if (interruptTriggered) { + try { + IOUtil.write1(fd1, (byte)0); + } catch (IOException ioe) { + throw new InternalError(ioe); + } + } + } + + return true; + } + @Override protected int doSelect(Consumer action, long timeout) throws IOException @@ -124,9 +212,11 @@ protected int doSelect(Consumer action, long timeout) do { long startTime = timedPoll ? System.nanoTime() : 0; - numEntries = WispEngine.transparentWispSwitch() ? + do { + numEntries = WispEngine.transparentWispSwitch() ? handleEPollWithWisp(to) : EPoll.wait(epfd, pollArrayAddress, NUM_EPOLLEVENTS, to); + } while (processCheckpointRestore()); if (numEntries == IOStatus.INTERRUPTED && timedPoll) { // timed poll interrupted so need to adjust timeout long adjust = System.nanoTime() - startTime; @@ -218,7 +308,8 @@ private int processEvents(int numEntries, Consumer action) } } - if (interrupted || WispEngine.transparentWispSwitch() && WEA.useDirectSelectorWakeup() && status.get() == INTERRUPTED) { + if ((interrupted && !(Thread.currentThread() instanceof MoveToCheckpointThread)) + || (WispEngine.transparentWispSwitch() && WEA.useDirectSelectorWakeup() && status.get() == INTERRUPTED)) { clearInterrupt(); } @@ -298,4 +389,60 @@ private void clearInterrupt() throws IOException { interruptTriggered = false; } } + + @Override + public void beforeCheckpoint(Context context) throws Exception { + if (!isOpen()) { + return; + } + + synchronized (interruptLock) { + checkpointState = CheckpointRestoreState.CHECKPOINT_TRANSITION; + IOUtil.write1(fd1, (byte)0); + int tries = 5; + while (checkpointState == CheckpointRestoreState.CHECKPOINT_TRANSITION && 0 < tries--) { + try { + interruptLock.wait(5); + } catch (InterruptedException e) { + } + } + if (checkpointState == CheckpointRestoreState.CHECKPOINT_TRANSITION) { + Thread thr = new MoveToCheckpointThread(this); + thr.setDaemon(true); + thr.start(); + } + while (checkpointState == CheckpointRestoreState.CHECKPOINT_TRANSITION) { + try { + interruptLock.wait(); + } catch (InterruptedException e) { + } + } + if (checkpointState == CheckpointRestoreState.CHECKPOINT_ERROR) { + throw new IllegalSelectorException(); + } + } + } + + @Override + public void afterRestore(Context context) throws Exception { + if (!isOpen()) { + return; + } + + synchronized (interruptLock) { + checkpointState = CheckpointRestoreState.RESTORE_TRANSITION; + interruptLock.notifyAll(); + while (checkpointState == CheckpointRestoreState.RESTORE_TRANSITION) { + try { + interruptLock.wait(); + } catch (InterruptedException e) { + } + } + } + } + + @Override + public Priority getPriority() { + return Priority.EPOLLSELECTOR; + } } diff --git a/src/java.base/share/classes/java/lang/ref/Reference.java b/src/java.base/share/classes/java/lang/ref/Reference.java index e2044d55e17..f2a228f6f81 100644 --- a/src/java.base/share/classes/java/lang/ref/Reference.java +++ b/src/java.base/share/classes/java/lang/ref/Reference.java @@ -25,6 +25,9 @@ package java.lang.ref; +import jdk.crac.Context; +import jdk.crac.Resource; +import jdk.internal.crac.JDKResource; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.HotSpotIntrinsicCandidate; import jdk.internal.misc.JavaLangRefAccess; @@ -233,6 +236,8 @@ public void run() { private static final Object processPendingLock = new Object(); private static boolean processPendingActive = false; + private static JDKResource referenceHandlerResource; + private static void processPendingReferences() { // Only the singleton reference processing thread calls // waitForReferencePendingList() and getAndClearReferencePendingList(). @@ -316,6 +321,27 @@ public void runFinalization() { Finalizer.runFinalization(); } }); + + if (jdk.crac.Configuration.checkpointEnabled()) { + referenceHandlerResource = new JDKResource() { + @Override + public Priority getPriority() { + return Priority.REFERENCE_HANDLER; + } + + @Override + public void beforeCheckpoint(Context context) throws Exception { + System.gc(); + // TODO ensure GC done processing all References + while (waitForReferenceProcessing()); + } + + @Override + public void afterRestore(Context context) throws Exception { + } + }; + jdk.internal.crac.Core.getJDKContext().register(referenceHandlerResource); + } } /* -- Referent accessor and setters -- */ diff --git a/src/java.base/share/classes/java/lang/ref/ReferenceQueue.java b/src/java.base/share/classes/java/lang/ref/ReferenceQueue.java index e1114054755..e9144c54375 100644 --- a/src/java.base/share/classes/java/lang/ref/ReferenceQueue.java +++ b/src/java.base/share/classes/java/lang/ref/ReferenceQueue.java @@ -56,6 +56,7 @@ private static class Lock { }; private final Lock lock = new Lock(); private volatile Reference head; private long queueLength = 0; + private int nWaiters = 0; boolean enqueue(Reference r) { /* Called only by Reference class */ synchronized (lock) { @@ -152,7 +153,10 @@ public Reference remove(long timeout) if (r != null) return r; long start = (timeout == 0) ? 0 : System.nanoTime(); for (;;) { + ++nWaiters; + lock.notifyAll(); lock.wait(timeout); + --nWaiters; r = reallyPoll(); if (r != null) return r; if (timeout != 0) { @@ -204,4 +208,17 @@ void forEach(Consumer> action) { } } } + + /** + * Blocks calling thread until the specified number of threads are blocked with no reference available. + * @param nWaiters number of threads to wait + * @throws InterruptedException If the wait is interrupted + */ + public void waitForWaiters(int nWaiters) throws InterruptedException { + synchronized (lock) { + while (head != null || this.nWaiters < nWaiters) { + lock.wait(); + } + } + } } diff --git a/src/java.base/share/classes/java/net/InetAddress.java b/src/java.base/share/classes/java/net/InetAddress.java index c739ddf89fb..6428e6374d7 100644 --- a/src/java.base/share/classes/java/net/InetAddress.java +++ b/src/java.base/share/classes/java/net/InetAddress.java @@ -47,6 +47,10 @@ import jdk.internal.misc.JavaNetInetAddressAccess; import jdk.internal.misc.SharedSecrets; +import jdk.crac.Context; +import jdk.crac.Resource; +import jdk.internal.crac.Core; +import jdk.internal.crac.JDKResource; import sun.security.action.*; import sun.net.InetAddressCachePolicy; import sun.net.util.IPAddressUtil; @@ -300,6 +304,9 @@ InetAddressHolder holder() { /** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = 3286316764910316507L; + /* Resource registration uses weak references; we need to keep it locally. */ + private static JDKResource checkpointListener; + /* * Load net library into runtime, and perform initializations. */ @@ -339,6 +346,28 @@ public InetAddress getByName(String hostName, } ); init(); + if (jdk.crac.Configuration.checkpointEnabled()) { + // DNS cache is cleared before the checkpoint; application restored at a later point + // or in a different environment should query DNS again. + checkpointListener = new JDKResource() { + @Override + public Priority getPriority() { + return Priority.NORMAL; + } + + @Override + public void beforeCheckpoint(Context context) throws Exception { + cache.clear(); + expirySet.clear(); + cachedLocalHost = null; + } + + @Override + public void afterRestore(Context context) throws Exception { + } + }; + Core.getJDKContext().register(checkpointListener); + } } /** diff --git a/src/java.base/share/classes/java/util/zip/Deflater.java b/src/java.base/share/classes/java/util/zip/Deflater.java index 2586600d349..a9aeafaea36 100644 --- a/src/java.base/share/classes/java/util/zip/Deflater.java +++ b/src/java.base/share/classes/java/util/zip/Deflater.java @@ -89,13 +89,6 @@ * to perform cleanup should be modified to use alternative cleanup mechanisms such * as {@link java.lang.ref.Cleaner} and remove the overriding {@code finalize} method. * - * @implSpec - * If this {@code Deflater} has been subclassed and the {@code end} method has been - * overridden, the {@code end} method will be called by the finalization when the - * deflater is unreachable. But the subclasses should not depend on this specific - * implementation; the finalization is not reliable and the {@code finalize} method - * is deprecated to be removed. - * * @see Inflater * @author David Connelly * @since 1.1 @@ -204,8 +197,8 @@ public class Deflater { public Deflater(int level, boolean nowrap) { this.level = level; this.strategy = DEFAULT_STRATEGY; - this.zsRef = DeflaterZStreamRef.get(this, - init(level, DEFAULT_STRATEGY, nowrap)); + this.zsRef = new DeflaterZStreamRef(this, + init(level, DEFAULT_STRATEGY, nowrap)); } /** @@ -901,21 +894,6 @@ public void end() { } } - /** - * Closes the compressor when garbage is collected. - * - * @deprecated The {@code finalize} method has been deprecated and will be - * removed. It is implemented as a no-op. Subclasses that override - * {@code finalize} in order to perform cleanup should be modified to use - * alternative cleanup mechanisms and to remove the overriding {@code finalize} - * method. The recommended cleanup for compressor is to explicitly call - * {@code end} method when it is no longer in use. If the {@code end} is - * not invoked explicitly the resource of the compressor will be released - * when the instance becomes unreachable. - */ - @Deprecated(since="9", forRemoval=true) - protected void finalize() {} - private void ensureOpen() { assert Thread.holdsLock(zsRef); if (zsRef.address() == 0) @@ -987,43 +965,5 @@ public synchronized void run() { } } - /* - * If {@code Deflater} has been subclassed and the {@code end} method is - * overridden, uses {@code finalizer} mechanism for resource cleanup. So - * {@code end} method can be called when the {@code Deflater} is unreachable. - * This mechanism will be removed when the {@code finalize} method is - * removed from {@code Deflater}. - */ - static DeflaterZStreamRef get(Deflater owner, long addr) { - Class clz = owner.getClass(); - while (clz != Deflater.class) { - try { - clz.getDeclaredMethod("end"); - return new FinalizableZStreamRef(owner, addr); - } catch (NoSuchMethodException nsme) {} - clz = clz.getSuperclass(); - } - return new DeflaterZStreamRef(owner, addr); - } - - private static class FinalizableZStreamRef extends DeflaterZStreamRef { - final Deflater owner; - - FinalizableZStreamRef (Deflater owner, long addr) { - super(null, addr); - this.owner = owner; - } - - @Override - void clean() { - run(); - } - - @Override - @SuppressWarnings("deprecation") - protected void finalize() { - owner.end(); - } - } } } diff --git a/src/java.base/share/classes/java/util/zip/Inflater.java b/src/java.base/share/classes/java/util/zip/Inflater.java index 1b448a1b7e4..501d1ffa0b6 100644 --- a/src/java.base/share/classes/java/util/zip/Inflater.java +++ b/src/java.base/share/classes/java/util/zip/Inflater.java @@ -87,13 +87,6 @@ * to perform cleanup should be modified to use alternative cleanup mechanisms such * as {@link java.lang.ref.Cleaner} and remove the overriding {@code finalize} method. * - * @implSpec - * If this {@code Inflater} has been subclassed and the {@code end} method has been - * overridden, the {@code end} method will be called by the finalization when the - * inflater is unreachable. But the subclasses should not depend on this specific - * implementation; the finalization is not reliable and the {@code finalize} method - * is deprecated to be removed. - * * @see Deflater * @author David Connelly * @since 1.1 @@ -135,7 +128,7 @@ public class Inflater { * @param nowrap if true then support GZIP compatible compression */ public Inflater(boolean nowrap) { - this.zsRef = InflaterZStreamRef.get(this, init(nowrap)); + this.zsRef = new InflaterZStreamRef(this, init(nowrap)); } /** @@ -714,25 +707,6 @@ public void end() { } } - /** - * Closes the decompressor when garbage is collected. - * - * @implSpec - * If this {@code Inflater} has been subclassed and the {@code end} method - * has been overridden, the {@code end} method will be called when the - * inflater is unreachable. - * - * @deprecated The {@code finalize} method has been deprecated and will be - * removed. It is implemented as a no-op. Subclasses that override - * {@code finalize} in order to perform cleanup should be modified to use - * alternative cleanup mechanisms and remove the overriding {@code finalize} - * method. The recommended cleanup for compressor is to explicitly call - * {@code end} method when it is no longer in use. If the {@code end} is - * not invoked explicitly the resource of the compressor will be released - * when the instance becomes unreachable, - */ - @Deprecated(since="9", forRemoval=true) - protected void finalize() {} private void ensureOpen () { assert Thread.holdsLock(zsRef); @@ -792,43 +766,5 @@ public synchronized void run() { } } - /* - * If {@code Inflater} has been subclassed and the {@code end} method is - * overridden, uses {@code finalizer} mechanism for resource cleanup. So - * {@code end} method can be called when the {@code Inflater} is unreachable. - * This mechanism will be removed when the {@code finalize} method is - * removed from {@code Inflater}. - */ - static InflaterZStreamRef get(Inflater owner, long addr) { - Class clz = owner.getClass(); - while (clz != Inflater.class) { - try { - clz.getDeclaredMethod("end"); - return new FinalizableZStreamRef(owner, addr); - } catch (NoSuchMethodException nsme) {} - clz = clz.getSuperclass(); - } - return new InflaterZStreamRef(owner, addr); - } - - private static class FinalizableZStreamRef extends InflaterZStreamRef { - final Inflater owner; - - FinalizableZStreamRef(Inflater owner, long addr) { - super(null, addr); - this.owner = owner; - } - - @Override - void clean() { - run(); - } - - @Override - @SuppressWarnings("deprecation") - protected void finalize() { - owner.end(); - } - } } } diff --git a/src/java.base/share/classes/java/util/zip/ZipFile.java b/src/java.base/share/classes/java/util/zip/ZipFile.java index 649ee9ec8ab..a9403c7170a 100644 --- a/src/java.base/share/classes/java/util/zip/ZipFile.java +++ b/src/java.base/share/classes/java/util/zip/ZipFile.java @@ -30,6 +30,7 @@ import java.io.IOException; import java.io.EOFException; import java.io.File; +import java.io.FileDescriptor; import java.io.RandomAccessFile; import java.io.UncheckedIOException; import java.lang.ref.Cleaner.Cleanable; @@ -61,6 +62,7 @@ import jdk.internal.misc.JavaLangAccess; import jdk.internal.misc.JavaUtilZipFileAccess; import jdk.internal.misc.SharedSecrets; +import jdk.internal.crac.Core; import jdk.internal.misc.VM; import jdk.internal.perf.PerfCounter; import jdk.internal.ref.CleanerFactory; @@ -88,13 +90,6 @@ * cleanup mechanisms such as {@link java.lang.ref.Cleaner} and remove the overriding * {@code finalize} method. * - * @implSpec - * If this {@code ZipFile} has been subclassed and the {@code close} method has - * been overridden, the {@code close} method will be called by the finalization - * when {@code ZipFile} is unreachable. But the subclasses should not depend on - * this specific implementation; the finalization is not reliable and the - * {@code finalize} method is deprecated to be removed. - * * @author David Connelly * @since 1.1 */ @@ -255,7 +250,7 @@ public ZipFile(File file, int mode, Charset charset) throws IOException this.name = name; long t0 = System.nanoTime(); - this.res = CleanableResource.get(this, file, mode); + this.res = new CleanableResource(this, file, mode); PerfCounter.getZipFileOpenTime().addElapsedTimeFrom(t0); PerfCounter.getZipFileCount().increment(); @@ -841,42 +836,11 @@ public void run() { this.zsrc = Source.get(file, (mode & OPEN_DELETE) != 0, zc); } - /* - * If {@code ZipFile} has been subclassed and the {@code close} method is - * overridden, uses the {@code finalizer} mechanism for resource cleanup. - * So {@code close} method can be called when the the {@code ZipFile} is - * unreachable. This mechanism will be removed when {@code finalize} method - * is removed from {@code ZipFile}. - */ - static CleanableResource get(ZipFile zf, File file, int mode) - throws IOException { - Class clz = zf.getClass(); - while (clz != ZipFile.class && clz != JarFile.class) { - if (JLA.getDeclaredPublicMethods(clz, "close").size() != 0) { - return new FinalizableResource(zf, file, mode); + public void beforeCheckpoint() { + if (zsrc != null) { + synchronized (zsrc) { + zsrc.beforeCheckpoint(); } - clz = clz.getSuperclass(); - } - return new CleanableResource(zf, file, mode); - } - - static class FinalizableResource extends CleanableResource { - ZipFile zf; - FinalizableResource(ZipFile zf, File file, int mode) - throws IOException { - super(file, mode, zf.zc); - this.zf = zf; - } - - @Override - void clean() { - run(); - } - - @Override - @SuppressWarnings("deprecation") - protected void finalize() throws IOException { - zf.close(); } } } @@ -907,25 +871,6 @@ public void close() throws IOException { } } - /** - * Ensures that the system resources held by this ZipFile object are - * released when there are no more references to it. - * - * @deprecated The {@code finalize} method has been deprecated and will be - * removed. It is implemented as a no-op. Subclasses that override - * {@code finalize} in order to perform cleanup should be modified to - * use alternative cleanup mechanisms and to remove the overriding - * {@code finalize} method. The recommended cleanup for ZipFile object - * is to explicitly invoke {@code close} method when it is no longer in - * use, or use try-with-resources. If the {@code close} is not invoked - * explicitly the resources held by this object will be released when - * the instance becomes unreachable. - * - * @throws IOException if an I/O error has occurred - */ - @Deprecated(since="9", forRemoval=true) - protected void finalize() throws IOException {} - private void ensureOpen() { if (closeRequested) { throw new IllegalStateException("zip file closed"); @@ -1129,6 +1074,10 @@ private String[] getMetaInfEntryNames() { } } + private synchronized void beforeCheckpoint() { + res.beforeCheckpoint(); + } + private static boolean isWindows; private static final JavaLangAccess JLA; @@ -1190,8 +1139,11 @@ public int getExtraAttributes(ZipEntry ze) { public void setExtraAttributes(ZipEntry ze, int extraAttrs) { ze.extraAttributes = extraAttrs; } - - } + @Override + public void beforeCheckpoint(ZipFile zip) { + zip.beforeCheckpoint(); + } + } ); JLA = jdk.internal.misc.SharedSecrets.getJavaLangAccess(); isWindows = VM.getSavedProperty("os.name").contains("Windows"); @@ -1227,7 +1179,7 @@ private static class Source { // private Entry[] entries; // array of hashed cen entry // // To reduce the total size of entries further, we use a int[] here to store 3 "int" - // {@code hash}, {@code next and {@code "pos for each entry. The entry can then be + // {@code hash}, {@code next} and {@code "pos"} for each entry. The entry can then be // referred by their index of their positions in the {@code entries}. // private int[] entries; // array of hashed cen entry @@ -1883,5 +1835,18 @@ private static int countCENHeaders(byte[] cen, int size) { count++; return count; } + + public void beforeCheckpoint() { + synchronized (zfile) { + FileDescriptor fd = null; + try { + fd = zfile.getFD(); + } catch (IOException e) { + } + if (fd != null) { + Core.registerPersistent(fd); + } + } + } } } diff --git a/src/java.base/share/classes/javax/crac/CheckpointException.java b/src/java.base/share/classes/javax/crac/CheckpointException.java new file mode 100644 index 00000000000..81b0e8d29d9 --- /dev/null +++ b/src/java.base/share/classes/javax/crac/CheckpointException.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2017, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package javax.crac; + +/** + * Suppresses exceptions thrown during checkpoint notification. + */ +public class CheckpointException extends Exception { + private static final long serialVersionUID = 6859967688386143096L; + + /** + * Creates a {@code CheckpointException}. + */ + public CheckpointException() { + super(); + } +} diff --git a/src/java.base/share/classes/javax/crac/Context.java b/src/java.base/share/classes/javax/crac/Context.java new file mode 100644 index 00000000000..024ac3e906e --- /dev/null +++ b/src/java.base/share/classes/javax/crac/Context.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package javax.crac; + +/** + * A {@code Resource} that allows other {@code Resource}s to be registered with it. + * + *

{@code Context} implementation overrides {@code beforeCheckpoint} and {@code afterRestore}, defining how the notification about checkpoint and restore will be distributed by the {@code Context} hierarchy. + * + *

A {@code Context} implementor is encouraged to respect properties of the global {@code Context}. + */ +public abstract class Context implements Resource { + + /** Creates a {@code Context}. + */ + protected Context() { + } + + @Override + public abstract void beforeCheckpoint(Context context) + throws CheckpointException; + + @Override + public abstract void afterRestore(Context context) + throws RestoreException; + + /** + * Registers a {@code Resource} with this {@code Context}. + * + * @param resource {@code Resource} to be registered. + * @throws NullPointerException if {@code resource} is {@code null} + */ + public abstract void register(R resource); +} diff --git a/src/java.base/share/classes/javax/crac/ContextWrapper.java b/src/java.base/share/classes/javax/crac/ContextWrapper.java new file mode 100644 index 00000000000..e566f8f081c --- /dev/null +++ b/src/java.base/share/classes/javax/crac/ContextWrapper.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package javax.crac; + +class ContextWrapper extends Context { + private final jdk.crac.Context context; + + public ContextWrapper(jdk.crac.Context context) { + this.context = context; + } + + private static jdk.crac.Context convertContext( + Context context) { + return context instanceof ContextWrapper ? + ((ContextWrapper)context).context : + null; + } + + @Override + public void beforeCheckpoint(Context context) + throws CheckpointException { + try { + this.context.beforeCheckpoint(convertContext(context)); + } catch (jdk.crac.CheckpointException e) { + CheckpointException newException = new CheckpointException(); + for (Throwable t : e.getSuppressed()) { + newException.addSuppressed(t); + } + throw newException; + } + } + + @Override + public void afterRestore(Context context) + throws RestoreException { + try { + this.context.afterRestore(convertContext(context)); + } catch (jdk.crac.RestoreException e) { + RestoreException newException = new RestoreException(); + for (Throwable t : e.getSuppressed()) { + newException.addSuppressed(t); + } + throw newException; + } + } + + @Override + public void register(Resource r) { + if (jdk.crac.Configuration.checkpointEnabled()) { + ResourceWrapper wrapper = new ResourceWrapper(this, r); + context.register(wrapper); + } + } + + @Override + public String toString() { + return "ContextWrapper[" + context.toString() + "]"; + } +} + diff --git a/src/java.base/share/classes/javax/crac/Core.java b/src/java.base/share/classes/javax/crac/Core.java new file mode 100644 index 00000000000..0657b5735f2 --- /dev/null +++ b/src/java.base/share/classes/javax/crac/Core.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2023, Alibaba Group Holding Limited. All rights reserved. + * Copyright (c) 2017, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package javax.crac; + +import jdk.crac.impl.OrderedContext; + +/** + * The coordination service. + */ +public class Core { + + /** + * save the pseudo persistent file to image dir, + * then restore to origin path + */ + public static final int SAVE_RESTORE = 0x01; + + /** + * save the pseudo persistent file to image dir, + * but not restore. + */ + public static final int SAVE_ONLY = 0x02; + + /** + * If the file exist when restore, override the files + */ + public static final int OVERRIDE_WHEN_RESTORE = 0x04; + + /** + * copy the file from image dir to origin file path when restore + */ + public static final int COPY_WHEN_RESTORE = 0x08; + + /** + * create a symbol link when restore a file. + * This should a better performance than COPY_WHEN_RESTORE if file is large + */ + public static final int SYMLINK_WHEN_RESTORE = 0x10; + + /** + * all flags set that test if given mode is valid. + */ + private static final int ALL_FLAGS = SAVE_RESTORE | SAVE_ONLY | OVERRIDE_WHEN_RESTORE + | COPY_WHEN_RESTORE | SYMLINK_WHEN_RESTORE; + + + /** This class is not instantiable. */ + private Core() { + } + + private static final Context globalContext = new ContextWrapper(new OrderedContext()); + static { + jdk.crac.Core.getGlobalContext().register(new ResourceWrapper(null, globalContext)); + } + + /** + * Gets the global {@code Context} for checkpoint/restore notifications. + * + * @return the global {@code Context} + */ + public static Context getGlobalContext() { + return globalContext; + } + + /** + * Requests checkpoint and returns upon a successful restore. + * May throw an exception if the checkpoint or restore are unsuccessful. + * + * @throws CheckpointException if an exception occured during checkpoint + * notification and the execution continues in the original Java instance. + * @throws RestoreException if an exception occured during restore + * notification and execution continues in a new Java instance. + * @throws UnsupportedOperationException if checkpoint/restore is not + * supported, no notification performed and the execution continues in + * the original Java instance. + */ + public static void checkpointRestore() throws + CheckpointException, + RestoreException { + try { + jdk.crac.Core.checkpointRestore(); + } catch (jdk.crac.CheckpointException e) { + CheckpointException newException = new CheckpointException(); + for (Throwable t : e.getSuppressed()) { + newException.addSuppressed(t); + } + throw newException; + } catch (jdk.crac.RestoreException e) { + RestoreException newException = new RestoreException(); + for (Throwable t : e.getSuppressed()) { + newException.addSuppressed(t); + } + throw newException; + } + } + + /** + * Pseudo persistent is the file that persistent when checkpoint. + * This file can be modified after restore, but the change is discard when restore again. + * Register the file path to JVM so that JVM can ignore the file check when checkpoint + * @param absoluteFilePath the file path that need to register + * @param mode control the action when checkpoint and restore pseudo persistent file. + * See the constants define in current class. + */ + public static void registerPseudoPersistent(String absoluteFilePath, int mode) { + if ((mode & ~ALL_FLAGS) != 0) { + throw new IllegalArgumentException("Unknown mode 0x" + + Integer.toHexString(mode)); + } + if ((mode & SAVE_RESTORE) != 0 && (mode & SAVE_ONLY) != 0) { + throw new IllegalArgumentException("SAVE_RESTORE and SAVE_ONLY are exclusive"); + } + if ((mode & COPY_WHEN_RESTORE) != 0 && (mode & SYMLINK_WHEN_RESTORE) != 0) { + throw new IllegalArgumentException("COPY_WHEN_RESTORE and SYMLINK_WHEN_RESTORE are exclusive"); + } + + jdk.crac.Core.registerPseudoPersistent(absoluteFilePath, mode); + } + + /** + * Unregister the file that register in registerPseudoPersistent + * @param absoluteFilePath the file path that need to unregister + */ + public static void unregisterPseudoPersistent(String absoluteFilePath) { + jdk.crac.Core.unregisterPseudoPersistent(absoluteFilePath); + } + + /** + * append path to app clasloader's classpath. + * @param path the file need to append + * @throws jdk.crac.CheckpointException this method can be called only when restore in progress + */ + public static void appendToAppClassLoaderClassPath(String path) throws jdk.crac.CheckpointException { + jdk.crac.Core.appendToAppClassLoaderClassPath(path); + } +} diff --git a/src/java.base/share/classes/javax/crac/Resource.java b/src/java.base/share/classes/javax/crac/Resource.java new file mode 100644 index 00000000000..2c11778285b --- /dev/null +++ b/src/java.base/share/classes/javax/crac/Resource.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2017, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package javax.crac; + +/** + * An interface for receiving checkpoint/restore notifications. + * + *

The class that is interested in receiving a checkpoint/restore notification + * implements this interface, and the object created with that class is + * registered with a {@code Context}, using {@code register} method. + */ +public interface Resource { + + /** + * Invoked by a {@code Context} as a notification about checkpoint. + * + * @param context {@code Context} providing notification + * @throws Exception if the method have failed + */ + void beforeCheckpoint(Context context) throws Exception; + + /** + * Invoked by a {@code Context} as a notification about restore. + * + * @param context {@code Context} providing notification + * @throws Exception if the method have failed + */ + void afterRestore(Context context) throws Exception; +} diff --git a/src/java.base/share/classes/javax/crac/ResourceWrapper.java b/src/java.base/share/classes/javax/crac/ResourceWrapper.java new file mode 100644 index 00000000000..bae72b658ef --- /dev/null +++ b/src/java.base/share/classes/javax/crac/ResourceWrapper.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2019, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package javax.crac; + +import java.lang.ref.WeakReference; +import java.util.WeakHashMap; + +class ResourceWrapper extends WeakReference implements jdk.crac.Resource { + private static WeakHashMap weakMap = new WeakHashMap<>(); + + // Create strong reference to avoid losing the Resource. + // It's set unconditionally in beforeCheckpoint and cleaned in afterRestore + // (latter is called regardless of beforeCheckpoint result). + private Resource strongRef; + + private final Context context; + + public ResourceWrapper(Context context, Resource resource) { + super(resource); + weakMap.put(resource, this); + strongRef = null; + this.context = context; + } + + @Override + public String toString() { + Resource r = get(); + if (r != null) { + return "ResourceWrapper[" + r + "]"; + } else { + return "ResourceWrapper[null]"; + } + } + + @Override + public void beforeCheckpoint(jdk.crac.Context context) + throws Exception { + Resource r = get(); + strongRef = r; + if (r != null) { + try { + r.beforeCheckpoint(this.context); + } catch (CheckpointException e) { + Exception newException = new jdk.crac.CheckpointException(); + for (Throwable t : e.getSuppressed()) { + newException.addSuppressed(t); + } + throw newException; + } + } + } + + @Override + public void afterRestore(jdk.crac.Context context) throws Exception { + Resource r = get(); + strongRef = null; + if (r != null) { + try { + r.afterRestore(this.context); + } catch (RestoreException e) { + Exception newException = new jdk.crac.RestoreException(); + for (Throwable t : e.getSuppressed()) { + newException.addSuppressed(t); + } + throw newException; + } + } + } +} diff --git a/src/java.base/share/classes/javax/crac/RestoreException.java b/src/java.base/share/classes/javax/crac/RestoreException.java new file mode 100644 index 00000000000..0bea2487782 --- /dev/null +++ b/src/java.base/share/classes/javax/crac/RestoreException.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2017, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package javax.crac; + +/** + * Suppresses exceptions thrown during restore notification. + */ +public class RestoreException extends Exception { + private static final long serialVersionUID = -4091592505524280559L; + + /** + * Creates a {@code RestoreException}. + */ + public RestoreException() { + super(); + } +} + + diff --git a/src/java.base/share/classes/javax/crac/package-info.java b/src/java.base/share/classes/javax/crac/package-info.java new file mode 100644 index 00000000000..d024d9b03b6 --- /dev/null +++ b/src/java.base/share/classes/javax/crac/package-info.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2019, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/** + * Provides explicit coordination with a checkpoint/restore mechanism. + * A checkpoint/restore implementation may capture process or application state images for later continuation. + * Coordination allows application code to prepare for a checkpoint and to adapt to changes in the execution environment after a restore. + *

+ * Successful checkpointing and restoration may require that no checkpointed state exist that might not be validly reproducible when +restoring instances from the image. + * Coordination enables application to proactively discard problematic state ahead of checkpointing, and to reestablish needed state upon restoration. + * A Java implementation may provide detection of some subsets of state that are known to prevent successful checkpoints. + *

+ * For example, a state of opened file descriptors or socket may be impossible to store in the image. + * The implementation may detect such resources and then prevent checkpoint. + * The application must then close file descriptors and sockets to be successfully checkpointed. + * Files and sockets may be opened back after restore, then the application is responsible for processing possible exceptions. + *

+ * {@link Resource} is an interface for receiving checkpoint/restore notifications. + * In order to be notified, {@code Resource} needs to be registered in a {@link Context}. + * {@link Core} is a core interface for coordination. It provides the global {@code Context} which can be used as default choice. + * The global {@code Context} have properties listed below, one can define a custom {@code Context} and register it with the global one. + * {@code Core} has also a method to request checkpoint. + *

+ * Methods of {@code Resource} are invoked as a notification of checkpoint and restore. + * If a {@code Resource} is incapable to process notification, corresponding method throws an exception. + * The global {@code Context} ensures that exceptions are propagated to a requester of checkpoint/restore. + *

+ * {@code Context} is a {@code Resource}, that allows other {@code Resource}s to be registered with it. + * {@code Context} defines how {@code Resource}s are notified and may provide different guarantees compared to the global {@code Context}, such as order of notification. + * A class may extend {@code Context} and define custom rules of notification processing by overriding {@code Resource} method. + * Since a {@code Context} may be registered with other {@code Context}, they form a {@code Context} hierarchy. + *

+ * Checkpoint can requested by {@code Core.checkpointRestore} or by some other way. + * Then checkpoint notification of the global {@code Context} is performed. + * If the global {@code Context} have not thrown {@code CheckpointException}, the current Java instance is used to create the image in a platform dependent way. + * The current instance is terminated. + * Later, a new instance is created by some means, for example via Java launcher in a special mode. + * The new instance is started at the point where the image was created, it is followed by the restore notification. + * Exceptions from restore notification are provided as suppressed ones by a {@code RestoreException} (in a sense of {@link Throwable#addSuppressed}). + *

+ * If the global {@code Context} throws an exception during checkpoint notification then restore notificaion starts immediately without the image creation. + * In this case, exceptions from checkpoint and restore notifications are provided as suppressed ones by {@code CheckpointException}. + *

+ * {@code UnsupportedOperationException} is thrown if the service is not supported. + * No notification is performed in this case. + *

Global Context Properties

+ * Java Runtime maintains the global {@code Context} with following properties. + * An implementor is encouraged to define {@code Context} with the properties of the global {@code Context}. + *
    + *
  • The {@code Context} maintains a weak reference to registered {@code Resource}. + *
  • + *
  • Order of checkpoint notification is the reverse order of registration. + * Restore notification order is the reverse of checkpoint one, that is, forward order of registration. + *
  • + *
  • For single {@code Resource} registered in this {@code Context}: + *
      + *
    • {@code Resource} is always notified of checkpoint, regardless of other {@code Resource} notifications have thrown an exception or not, + *
    • + *
    • {@code Resource} is always notified of restore, regardless of its checkpoint or others' restore notification have thrown an exception or not. + *
    • + *
    • When an exception is thrown during notificaion, it is caught by the {@code Context} and is suppressed by a {@code CheckpointException} or {@code RestoreException}, depends on the throwing method. + *
    • + *
    • When the {@code Resource} is a {@code Context} and it throws {@code CheckpointException} or {@code RestoreException}, exceptions suppressed by the original exception are suppressed by another {@code CheckpointException} or {@code RestoreException}, depends on the throwing method. + *
    • + *
    + *
  • All exceptions thrown by {@code Resource} are suppressed by {@code CheckpointException} or {@code RestoreException} thrown by the {@code Context}. + *
  • + *
+ * + * @since TBD + */ + +package javax.crac; + diff --git a/src/java.base/share/classes/jdk/crac/CheckpointException.java b/src/java.base/share/classes/jdk/crac/CheckpointException.java new file mode 100644 index 00000000000..f1fc01b4a77 --- /dev/null +++ b/src/java.base/share/classes/jdk/crac/CheckpointException.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2017, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.crac; + +/** + * Suppresses exceptions thrown during checkpoint notification. + */ +public class CheckpointException extends Exception { + private static final long serialVersionUID = 8879167591426115859L; + + /** + * Creates a {@code CheckpointException}. + */ + public CheckpointException() { + super(); + } + + /** + * Constructs a {@code CheckpointException} with the specified + * detail message. + * + * @param message the detail message. + */ + public CheckpointException(String message) { + super(message); + } +} diff --git a/src/java.base/share/classes/jdk/crac/Configuration.java b/src/java.base/share/classes/jdk/crac/Configuration.java new file mode 100644 index 00000000000..751e4fe3eb1 --- /dev/null +++ b/src/java.base/share/classes/jdk/crac/Configuration.java @@ -0,0 +1,31 @@ +package jdk.crac; + +/* + * Copyright (c) 2023, Alibaba Group Holding Limited. 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. + */ +public class Configuration { + private static native void registerNatives(); + static { + registerNatives(); + } + public static native boolean checkpointEnabled(); +} diff --git a/src/java.base/share/classes/jdk/crac/Context.java b/src/java.base/share/classes/jdk/crac/Context.java new file mode 100644 index 00000000000..4bb34266f07 --- /dev/null +++ b/src/java.base/share/classes/jdk/crac/Context.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.crac; + +/** + * A {@code Resource} that allows other {@code Resource}s to be registered with it. + * + *

{@code Context} implementation overrides {@code beforeCheckpoint} and {@code afterRestore}, defining how the notification about checkpoint and restore will be distributed by the {@code Context} hierarchy. + * + *

A {@code Context} implementor is encouraged to respect properties of the global {@code Context}. + */ +public abstract class Context implements Resource { + + /** Creates a {@code Context}. + */ + protected Context() { + } + + @Override + public abstract void beforeCheckpoint(Context context) + throws CheckpointException; + + @Override + public abstract void afterRestore(Context context) + throws RestoreException; + + /** + * Registers a {@code Resource} with this {@code Context}. + * + * @param resource {@code Resource} to be registered. + * @throws NullPointerException if {@code resource} is {@code null} + */ + public abstract void register(R resource); +} diff --git a/src/java.base/share/classes/jdk/crac/Core.java b/src/java.base/share/classes/jdk/crac/Core.java new file mode 100644 index 00000000000..deba60b96cd --- /dev/null +++ b/src/java.base/share/classes/jdk/crac/Core.java @@ -0,0 +1,318 @@ +/* + * Copyright (c) 2023, Alibaba Group Holding Limited. All rights reserved. + * Copyright (c) 2017, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.crac; + +import jdk.crac.impl.CheckpointOpenFileException; +import jdk.crac.impl.CheckpointOpenResourceException; +import jdk.crac.impl.CheckpointOpenSocketException; +import jdk.crac.impl.OrderedContext; +import java.io.StringWriter; +import java.io.PrintWriter; + +import jdk.internal.misc.SharedSecrets; +import sun.security.action.GetBooleanAction; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.Arrays; + +/** + * The coordination service. + */ +public class Core { + private static final int JVM_CHECKPOINT_OK = 0; + private static final int JVM_CHECKPOINT_ERROR = 1; + private static final int JVM_CHECKPOINT_NONE = 2; + + private static final int JVM_CR_FAIL = 0; + private static final int JVM_CR_FAIL_FILE = 1; + private static final int JVM_CR_FAIL_SOCK = 2; + private static final int JVM_CR_FAIL_PIPE = 3; + + private static final long JCMD_STREAM_NULL = 0; + + private static native Object[] checkpointRestore0(boolean dryRun, long jcmdStream); + private static final Object checkpointRestoreLock = new Object(); + private static boolean checkpointInProgress = false; + private static volatile boolean restoreInProgress = false; + + private static class FlagsHolder { + public static final boolean TRACE_STARTUP_TIME = + GetBooleanAction.privilegedGetProperty("jdk.crac.trace-startup-time"); + public static final boolean HOLD_ON_CR_ERROR = + GetBooleanAction.privilegedGetProperty("jdk.crac.hold-on-cr-error"); + } + + private static final Context globalContext = new OrderedContext(); + static { + // force JDK context initialization + jdk.internal.crac.Core.getJDKContext(); + } + + /** This class is not instantiable. */ + private Core() { + } + + private static void translateJVMExceptions(int[] codes, String[] messages, + CheckpointException exception) { + assert codes.length == messages.length; + final int length = codes.length; + + for (int i = 0; i < length; ++i) { + switch(codes[i]) { + case JVM_CR_FAIL_FILE: + exception.addSuppressed( + new CheckpointOpenFileException(messages[i])); + break; + case JVM_CR_FAIL_SOCK: + exception.addSuppressed( + new CheckpointOpenSocketException(messages[i])); + break; + case JVM_CR_FAIL_PIPE: + // FALLTHROUGH + default: + exception.addSuppressed( + new CheckpointOpenResourceException(messages[i])); + break; + } + } + } + + /** + * Gets the global {@code Context} for checkpoint/restore notifications. + * + * @return the global {@code Context} + */ + public static Context getGlobalContext() { + return globalContext; + } + + @SuppressWarnings("removal") + private static void checkpointRestore1(long jcmdStream) throws + CheckpointException, + RestoreException { + CheckpointException checkpointException = null; + + try { + globalContext.beforeCheckpoint(null); + } catch (CheckpointException ce) { + checkpointException = new CheckpointException(); + for (Throwable t : ce.getSuppressed()) { + checkpointException.addSuppressed(t); + } + } + + final Object[] bundle = checkpointRestore0(checkpointException != null, jcmdStream); + final int retCode = (Integer)bundle[0]; + final String newArguments = (String)bundle[1]; + final String[] newProperties = (String[])bundle[2]; + final int[] codes = (int[])bundle[3]; + final String[] messages = (String[])bundle[4]; + + if (FlagsHolder.TRACE_STARTUP_TIME) { + System.out.println("STARTUPTIME " + System.nanoTime() + " restore"); + } + + if (retCode != JVM_CHECKPOINT_OK) { + if (checkpointException == null) { + checkpointException = new CheckpointException(); + } + switch (retCode) { + case JVM_CHECKPOINT_ERROR: + translateJVMExceptions(codes, messages, checkpointException); + break; + case JVM_CHECKPOINT_NONE: + checkpointException.addSuppressed( + new RuntimeException("C/R is not configured")); + break; + default: + checkpointException.addSuppressed( + new RuntimeException("Unknown C/R result: " + retCode)); + } + } + + if (retCode == JVM_CHECKPOINT_ERROR && FlagsHolder.HOLD_ON_CR_ERROR) { + try { + if (checkpointException != null && checkpointException.getSuppressed() != null) { + for (Throwable t : checkpointException.getSuppressed()) { + t.printStackTrace(); + } + } + Thread.sleep(Long.MAX_VALUE); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + if (newProperties != null && newProperties.length > 0) { + Arrays.stream(newProperties).map(propStr -> propStr.split("=", 2)).forEach(pair -> { + AccessController.doPrivileged( + (PrivilegedAction)() -> + System.setProperty(pair[0], pair.length == 2 ? pair[1] : "")); + }); + } + + RestoreException restoreException = null; + try { + restoreInProgress = true; + globalContext.afterRestore(null); + } catch (RestoreException re) { + if (checkpointException == null) { + restoreException = re; + } else { + for (Throwable t : re.getSuppressed()) { + checkpointException.addSuppressed(t); + } + } + } finally { + restoreInProgress = false; + } + + /** + * We need fail back to normal startup. + if (newArguments != null && newArguments.length() > 0) { + String[] args = newArguments.split(" "); + if (args.length > 0) { + try { + Method newMain = AccessController.doPrivileged(new PrivilegedExceptionAction() { + @Override + public Method run() throws Exception { + Class < ?> newMainClass = Class.forName(args[0], false, + ClassLoader.getSystemClassLoader()); + Method newMain = newMainClass.getDeclaredMethod("main", + String[].class); + newMain.setAccessible(true); + return newMain; + } + }); + newMain.invoke(null, + (Object)Arrays.copyOfRange(args, 1, args.length)); + } catch (PrivilegedActionException | + InvocationTargetException | + IllegalAccessException e) { + assert checkpointException == null : + "should not have new arguments"; + if (restoreException == null) { + restoreException = new RestoreException(); + } + restoreException.addSuppressed(e); + } + } + } + */ + + assert checkpointException == null || restoreException == null; + if (checkpointException != null) { + throw checkpointException; + } else if (restoreException != null) { + throw restoreException; + } + } + + /** + * Requests checkpoint and returns upon a successful restore. + * May throw an exception if the checkpoint or restore are unsuccessful. + * + * @throws CheckpointException if an exception occured during checkpoint + * notification and the execution continues in the original Java instance. + * @throws RestoreException if an exception occured during restore + * notification and execution continues in a new Java instance. + * @throws UnsupportedOperationException if checkpoint/restore is not + * supported, no notification performed and the execution continues in + * the original Java instance. + */ + public static void checkpointRestore() throws + CheckpointException, + RestoreException { + checkpointRestore(JCMD_STREAM_NULL); + } + + private static void checkpointRestore(long jcmdStream) throws + CheckpointException, + RestoreException { + // checkpointRestoreLock protects against the simultaneous + // call of checkpointRestore from different threads. + synchronized (checkpointRestoreLock) { + // checkpointInProgress protects against recursive + // checkpointRestore from resource's + // beforeCheckpoint/afterRestore methods + if (!checkpointInProgress) { + checkpointInProgress = true; + try { + checkpointRestore1(jcmdStream); + } finally { + if (FlagsHolder.TRACE_STARTUP_TIME) { + System.out.println("STARTUPTIME " + System.nanoTime() + " restore-finish"); + } + checkpointInProgress = false; + } + } else { + throw new CheckpointException("Recursive checkpoint is not allowed"); + } + } + } + + /* called by VM */ + private static String checkpointRestoreInternal(long jcmdStream) { + try { + checkpointRestore(jcmdStream); + } catch (CheckpointException e) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + return sw.toString(); + } catch (RestoreException e) { + e.printStackTrace(); + return null; + } + return null; + } + + public static void registerPseudoPersistent(String absoluteFilePath, int mode) { + jdk.internal.crac.Core.registerPseudoPersistent(absoluteFilePath, mode); + } + + public static void unregisterPseudoPersistent(String absoluteFilePath) { + jdk.internal.crac.Core.unregisterPseudoPersistent(absoluteFilePath); + } + /** + * append path to app clasloader's classpath. + * @param path + * @throws jdk.crac.CheckpointException + */ + public static void appendToAppClassLoaderClassPath(String path) throws CheckpointException { + if (!restoreInProgress) { + throw new CheckpointException("Allow call appendToAppClassLoaderClassPath only when restore in progress"); + } + SharedSecrets.getJavaAppClassLoaderAccess().appendToClassPath(path); + } +} diff --git a/src/java.base/share/classes/jdk/crac/Resource.java b/src/java.base/share/classes/jdk/crac/Resource.java new file mode 100644 index 00000000000..c882c70a2f5 --- /dev/null +++ b/src/java.base/share/classes/jdk/crac/Resource.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2017, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.crac; + +/** + * An interface for receiving checkpoint/restore notifications. + * + *

The class that is interested in receiving a checkpoint/restore notification + * implements this interface, and the object created with that class is + * registered with a {@code Context}, using {@code register} method. + */ +public interface Resource { + + /** + * Invoked by a {@code Context} as a notification about checkpoint. + * + * @param context {@code Context} providing notification + * @throws Exception if the method have failed + */ + void beforeCheckpoint(Context context) throws Exception; + + /** + * Invoked by a {@code Context} as a notification about restore. + * + * @param context {@code Context} providing notification + * @throws Exception if the method have failed + */ + void afterRestore(Context context) throws Exception; +} diff --git a/src/java.base/share/classes/jdk/crac/RestoreException.java b/src/java.base/share/classes/jdk/crac/RestoreException.java new file mode 100644 index 00000000000..90753c8b6eb --- /dev/null +++ b/src/java.base/share/classes/jdk/crac/RestoreException.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2017, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.crac; + +/** + * Suppresses exceptions thrown during restore notification. + */ +public class RestoreException extends Exception { + private static final long serialVersionUID = 5235124335683732665L; + + /** + * Creates a {@code RestoreException}. + */ + public RestoreException() { + super(); + } + + /** + * Constructs a {@code RestoreException} with the specified + * detail message. + * + * @param message the detail message. + */ + public RestoreException(String message) { + super(message); + } +} diff --git a/src/java.base/share/classes/jdk/crac/impl/AbstractContextImpl.java b/src/java.base/share/classes/jdk/crac/impl/AbstractContextImpl.java new file mode 100644 index 00000000000..0cb632f3915 --- /dev/null +++ b/src/java.base/share/classes/jdk/crac/impl/AbstractContextImpl.java @@ -0,0 +1,129 @@ +// Copyright 2019-2020 Azul Systems, Inc. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +package jdk.crac.impl; + +import jdk.crac.CheckpointException; +import jdk.crac.Context; +import jdk.crac.Resource; +import jdk.crac.RestoreException; +import sun.security.action.GetBooleanAction; + +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.stream.Collectors; + +public abstract class AbstractContextImpl extends Context { + + private static class FlagsHolder { + public static final boolean DEBUG = + GetBooleanAction.privilegedGetProperty("jdk.crac.debug"); + } + + private WeakHashMap checkpointQ = new WeakHashMap<>(); + private List restoreQ = null; + private Comparator> comparator; + + protected AbstractContextImpl(Comparator> comparator) { + this.comparator = comparator; + } + + protected synchronized void register(R resource, P payload) { + checkpointQ.put(resource, payload); + } + + @Override + public synchronized void beforeCheckpoint(Context context) throws CheckpointException { + List resources = checkpointQ.entrySet().stream() + .sorted(comparator) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + + CheckpointException exception = new CheckpointException(); + for (Resource r : resources) { + long start = 0L; + boolean ok = false; + if (FlagsHolder.DEBUG) { + start = System.currentTimeMillis(); + } + try { + r.beforeCheckpoint(this); + ok = true; + } catch (CheckpointException e) { + for (Throwable t : e.getSuppressed()) { + exception.addSuppressed(t); + } + } catch (Exception e) { + exception.addSuppressed(e); + } + if (FlagsHolder.DEBUG) { + printDebugLog("beforeCheckpoint", r, start, ok); + } + } + + Collections.reverse(resources); + restoreQ = resources; + + if (0 < exception.getSuppressed().length) { + throw exception; + } + } + + @Override + public synchronized void afterRestore(Context context) throws RestoreException { + RestoreException exception = new RestoreException(); + for (Resource r : restoreQ) { + long start = 0L; + boolean ok = false; + if (FlagsHolder.DEBUG) { + start = System.currentTimeMillis(); + } + try { + r.afterRestore(this); + ok = true; + } catch (RestoreException e) { + for (Throwable t : e.getSuppressed()) { + exception.addSuppressed(t); + } + } catch (Exception e) { + exception.addSuppressed(e); + } + if (FlagsHolder.DEBUG) { + printDebugLog("afterRestore", r, start, ok); + } + } + restoreQ = null; + + if (0 < exception.getSuppressed().length) { + throw exception; + } + } + + private void printDebugLog(String msg, Resource resource, long start, boolean ok) { + StringBuffer sb = new StringBuffer(256); + sb.append('[').append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date())).append(']'); + sb.append(msg).append(' ').append(resource); + sb.append(" cost:").append(System.currentTimeMillis() - start).append("ms"); + sb.append(" result:").append(ok); + System.err.println(sb); + } +} diff --git a/src/java.base/share/classes/jdk/crac/impl/CheckpointOpenFileException.java b/src/java.base/share/classes/jdk/crac/impl/CheckpointOpenFileException.java new file mode 100644 index 00000000000..d2736bb3762 --- /dev/null +++ b/src/java.base/share/classes/jdk/crac/impl/CheckpointOpenFileException.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.crac.impl; + +public class CheckpointOpenFileException extends + CheckpointOpenResourceException { + private static final long serialVersionUID = 4696394478625532246L; + + public CheckpointOpenFileException(String details) { + super(details); + } +} diff --git a/src/java.base/share/classes/jdk/crac/impl/CheckpointOpenResourceException.java b/src/java.base/share/classes/jdk/crac/impl/CheckpointOpenResourceException.java new file mode 100644 index 00000000000..d119eb87fbe --- /dev/null +++ b/src/java.base/share/classes/jdk/crac/impl/CheckpointOpenResourceException.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.crac.impl; + +public class CheckpointOpenResourceException extends Exception { + private static final long serialVersionUID = -3858375642480846931L; + + public CheckpointOpenResourceException(String details) { + super(details); + } +} diff --git a/src/java.base/share/classes/jdk/crac/impl/CheckpointOpenSocketException.java b/src/java.base/share/classes/jdk/crac/impl/CheckpointOpenSocketException.java new file mode 100644 index 00000000000..31f04746b96 --- /dev/null +++ b/src/java.base/share/classes/jdk/crac/impl/CheckpointOpenSocketException.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.crac.impl; + +public class CheckpointOpenSocketException extends + CheckpointOpenResourceException { + private static final long serialVersionUID = 4778540502218641776L; + + public CheckpointOpenSocketException(String details) { + super(details); + } +} diff --git a/src/java.base/share/classes/jdk/crac/impl/OrderedContext.java b/src/java.base/share/classes/jdk/crac/impl/OrderedContext.java new file mode 100644 index 00000000000..afeececd3b4 --- /dev/null +++ b/src/java.base/share/classes/jdk/crac/impl/OrderedContext.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.crac.impl; + +import jdk.crac.Resource; +import jdk.crac.impl.AbstractContextImpl; + +import java.util.Comparator; +import java.util.Map; + +public class OrderedContext extends AbstractContextImpl { + private long order; + + static class ContextComparator implements Comparator> { + @Override + public int compare(Map.Entry o1, Map.Entry o2) { + return (int)(o2.getValue() - o1.getValue()); + } + } + + public OrderedContext() { + super(new ContextComparator()); + } + + @Override + public synchronized void register(Resource r) { + register(r, order++); + } +} diff --git a/src/java.base/share/classes/jdk/crac/package-info.java b/src/java.base/share/classes/jdk/crac/package-info.java new file mode 100644 index 00000000000..7490f9c2541 --- /dev/null +++ b/src/java.base/share/classes/jdk/crac/package-info.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2019, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/** + * Provides explicit coordination with a checkpoint/restore mechanism. + * A checkpoint/restore implementation may capture process or application state images for later continuation. + * Coordination allows application code to prepare for a checkpoint and to adapt to changes in the execution environment after a restore. + *

+ * Successful checkpointing and restoration may require that no checkpointed state exist that might not be validly reproducible when +restoring instances from the image. + * Coordination enables application to proactively discard problematic state ahead of checkpointing, and to reestablish needed state upon restoration. + * A Java implementation may provide detection of some subsets of state that are known to prevent successful checkpoints. + *

+ * For example, a state of opened file descriptors or socket may be impossible to store in the image. + * The implementation may detect such resources and then prevent checkpoint. + * The application must then close file descriptors and sockets to be successfully checkpointed. + * Files and sockets may be opened back after restore, then the application is responsible for processing possible exceptions. + *

+ * {@link Resource} is an interface for receiving checkpoint/restore notifications. + * In order to be notified, {@code Resource} needs to be registered in a {@link Context}. + * {@link Core} is a core interface for coordination. It provides the global {@code Context} which can be used as default choice. + * The global {@code Context} have properties listed below, one can define a custom {@code Context} and register it with the global one. + * {@code Core} has also a method to request checkpoint. + *

+ * Methods of {@code Resource} are invoked as a notification of checkpoint and restore. + * If a {@code Resource} is incapable to process notification, corresponding method throws an exception. + * The global {@code Context} ensures that exceptions are propagated to a requester of checkpoint/restore. + *

+ * {@code Context} is a {@code Resource}, that allows other {@code Resource}s to be registered with it. + * {@code Context} defines how {@code Resource}s are notified and may provide different guarantees compared to the global {@code Context}, such as order of notification. + * A class may extend {@code Context} and define custom rules of notification processing by overriding {@code Resource} method. + * Since a {@code Context} may be registered with other {@code Context}, they form a {@code Context} hierarchy. + *

+ * Checkpoint can requested by {@code Core.checkpointRestore} or by some other way. + * Then checkpoint notification of the global {@code Context} is performed. + * If the global {@code Context} have not thrown {@code CheckpointException}, the current Java instance is used to create the image in a platform dependent way. + * The current instance is terminated. + * Later, a new instance is created by some means, for example via Java launcher in a special mode. + * The new instance is started at the point where the image was created, it is followed by the restore notification. + * Exceptions from restore notification are provided as suppressed ones by a {@code RestoreException} (in a sense of {@link Throwable#addSuppressed}). + *

+ * If the global {@code Context} throws an exception during checkpoint notification then restore notificaion starts immediately without the image creation. + * In this case, exceptions from checkpoint and restore notifications are provided as suppressed ones by {@code CheckpointException}. + *

+ * {@code UnsupportedOperationException} is thrown if the service is not supported. + * No notification is performed in this case. + *

Global Context Properties

+ * Java Runtime maintains the global {@code Context} with following properties. + * An implementor is encouraged to define {@code Context} with the properties of the global {@code Context}. + *
    + *
  • The {@code Context} maintains a weak reference to registered {@code Resource}. + *
  • + *
  • Order of checkpoint notification is the reverse order of registration. + * Restore notification order is the reverse of checkpoint one, that is, forward order of registration. + *
  • + *
  • For single {@code Resource} registered in this {@code Context}: + *
      + *
    • {@code Resource} is always notified of checkpoint, regardless of other {@code Resource} notifications have thrown an exception or not, + *
    • + *
    • {@code Resource} is always notified of restore, regardless of its checkpoint or others' restore notification have thrown an exception or not. + *
    • + *
    • When an exception is thrown during notificaion, it is caught by the {@code Context} and is suppressed by a {@code CheckpointException} or {@code RestoreException}, depends on the throwing method. + *
    • + *
    • When the {@code Resource} is a {@code Context} and it throws {@code CheckpointException} or {@code RestoreException}, exceptions suppressed by the original exception are suppressed by another {@code CheckpointException} or {@code RestoreException}, depends on the throwing method. + *
    • + *
    + *
  • All exceptions thrown by {@code Resource} are suppressed by {@code CheckpointException} or {@code RestoreException} thrown by the {@code Context}. + *
  • + *
+ * + * @since TBD + */ + +package jdk.crac; + diff --git a/src/java.base/share/classes/jdk/internal/crac/Core.java b/src/java.base/share/classes/jdk/internal/crac/Core.java new file mode 100644 index 00000000000..6f7ba497040 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/crac/Core.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023, Alibaba Group Holding Limited. All rights reserved. + * Copyright (c) 2019, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.crac; + +import java.io.FileDescriptor; + +public class Core { + private static JDKContext JDKContext; + + private static native void registerPersistent0(FileDescriptor fd); + + private static native void registerPseudoPersistent0(String absoluteFilePath, int mode); + + private static native void unregisterPseudoPersistent0(String absoluteFilePath); + + static { + JDKContext = new JDKContext(); + jdk.crac.Core.getGlobalContext().register(JDKContext); + } + + public static JDKContext getJDKContext() { + return JDKContext; + } + + public static void registerPersistent(FileDescriptor fd) { + registerPersistent0(fd); + } + + public static void registerPseudoPersistent(String absoluteFilePath, int mode) { + registerPseudoPersistent0(absoluteFilePath, mode); + } + + public static void unregisterPseudoPersistent(String absoluteFilePath) { + unregisterPseudoPersistent0(absoluteFilePath); + } +} diff --git a/src/java.base/share/classes/jdk/internal/crac/JDKContext.java b/src/java.base/share/classes/jdk/internal/crac/JDKContext.java new file mode 100644 index 00000000000..7bca4bc3a4b --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/crac/JDKContext.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.crac; + +import jdk.crac.impl.AbstractContextImpl; + +import java.util.Comparator; +import java.util.Map; + +public class JDKContext extends AbstractContextImpl { + static class ContextComparator implements Comparator> { + @Override + public int compare(Map.Entry o1, Map.Entry o2) { + return o1.getKey().getPriority().compareTo(o2.getKey().getPriority()); + } + } + + JDKContext() { + super(new ContextComparator()); + } + + @Override + public void register(JDKResource resource) { + register(resource, null); + } +} diff --git a/src/java.base/share/classes/jdk/internal/crac/JDKResource.java b/src/java.base/share/classes/jdk/internal/crac/JDKResource.java new file mode 100644 index 00000000000..11b5c76647f --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/crac/JDKResource.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2019, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.crac; + +import jdk.crac.Resource; + +public interface JDKResource extends Resource { + /** + * JDK Resource priorities. + * Priorities are defined in the order from lowest to highest. + * Most resources should use priority NORMAL (the lowest priority). + * Other priorities define sequence of checkpoint notification + * for dependent resources. + * Checkpoint notification will be processed in the order from the lowest + * to the highest priorities. + * Restore notification will be processed in the revers order: + * from the highest to the lowest priorities. + * JDK resources with the same priority will be notified about checkpoint + * in the reverse order of registration. + * JDK resources with the same priority will be notified about restore + * in the direct order of registration. + */ + enum Priority { + /** + * Most resources should use this option. + */ + NORMAL, + /** + * Priority of the + * sun.nio.ch.EPollSelectorImpl resource + */ + EPOLLSELECTOR, + /** + * Priority of the + * sun.security.provider.NativePRNG resource + */ + NATIVE_PRNG, + /** + * Priority of the + * sun.security.provider.SecureRandom resource + */ + SECURE_RANDOM, + /** + * Priority of the + * sun.security.provider.SecureRandom.SeederHolder resource + */ + SEEDER_HOLDER, + + /* Keep next priorities last to ensure handling of pending References + * appeared on earlier priorities. */ + + /** + * Priority of the + * java.lan.ref.Reference static resource + */ + REFERENCE_HANDLER, + /** + * Priority of the + * jdk.internal.ref.CleanerImpl resources + */ + CLEANERS, + }; + + Priority getPriority(); +} diff --git a/src/java.base/share/classes/jdk/internal/loader/ClassLoaders.java b/src/java.base/share/classes/jdk/internal/loader/ClassLoaders.java index f7c71e63265..d158eb70dc7 100644 --- a/src/java.base/share/classes/jdk/internal/loader/ClassLoaders.java +++ b/src/java.base/share/classes/jdk/internal/loader/ClassLoaders.java @@ -33,6 +33,7 @@ import java.security.PermissionCollection; import java.util.jar.Manifest; +import jdk.internal.misc.JavaAppClassLoaderAccess; import jdk.internal.misc.JavaLangAccess; import jdk.internal.misc.SharedSecrets; import jdk.internal.misc.VM; @@ -159,6 +160,12 @@ private static class AppClassLoader extends BuiltinClassLoader { AppClassLoader(PlatformClassLoader parent, URLClassPath ucp) { super("app", parent, ucp); this.ucp = ucp; + SharedSecrets.setJavaAppClassLoaderAccess(new JavaAppClassLoaderAccess() { + @Override + public void appendToClassPath(String path) { + ucp.addFile(path); + } + }); } @Override diff --git a/src/java.base/share/classes/jdk/internal/loader/URLClassPath.java b/src/java.base/share/classes/jdk/internal/loader/URLClassPath.java index 80a86a290fc..cbb4dbc827e 100644 --- a/src/java.base/share/classes/jdk/internal/loader/URLClassPath.java +++ b/src/java.base/share/classes/jdk/internal/loader/URLClassPath.java @@ -72,7 +72,9 @@ import jdk.internal.misc.JavaNetURLAccess; import jdk.internal.misc.JavaUtilZipFileAccess; import jdk.internal.misc.SharedSecrets; +import jdk.crac.Context; import jdk.internal.util.jar.InvalidJarIndexError; +import jdk.internal.util.jar.PersistentJarFile; import jdk.internal.util.jar.JarIndex; import sun.net.util.URLUtil; import sun.net.www.ParseUtil; @@ -810,7 +812,7 @@ private JarFile getJarFile(URL url) throws IOException { if (!p.exists()) { throw new FileNotFoundException(p.getPath()); } - return checkJar(new JarFile(new File(p.getPath()), true, ZipFile.OPEN_READ, + return checkJar(new PersistentJarFile(new File(p.getPath()), true, ZipFile.OPEN_READ, JarFile.runtimeVersion())); } URLConnection uc = (new URL(getBaseURL(), "#runtime")).openConnection(); diff --git a/src/java.base/share/classes/jdk/internal/misc/JavaAppClassLoaderAccess.java b/src/java.base/share/classes/jdk/internal/misc/JavaAppClassLoaderAccess.java new file mode 100644 index 00000000000..4de7d44c7c9 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/misc/JavaAppClassLoaderAccess.java @@ -0,0 +1,10 @@ +package jdk.internal.misc; + +public interface JavaAppClassLoaderAccess { + + /** + * append path to app clasloader's classpath. + * @param path + */ + void appendToClassPath(String path); +} diff --git a/src/java.base/share/classes/jdk/internal/misc/JavaUtilZipFileAccess.java b/src/java.base/share/classes/jdk/internal/misc/JavaUtilZipFileAccess.java index 5ef89f5e71c..e5abfd9c9c9 100644 --- a/src/java.base/share/classes/jdk/internal/misc/JavaUtilZipFileAccess.java +++ b/src/java.base/share/classes/jdk/internal/misc/JavaUtilZipFileAccess.java @@ -44,5 +44,5 @@ public interface JavaUtilZipFileAccess { public Stream entryNameStream(ZipFile zip); public void setExtraAttributes(ZipEntry ze, int extraAttrs); public int getExtraAttributes(ZipEntry ze); + public void beforeCheckpoint(ZipFile zip); } - diff --git a/src/java.base/share/classes/jdk/internal/misc/SharedSecrets.java b/src/java.base/share/classes/jdk/internal/misc/SharedSecrets.java index 5bc527f20c6..767da571704 100644 --- a/src/java.base/share/classes/jdk/internal/misc/SharedSecrets.java +++ b/src/java.base/share/classes/jdk/internal/misc/SharedSecrets.java @@ -83,6 +83,7 @@ public class SharedSecrets { private static IOEventAccess ioEventAccess; private static RCMAccesss rcmAccesss; private static WispFileSyncIOAccess wispFileSyncIOAccess; + private static JavaAppClassLoaderAccess javaAppClassLoaderAccess; public static JavaUtilJarAccess javaUtilJarAccess() { if (javaUtilJarAccess == null) { @@ -417,4 +418,11 @@ public static void setWispFileSyncIOAccess(WispFileSyncIOAccess wispAsyncIOAcces SharedSecrets.wispFileSyncIOAccess = wispAsyncIOAccess; } + public static JavaAppClassLoaderAccess getJavaAppClassLoaderAccess() { + return javaAppClassLoaderAccess; + } + + public static void setJavaAppClassLoaderAccess(JavaAppClassLoaderAccess javaAppClassLoaderAccess) { + SharedSecrets.javaAppClassLoaderAccess = javaAppClassLoaderAccess; + } } diff --git a/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java b/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java index 3e30e8ee53d..dcb984f52df 100644 --- a/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java +++ b/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java @@ -51,6 +51,7 @@ import jdk.internal.jmod.JmodFile; import jdk.internal.module.ModuleHashes.HashSupplier; +import jdk.internal.util.jar.PersistentJarFile; import sun.net.www.ParseUtil; @@ -225,7 +226,7 @@ static class JarModuleReader extends SafeCloseModuleReader { static JarFile newJarFile(Path path) { try { - return new JarFile(new File(path.toString()), + return new PersistentJarFile(new File(path.toString()), true, // verify ZipFile.OPEN_READ, JarFile.runtimeVersion()); diff --git a/src/java.base/share/classes/jdk/internal/ref/CleanerImpl.java b/src/java.base/share/classes/jdk/internal/ref/CleanerImpl.java index f141efae356..b4782fb5a5a 100644 --- a/src/java.base/share/classes/jdk/internal/ref/CleanerImpl.java +++ b/src/java.base/share/classes/jdk/internal/ref/CleanerImpl.java @@ -34,13 +34,16 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; +import jdk.crac.Context; +import jdk.crac.Resource; +import jdk.internal.crac.JDKResource; import jdk.internal.misc.InnocuousThread; /** * CleanerImpl manages a set of object references and corresponding cleaning actions. * CleanerImpl provides the functionality of {@link java.lang.ref.Cleaner}. */ -public final class CleanerImpl implements Runnable { +public final class CleanerImpl implements Runnable, JDKResource { /** * An object to access the CleanerImpl from a Cleaner; set by Cleaner init. @@ -89,6 +92,9 @@ public CleanerImpl() { phantomCleanableList = new PhantomCleanableRef(); weakCleanableList = new WeakCleanableRef(); softCleanableList = new SoftCleanableRef(); + if (jdk.crac.Configuration.checkpointEnabled()) { + jdk.internal.crac.Core.getJDKContext().register(this); + } } /** @@ -156,6 +162,20 @@ public void run() { } } + @Override + public Priority getPriority() { + return Priority.CLEANERS; + } + + @Override + public void beforeCheckpoint(Context context) throws Exception { + queue.waitForWaiters(1); + } + + @Override + public void afterRestore(Context context) throws Exception { + } + /** * Perform cleaning on an unreachable PhantomReference. */ diff --git a/src/java.base/share/classes/jdk/internal/util/jar/PersistentJarFile.java b/src/java.base/share/classes/jdk/internal/util/jar/PersistentJarFile.java new file mode 100644 index 00000000000..ebfd0375f91 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/util/jar/PersistentJarFile.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.util.jar; + +import jdk.crac.Context; +import jdk.crac.Resource; +import jdk.internal.misc.SharedSecrets; +import jdk.internal.crac.Core; +import jdk.internal.crac.JDKResource; + +import java.io.File; +import java.io.IOException; +import java.util.jar.JarFile; + +public class PersistentJarFile extends JarFile implements JDKResource { + // PersistentJarFile is ed when loading classes on the module path; + // when initializing the logger an implementation of logging is looked up through + // service-loading and that causes a recursion in opening the module. + // Therefore, we isolate the logger into a subclass and initialize only when needed. + private static class LoggerContainer { + private static final System.Logger logger = System.getLogger("jdk.crac"); + } + + public PersistentJarFile(File file, boolean b, int openRead, Runtime.Version runtimeVersion) throws IOException { + super(file, b, openRead, runtimeVersion); + if (jdk.crac.Configuration.checkpointEnabled()) { + Core.getJDKContext().register(this); + } + } + + @Override + public void beforeCheckpoint(Context context) throws Exception { + LoggerContainer.logger.log(System.Logger.Level.INFO, this.getName() + " is recorded as always available on restore"); + SharedSecrets.getJavaUtilZipFileAccess().beforeCheckpoint(this); + } + + @Override + public void afterRestore(Context context) throws Exception { + // do nothing, no fixup required + } + + @Override + public Priority getPriority() { + return Priority.NORMAL; + } +} diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java index 263590746cd..2d75f9442a9 100644 --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -131,7 +131,9 @@ exports com.alibaba.rcm; exports com.alibaba.wisp.engine; exports com.alibaba.util; + exports javax.crac; + exports jdk.crac; // additional qualified exports may be inserted at build time // see make/gensrc/GenModuleInfo.gmk @@ -348,6 +350,8 @@ jdk.localedata; exports com.alibaba.rcm.internal to jdk.management; + exports jdk.internal.crac to + java.rmi; // the service types defined by the APIs in this module diff --git a/src/java.base/share/classes/sun/security/action/GetBooleanAction.java b/src/java.base/share/classes/sun/security/action/GetBooleanAction.java index d2bcb105d3a..56c5d61bff4 100644 --- a/src/java.base/share/classes/sun/security/action/GetBooleanAction.java +++ b/src/java.base/share/classes/sun/security/action/GetBooleanAction.java @@ -25,6 +25,7 @@ package sun.security.action; +import java.security.AccessController; /** * A convenience class for retrieving the boolean value of a system property * as a privileged action. @@ -69,4 +70,26 @@ public GetBooleanAction(String theProp) { public Boolean run() { return Boolean.getBoolean(theProp); } + + /** + * Convenience method to get a property without going through doPrivileged + * if no security manager is present. This is unsafe for inclusion in a + * public API but allowable here since this class is now encapsulated. + * + * Note that this method performs a privileged action using caller-provided + * inputs. The caller of this method should take care to ensure that the + * inputs are not tainted and the returned property is not made accessible + * to untrusted code if it contains sensitive information. + * + * @param theProp the name of the system property. + */ + @SuppressWarnings("removal") + public static boolean privilegedGetProperty(String theProp) { + if (System.getSecurityManager() == null) { + return Boolean.getBoolean(theProp); + } else { + return AccessController.doPrivileged( + new GetBooleanAction(theProp)); + } + } } diff --git a/src/java.base/share/classes/sun/security/provider/SecureRandom.java b/src/java.base/share/classes/sun/security/provider/SecureRandom.java index c55dba0ed0a..5b3ba2e7959 100644 --- a/src/java.base/share/classes/sun/security/provider/SecureRandom.java +++ b/src/java.base/share/classes/sun/security/provider/SecureRandom.java @@ -25,12 +25,17 @@ package sun.security.provider; +import jdk.crac.Context; +import jdk.crac.Resource; + import java.io.IOException; import java.io.InvalidObjectException; import java.security.MessageDigest; import java.security.SecureRandomSpi; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; +import java.util.Arrays; +import java.util.concurrent.locks.ReentrantLock; /** *

This class provides a crytpographically strong pseudo-random number @@ -54,7 +59,7 @@ */ public final class SecureRandom extends SecureRandomSpi -implements java.io.Serializable { +implements java.io.Serializable, jdk.internal.crac.JDKResource { private static final long serialVersionUID = 3581829991155417889L; @@ -63,6 +68,8 @@ public final class SecureRandom extends SecureRandomSpi private byte[] state; private byte[] remainder; private int remCount; + private boolean clearStateOnCheckpoint = true; + private ReentrantLock objLock = new ReentrantLock(); /** * This empty constructor automatically seeds the generator. We attempt @@ -114,6 +121,9 @@ private void init(byte[] seed) { if (seed != null) { engineSetSeed(seed); } + if (jdk.crac.Configuration.checkpointEnabled()) { + jdk.internal.crac.Core.getJDKContext().register(this); + } } /** @@ -149,7 +159,20 @@ public byte[] engineGenerateSeed(int numBytes) { * @param seed the seed. */ @Override - public synchronized void engineSetSeed(byte[] seed) { + public void engineSetSeed(byte[] seed) { + objLock.lock(); + try { + // check if objLock has not been already acquired in beforeCheckpoint + if(objLock.getHoldCount() > 1) { + throw new IllegalStateException("SHA1PRNG object is invalidated"); + } + setSeedImpl(seed); + } finally { + objLock.unlock(); + } + } + + private void setSeedImpl(byte[] seed) { if (state != null) { digest.update(state); for (int i = 0; i < state.length; i++) { @@ -158,6 +181,7 @@ public synchronized void engineSetSeed(byte[] seed) { } state = digest.digest(seed); remCount = 0; + clearStateOnCheckpoint = false; } private static void updateState(byte[] state, byte[] output) { @@ -185,25 +209,80 @@ private static void updateState(byte[] state, byte[] output) { } } + private void invalidate() { + assert objLock.isHeldByCurrentThread(); + if (state != null) { + Arrays.fill(state, (byte)0); + } + state = null; + if (remainder != null) { + Arrays.fill(remainder, (byte)0); + } + remainder = null; + remCount = 0; + } + + @Override + public void beforeCheckpoint(Context context) throws Exception { + objLock.lock(); + if (clearStateOnCheckpoint) { + invalidate(); + } + } + + @Override + public void afterRestore(Context context) throws Exception { + objLock.unlock(); + } + + @Override + public Priority getPriority() { + return Priority.SECURE_RANDOM; + } + /** * This static object will be seeded by SeedGenerator, and used * to seed future instances of SHA1PRNG SecureRandoms. *

* Bloch, Effective Java Second Edition: Item 71 */ - private static class SeederHolder { - - private static final SecureRandom seeder; + private static class SeederHolder implements jdk.internal.crac.JDKResource { + private static final SeederHolder seederHolder = new SeederHolder(); + private final SecureRandom seeder; - static { + private SeederHolder() { /* * Call to SeedGenerator.generateSeed() to add additional * seed material (likely from the Native implementation). */ seeder = new SecureRandom(SeedGenerator.getSystemEntropy()); - byte [] b = new byte[DIGEST_SIZE]; + byte[] b = new byte[DIGEST_SIZE]; SeedGenerator.generateSeed(b); seeder.engineSetSeed(b); + if (jdk.crac.Configuration.checkpointEnabled()) { + jdk.internal.crac.Core.getJDKContext().register(this); + } + } + + public static SecureRandom getSeeder() { + return seederHolder.seeder; + } + + @Override + public void beforeCheckpoint(Context context) throws Exception { + seeder.invalidate(); + } + + @Override + public void afterRestore(Context context) throws Exception { + byte[] b = new byte[DIGEST_SIZE]; + SeedGenerator.generateSeed(b); + seeder.setSeedImpl(b); + } + + @Override + public Priority getPriority() { + return Priority.SEEDER_HOLDER; } } @@ -213,53 +292,60 @@ private static class SeederHolder { * @param result the array to be filled in with random bytes. */ @Override - public synchronized void engineNextBytes(byte[] result) { - int index = 0; - int todo; - byte[] output = remainder; - - if (state == null) { - byte[] seed = new byte[DIGEST_SIZE]; - SeederHolder.seeder.engineNextBytes(seed); - state = digest.digest(seed); - } + public void engineNextBytes(byte[] result) { + objLock.lock(); + try { + // verify if objLock is already acquired in beforeCheckpoint + if(objLock.getHoldCount() > 1) { + throw new IllegalStateException("SHA1PRNG object is invalidated"); + } + int index = 0; + int todo; + byte[] output = remainder; - // Use remainder from last time - int r = remCount; - if (r > 0) { - // How many bytes? - todo = (result.length - index) < (DIGEST_SIZE - r) ? + if (state == null) { + byte[] seed = new byte[DIGEST_SIZE]; + SeederHolder.getSeeder().engineNextBytes(seed); + state = digest.digest(seed); + } + // Use remainder from last time + int r = remCount; + if (r > 0) { + // How many bytes? + todo = (result.length - index) < (DIGEST_SIZE - r) ? (result.length - index) : (DIGEST_SIZE - r); - // Copy the bytes, zero the buffer - for (int i = 0; i < todo; i++) { - result[i] = output[r]; - output[r++] = 0; + // Copy the bytes, zero the buffer + for (int i = 0; i < todo; i++) { + result[i] = output[r]; + output[r++] = 0; + } + remCount += todo; + index += todo; } - remCount += todo; - index += todo; - } - // If we need more bytes, make them. - while (index < result.length) { - // Step the state - digest.update(state); - output = digest.digest(); - updateState(state, output); - - // How many bytes? - todo = (result.length - index) > DIGEST_SIZE ? - DIGEST_SIZE : result.length - index; - // Copy the bytes, zero the buffer - for (int i = 0; i < todo; i++) { - result[index++] = output[i]; - output[i] = 0; + // If we need more bytes, make them. + while (index < result.length) { + // Step the state + digest.update(state); + output = digest.digest(); + updateState(state, output); + // How many bytes? + todo = (result.length - index) > DIGEST_SIZE ? + DIGEST_SIZE : result.length - index; + // Copy the bytes, zero the buffer + for (int i = 0; i < todo; i++) { + result[index++] = output[i]; + output[i] = 0; + } + remCount += todo; } - remCount += todo; - } - // Store remainder for next time - remainder = output; - remCount %= DIGEST_SIZE; + // Store remainder for next time + remainder = output; + remCount %= DIGEST_SIZE; + } finally { + objLock.unlock(); + } } /* diff --git a/src/java.base/share/native/launcher/main.c b/src/java.base/share/native/launcher/main.c index b734fe2ba78..ebbc9134f96 100644 --- a/src/java.base/share/native/launcher/main.c +++ b/src/java.base/share/native/launcher/main.c @@ -93,6 +93,80 @@ WinMain(HINSTANCE inst, HINSTANCE previnst, LPSTR cmdline, int cmdshow) __initenv = _environ; #else /* JAVAW */ + +#include + +static int is_checkpoint = 0; + +static void parse_checkpoint(const char *arg) { + if (!is_checkpoint) { + const char *checkpoint_arg = "-XX:CRaCCheckpointTo"; + const int len = strlen(checkpoint_arg); + if (0 == strncmp(arg, checkpoint_arg, len)) { + is_checkpoint = 1; + } + } +} + +static pid_t g_child_pid = -1; + +static int wait_for_children() { + int status = -1; + pid_t pid; + do { + int st = 0; + pid = wait(&st); + if (pid == g_child_pid) { + status = st; + } + } while (-1 != pid || ECHILD != errno); + + if (WIFEXITED(status)) { + return WEXITSTATUS(status); + } + + if (WIFSIGNALED(status)) { + // Try to terminate the current process with the same signal + // as the child process was terminated + const int sig = WTERMSIG(status); + signal(sig, SIG_DFL); + raise(sig); + // Signal was ignored, return 128+n as bash does + // see https://linux.die.net/man/1/bash + return 128+sig; + } + + return 1; +} + +static void sighandler(int sig, siginfo_t *info, void *param) { + if (0 < g_child_pid) { + kill(g_child_pid, sig); + } +} + +static void setup_sighandler() { + struct sigaction sigact; + sigfillset(&sigact.sa_mask); + sigact.sa_flags = SA_SIGINFO; + sigact.sa_sigaction = sighandler; + + for (int sig = 1; sig < __SIGRTMIN; ++sig) { + if (sig == SIGKILL || sig == SIGSTOP) { + continue; + } + if (-1 == sigaction(sig, &sigact, NULL)) { + perror("sigaction"); + } + } + + sigset_t allset; + sigfillset(&allset); + if (-1 == sigprocmask(SIG_UNBLOCK, &allset, NULL)) { + perror("sigprocmask"); + } +} + JNIEXPORT int main(int argc, char **argv) { @@ -183,6 +257,7 @@ main(int argc, char **argv) } // Iterate the rest of command line for (i = 1; i < argc; i++) { + parse_checkpoint(argv[i]); JLI_List argsInFile = JLI_PreprocessArg(argv[i], JNI_TRUE); if (NULL == argsInFile) { JLI_List_add(args, JLI_StringDup(argv[i])); @@ -202,6 +277,18 @@ main(int argc, char **argv) JLI_List_add(args, NULL); margv = args->elements; } + + // Avoid unexpected process completion when checkpointing under docker container run + // by creating the main process waiting for children before exit. + if (is_checkpoint && 1 == getpid()) { + g_child_pid = fork(); + if (0 < g_child_pid) { + // The main process should forward signals to the child. + setup_sighandler(); + const int status = wait_for_children(); + exit(status); + } + } #endif /* WIN32 */ return JLI_Launch(margc, margv, jargc, (const char**) jargv, diff --git a/src/java.base/share/native/libjava/CracConfiguration.c b/src/java.base/share/native/libjava/CracConfiguration.c new file mode 100644 index 00000000000..8fdcb6ea95c --- /dev/null +++ b/src/java.base/share/native/libjava/CracConfiguration.c @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023, Alibaba Group Holding Limited. 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. + */ + +#include "jni.h" +#include "jni_util.h" +#include "jvm.h" +#include "jmm.h" +#include "jdk_crac_Configuration.h" +#define ARRAY_LENGTH(a) (sizeof(a)/sizeof(a[0])) + +static JNINativeMethod methods[] = { + {"checkpointEnabled", "()Z", (void *) &JVM_CheckpointEnabled} +}; + +JNIEXPORT void JNICALL +Java_jdk_crac_Configuration_registerNatives(JNIEnv *env, jclass cls) +{ + (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods)); +} + diff --git a/src/java.base/share/native/libjava/CracCore.c b/src/java.base/share/native/libjava/CracCore.c new file mode 100644 index 00000000000..8b2001fb456 --- /dev/null +++ b/src/java.base/share/native/libjava/CracCore.c @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2023, Alibaba Group Holding Limited. All rights reserved. + * Copyright (c) 2017, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/** \file */ + +#include "jni.h" +#include "jvm.h" +#include "jni_util.h" +#include "io_util.h" +#include "io_util_md.h" + +#include "jdk_crac_Core.h" +#include "jdk_internal_crac_Core.h" + +JNIEXPORT jobjectArray JNICALL +Java_jdk_crac_Core_checkpointRestore0(JNIEnv *env, jclass ignore, jboolean dry_run, jlong jcmd_stream) +{ + return JVM_Checkpoint(env, dry_run, jcmd_stream); +} + +JNIEXPORT void JNICALL Java_jdk_internal_crac_Core_registerPersistent0 + (JNIEnv *env, jclass ignore, jobject fileDesc) +{ + jint fd = THIS_FD(fileDesc); + + struct stat st; + if (-1 == fstat(fd, &st)) { + return; + } + + JVM_RegisterPersistent(env, fd, st.st_dev, st.st_ino); +} + +JNIEXPORT void JNICALL Java_jdk_internal_crac_Core_registerPseudoPersistent0 + (JNIEnv *env, jclass ignore, jclass absolutFilePath, jint mode) +{ + JVM_RegisterPseudoPersistent(env, absolutFilePath, mode); +} + +JNIEXPORT void JNICALL Java_jdk_internal_crac_Core_unregisterPseudoPersistent0 + (JNIEnv *env, jclass ignore, jclass absolutFilePath) +{ + JVM_UnregisterPseudoPersistent(env, absolutFilePath); +} diff --git a/src/java.base/unix/classes/java/lang/ProcessEnvironment.java b/src/java.base/unix/classes/java/lang/ProcessEnvironment.java index e4341e5322c..eed932ac693 100644 --- a/src/java.base/unix/classes/java/lang/ProcessEnvironment.java +++ b/src/java.base/unix/classes/java/lang/ProcessEnvironment.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -54,14 +55,36 @@ package java.lang; +import jdk.crac.Context; +import jdk.crac.Resource; + import java.io.*; import java.util.*; final class ProcessEnvironment { - private static final HashMap theEnvironment; - private static final Map theUnmodifiableEnvironment; + private static class CracSubscriber + implements jdk.internal.crac.JDKResource { + + @Override + public void beforeCheckpoint(Context context) throws Exception { + } + + @Override + public void afterRestore(Context context) throws Exception { + ProcessEnvironment.updateEnvironment(); + } + + @Override + public Priority getPriority() { + return Priority.NORMAL; + } + } + + private static HashMap theEnvironment; + private static Map theUnmodifiableEnvironment; + private static CracSubscriber theCracSubscriber; static final int MIN_NAME_LENGTH = 0; static { @@ -78,6 +101,11 @@ final class ProcessEnvironment theUnmodifiableEnvironment = Collections.unmodifiableMap (new StringEnvironment(theEnvironment)); + + if (jdk.crac.Configuration.checkpointEnabled()) { + theCracSubscriber = new CracSubscriber(); + jdk.internal.crac.Core.getJDKContext().register(theCracSubscriber); + } } /* Only for use by System.getenv(String) */ @@ -102,6 +130,20 @@ static Map emptyEnvironment(int capacity) { return new StringEnvironment(new HashMap<>(capacity)); } + static private void updateEnvironment() { + byte[][] environ = environ(); + // Read environment variables back to front, + // so that earlier variables override later ones. + for (int i = environ.length-1; i > 0; i-=2) { + theEnvironment.put(Variable.valueOf(environ[i-1]), + Value.valueOf(environ[i])); + } + + theUnmodifiableEnvironment + = Collections.unmodifiableMap + (new StringEnvironment(theEnvironment)); + } + private static native byte[][] environ(); // This class is not instantiable. diff --git a/src/java.base/unix/classes/java/net/PlainSocketImpl.java b/src/java.base/unix/classes/java/net/PlainSocketImpl.java index 83b685a927e..18c70a3f333 100644 --- a/src/java.base/unix/classes/java/net/PlainSocketImpl.java +++ b/src/java.base/unix/classes/java/net/PlainSocketImpl.java @@ -31,6 +31,12 @@ import sun.net.ext.ExtendedSocketOptions; import static sun.net.ext.ExtendedSocketOptions.SOCK_STREAM; +import jdk.crac.Context; +import jdk.crac.Resource; +import jdk.internal.crac.Core; +import jdk.internal.crac.JDKResource; +import jdk.internal.crac.JDKResource.Priority; + /* * On Unix systems we simply delegate to native methods. * @@ -39,8 +45,35 @@ class PlainSocketImpl extends AbstractPlainSocketImpl { + static class ResourceProxy implements JDKResource { + @Override + public void beforeCheckpoint(Context context) throws Exception { + PlainSocketImpl.beforeCheckpoint(); + } + + @Override + public void afterRestore(Context context) throws Exception { + PlainSocketImpl.afterRestore(); + } + + @Override + public Priority getPriority() { + return Priority.NORMAL; + } + } + + static Object closeLock = new Object(); + static boolean forceNonDeferedClose; + static int closeCnt; + + static JDKResource resourceProxy; + static { initProto(); + if (jdk.crac.Configuration.checkpointEnabled()) { + resourceProxy = new ResourceProxy(); + Core.getJDKContext().register(resourceProxy); + } } /** @@ -125,6 +158,50 @@ protected void socketSetOption(int opt, boolean b, Object val) throws SocketExce native void socketCreate(boolean isServer) throws IOException; + @Override + void socketClose0(boolean useDeferredClose) throws IOException { + if (useDeferredClose) { + synchronized (closeLock) { + if (forceNonDeferedClose) { + useDeferredClose = false; + } + if (useDeferredClose) { + ++closeCnt; + } + } + } + + try { + socketClose1(useDeferredClose); + } finally { + if (useDeferredClose) { + synchronized (closeLock) { + --closeCnt; + if (forceNonDeferedClose && closeCnt == 0) { + closeLock.notifyAll(); + } + } + } + } + } + + static void beforeCheckpoint() throws InterruptedException { + synchronized (closeLock) { + forceNonDeferedClose = true; + while (closeCnt != 0) { + closeLock.wait(); + } + beforeCheckpoint0(); + } + } + + static void afterRestore() { + synchronized (closeLock) { + afterRestore0(); + forceNonDeferedClose = false; + } + } + native void socketConnect(InetAddress address, int port, int timeout) throws IOException; @@ -137,7 +214,7 @@ native void socketBind(InetAddress address, int port) native int socketAvailable() throws IOException; - native void socketClose0(boolean useDeferredClose) throws IOException; + native void socketClose1(boolean useDeferredClose) throws IOException; native void socketShutdown(int howto) throws IOException; @@ -149,4 +226,8 @@ native void socketSetOption0(int cmd, boolean on, Object value) native int socketGetOption(int opt, Object iaContainerObj) throws SocketException; native void socketSendUrgentData(int data) throws IOException; + + static native void beforeCheckpoint0(); + + static native void afterRestore0(); } diff --git a/src/java.base/unix/classes/sun/net/www/protocol/jar/JarFileFactory.java b/src/java.base/unix/classes/sun/net/www/protocol/jar/JarFileFactory.java index 2001c382324..59d6ad3ed31 100644 --- a/src/java.base/unix/classes/sun/net/www/protocol/jar/JarFileFactory.java +++ b/src/java.base/unix/classes/sun/net/www/protocol/jar/JarFileFactory.java @@ -30,9 +30,14 @@ import java.net.URL; import java.net.URLConnection; import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; import java.util.jar.JarFile; import java.security.Permission; + import sun.net.util.URLUtil; +import jdk.crac.Context; +import jdk.crac.Resource; /* A factory for cached JAR file. This class is used to both retrieve * and cache Jar files. @@ -40,7 +45,7 @@ * @author Benjamin Renaud * @since 1.2 */ -class JarFileFactory implements URLJarFile.URLJarFileCloseController { +class JarFileFactory implements URLJarFile.URLJarFileCloseController, jdk.internal.crac.JDKResource { /* the url to file cache */ private static final HashMap fileCache = new HashMap<>(); @@ -50,6 +55,12 @@ class JarFileFactory implements URLJarFile.URLJarFileCloseController { private static final JarFileFactory instance = new JarFileFactory(); + static { + if (jdk.crac.Configuration.checkpointEnabled()) { + jdk.internal.crac.Core.getJDKContext().register(instance); + } + } + private JarFileFactory() { } public static JarFileFactory getInstance() { @@ -166,4 +177,35 @@ private Permission getPermission(JarFile jarFile) { return null; } + + @Override + public Priority getPriority() { + return Priority.NORMAL; + } + + @Override + public void beforeCheckpoint(Context context) throws Exception { + // Need to clear cached entries that are held by the factory only (e.g. + // after JarURLInputStream.close with useCaches == true). Creating a + // temporary weak cache and triggering GC to get know JARs really in + // use. + synchronized (instance) { + WeakHashMap weakMap = new WeakHashMap<>(urlCache); + fileCache.clear(); + urlCache.clear(); + + System.gc(); + + weakMap.forEach((JarFile jarFile, URL url) -> { + String key = urlKey(url); + urlCache.put(jarFile, url); + fileCache.put(key, jarFile); + }); + } + } + + @Override + public void afterRestore(Context context) throws Exception { + } + } diff --git a/src/java.base/unix/classes/sun/nio/ch/FileDispatcherImpl.java b/src/java.base/unix/classes/sun/nio/ch/FileDispatcherImpl.java index b54b71fa506..97cf53e10e9 100644 --- a/src/java.base/unix/classes/sun/nio/ch/FileDispatcherImpl.java +++ b/src/java.base/unix/classes/sun/nio/ch/FileDispatcherImpl.java @@ -30,12 +30,46 @@ import jdk.internal.misc.SharedSecrets; import jdk.internal.misc.JavaIOFileDescriptorAccess; +import jdk.crac.Context; +import jdk.crac.Resource; +import jdk.internal.misc.JavaIOFileDescriptorAccess; +import jdk.internal.misc.SharedSecrets; +import jdk.internal.crac.Core; +import jdk.internal.crac.JDKResource; +import jdk.internal.crac.JDKResource.Priority; class FileDispatcherImpl extends FileDispatcher { + static class ResourceProxy implements JDKResource { + @Override + public void beforeCheckpoint(Context context) throws Exception { + FileDispatcherImpl.beforeCheckpoint(); + } + + @Override + public void afterRestore(Context context) + throws IOException { + FileDispatcherImpl.afterRestore(); + } + + @Override + public Priority getPriority() { + return Priority.NORMAL; + } + } + + static Object closeLock = new Object(); + static boolean forceNonDeferedClose; + static int closeCnt; + + static ResourceProxy resourceProxy; static { IOUtil.load(); init(); + if (jdk.crac.Configuration.checkpointEnabled()) { + resourceProxy = new ResourceProxy(); + Core.getJDKContext().register(resourceProxy); + } } private static final JavaIOFileDescriptorAccess fdAccess = @@ -137,7 +171,30 @@ void close(FileDescriptor fd) throws IOException { } void preClose(FileDescriptor fd) throws IOException { - preClose0(fd); + boolean doPreclose = true; + synchronized (closeLock) { + if (forceNonDeferedClose) { + doPreclose = false; + } + if (doPreclose) { + ++closeCnt; + } + } + + if (!doPreclose) { + return; + } + + try { + preClose0(fd); + } finally { + synchronized (closeLock) { + closeCnt--; + if (forceNonDeferedClose && closeCnt == 0) { + closeLock.notifyAll(); + } + } + } } FileDescriptor duplicateForMapping(FileDescriptor fd) { @@ -165,6 +222,23 @@ int setDirectIO(FileDescriptor fd, String path) { return result; } + static void beforeCheckpoint() throws InterruptedException { + synchronized (closeLock) { + forceNonDeferedClose = true; + while (closeCnt != 0) { + closeLock.wait(); + } + beforeCheckpoint0(); + } + } + + static void afterRestore() throws IOException { + synchronized (closeLock) { + afterRestore0(); + forceNonDeferedClose = false; + } + } + // -- Native methods -- static native int read0(FileDescriptor fd, long address, int len) @@ -214,4 +288,7 @@ static native void release0(FileDescriptor fd, long pos, long size) static native void init(); + static native void beforeCheckpoint0(); + + static native void afterRestore0() throws IOException; } diff --git a/src/java.base/unix/classes/sun/security/provider/NativePRNG.java b/src/java.base/unix/classes/sun/security/provider/NativePRNG.java index 46e6764a411..c043325292b 100644 --- a/src/java.base/unix/classes/sun/security/provider/NativePRNG.java +++ b/src/java.base/unix/classes/sun/security/provider/NativePRNG.java @@ -29,7 +29,11 @@ import java.net.*; import java.security.*; import java.util.Arrays; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import jdk.crac.Context; +import jdk.crac.Resource; +import jdk.internal.crac.JDKResource; import sun.security.util.Debug; /** @@ -328,7 +332,7 @@ protected byte[] engineGenerateSeed(int numBytes) { /** * Nested class doing the actual work. Singleton, see INSTANCE above. */ - private static class RandomIO { + private static class RandomIO implements JDKResource { // we buffer data we read from the "next" file for efficiency, // but we limit the lifetime to avoid using stale bits @@ -382,6 +386,10 @@ private static class RandomIO { // mutex lock for setSeed() private final Object LOCK_SET_SEED = new Object(); + // lock for checkpoint/restore + // allows clearing mixRandom and internal buffer before checkpoint + private final ReentrantReadWriteLock crLock = new ReentrantReadWriteLock(); + // constructor, called only once from initIO() private RandomIO(File seedFile, File nextFile) throws IOException { this.seedFile = seedFile; @@ -473,7 +481,15 @@ public OutputStream run() { // for write, but actual write is not permitted. } } - getMixRandom().engineSetSeed(seed); + crLock.readLock().lock(); + try { + if(crLock.getWriteHoldCount() != 0) { + throw new IllegalStateException("PRNG object is invalidated"); + } + getMixRandom().engineSetSeed(seed); + } finally { + crLock.readLock().unlock(); + } } } @@ -531,7 +547,11 @@ private void ensureBufferValid() throws IOException { // read from "next" and XOR with bytes generated by the // mixing SHA1PRNG private void implNextBytes(byte[] data) { + crLock.readLock().lock(); try { + if(crLock.getWriteHoldCount() != 0) { + throw new IllegalStateException("PRNG object is invalidated"); + } getMixRandom().engineNextBytes(data); int data_len = data.length; int ofs = 0; @@ -564,7 +584,29 @@ private void implNextBytes(byte[] data) { } } catch (IOException e){ throw new ProviderException("nextBytes() failed", e); + } finally { + crLock.readLock().unlock(); } } + + @Override + public void beforeCheckpoint(Context context) throws Exception { + crLock.writeLock().lock(); + mixRandom = null; + buffered = 0; + lastRead = 0; + Arrays.fill(nextBuffer, (byte)0); } + + @Override + public void afterRestore(Context context) throws Exception { + crLock.writeLock().unlock(); + } + + @Override + public Priority getPriority() { + return Priority.NATIVE_PRNG; + } + + } } diff --git a/src/java.base/unix/native/criuengine/criuengine.c b/src/java.base/unix/native/criuengine/criuengine.c new file mode 100644 index 00000000000..3de335b90ad --- /dev/null +++ b/src/java.base/unix/native/criuengine/criuengine.c @@ -0,0 +1,939 @@ +/* + * Copyright (c) 2023, Alibaba Group Holding Limited. All rights reserved. + * Copyright (c) 2017, 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RESTORE_SIGNAL (SIGRTMIN + 2) + +#define PERFDATA_NAME "perfdata" +#define METADATA "metadata" +#define PSEUDO_FILE_SUFFIX ".dat" +#define PSEUDO_FILE_PREFIX "pp" +#define PIPE_FDS "pipefds" +#define MAX_PIPE_FDS 5 +#define PIPEFS_MAGIC 0x50495045 + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + +typedef int (*pseudo_file_func)(const char *, int, int, const char *); + +enum PseudoPersistentMode { + SAVE_RESTORE = 0x01, + SAVE_ONLY = 0x02, + OVERRIDE_WHEN_RESTORE = 0x04, + COPY_WHEN_RESTORE = 0x08, + SYMLINK_WHEN_RESTORE = 0x10, + SKIP_LOG_FILES = 0x20 +}; + +static int create_cppath(const char *imagedir); + +static int write_config_pipefds(const char *imagedir, const char *config_pipefds); +static int append_pipeinfo(const char* imagedir, const char* jvm_pid); +static int is_exist_pipefd(const char *jvm_pid, int fd); +static int read_fd_link(const char *jvm_pid, int fd, char *link, size_t len); + +static int restore_validate(char *criu, char *image_dir, char *jvm_version, const char* unprivileged); +static int check_metadata(char *image_dir, char *jvm_version); +static int create_metadata(const char *image_dir, const char *jvm_version); +static int read_pseudo_file(const char *imagedir, pseudo_file_func func, const char* desc); +static int restore_pseudo_persistent_file(const char *imagedir, int id, int mode, const char *src); +static int exec_criu_command(const char *criu, const char *args[]); +static int add_unprivileged_opt(const char *unprivileged, const char **opts, + int size) { + if (!strcmp("true", unprivileged)) { + for (int pos = 0 ; pos < size; pos++) { + if (opts[pos] == NULL) { + if (pos == size - 1) { + fprintf(stderr, "criu option array is not enough to add the --unprivileged option."); + return 1; + } else { + opts[pos] = "--unprivileged"; + opts[pos + 1] = NULL; + return 0; + } + } + } + } + return 0; +} + +static int g_pid; + +static int kickjvm(pid_t jvm, int code) { + union sigval sv = { .sival_int = code }; + if (-1 == sigqueue(jvm, RESTORE_SIGNAL, sv)) { + perror("sigqueue"); + return 1; + } + return 0; +} + +static int checkpoint(pid_t jvm, const char *basedir, const char *self, + const char *criu, const char *imagedir, + const char *validate_before_restore, + const char *jvm_version, + const char *unprivileged, + const char *config_pipefds) { + const char *dump_cpuinfo[32] = {criu, "cpuinfo", "dump", + "-D", imagedir, NULL}; + pid_t pid = fork(); + if (pid < 0) { + perror("cannot fork() for checkpoint."); + return 1; + } else if (pid) { + // main process + wait(NULL); + return 0; + } + + pid_t parent_before = getpid(); + + // child + pid = fork(); + if (pid < 0) { + perror("cannot fork() for move out of JVM process hierarchy"); + return 1; + } else if (pid) { + exit(0); + } + + // grand-child + pid_t parent = getppid(); + int tries = 300; + while (parent != 1 && 0 < tries--) { + usleep(10); + parent = getppid(); + } + + if (parent == parent_before) { + fprintf(stderr, "can't move out of JVM process hierarchy"); + kickjvm(jvm, -1); + exit(0); + } + + // cppath must write before call criu. criuengine may be exit immediately if + // criu kill jvm when running in + // a container. + create_cppath(imagedir); + if (!strcmp("true", validate_before_restore)) { + if (create_metadata(imagedir, jvm_version)) { + return 1; + } + if (add_unprivileged_opt(unprivileged, dump_cpuinfo, ARRAY_SIZE(dump_cpuinfo))) { + return 1; + } + if (exec_criu_command(criu, dump_cpuinfo)) { + return 1; + } + } + + //write pipe fds to file first, then write actually pipe info in post_dump + //because the dumpee process is freeze,if write before checkpoint there some files may changed. + if (strlen(config_pipefds) > 0) { + if (write_config_pipefds(imagedir, config_pipefds)) { + return 1; + } + } + + char *leave_running = getenv("CRAC_CRIU_LEAVE_RUNNING"); + + char jvmpidchar[32]; + snprintf(jvmpidchar, sizeof(jvmpidchar), "%d", jvm); + + pid_t child = fork(); + if (child < 0) { + perror("cannot fork() for criu"); + return 1; + } else if (!child) { + const char *args[32] = { + criu, "dump", "-t", jvmpidchar, "-D", imagedir, + "--shell-job", "-v4", "-o", "dump4.log", // -D without -W makes criu cd to image dir for logs + "--action-script", self + }; + const char **arg = args + 12; + + if (leave_running) { + *arg++ = "-R"; + } + if (!strcmp("true", unprivileged)) { + *arg++ = "--unprivileged"; + } + + char *criuopts = getenv("CRAC_CRIU_OPTS"); + if (criuopts) { + char *criuopt = strtok(criuopts, " "); + while (criuopt && + ARRAY_SIZE(args) >= + (size_t)(arg - args) + 1 /* account for trailing NULL */) { + *arg++ = criuopt; + criuopt = strtok(NULL, " "); + } + if (criuopt) { + fprintf(stderr, + "Warning: too many arguments in CRAC_CRIU_OPTS (dropped from " + "'%s')\n", + criuopt); + } + } + *arg++ = NULL; + + char resolved_path[PATH_MAX]; + if (realpath(imagedir, resolved_path) == NULL) { + perror("get real path for image dir error"); + exit(1); + } + setenv("CRAC_IMAGE_DIR", resolved_path, 1); + execv(criu, (char **)args); + perror("criu dump"); + exit(1); + } + + int status; + if (child != wait(&status) || !WIFEXITED(status) || WEXITSTATUS(status)) { + kickjvm(jvm, -1); + } else if (leave_running) { + kickjvm(jvm, 0); + } + + exit(0); +} + +static int restore(const char *basedir, + const char *self, + const char *criu, + const char *imagedir, + const char *unprivileged) { + char *cppathpath; + if (-1 == asprintf(&cppathpath, "%s/cppath", imagedir)) { + return 1; + } + + int fd = open(cppathpath, O_RDONLY); + if (fd < 0) { + perror("open cppath"); + return 1; + } + + char cppath[PATH_MAX]; + int cppathlen = 0; + int r; + while ((r = read(fd, cppath + cppathlen, sizeof(cppath) - cppathlen - 1)) != 0) { + if (r < 0 && errno == EINTR) { + continue; + } + if (r < 0) { + perror("read cppath"); + return 1; + } + cppathlen += r; + } + cppath[cppathlen] = '\0'; + + close(fd); + + if (read_pseudo_file(imagedir, restore_pseudo_persistent_file, + "restore pseudo persistent file ")) { + return 1; + } + + char *inherit_perfdata = NULL; + char *perfdatapath; + if (-1 == asprintf(&perfdatapath, "%s/" PERFDATA_NAME, imagedir)) { + return 1; + } + int perfdatafd = open(perfdatapath, O_RDWR); + if (0 < perfdatafd) { + if (-1 == asprintf(&inherit_perfdata, "fd[%d]:%s/" PERFDATA_NAME, + perfdatafd, + cppath[0] == '/' ? cppath + 1 : cppath)) { + return 1; + } + } + + const char* args[32] = { + criu, + "restore", + "-W", ".", + "--shell-job", + "--action-script", self, + "-D", imagedir, + "-v1" + }; + const char** arg = args + 10; + if (inherit_perfdata) { + *arg++ = "--inherit-fd"; + *arg++ = inherit_perfdata; + } + if (!strcmp("true", unprivileged)) { + *arg++ = "--unprivileged"; + } + + char *pipefds_path; + struct stat st; + if (-1 == asprintf(&pipefds_path, "%s/" PIPE_FDS, imagedir)) { + return 1; + } + if (stat(pipefds_path, &st) == 0) { + FILE *f = fopen(pipefds_path, "r"); + if (f == NULL) { + perror("open pipefds when restore failed"); + return 1; + } + char buff[1024]; + //the first line is the config,need skip + if (fgets(buff, sizeof(buff), f) == NULL) { + perror("read the first line of pipefds failed"); + fclose(f); + return 1; + } + + int cnt = 0; + while (fgets(buff, sizeof(buff), f) != NULL) { + if (cnt > MAX_PIPE_FDS) { + fprintf(stderr, "Support max pipe fds : %d, others are ignored!\n", MAX_PIPE_FDS); + break; + } + char *p = strchr(buff, '\n'); + if (p) { + *p = 0; + } + p = strchr(buff, ','); + if (!p) { + fprintf(stderr, "invalid %s file, miss comma. %s \n", pipefds_path, buff); + fclose(f); + return 1; + } + *p = 0; + int pipe_fd = atoi(buff); + *arg++ = "--inherit-fd"; + char *inherit_fd_value = NULL; + if (-1 == asprintf(&inherit_fd_value, "fd[%d]:%s", pipe_fd, p+1)) { + fclose(f); + return 1; + } + *arg++ = inherit_fd_value; + cnt++; + } + fclose(f); + } + + const char* tail[] = { + "--exec-cmd", "--", self, "restorewait", + NULL + }; + char *criuopts = getenv("CRAC_CRIU_OPTS"); + if (criuopts) { + char* criuopt = strtok(criuopts, " "); + while (criuopt && ARRAY_SIZE(args) >= (size_t)(arg - args + ARRAY_SIZE(tail))) { + *arg++ = criuopt; + criuopt = strtok(NULL, " "); + } + if (criuopt) { + fprintf(stderr, "Warning: too many arguments in CRAC_CRIU_OPTS (dropped from '%s')\n", criuopt); + } + } + + memcpy(arg, tail, sizeof(tail)); + + execv(criu, (char**)args); + perror("exec criu"); + return 1; +} + +#define MSGPREFIX "" + +static int post_resume(void) { + char *pidstr = getenv("CRTOOLS_INIT_PID"); + if (!pidstr) { + fprintf(stderr, MSGPREFIX "cannot find CRTOOLS_INIT_PID env\n"); + return 1; + } + int pid = atoi(pidstr); + + char *strid = getenv("CRAC_NEW_ARGS_ID"); + return kickjvm(pid, strid ? atoi(strid) : 0); +} + +static int copy_file(const char *to, const char *from) { + int fd_to, fd_from; + char buf[4096]; + ssize_t nread; + int saved_errno; + + fd_from = open(from, O_RDONLY); + if (fd_from < 0) + return -1; + + fd_to = open(to, O_WRONLY | O_CREAT | O_EXCL, 0666); + if (fd_to < 0) + goto out_error; + + while ((nread = read(fd_from, buf, sizeof buf)) > 0) { + char *out_ptr = buf; + ssize_t nwritten; + + do { + nwritten = write(fd_to, out_ptr, nread); + + if (nwritten >= 0) { + nread -= nwritten; + out_ptr += nwritten; + } else if (errno != EINTR) { + goto out_error; + } + } while (nread > 0); + } + + if (nread == 0) { + if (close(fd_to) < 0) { + fd_to = -1; + goto out_error; + } + close(fd_from); + + /* Success! */ + return 0; + } + + out_error: + saved_errno = errno; + + close(fd_from); + if (fd_to >= 0) + close(fd_to); + + errno = saved_errno; + return -1; +} + +static int checkpoint_pseudo_persistent_file(const char *imagedir, int id, int mode, const char *src) { + if ((mode & SAVE_ONLY) || (mode & SAVE_RESTORE)) { + char dest[PATH_MAX]; + snprintf(dest, PATH_MAX, "%s/%s%d%s", imagedir, PSEUDO_FILE_PREFIX, id, PSEUDO_FILE_SUFFIX); + return copy_file(dest, src); + } else { + return 0; + } +} + +static int do_mkdir(const char *path, mode_t mode) { + struct stat st; + int status = 0; + + if (stat(path, &st) != 0) { + /* Directory does not exist. EEXIST for race condition */ + if (mkdir(path, mode) != 0 && errno != EEXIST) + status = -1; + } else if (!S_ISDIR(st.st_mode)) { + errno = ENOTDIR; + status = -1; + } + return status; +} + +int mkpath(const char *path, mode_t mode) { + char *pp; + char *sp; + int status; + char *copypath = strdup(path); + + status = 0; + pp = copypath; + while (status == 0 && (sp = strchr(pp, '/')) != 0) { + if (sp != pp) { + /* Neither root nor double slash in path */ + *sp = '\0'; + status = do_mkdir(copypath, mode); + *sp = '/'; + } + pp = sp + 1; + } + free(copypath); + return status; +} + +static int restore_pseudo_persistent_file(const char *imagedir, int id, int mode, const char *dest) { + if (!(mode & SAVE_RESTORE) && !(mode & SKIP_LOG_FILES)) { + return 0; + } + struct stat st; + if (stat(dest, &st) == 0) { + //default action is skip if dest file exist. + if (!(mode & OVERRIDE_WHEN_RESTORE)) { + return 0; + } + } + + char src[PATH_MAX]; + snprintf(src, PATH_MAX, "%s/%s%d%s", imagedir, PSEUDO_FILE_PREFIX, id, PSEUDO_FILE_SUFFIX); + if (mode & COPY_WHEN_RESTORE) { + if (mkpath(dest, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) { + perror(dest); + return 1; + } + return copy_file(dest, src); + } else if (mode & SYMLINK_WHEN_RESTORE) { + if (mkpath(dest, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) { + perror(dest); + return 1; + } + if (symlink(src, dest)) { + fprintf(stderr, "symlink %s to %s failed ,error: %s \n", + dest, src, strerror(errno)); + return 1; + } + } else if (mode & SKIP_LOG_FILES) { + if (mkpath(dest, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) { + perror(dest); + return 1; + } + //create empty file for skip log files. + FILE *f = fopen(dest, "a"); + if (f == NULL) { + fprintf(stderr, "create file %s for log file failed. error: %s\n", + dest, strerror(errno)); + return 1; + } + fclose(f); + } + return 0; +} + +static int read_pseudo_file(const char *imagedir, pseudo_file_func func, const char* desc) { + char filepath[PATH_MAX]; + snprintf(filepath, PATH_MAX, "%s/pseudopersistent", imagedir); + + //this file is not mandatory, return success if not exist. + struct stat st; + if (stat(filepath, &st) != 0) { + return 0; + } + + FILE *f = fopen(filepath, "r"); + if (f == NULL) { + fprintf(stderr, "open file: %s for read failed, error: %s \n", + filepath, strerror(errno)); + return 1; + } + + int ret = 0; + int id = 0; + //an integer,file path + char buff[PATH_MAX + 32]; + while (fgets(buff, sizeof(buff), f) != NULL) { + char *p = strchr(buff, '\n'); + if (p) { + *p = 0; + } + p = strchr(buff, ','); + if (!p) { + fprintf(stderr, "invalid %s file, miss comma\n", filepath); + ret = 1; + break; + } + *p = 0; + int mode = atoi(buff); + + if (func(imagedir, id, mode, p + 1)) { + fprintf(stderr, "%s with file %s failed \n", desc, p + 1); + ret = 1; + break; + } + id++; + } + fclose(f); + return ret; +} + +static int post_dump(void) { + //"CRTOOLS_IMAGE_DIR" is a symbol link like "/proc/227/fd/100/", + //and 227 is the pid of criu.If run with unprivileged mode, current + //process has no permission to access the fd 100. + //So use CRAC_IMAGE_DIR environment which setting before checkpointing. + char *imagedir = getenv("CRAC_IMAGE_DIR"); + if (!imagedir) { + fprintf(stderr, MSGPREFIX "cannot find CRAC_IMAGE_DIR env\n"); + return 1; + } + char *jvm_pid= getenv("CRTOOLS_INIT_PID"); + if (!jvm_pid) { + fprintf(stderr, MSGPREFIX "cannot find CRTOOLS_INIT_PID env in post_dump callback\n"); + return 1; + } + if (append_pipeinfo(imagedir, jvm_pid)) { + return 1; + } + return read_pseudo_file(imagedir, checkpoint_pseudo_persistent_file, "checkpoint pseudo persistent file "); +} + +static int create_cppath(const char *imagedir) { + char realdir[PATH_MAX]; + + if (!realpath(imagedir, realdir)) { + fprintf(stderr, MSGPREFIX "cannot canonicalize %s: %s\n", imagedir, strerror(errno)); + return 1; + } + + int dirfd = open(realdir, O_DIRECTORY); + if (dirfd < 0) { + fprintf(stderr, MSGPREFIX "can not open image dir %s: %s\n", realdir, strerror(errno)); + return 1; + } + + int fd = openat(dirfd, "cppath", O_CREAT | O_WRONLY | O_TRUNC, 0644); + if (fd < 0) { + fprintf(stderr, MSGPREFIX "can not open file %s/cppath: %s\n", realdir, strerror(errno)); + return 1; + } + + if (write(fd, realdir, strlen(realdir)) < 0) { + fprintf(stderr, MSGPREFIX "can not write %s/cppath: %s\n", realdir, strerror(errno)); + return 1; + } + return 0; +} + +static void sighandler(int sig, siginfo_t *info, void *uc) { + if (0 <= g_pid) { + kill(g_pid, sig); + } +} + +static int restorewait(void) { + char *pidstr = getenv("CRTOOLS_INIT_PID"); + if (!pidstr) { + fprintf(stderr, MSGPREFIX "no CRTOOLS_INIT_PID: signals may not be delivered\n"); + } + g_pid = pidstr ? atoi(pidstr) : -1; + + struct sigaction sigact; + sigfillset(&sigact.sa_mask); + sigact.sa_flags = SA_SIGINFO; + sigact.sa_sigaction = sighandler; + + int sig; + for (sig = 1; sig <= 31; ++sig) { + if (sig == SIGKILL || sig == SIGSTOP) { + continue; + } + if (-1 == sigaction(sig, &sigact, NULL)) { + perror("sigaction"); + } + } + + sigset_t allset; + sigfillset(&allset); + if (-1 == sigprocmask(SIG_UNBLOCK, &allset, NULL)) { + perror(MSGPREFIX "sigprocmask"); + } + + int status; + int ret; + do { + ret = waitpid(g_pid, &status, 0); + } while (ret == -1 && errno == EINTR); + + if (ret == -1) { + perror(MSGPREFIX "waitpid"); + return 1; + } + + if (WIFEXITED(status)) { + return WEXITSTATUS(status); + } + + if (WIFSIGNALED(status)) { + // Try to terminate the current process with the same signal + // as the child process was terminated + const int sig = WTERMSIG(status); + signal(sig, SIG_DFL); + raise(sig); + // Signal was ignored, return 128+n as bash does + // see https://linux.die.net/man/1/bash + return 128+sig; + } + + return 1; +} + +int main(int argc, char *argv[]) { + char* action; + if ((action = argv[1])) { + char* imagedir = argv[2]; + + char *basedir = dirname(strdup(argv[0])); + + char *criu = getenv("CRAC_CRIU_PATH"); + if (!criu) { + if (-1 == asprintf(&criu, "%s/criu", basedir)) { + return 1; + } + struct stat st; + if (stat(criu, &st)) { + /* some problem with the bundled criu */ + criu = "/usr/sbin/criu"; + if (stat(criu, &st)) { + fprintf(stderr, "cannot find CRIU to use\n"); + return 1; + } + } + } + + if (!strcmp(action, "checkpoint")) { + pid_t jvm = getppid(); + return checkpoint(jvm, basedir, argv[0], criu, imagedir, argv[3], + argv[4], argv[5], argv[6]); + } else if (!strcmp(action, "restore")) { + return restore(basedir, argv[0], criu, imagedir, argv[3]); + } else if (!strcmp(action, + "restorewait")) { // called by CRIU --exec-cmd + return restorewait(); + } else if (!strcmp(action, "restorevalidate")) { + return restore_validate(criu, imagedir, argv[3], argv[4]); + } else { + fprintf(stderr, "unknown command-line action: %s\n", action); + return 1; + } + } else if ((action = getenv("CRTOOLS_SCRIPT_ACTION"))) { // called by CRIU --action-script + if (!strcmp(action, "post-resume")) { + return post_resume(); + } else if (!strcmp(action, "post-dump")) { + return post_dump(); + } else { + // ignore other notifications + return 0; + } + } else { + fprintf(stderr, "unknown context\n"); + } + + return 1; +} + +static int restore_validate(char *criu, char *image_dir, char *jvm_version, const char* unprivileged) { + const char *args[32] = {criu, "cpuinfo", "check", "--cpu-cap=jvm", + "-D", image_dir, NULL}; + if (add_unprivileged_opt(unprivileged, args, ARRAY_SIZE(args))) { + return -1; + } + if (!check_metadata(image_dir, jvm_version)) { + return exec_criu_command(criu, args); + } + return -1; +} + +static int check_metadata(char *image_dir, char *jvm_version) { + char *metadata_path; + char buff[1024]; + int ret = -1; + if (-1 == asprintf(&metadata_path, "%s/" METADATA, image_dir)) { + return -1; + } + FILE *f = fopen(metadata_path, "r"); + if (f == NULL) { + fprintf(stderr, "open file: %s for read failed, error: %s \n", + metadata_path, strerror(errno)); + free(metadata_path); + return -1; + } + + if (fgets(buff, sizeof(buff), f) == NULL) { + fprintf(stderr, "empty metadata\n"); + } else { + ret = strncmp(jvm_version, buff, strlen(jvm_version)); + if (ret) { + fprintf(stderr, "vm version %s != %s\n", buff, jvm_version); + } + } + fclose(f); + free(metadata_path); + return ret; +} + +static int create_metadata(const char *image_dir, const char *jvm_version) { + char *metadata_path; + int ret = -1; + + if (-1 == asprintf(&metadata_path, "%s/" METADATA, image_dir)) { + return -1; + } + FILE *f = fopen(metadata_path, "w"); + if (f == NULL) { + fprintf(stderr, "open file: %s for write failed, error: %s\n", + metadata_path, strerror(errno)); + free(metadata_path); + return -1; + } + + if (fprintf(f, "%s\n", jvm_version) < 0) { + fprintf(stderr, "write jvm version to metadata failed!\n"); + } else { + ret = 0; + } + + fclose(f); + free(metadata_path); + return ret; +} + +static int exec_criu_command(const char *criu, const char *args[]) { + pid_t pid = fork(); + if (pid < 0) { + perror("cannot fork for criu"); + return -1; + } else if (pid == 0) { + execv(criu, (char **)args); + perror("execv"); + exit(1); + } + + int status; + int ret; + do { + ret = waitpid(pid, &status, 0); + } while (ret == -1 && errno == EINTR); + + if (ret == -1 || !WIFEXITED(status)) { + return -1; + } + return WEXITSTATUS(status) == 0 ? 0 : -1; +} + +static int write_config_pipefds(const char *imagedir, const char *config_pipefds) { + char *path = NULL; + int ret = 0; + if (-1 == asprintf(&path, "%s/" PIPE_FDS, imagedir)) { + return -1; + } + FILE *f = fopen(path, "w"); + if (f == NULL) { + ret = -1; + perror("open pipefds when write config failed"); + goto err; + } + + if (fprintf(f, "%s\n", config_pipefds) < 0) { + ret = -1; + perror("write config to pipefds failed"); + goto err; + } + +err: + if (f != NULL) { + fclose(f); + } + free(path); + return ret; +} + +static int append_pipeinfo(const char* imagedir,const char* jvm_pid) { + struct stat st; + char *path = NULL; + char buff[1024]; + + if (-1 == asprintf(&path, "%s/" PIPE_FDS, imagedir)) { + fprintf(stderr, "asprintf for pipefds failed. imagedir:%s", imagedir); + return -1; + } + + if (stat(path, &st) != 0) { + fprintf(stderr, "%s not exist.", path); + free(path); + return 0; + } + + FILE* f = fopen(path, "a+"); + if (f == NULL) { + perror("open pipefds for appending failed"); + free(path); + return -1; + } + + free(path); + + if (fgets(buff, sizeof(buff), f) == NULL) { + perror("read from pipefds error"); + fclose(f); + return -1; + } + + char *p = strchr(buff, '\n'); + if (p) { + *p = 0; + } + char fdpath[PATH_MAX]; + char *token = strtok(buff, ","); + while (token != NULL) { + int fd = atoi(token); + if (is_exist_pipefd(jvm_pid, fd)) { + if (read_fd_link(jvm_pid, fd, fdpath, sizeof(fdpath)) == -1) { + fclose(f); + return -1; + } + fprintf(f, "%d,%s\n", fd, fdpath); + } + token = strtok(NULL, ","); + } + fclose(f); + return 0; +} + +static int is_exist_pipefd(const char *jvm_pid, int fd) { + struct statfs fsbuf; + char fdpath[64]; + snprintf(fdpath, sizeof(fdpath), "/proc/%s/fd/%d", jvm_pid, fd); + if (statfs(fdpath, &fsbuf) < 0) { + return 0; + } + return fsbuf.f_type == PIPEFS_MAGIC; +} + +static int read_fd_link(const char* jvm_pid, int fd, char *link, size_t len) { + char fdpath[64]; + snprintf(fdpath, sizeof(fdpath), "/proc/%s/fd/%d", jvm_pid, fd); + int ret = readlink(fdpath, link, len); + if (ret == -1) { + perror(fdpath); + return ret; + } + link[(unsigned)ret < len ? (unsigned)ret : len - 1] = '\0'; + return ret; +} diff --git a/src/java.base/unix/native/libjli/java_md_solinux.c b/src/java.base/unix/native/libjli/java_md_solinux.c index 3cf06cdc4b6..ece6829a183 100644 --- a/src/java.base/unix/native/libjli/java_md_solinux.c +++ b/src/java.base/unix/native/libjli/java_md_solinux.c @@ -756,6 +756,15 @@ CallJavaMainInNewThread(jlong stack_size, void* args) { pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + { + const int restore_signal = SIGRTMIN + 2; + // block restore_signal in launcher thread to allow JVM handle it + sigset_t block_sig; + sigemptyset(&block_sig); + sigaddset(&block_sig, restore_signal); + pthread_sigmask(SIG_BLOCK, &block_sig, NULL); + } + if (stack_size > 0) { pthread_attr_setstacksize(&attr, stack_size); } diff --git a/src/java.base/unix/native/libnet/PlainSocketImpl.c b/src/java.base/unix/native/libnet/PlainSocketImpl.c index 57677190ab0..409c45bb94b 100644 --- a/src/java.base/unix/native/libnet/PlainSocketImpl.c +++ b/src/java.base/unix/native/libnet/PlainSocketImpl.c @@ -144,6 +144,19 @@ Java_java_net_PlainSocketImpl_initProto(JNIEnv *env, jclass cls) { marker_fd = getMarkerFD(); } +JNIEXPORT void JNICALL +Java_java_net_PlainSocketImpl_beforeCheckpoint0(JNIEnv *env, jclass cls) { + /* synchronized by closeLock */ + close(marker_fd); + marker_fd = -1; +} + +JNIEXPORT void JNICALL +Java_java_net_PlainSocketImpl_afterRestore0(JNIEnv *env, jclass cls) { + /* synchronized by closeLock */ + marker_fd = getMarkerFD(); +} + /* a global reference to the java.net.SocketException class. In * socketCreate, we ensure that this is initialized. This is to * prevent the problem where socketCreate runs out of file @@ -766,7 +779,7 @@ Java_java_net_PlainSocketImpl_socketAvailable(JNIEnv *env, jobject this) { * Signature: (Z)V */ JNIEXPORT void JNICALL -Java_java_net_PlainSocketImpl_socketClose0(JNIEnv *env, jobject this, +Java_java_net_PlainSocketImpl_socketClose1(JNIEnv *env, jobject this, jboolean useDeferredClose) { jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID); diff --git a/src/java.base/unix/native/libnio/ch/FileDispatcherImpl.c b/src/java.base/unix/native/libnio/ch/FileDispatcherImpl.c index e31142ad36d..d3f22f7aa05 100644 --- a/src/java.base/unix/native/libnio/ch/FileDispatcherImpl.c +++ b/src/java.base/unix/native/libnio/ch/FileDispatcherImpl.c @@ -75,6 +75,27 @@ Java_sun_nio_ch_FileDispatcherImpl_init(JNIEnv *env, jclass cl) close(sp[1]); } +JNIEXPORT void JNICALL +Java_sun_nio_ch_FileDispatcherImpl_beforeCheckpoint0(JNIEnv *env, jclass cl) +{ + /* synchronized by closeLock */ + close(preCloseFD); + preCloseFD = -1; +} + +JNIEXPORT void JNICALL +Java_sun_nio_ch_FileDispatcherImpl_afterRestore0(JNIEnv *env, jclass cl) +{ + /* synchronized by closeLock */ + int sp[2]; + if (socketpair(PF_UNIX, SOCK_STREAM, 0, sp) < 0) { + JNU_ThrowIOExceptionWithLastError(env, "socketpair failed"); + return; + } + preCloseFD = sp[0]; + close(sp[1]); +} + JNIEXPORT jint JNICALL Java_sun_nio_ch_FileDispatcherImpl_read0(JNIEnv *env, jclass clazz, jobject fdo, jlong address, jint len) diff --git a/src/java.base/unix/native/pauseengine/pauseengine.c b/src/java.base/unix/native/pauseengine/pauseengine.c new file mode 100644 index 00000000000..c83f3fa48db --- /dev/null +++ b/src/java.base/unix/native/pauseengine/pauseengine.c @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +#include +#include +#include +#include +#include + +#define RESTORE_SIGNAL (SIGRTMIN + 2) + +static int kickjvm(pid_t jvm, int code) { + union sigval sv = { .sival_int = code }; + if (-1 == sigqueue(jvm, RESTORE_SIGNAL, sv)) { + perror("sigqueue"); + return 1; + } + return 0; +} + +int main(int argc, char *argv[]) { + char* action = argv[1]; + char* imagedir = argv[2]; + + char *pidpath; + if (-1 == asprintf(&pidpath, "%s/pid", imagedir)) { + return 1; + } + + if (!strcmp(action, "checkpoint")) { + pid_t jvm = getppid(); + + FILE *pidfile = fopen(pidpath, "w"); + if (!pidfile) { + perror("fopen pidfile"); + kickjvm(jvm, -1); + return 1; + } + + fprintf(pidfile, "%d\n", jvm); + fclose(pidfile); + + } else if (!strcmp(action, "restore")) { + FILE *pidfile = fopen(pidpath, "r"); + if (!pidfile) { + perror("fopen pidfile"); + return 1; + } + + pid_t jvm; + if (1 != fscanf(pidfile, "%d", &jvm)) { + fclose(pidfile); + fprintf(stderr, "cannot read pid\n"); + return 1; + } + fclose(pidfile); + + char *strid = getenv("CRAC_NEW_ARGS_ID"); + if (kickjvm(jvm, strid ? atoi(strid) : 0)) { + return 1; + } + + } else if (!strcmp(action, "restorevalidate")) { + return 0; + } else { + fprintf(stderr, "unknown action: %s\n", action); + return 1; + } + + return 0; +} diff --git a/src/java.base/unix/native/simengine/simengine.c b/src/java.base/unix/native/simengine/simengine.c new file mode 100644 index 00000000000..4ff4b02f79c --- /dev/null +++ b/src/java.base/unix/native/simengine/simengine.c @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2021, Azul Systems, Inc. All rights reserved. + * Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +#include +#include +#include +#include +#include + +#define RESTORE_SIGNAL (SIGRTMIN + 2) + +static int kickjvm(pid_t jvm, int code) { + union sigval sv = { .sival_int = code }; + if (-1 == sigqueue(jvm, RESTORE_SIGNAL, sv)) { + perror("sigqueue"); + return 1; + } + return 0; +} + +int main(int argc, char *argv[]) { + char* action = argv[1]; + + if (!strcmp(action, "checkpoint")) { + const char* argsidstr = getenv("SIM_CRAC_NEW_ARGS_ID"); + int argsid = argsidstr ? atoi(argsidstr) : 0; + pid_t jvm = getppid(); + kickjvm(jvm, argsid); + } else if (!strcmp(action, "restore")) { + char *strid = getenv("CRAC_NEW_ARGS_ID"); + printf("SIM_CRAC_NEW_ARGS_ID=%s\n", strid ? strid : "0"); + } else if (!strcmp(action, "restorevalidate")) { + return 0; + } else { + fprintf(stderr, "unknown action: %s\n", action); + return 1; + } + + return 0; +} diff --git a/src/java.management/share/classes/sun/management/VMManagement.java b/src/java.management/share/classes/sun/management/VMManagement.java index f4445f0225a..b959858a441 100644 --- a/src/java.management/share/classes/sun/management/VMManagement.java +++ b/src/java.management/share/classes/sun/management/VMManagement.java @@ -105,4 +105,8 @@ public interface VMManagement { // Performance counter support public List getInternalCounters(String pattern); + + // CRaC support + public long getRestoreTime(); + public long getUptimeSinceRestore(); } diff --git a/src/java.management/share/classes/sun/management/VMManagementImpl.java b/src/java.management/share/classes/sun/management/VMManagementImpl.java index 595a6bf48f5..fd7cd50d552 100644 --- a/src/java.management/share/classes/sun/management/VMManagementImpl.java +++ b/src/java.management/share/classes/sun/management/VMManagementImpl.java @@ -280,4 +280,16 @@ public List getInternalCounters(String pattern) { return Collections.emptyList(); } } + + // CRaC support + private native long getRestoreTime0(); + private native long getUptimeSinceRestore0(); + + public long getRestoreTime() { + return getRestoreTime0(); + } + + public long getUptimeSinceRestore() { + return getUptimeSinceRestore0(); + } } diff --git a/src/java.management/share/native/libmanagement/VMManagementImpl.c b/src/java.management/share/native/libmanagement/VMManagementImpl.c index 38a5b6af0cc..d3458656e28 100644 --- a/src/java.management/share/native/libmanagement/VMManagementImpl.c +++ b/src/java.management/share/native/libmanagement/VMManagementImpl.c @@ -322,3 +322,19 @@ Java_sun_management_VMManagementImpl_getClassVerificationTime return jmm_interface->GetLongAttribute(env, NULL, JMM_CLASS_VERIFY_TOTAL_TIME_MS); } + +JNIEXPORT jlong JNICALL +Java_sun_management_VMManagementImpl_getRestoreTime0 + (JNIEnv *env, jobject dummy) +{ + return jmm_interface->GetLongAttribute(env, NULL, + JMM_JVM_RESTORE_START_TIME_MS); +} + +JNIEXPORT jlong JNICALL +Java_sun_management_VMManagementImpl_getUptimeSinceRestore0 + (JNIEnv *env, jobject dummy) +{ + return jmm_interface->GetLongAttribute(env, NULL, + JMM_JVM_UPTIME_SINCE_RESTORE_MS); +} diff --git a/src/java.rmi/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java b/src/java.rmi/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java index a5209affe3a..35b4c6d1727 100644 --- a/src/java.rmi/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java +++ b/src/java.rmi/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java @@ -46,6 +46,11 @@ import java.util.LinkedList; import java.util.Map; import java.util.Set; + +import jdk.crac.Context; +import jdk.internal.crac.Core; +import jdk.internal.crac.JDKResource; +import jdk.crac.Resource; import sun.rmi.runtime.Log; import sun.rmi.runtime.NewThreadAction; import sun.rmi.transport.Channel; @@ -78,6 +83,8 @@ public class TCPEndpoint implements Endpoint { private static String localHost; /** true if real local host name is known yet */ private static boolean localHostKnown; + /** Resample localhost */ + private static JDKResource localHostResource; // this should be a *private* method since it is privileged private static int getInt(String name, int def) { @@ -105,42 +112,64 @@ private static String getHostnameProperty() { * inablility to get fully qualified host name from VM. */ static { - localHostKnown = true; - localHost = getHostnameProperty(); + Runnable getLocalHostRunnable = () -> { + localHostKnown = true; + localHost = getHostnameProperty(); - // could try querying CGI program here? - if (localHost == null) { - try { - InetAddress localAddr = InetAddress.getLocalHost(); - byte[] raw = localAddr.getAddress(); - if ((raw[0] == 127) && - (raw[1] == 0) && - (raw[2] == 0) && - (raw[3] == 1)) { - localHostKnown = false; - } + // could try querying CGI program here? + if (localHost == null) { + try { + InetAddress localAddr = InetAddress.getLocalHost(); + byte[] raw = localAddr.getAddress(); + if ((raw[0] == 127) && + (raw[1] == 0) && + (raw[2] == 0) && + (raw[3] == 1)) { + localHostKnown = false; + } - /* if the user wishes to use a fully qualified domain - * name then attempt to find one. - */ - if (getBoolean("java.rmi.server.useLocalHostName")) { - localHost = FQDN.attemptFQDN(localAddr); - } else { - /* default to using ip addresses, names will - * work across seperate domains. + /* if the user wishes to use a fully qualified domain + * name then attempt to find one. */ - localHost = localAddr.getHostAddress(); + if (getBoolean("java.rmi.server.useLocalHostName")) { + localHost = FQDN.attemptFQDN(localAddr); + } else { + /* default to using ip addresses, names will + * work across seperate domains. + */ + localHost = localAddr.getHostAddress(); + } + } catch (Exception e) { + localHostKnown = false; + localHost = null; } - } catch (Exception e) { - localHostKnown = false; - localHost = null; } - } - if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) { - TCPTransport.tcpLog.log(Log.BRIEF, - "localHostKnown = " + localHostKnown + - ", localHost = " + localHost); + if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) { + TCPTransport.tcpLog.log(Log.BRIEF, + "localHostKnown = " + localHostKnown + + ", localHost = " + localHost); + } + }; + getLocalHostRunnable.run(); + + if (jdk.crac.Configuration.checkpointEnabled()) { + localHostResource = new JDKResource() { + @Override + public Priority getPriority() { + return Priority.NORMAL; + } + + @Override + public void beforeCheckpoint(Context context) throws Exception { + } + + @Override + public void afterRestore(Context context) throws Exception { + getLocalHostRunnable.run(); + } + }; + Core.getJDKContext().register(localHostResource); } } diff --git a/src/java.rmi/share/classes/sun/rmi/transport/tcp/TCPTransport.java b/src/java.rmi/share/classes/sun/rmi/transport/tcp/TCPTransport.java index 251b7f6cef8..641b9f124a2 100644 --- a/src/java.rmi/share/classes/sun/rmi/transport/tcp/TCPTransport.java +++ b/src/java.rmi/share/classes/sun/rmi/transport/tcp/TCPTransport.java @@ -67,7 +67,6 @@ import java.util.concurrent.atomic.AtomicInteger; import sun.rmi.runtime.Log; import sun.rmi.runtime.NewThreadAction; -import sun.rmi.transport.Channel; import sun.rmi.transport.Connection; import sun.rmi.transport.DGCAckHandler; import sun.rmi.transport.Endpoint; diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/builder/DefaultImageBuilder.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/builder/DefaultImageBuilder.java index aa3c66d44f2..06e339056ad 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/builder/DefaultImageBuilder.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/builder/DefaultImageBuilder.java @@ -200,8 +200,12 @@ public void storeFiles(ResourcePool files) { Path lib = root.resolve(LIB_DIRNAME); if (Files.isDirectory(lib)) { Files.find(lib, 2, (path, attrs) -> { - return path.getFileName().toString().equals("jspawnhelper") - || path.getFileName().toString().equals("jexec"); + String fileName = path.getFileName().toString(); + return fileName.equals("jspawnhelper") + || fileName.equals("jexec") + || fileName.equals("criuengine") + || fileName.equals("pauseengine") + || fileName.equals("simengine"); }).forEach(this::setExecutable); } diff --git a/src/jdk.management/share/classes/com/sun/management/internal/PlatformMBeanProviderImpl.java b/src/jdk.management/share/classes/com/sun/management/internal/PlatformMBeanProviderImpl.java index 6c4f61e7e93..63a25543a85 100644 --- a/src/jdk.management/share/classes/com/sun/management/internal/PlatformMBeanProviderImpl.java +++ b/src/jdk.management/share/classes/com/sun/management/internal/PlatformMBeanProviderImpl.java @@ -28,6 +28,7 @@ import com.alibaba.management.WispCounterMXBean; import com.alibaba.management.internal.ResourceContainerMXBeanImpl; import com.alibaba.management.internal.WispCounterMXBeanImpl; +import jdk.crac.management.CRaCMXBean; import com.sun.management.DiagnosticCommandMBean; import com.sun.management.HotSpotDiagnosticMXBean; import com.sun.management.ThreadMXBean; @@ -45,6 +46,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import javax.management.DynamicMBean; + +import jdk.crac.management.internal.CRaCImpl; import sun.management.ManagementFactoryHelper; import sun.management.spi.PlatformMBeanProvider; @@ -58,6 +61,11 @@ public final class PlatformMBeanProviderImpl extends PlatformMBeanProvider { private static WispCounterMXBean wispCounterMBean = null; private static ResourceContainerMXBean resourceContainerMXBean = null; + // CRaC + private static CRaCMXBean cracMXBean = null; + public static final String CRAC_MXBEAN_NAME = + "jdk.management:type=CRaC"; + static { AccessController.doPrivileged((PrivilegedAction) () -> { System.loadLibrary("management_ext"); @@ -331,6 +339,32 @@ public Map nameToMBeanMap() { }); + /** + * CRaC MXBean + */ + initMBeanList.add(new PlatformComponent() { + private final Set cracMXBeanInterfaceNames = + Collections.singleton("jdk.crac.management.CRaCMXBean"); + @Override + public Set> mbeanInterfaces() { + return Collections.singleton(CRaCMXBean.class); + } + @Override + public Set mbeanInterfaceNames() { + return cracMXBeanInterfaceNames; + } + @Override + public String getObjectNamePattern() { + return CRAC_MXBEAN_NAME; + } + @Override + public Map nameToMBeanMap() { + return Collections.singletonMap( + CRAC_MXBEAN_NAME, + getCRaCMXBean()); + } + }); + initMBeanList.trimToSize(); return initMBeanList; } @@ -362,4 +396,11 @@ private static synchronized WispCounterMXBean getWispCounterMXBean() { } return wispCounterMBean; } + + private static synchronized CRaCMXBean getCRaCMXBean() { + if (cracMXBean == null) { + cracMXBean = new CRaCImpl(ManagementFactoryHelper.getVMManagement()); + } + return cracMXBean; + } } diff --git a/src/jdk.management/share/classes/jdk/crac/management/CRaCMXBean.java b/src/jdk.management/share/classes/jdk/crac/management/CRaCMXBean.java new file mode 100644 index 00000000000..c881e2ecdbb --- /dev/null +++ b/src/jdk.management/share/classes/jdk/crac/management/CRaCMXBean.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.crac.management; + +import java.lang.management.ManagementFactory; +import java.lang.management.PlatformManagedObject; +import java.lang.management.RuntimeMXBean; + +/** + * Management interface for the CRaC functionality of the Java virtual machine. + */ +public interface CRaCMXBean extends PlatformManagedObject { + + /** + * Returns the time since the Java virtual machine restore was initiated. + * If the machine was not restored, returns -1. + * + * @see RuntimeMXBean#getStartTime() + * @return uptime of the Java virtual machine in milliseconds. + */ + public long getUptimeSinceRestore(); + + /** + * Returns the time when the Java virtual machine restore was initiated. + * The value is the number of milliseconds since the start of the epoch. + * If the machine was not restored, returns -1. + * + * @see RuntimeMXBean#getUptime() + * @return start time of the Java virtual machine in milliseconds. + */ + public long getRestoreTime(); + + /** + * Returns the implementation of the MXBean. + * + * @return implementation of the MXBean. + */ + public static CRaCMXBean getCRaCMXBean() { + return ManagementFactory.getPlatformMXBean(CRaCMXBean.class); + } + +} diff --git a/src/jdk.management/share/classes/jdk/crac/management/internal/CRaCImpl.java b/src/jdk.management/share/classes/jdk/crac/management/internal/CRaCImpl.java new file mode 100644 index 00000000000..c4ae9911031 --- /dev/null +++ b/src/jdk.management/share/classes/jdk/crac/management/internal/CRaCImpl.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.crac.management.internal; + +import com.sun.management.internal.PlatformMBeanProviderImpl; +import jdk.crac.management.CRaCMXBean; +import sun.management.Util; +import sun.management.VMManagement; + +import javax.management.ObjectName; + +public class CRaCImpl implements CRaCMXBean { + private final VMManagement vm; + + public CRaCImpl(VMManagement vm) { + this.vm = vm; + } + + @Override + public long getUptimeSinceRestore() { + return vm.getUptimeSinceRestore(); + } + + @Override + public long getRestoreTime() { + return vm.getRestoreTime(); + } + + @Override + public ObjectName getObjectName() { + return Util.newObjectName(PlatformMBeanProviderImpl.CRAC_MXBEAN_NAME); + } +} diff --git a/src/jdk.management/share/classes/jdk/crac/management/package-info.java b/src/jdk.management/share/classes/jdk/crac/management/package-info.java new file mode 100644 index 00000000000..66970631b79 --- /dev/null +++ b/src/jdk.management/share/classes/jdk/crac/management/package-info.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +/** + * This package contains management interfaces for CRaC. + */ + +package jdk.crac.management; diff --git a/src/jdk.management/share/classes/module-info.java b/src/jdk.management/share/classes/module-info.java index c0d7ec3a02a..719d875e34b 100644 --- a/src/jdk.management/share/classes/module-info.java +++ b/src/jdk.management/share/classes/module-info.java @@ -34,8 +34,8 @@ exports com.sun.management; exports com.alibaba.management; + exports jdk.crac.management; provides sun.management.spi.PlatformMBeanProvider with com.sun.management.internal.PlatformMBeanProviderImpl; } - diff --git a/test/jdk/TEST.ROOT b/test/jdk/TEST.ROOT index 02930fc4d7d..9802f49c3fb 100644 --- a/test/jdk/TEST.ROOT +++ b/test/jdk/TEST.ROOT @@ -31,7 +31,7 @@ exclusiveAccess.dirs=java/rmi/Naming java/util/prefs sun/management/jmxremote su sun/security/mscapi java/util/stream java/util/Arrays/largeMemory \ java/util/BitSet/stream javax/rmi java/net/httpclient/websocket \ sanity/client sun/tools/jhsdb \ -com/alibaba/wisp/exclusive com/alibaba/wisp2/exclusive +com/alibaba/wisp/exclusive com/alibaba/wisp2/exclusive jdk/crac/java # Group definitions groups=TEST.groups diff --git a/test/jdk/java/util/zip/ZipFile/FinalizeZipFile.java b/test/jdk/java/util/zip/ZipFile/FinalizeZipFile.java deleted file mode 100644 index 8641e183341..00000000000 --- a/test/jdk/java/util/zip/ZipFile/FinalizeZipFile.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (c) 2010, 2014, Oracle and/or its affiliates. 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. - */ - -/* @test - * @bug 7007609 7009618 - * @summary Check that ZipFile objects are always collected - * @key randomness - */ - -import java.io.*; -import java.util.Random; -import java.util.zip.*; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -public class FinalizeZipFile { - - private static final CountDownLatch finalizersDone = new CountDownLatch(3); - - private static class InstrumentedZipFile extends ZipFile { - - public InstrumentedZipFile(File f) throws Exception { - super(f); - System.out.printf("Using %s%n", f.getPath()); - } - @Override - protected void finalize() throws IOException { - System.out.printf("Killing %s%n", getName()); - super.finalize(); - finalizersDone.countDown(); - } - } - - private static void makeGarbage() throws Throwable { - final Random rnd = new Random(); - // Create some ZipFiles. - // Find some .jar files in test directory. - final File testdir = new File(System.getProperty("test.src", ".")); - check(testdir.isDirectory()); - final File[] jars = testdir.listFiles( - new FilenameFilter() { - public boolean accept(File dir, String name) { - return name.endsWith(".jar");}}); - check(jars.length > 1); - - new InstrumentedZipFile(jars[rnd.nextInt(jars.length)]).close(); - new InstrumentedZipFile(jars[rnd.nextInt(jars.length)]).close(); - - // Create a ZipFile and get an input stream from it - for (int i = 0; i < jars.length + 10; i++) { - ZipFile zf = new InstrumentedZipFile(jars[rnd.nextInt(jars.length)]); - ZipEntry ze = zf.getEntry("META-INF/MANIFEST.MF"); - if (ze != null) { - InputStream is = zf.getInputStream(ze); - break; - } - } - } - - public static void realMain(String[] args) throws Throwable { - makeGarbage(); - while (!finalizersDone.await(10, TimeUnit.MILLISECONDS)) { - System.gc(); - } - // Not all ZipFiles were collected? - equal(finalizersDone.getCount(), 0L); - } - - //--------------------- Infrastructure --------------------------- - static volatile int passed = 0, failed = 0; - static void pass() {passed++;} - static void fail() {failed++; Thread.dumpStack();} - static void fail(String msg) {System.out.println(msg); fail();} - static void unexpected(Throwable t) {failed++; t.printStackTrace();} - static void check(boolean cond) {if (cond) pass(); else fail();} - static void equal(Object x, Object y) { - if (x == null ? y == null : x.equals(y)) pass(); - else fail(x + " not equal to " + y);} - public static void main(String[] args) throws Throwable { - try {realMain(args);} catch (Throwable t) {unexpected(t);} - System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed); - if (failed > 0) throw new AssertionError("Some tests failed");} -} diff --git a/test/jdk/java/util/zip/ZipFile/TestCleaner.java b/test/jdk/java/util/zip/ZipFile/TestCleaner.java index 82c6209dccc..d24aa5045fd 100644 --- a/test/jdk/java/util/zip/ZipFile/TestCleaner.java +++ b/test/jdk/java/util/zip/ZipFile/TestCleaner.java @@ -31,8 +31,6 @@ import java.io.*; import java.lang.reflect.*; import java.util.*; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import java.util.zip.*; import jdk.internal.vm.annotation.DontInline; import static java.nio.charset.StandardCharsets.US_ASCII; @@ -56,34 +54,6 @@ private static long addrOf(Object obj) { } } - private static class SubclassedInflater extends Inflater { - CountDownLatch endCountDown; - - SubclassedInflater(CountDownLatch endCountDown) { - this.endCountDown = endCountDown; - } - - @Override - public void end() { - super.end(); - endCountDown.countDown(); - } - } - - private static class SubclassedDeflater extends Deflater { - CountDownLatch endCountDown; - - SubclassedDeflater(CountDownLatch endCountDown) { - this.endCountDown = endCountDown; - } - - @Override - public void end() { - super.end(); - endCountDown.countDown(); - } - } - // verify the "native resource" of In/Deflater has been cleaned private static void testDeInflater() throws Throwable { Field zsRefDef = Deflater.class.getDeclaredField("zsRef"); @@ -124,44 +94,6 @@ private static void testDeInflater() throws Throwable { if (cnt != 0) throw new RuntimeException("cleaner failed to clean : " + cnt); - // test subclassed Deflater/Inflater, for behavioral compatibility. - // should be removed if the finalize() method is finally removed. - var endCountDown = new CountDownLatch(20); - for (int i = 0; i < 10; i++) { - var def = new SubclassedDeflater(endCountDown); - def.setInput("hello".getBytes()); - def.finish(); - n = def.deflate(buf1); - - var inf = new SubclassedInflater(endCountDown); - inf.setInput(buf1, 0, n); - n = inf.inflate(buf2); - if (!"hello".equals(new String(buf2, 0, n))) { - throw new RuntimeException("compression/decompression failed"); - } - } - while (!endCountDown.await(10, TimeUnit.MILLISECONDS)) { - System.gc(); - } - if (endCountDown.getCount() != 0) - throw new RuntimeException("finalizer failed to clean : " + - endCountDown.getCount()); - } - - private static class SubclassedZipFile extends ZipFile { - CountDownLatch closeCountDown; - - SubclassedZipFile(File f, CountDownLatch closeCountDown) - throws IOException { - super(f); - this.closeCountDown = closeCountDown; - } - - @Override - public void close() throws IOException { - closeCountDown.countDown(); - super.close(); - } } @DontInline @@ -198,27 +130,6 @@ private static Object openAndCloseZipFile(File zip) throws Throwable { } } - @DontInline - private static void openAndCloseSubZipFile(File zip, CountDownLatch closeCountDown) - throws Throwable { - try { - try (var fos = new FileOutputStream(zip); - var zos = new ZipOutputStream(fos)) { - zos.putNextEntry(new ZipEntry("hello")); - zos.write("hello".getBytes(US_ASCII)); - zos.closeEntry(); - } - var zf = new SubclassedZipFile(zip, closeCountDown); - var es = zf.entries(); - while (es.hasMoreElements()) { - zf.getInputStream(es.nextElement()).read(); - } - es = null; - zf = null; - } finally { - zip.delete(); - } - } private static void testZipFile() throws Throwable { File dir = new File(System.getProperty("test.dir", ".")); @@ -241,16 +152,5 @@ private static void testZipFile() throws Throwable { throw new RuntimeException("cleaner failed to clean zipfile."); } } - - // test subclassed ZipFile, for behavioral compatibility. - // should be removed if the finalize() method is finally removed. - var closeCountDown = new CountDownLatch(1); - openAndCloseSubZipFile(zip, closeCountDown); - while (!closeCountDown.await(10, TimeUnit.MILLISECONDS)) { - System.gc(); - } - if (closeCountDown.getCount() != 0) - throw new RuntimeException("finalizer failed to clean : " + - closeCountDown.getCount()); } } diff --git a/test/jdk/jdk/crac/AppendAppClassLoaderTest.java b/test/jdk/jdk/crac/AppendAppClassLoaderTest.java new file mode 100644 index 00000000000..94800bcdc8d --- /dev/null +++ b/test/jdk/jdk/crac/AppendAppClassLoaderTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024, Alibaba Group Holding Limited. 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.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracProcess; +import jdk.test.lib.crac.CracTest; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.util.JarUtils; + +import java.lang.reflect.Method; +import java.nio.file.Paths; + +import jdk.crac.*; + +/** + * @test + * @summary Append to app classloader when restore. + * @library /test/lib + * @compile ./Foo.java + * @build AppendAppClassLoaderTest + * @run driver/timeout=60 jdk.test.lib.crac.CracTest + */ +public class AppendAppClassLoaderTest implements CracTest { + private final static String TEST_DIR = System.getProperty("test.classes"); + private final String FOO_JAR = "foo.jar"; + private Resource resource; + + @Override + public void test() throws Exception { + JarUtils.createJarFile(Paths.get(FOO_JAR), Paths.get(TEST_DIR), Paths.get(TEST_DIR, "Foo.class")); + //remove it avoid Foo in app classpath before call Core.appendToAppClassLoaderClassPath + Paths.get(TEST_DIR, "Foo.class").toFile().delete(); + CracBuilder cracBuilder = new CracBuilder().captureOutput(true) + .restorePipeStdOutErr(true); + CracProcess crProcess = cracBuilder.startCheckpoint(); + crProcess.waitForCheckpointed(); + OutputAnalyzer crOutputAnalyzer = crProcess.outputAnalyzer(); + crOutputAnalyzer.shouldContain("Foo should not found"); + + CracProcess restoreProcess = cracBuilder.doRestore(); + OutputAnalyzer restoreOutputAnalyzer = restoreProcess.outputAnalyzer(); + restoreOutputAnalyzer.shouldContain("A msg from Foo#hello"); + } + + @Override + public void exec() throws Exception { + try { + Class fooClass = ClassLoader.getSystemClassLoader().loadClass("Foo"); + //should not reach here + System.out.println(fooClass); + } catch (ClassNotFoundException cnf) { + System.out.println("Foo should not found"); + } + resource = new Resource() { + @Override + public void beforeCheckpoint(Context context) throws Exception { + } + + @Override + public void afterRestore(Context context) throws Exception { + Core.appendToAppClassLoaderClassPath(FOO_JAR); + } + }; + Core.getGlobalContext().register(resource); + Core.checkpointRestore(); + Class fooClass = ClassLoader.getSystemClassLoader().loadClass("Foo"); + Object o = fooClass.getConstructor().newInstance(); + Method m = fooClass.getDeclaredMethod("hello"); + m.invoke(o); + } +} diff --git a/test/jdk/jdk/crac/AppendOnlyFileTest.java b/test/jdk/jdk/crac/AppendOnlyFileTest.java new file mode 100644 index 00000000000..17500d2cb8e --- /dev/null +++ b/test/jdk/jdk/crac/AppendOnlyFileTest.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2024, Alibaba Group Holding Limited. 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 static jdk.test.lib.Asserts.*; + +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracProcess; +import jdk.test.lib.crac.CracTest; +import jdk.test.lib.crac.CracTestArg; +import jdk.test.lib.util.FileUtils; + +import javax.crac.*; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @test + * @library /test/lib + * @build AppendOnlyFileTest + * @summary Test C&R when open file with write&append mode that no need closed. + * @run driver/timeout=60 jdk.test.lib.crac.CracTest ALL KEEP_ALL + * @run driver/timeout=60 jdk.test.lib.crac.CracTest BY_EXT REMOVE_ALL + * @run driver/timeout=60 jdk.test.lib.crac.CracTest BY_FULL_PATH REMOVE_ONE_FILE + * @run driver/timeout=60 jdk.test.lib.crac.CracTest EXT_AND_FULL_PATH REMOVE_ONE_DIR + * + */ +public class AppendOnlyFileTest implements CracTest { + private static Path LOG_BASE; + private static List LOG_FILES = new ArrayList<>(); + private static String TEXT_BEFORE_CHECKPOINT = "text before checkpoint"; + private static String TEXT_AFTER_RESTORE = "text after restore"; + private static int DELETE_FILE_INDEX = 0; + private static int DELETE_DIR_INDEX = 2; + + static { + LOG_BASE = Path.of(System.getProperty("user.dir"), "testlogs"); + LOG_FILES.add(LOG_BASE.resolve("a.log")); + LOG_FILES.add(LOG_BASE.resolve("log1").resolve("b.msg")); + LOG_FILES.add(LOG_BASE.resolve("log2").resolve("c.txt")); + } + + private enum IgnoreAppendFileParamType { + ALL, + BY_EXT, + BY_FULL_PATH, + EXT_AND_FULL_PATH; + } + + private enum KeepFilesPolicy { + KEEP_ALL, + REMOVE_ALL, + REMOVE_ONE_FILE, + REMOVE_ONE_DIR + } + + private Resource resource; + + @CracTestArg(0) + IgnoreAppendFileParamType paramType; + + @CracTestArg(1) + KeepFilesPolicy keepFilesPolicy; + + @Override + public void test() throws Exception { + resetLogDir(); + + CracBuilder cracBuilder = new CracBuilder().printResources(true) + .appendOnlyFiles(getAppendOnlyParam(paramType)) + .captureOutput(true); + CracProcess crProcess = cracBuilder.startCheckpoint(); + int ret = crProcess.waitFor(); + if (ret != 137) { + crProcess.outputAnalyzer().reportDiagnosticSummary(); + assertEquals(137, ret, "Checkpointed process was not killed as expected."); + } + + keepFilesByPolicy(keepFilesPolicy); + cracBuilder.doRestore(); + validateFile(keepFilesPolicy); + } + + private static void resetLogDir() throws IOException { + if (LOG_BASE.toFile().exists()) { + FileUtils.deleteFileTreeWithRetry(LOG_BASE); + } + for (Path logFile : LOG_FILES) { + logFile.getParent().toFile().mkdirs(); + } + } + + @Override + public void exec() throws Exception { + List writers = LOG_FILES.stream().map((f) -> { + PrintWriter pw = openWithWriteAppendMode(f); + pw.println(TEXT_BEFORE_CHECKPOINT); + pw.flush(); + return pw; + }).collect(Collectors.toList()); + + Core.checkpointRestore(); + + writers.stream().forEach((pw) -> { + pw.println(TEXT_AFTER_RESTORE); + pw.flush(); + pw.close(); + }); + } + + private PrintWriter openWithWriteAppendMode(Path filePath) { + try { + return new PrintWriter(new BufferedWriter(new FileWriter(filePath.toFile(), true))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void validateFile(KeepFilesPolicy keepFilesPolicy) throws IOException { + for (int i = 0; i < LOG_FILES.size(); i++) { + Path logFile = LOG_FILES.get(i); + assertTrue(logFile.toFile().exists()); + List lines = Files.lines(logFile).collect(Collectors.toList()); + + assertTrue(lines.stream().anyMatch((t) -> t.equals(TEXT_AFTER_RESTORE))); + + if (keepFilesPolicy == KeepFilesPolicy.KEEP_ALL) { + assertContainBeforeCheckpoint(true, lines); + } else if (keepFilesPolicy == KeepFilesPolicy.REMOVE_ONE_FILE) { + assertContainBeforeCheckpoint(i != DELETE_FILE_INDEX, lines); + } else if (keepFilesPolicy == KeepFilesPolicy.REMOVE_ALL) { + assertContainBeforeCheckpoint(false, lines); + } else if (keepFilesPolicy == KeepFilesPolicy.REMOVE_ONE_DIR) { + assertContainBeforeCheckpoint(i != DELETE_DIR_INDEX, lines); + } + } + } + + private void assertContainBeforeCheckpoint(boolean contain, List lines) { + if (contain) { + assertTrue(lines.stream().anyMatch((t) -> t.equals(TEXT_BEFORE_CHECKPOINT))); + } else { + assertFalse(lines.stream().anyMatch((t) -> t.equals(TEXT_BEFORE_CHECKPOINT))); + } + } + + private void keepFilesByPolicy(KeepFilesPolicy keepFilesPolicy) throws IOException { + if (keepFilesPolicy == KeepFilesPolicy.REMOVE_ALL) { + FileUtils.deleteFileTreeWithRetry(LOG_BASE); + } else if (keepFilesPolicy == KeepFilesPolicy.REMOVE_ONE_FILE) { + assertTrue(LOG_FILES.get(DELETE_FILE_INDEX).toFile().delete()); + } else if (keepFilesPolicy == KeepFilesPolicy.REMOVE_ONE_DIR) { + FileUtils.deleteFileTreeWithRetry(LOG_FILES.get(DELETE_DIR_INDEX)); + } + } + + private String getAppendOnlyParam(IgnoreAppendFileParamType paramType) { + switch (paramType) { + case ALL: + return "*"; + case BY_EXT: + return "*.log,*.txt,*.msg"; + case BY_FULL_PATH: + return LOG_FILES.stream().map((p) -> getCanonicalPath(p)).collect(Collectors.joining(",")); + case EXT_AND_FULL_PATH: + return "*.log,*.msg," + getCanonicalPath(LOG_FILES.get(LOG_FILES.size() - 1)); + default: + throw new RuntimeException("unknown IgnoreAppendFileParamType type : " + paramType); + } + } + + private String getCanonicalPath(Path path) { + try { + return path.toFile().getCanonicalPath(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/test/jdk/jdk/crac/DryRunTest.java b/test/jdk/jdk/crac/DryRunTest.java new file mode 100644 index 00000000000..8c04f366448 --- /dev/null +++ b/test/jdk/jdk/crac/DryRunTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2022, 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 java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +import jdk.crac.*; +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracEngine; +import jdk.test.lib.crac.CracTest; + +/** + * @test DryRunTest + * @library /test/lib + * @build DryRunTest + * @run driver jdk.test.lib.crac.CracTest + */ +public class DryRunTest implements CracTest { + static class CRResource implements Resource { + @Override + public void beforeCheckpoint(Context context) throws Exception { + throw new RuntimeException("should not pass"); + } + + @Override + public void afterRestore(Context context) throws Exception { + } + } + + @Override + public void test() throws Exception { + new CracBuilder().engine(CracEngine.SIMULATE).printResources(true) + .startCheckpoint().waitForSuccess(); + } + + @Override + public void exec() throws Exception { + Resource resource = new CRResource(); + Core.getGlobalContext().register(resource); + + File tempFile = File.createTempFile("jtreg-DryRunTest", null); + FileOutputStream stream = new FileOutputStream(tempFile); + stream.write('j'); + + int exceptions = 0; + + try { + Core.checkpointRestore(); + } catch (CheckpointException ce) { + + ce.printStackTrace(); + + for (Throwable e : ce.getSuppressed()) { + String name = e.getClass().getName(); + switch (name) { + case "java.lang.RuntimeException": exceptions |= 0x1; break; + case "jdk.crac.impl.CheckpointOpenFileException": exceptions |= 0x2; break; + } + } + } + + stream.close(); + tempFile.delete(); + + if (exceptions != 0x3) { + throw new RuntimeException("fail " + exceptions); + } + } +} diff --git a/test/jdk/jdk/crac/Foo.java b/test/jdk/jdk/crac/Foo.java new file mode 100644 index 00000000000..5bc7df62040 --- /dev/null +++ b/test/jdk/jdk/crac/Foo.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024, Alibaba Group Holding Limited. 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. + */ +public class Foo { + public void hello() { + System.out.println("A msg from Foo#hello"); + } +} diff --git a/test/jdk/jdk/crac/JarFileFactoryCacheTest/JarFileFactoryCacheTest.java b/test/jdk/jdk/crac/JarFileFactoryCacheTest/JarFileFactoryCacheTest.java new file mode 100644 index 00000000000..8007687e3ff --- /dev/null +++ b/test/jdk/jdk/crac/JarFileFactoryCacheTest/JarFileFactoryCacheTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2022-2023, 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.*; +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracEngine; +import jdk.test.lib.crac.CracTest; + +import java.io.File; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * @test JarFileFactoryCacheTest + * @library /test/lib + * @build JarFileFactoryCacheTest + * @run driver jdk.test.lib.crac.CracTest + */ +public class JarFileFactoryCacheTest implements CracTest { + @Override + public void test() throws Exception { + new CracBuilder().engine(CracEngine.SIMULATE).printResources(true) + .startCheckpoint().waitForSuccess(); + } + + @Override + public void exec() throws Exception { + Path temp = Files.createTempDirectory(JarFileFactoryCacheTest.class.getName()); + Path testFilePath = temp.resolve("test.txt"); + try { + Files.writeString(testFilePath, "test\n"); + jdk.test.lib.util.JarUtils.createJarFile( + Path.of("test.jar"), temp, "test.txt"); + } finally { + File testTxt = testFilePath.toFile(); + if (testTxt.exists()) { + assert testTxt.delete(); + } + assert temp.toFile().delete(); + } + + URL url = new URL("jar:file:test.jar!/test.txt"); + InputStream inputStream = url.openStream(); + byte[] content = inputStream.readAllBytes(); + if (content.length != 5) { + throw new AssertionError("wrong content: " + new String(content)); + } + inputStream.close(); + // Nulling the variables is actually necessary! + inputStream = null; + url = null; + + Core.checkpointRestore(); + } +} diff --git a/test/jdk/jdk/crac/LazyProps.java b/test/jdk/jdk/crac/LazyProps.java new file mode 100644 index 00000000000..e232a4cdec0 --- /dev/null +++ b/test/jdk/jdk/crac/LazyProps.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022, 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.*; +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracEngine; +import jdk.test.lib.crac.CracTest; + +/** + * @test + * @library /test/lib + * @build LazyProps + * @run driver jdk.test.lib.crac.CracTest + */ +public class LazyProps implements CracTest { + @Override + public void test() throws Exception { + new CracBuilder().engine(CracEngine.SIMULATE) + .captureOutput(true) + .startCheckpoint().waitForSuccess() + .outputAnalyzer().shouldContain("beforeCheckpoint"); + } + + @Override + public void exec() throws RestoreException, CheckpointException { + Resource resource = new Resource() { + @Override + public void beforeCheckpoint(Context context) throws Exception { } + @Override + public void afterRestore(Context context) throws Exception { } + }; + Core.getGlobalContext().register(resource); + + System.setProperty("jdk.crac.debug", "true"); + Core.checkpointRestore(); + } +} diff --git a/test/jdk/jdk/crac/LeaveRunning.java b/test/jdk/jdk/crac/LeaveRunning.java new file mode 100644 index 00000000000..0773910da8c --- /dev/null +++ b/test/jdk/jdk/crac/LeaveRunning.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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.*; + +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracLogger; +import jdk.test.lib.crac.CracTest; +import jdk.test.lib.process.OutputAnalyzer; + +/** + * @test + * @library /test/lib + * @build LeaveRunning + * @run driver jdk.test.lib.crac.CracTest + */ +public class LeaveRunning extends CracLogger implements CracTest { + @Override + public void test() throws Exception { + CracBuilder builder = new CracBuilder().env("CRAC_CRIU_LEAVE_RUNNING", "") + .logToFile(true); + builder.startCheckpoint().waitForSuccess().fileOutputAnalyser().shouldContain(RESTORED_MESSAGE); + builder.doRestore().fileOutputAnalyser().shouldContain(RESTORED_MESSAGE); + } + + @Override + public void exec() throws Exception { + Core.checkpointRestore(); + writeLog(RESTORED_MESSAGE); + } +} diff --git a/test/jdk/jdk/crac/MXBean.java b/test/jdk/jdk/crac/MXBean.java new file mode 100644 index 00000000000..336e3eecd12 --- /dev/null +++ b/test/jdk/jdk/crac/MXBean.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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.*; +import jdk.crac.management.*; + +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracEngine; +import jdk.test.lib.crac.CracTest; +import jdk.test.lib.process.OutputAnalyzer; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; + +import static jdk.test.lib.Asserts.assertLT; + +/** + * @test + * @library /test/lib + * @build MXBean + * @run driver jdk.test.lib.crac.CracTest + */ +public class MXBean implements CracTest { + static final long TIME_TOLERANCE = 10_000; // ms + + @Override + public void exec() throws CheckpointException, RestoreException { + CRaCMXBean cracMXBean = CRaCMXBean.getCRaCMXBean(); + + Core.checkpointRestore(); + + System.out.println("UptimeSinceRestore " + cracMXBean.getUptimeSinceRestore()); + + long restoreTime = cracMXBean.getRestoreTime(); + System.out.println("RestoreTime " + restoreTime + " " + + DateTimeFormatter.ofPattern("E dd LLL yyyy HH:mm:ss.n").format( + Instant.ofEpochMilli(restoreTime) + .atZone(ZoneId.systemDefault()))); + } + + @Override + public void test() throws Exception { + long start = System.currentTimeMillis(); + + OutputAnalyzer output = new CracBuilder().engine(CracEngine.SIMULATE) + .captureOutput(true) + .startCheckpoint().waitForSuccess().outputAnalyzer(); + + long restoreUptime = Long.parseLong(output.firstMatch("UptimeSinceRestore ([0-9-]+)", 1)); + if (restoreUptime < 0 || TIME_TOLERANCE < restoreUptime) { + throw new Error("bad UptimeSinceRestore: " + restoreUptime); + } + + long restoreTime = Long.parseLong(output.firstMatch("RestoreTime ([0-9-]+)", 1)); + restoreTime -= start; + + assertLT(Math.abs(restoreTime), TIME_TOLERANCE, "bad RestoreTime: " + restoreTime); + } +} diff --git a/test/jdk/jdk/crac/MinimizeLoadedClass.java b/test/jdk/jdk/crac/MinimizeLoadedClass.java new file mode 100644 index 00000000000..ace205f6d1b --- /dev/null +++ b/test/jdk/jdk/crac/MinimizeLoadedClass.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2023, Alibaba Group Holding Limited. 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.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +import java.io.IOException; + +/* + * @test + * @summary Test only load 3 classes when crac not enabled. + * @library /test/lib + * @build TestHello + * @run main/othervm MinimizeLoadedClass + */ +public class MinimizeLoadedClass { + private static final String[] MIN_CLASS_SET = new String[]{ + "jdk.crac.Configuration", + "jdk.crac.Resource", + "jdk.internal.crac.JDKResource" + }; + + private static final String[] MORE_CLASS_SET = new String[]{ + "jdk.internal.crac.Core", + "jdk.crac.Context", + "jdk.crac.impl.AbstractContextImpl", + "jdk.internal.crac.JDKContext", + "jdk.internal.crac.JDKContext$ContextComparator", + "jdk.crac.Core", + "jdk.crac.impl.OrderedContext", + "jdk.crac.impl.OrderedContext$ContextComparator" + }; + + public static void main(String[] args) throws IOException { + runTest(new String[]{"-verbose:class","TestHello"}, new String[][]{MIN_CLASS_SET}, new String[][]{MORE_CLASS_SET}); + runTest(new String[]{"-verbose:class", "-XX:CRaCCheckpointTo=cr","TestHello"}, new String[][]{MIN_CLASS_SET, MORE_CLASS_SET}, new String[0][0]); + } + + private static void runTest(String[] cmds, String[][] includes, String[][] excludes) throws IOException { + ProcessBuilder pb = ProcessTools.createTestJvm(cmds); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + for (String[] include : includes) { + for (String str : include) { + output.shouldContain(str); + } + } + for (String[] exclude : excludes) { + for (String str : exclude) { + output.shouldNotContain(str); + } + } + } +} diff --git a/test/jdk/jdk/crac/PseudoPersistentFileTest.java b/test/jdk/jdk/crac/PseudoPersistentFileTest.java new file mode 100644 index 00000000000..e125b3d2244 --- /dev/null +++ b/test/jdk/jdk/crac/PseudoPersistentFileTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024, Alibaba Group Holding Limited. 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.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracProcess; +import jdk.test.lib.crac.CracTest; + +import javax.crac.*; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; + +/** + * @test + * @library /test/lib + * @build PseudoPersistentFileTest + * @run driver/timeout=60 jdk.test.lib.crac.CracTest + */ +public class PseudoPersistentFileTest implements CracTest { + + private Resource resource; + + @Override + public void test() throws Exception { + CracBuilder cracBuilder = new CracBuilder().captureOutput(true) + .restorePipeStdOutErr(true); + CracProcess crProcess = cracBuilder.startCheckpoint(); + crProcess.waitForCheckpointed(); + cracBuilder.doRestore(); + } + + @Override + public void exec() throws Exception { + File f1 = new File("f1.txt"); + BufferedWriter bw1 = new BufferedWriter(new FileWriter(f1)); + bw1.write("file f1"); + + File f2 = new File("f2.txt"); + BufferedWriter bw2 = new BufferedWriter(new FileWriter(f2)); + bw2.write("file f2"); + + resource = new Resource() { + @Override + public void beforeCheckpoint(Context context) throws Exception { + Core.registerPseudoPersistent(f1.getAbsolutePath(), Core.SAVE_RESTORE | Core.COPY_WHEN_RESTORE); + Core.registerPseudoPersistent(f2.getAbsolutePath(), Core.SAVE_RESTORE | Core.SYMLINK_WHEN_RESTORE); + } + + @Override + public void afterRestore(Context context) throws Exception { + } + }; + Core.getGlobalContext().register(resource); + Core.checkpointRestore(); + bw1.close(); + bw2.close(); + } +} diff --git a/test/jdk/jdk/crac/RefQueueTest.java b/test/jdk/jdk/crac/RefQueueTest.java new file mode 100644 index 00000000000..2cee22c2a7a --- /dev/null +++ b/test/jdk/jdk/crac/RefQueueTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2022, 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 java.io.*; +import java.lang.ref.Cleaner; + +import jdk.crac.*; +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracEngine; +import jdk.test.lib.crac.CracTest; + +/** + * @test + * @library /test/lib + * @build RefQueueTest + * @run driver jdk.test.lib.crac.CracTest + */ +public class RefQueueTest implements CracTest { + private static final Cleaner cleaner = Cleaner.create(); + + @Override + public void test() throws Exception { + new CracBuilder().engine(CracEngine.SIMULATE) + .startCheckpoint().waitForSuccess(); + } + + @Override + public void exec() throws Exception { + File badFile = File.createTempFile("jtreg-RefQueueTest", null); + OutputStream badStream = new FileOutputStream(badFile); + badStream.write('j'); + badFile.delete(); + + // the cleaner would be able to run right away + cleaner.register(new Object(), () -> { + try { + badStream.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + + // should close the file and only then go to the native checkpoint + Core.checkpointRestore(); + } +} diff --git a/test/jdk/jdk/crac/ResourceTest.java b/test/jdk/jdk/crac/ResourceTest.java new file mode 100644 index 00000000000..64fa59bcb1e --- /dev/null +++ b/test/jdk/jdk/crac/ResourceTest.java @@ -0,0 +1,114 @@ +// Copyright 2019-2020 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 Azul Systems, 385 Moffett Park Drive, Suite 115, Sunnyvale, +// CA 94089 USA or visit www.azul.com if you need additional information or +// have any questions. + +package jdk.test.jdk.crac; + +import jdk.crac.*; + +/** + * @test + * @compile ResourceTest.java + */ +public class ResourceTest { + static class CRResource implements Resource { + String id; + boolean[] throwCond; + int nCalls = 0; + CRResource(String id, boolean... throwCond) { + this.id = id; + this.throwCond = throwCond; + } + + void maybeException(String callId) throws Exception { + boolean t = nCalls < throwCond.length ? throwCond[nCalls] : throwCond[throwCond.length - 1]; + System.out.println(id + " " + callId + "(" + nCalls + ") throw? " + t); + ++nCalls; + if (t) { + throw new RuntimeException(id); + } + } + + @Override + public void beforeCheckpoint(Context context) throws Exception { + maybeException("beforeCheckpoint"); + } + + @Override + public void afterRestore(Context context) throws Exception { + maybeException("afterRestore"); + } + } + + static class SingleContext extends Context { + private Resource r; + + @Override + public void beforeCheckpoint(Context context) throws CheckpointException { + try { + r.beforeCheckpoint(this); + } catch (Exception e) { + CheckpointException newException = new CheckpointException(); + newException.addSuppressed(e); + throw newException; + } + } + + @Override + public void afterRestore(Context context) throws RestoreException { + try { + r.afterRestore(this); + } catch (Exception e) { + RestoreException newException = new RestoreException(); + newException.addSuppressed(e); + throw newException; + } + + } + + @Override + public void register(Resource r) { + this.r = r; + } + + public SingleContext(Resource r) { + register(r); + } + } + + static public void main(String[] args) throws Exception { + Core.getGlobalContext().register( + new CRResource("One", true, false)); + Core.getGlobalContext().register( + new SingleContext( + new CRResource("Two", false, true, false, true))); + //System.gc(); + int tries = 2; + for (int i = 0; i < 2; ++i) { + try { + jdk.crac.Core.checkpointRestore(); + } catch (CheckpointException e) { + e.printStackTrace(); + } catch (RestoreException e) { + e.printStackTrace(); + } + } + System.out.println("DONE"); + } +} diff --git a/test/jdk/jdk/crac/RestoreEnvironmentTest.java b/test/jdk/jdk/crac/RestoreEnvironmentTest.java new file mode 100644 index 00000000000..72d1eddb87c --- /dev/null +++ b/test/jdk/jdk/crac/RestoreEnvironmentTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022, 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 Azul Systems, 385 Moffett Park Drive, Suite 115, Sunnyvale + * CA 94089 USA or visit www.azul.com if you need additional information or + * have any questions. + */ + +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracLogger; +import jdk.test.lib.crac.CracTest; + +import java.io.IOException; + +/* + * @test RestoreEnvironmentTest + * @summary the test checks that actual environment variables are propagated into a restored process. + * @library /test/lib + * @build RestoreEnvironmentTest + * @run driver/timeout=120 jdk.test.lib.crac.CracTest + */ +public class RestoreEnvironmentTest extends CracLogger implements CracTest { + static final String TEST_VAR_NAME = "RESTORE_ENVIRONMENT_TEST_VAR"; + static final String BEFORE_CHECKPOINT = "BeforeCheckpoint"; + static final String AFTER_RESTORE = "AfterRestore"; + static final String NEW_VALUE = "NewValue"; + public static final String PREFIX = "(after restore) "; + + @Override + public void test() throws Exception { + CracBuilder builder = new CracBuilder().logToFile(true) + .env(TEST_VAR_NAME + 0, BEFORE_CHECKPOINT) + .env(TEST_VAR_NAME + 1, BEFORE_CHECKPOINT); + builder.doCheckpoint(); + builder.env(TEST_VAR_NAME + 1, AFTER_RESTORE); + builder.env(TEST_VAR_NAME + 2, NEW_VALUE); + builder.doRestore().fileOutputAnalyser() + .shouldContain(PREFIX + TEST_VAR_NAME + "0=" + BEFORE_CHECKPOINT) + .shouldContain(PREFIX + TEST_VAR_NAME + "1=" + AFTER_RESTORE) + .shouldContain(PREFIX + TEST_VAR_NAME + "2=" + NEW_VALUE); + } + + @Override + public void exec() throws Exception { + for (int i = 0; i < 3; ++i) { + var testVar = java.lang.System.getenv(TEST_VAR_NAME + i); + writeLog("(before checkpoint) " + TEST_VAR_NAME + i + "=" + testVar); + } + + jdk.crac.Core.checkpointRestore(); + + for (int i = 0; i < 3; ++i) { + var testVar = java.lang.System.getenv(TEST_VAR_NAME + i); + writeLog(PREFIX + TEST_VAR_NAME + i + "=" + testVar + ""); + } + } +} diff --git a/test/jdk/jdk/crac/RestorePipeFdTest.java b/test/jdk/jdk/crac/RestorePipeFdTest.java new file mode 100644 index 00000000000..49e720d7157 --- /dev/null +++ b/test/jdk/jdk/crac/RestorePipeFdTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024, Alibaba Group Holding Limited. 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.*; +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracProcess; +import jdk.test.lib.crac.CracTest; +import jdk.test.lib.process.OutputAnalyzer; + +/** + * @test + * @summary test if pipe can restore + * @library /test/lib + * @build RestorePipeFdTest + * @run driver/timeout=60 jdk.test.lib.crac.CracTest + */ +public class RestorePipeFdTest implements CracTest{ + private static final String MSG_1 = "[before checkpointRestore]this is an message from stdout"; + private static final String MSG_2 = "[before checkpointRestore]this is an message from stderr"; + private static final String MSG_3 = "[after checkpointRestore]this is an message from stdout"; + private static final String MSG_4 = "[after checkpointRestore]this is an message from stderr"; + + @Override + public void test() throws Exception { + CracBuilder cracBuilder = new CracBuilder().captureOutput(true) + .restorePipeStdOutErr(true); + CracProcess crProcess = cracBuilder.startCheckpoint(); + crProcess.waitForCheckpointed(); + OutputAnalyzer crOutputAnalyzer = crProcess.outputAnalyzer(); + crOutputAnalyzer.shouldContain(MSG_1); + crOutputAnalyzer.shouldContain(MSG_1); + + CracProcess restoreProcess = cracBuilder.doRestore(); + OutputAnalyzer restoreOutputAnalyzer = restoreProcess.outputAnalyzer(); + restoreOutputAnalyzer.shouldContain(MSG_3); + restoreOutputAnalyzer.shouldContain(MSG_4); + } + + @Override + public void exec() throws Exception { + System.out.println(MSG_1); + System.err.println(MSG_2); + Core.checkpointRestore(); + System.out.println(MSG_3); + System.err.println(MSG_4); + } +} diff --git a/test/jdk/jdk/crac/SecureRandom/InterlockTest.java b/test/jdk/jdk/crac/SecureRandom/InterlockTest.java new file mode 100644 index 00000000000..fd87f6e3e8c --- /dev/null +++ b/test/jdk/jdk/crac/SecureRandom/InterlockTest.java @@ -0,0 +1,163 @@ +// Copyright 2019-2021 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 Azul Systems, 385 Moffett Park Drive, Suite 115, Sunnyvale, +// CA 94089 USA or visit www.azul.com if you need additional information or +// have any questions. + +import jdk.crac.*; +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracTest; +import jdk.test.lib.crac.CracTestArg; + +import java.io.IOException; +import java.security.SecureRandom; + +/* + * @test + * @summary Verify that secure random is not interlocked during checkpoint/restore. + * @library /test/lib + * @build InterlockTest + * @run driver/timeout=60 jdk.test.lib.crac.CracTest SHA1PRNG 50 + * @run driver/timeout=60 jdk.test.lib.crac.CracTest NativePRNGBlocking 50 + * @run driver/timeout=60 jdk.test.lib.crac.CracTest NativePRNGNonBlocking 50 + * @run driver/timeout=60 jdk.test.lib.crac.CracTest NativePRNG 50 + */ +public class InterlockTest implements Resource, CracTest { + private static final long MIN_TIMEOUT = 100; + private static final long MAX_TIMEOUT = 1000; + + private boolean stop = false; + private SecureRandom sr; + + @CracTestArg(0) + String algName; + + @CracTestArg(1) + int numThreads; + + private class TestThread1 extends Thread { + @Override + public void run() { + while (!stop) { + set(); + } + } + }; + + private class TestThread2 extends Thread implements Resource { + private final SecureRandom sr; + + synchronized void set() { + sr.nextInt(); + } + synchronized void clean() { + sr.nextInt(); + } + + TestThread2() throws Exception { + sr = SecureRandom.getInstance(algName); + Core.getGlobalContext().register(this); + } + + @Override + public void run() { + while (!stop) { + set(); + } + } + + @Override + public void beforeCheckpoint(Context context) throws Exception { + clean(); + } + + @Override + public void afterRestore(Context context) throws Exception { + set(); + } + }; + + synchronized void clean() { + sr.nextInt(); + } + + synchronized void set() { + sr.nextInt(); + } + + @Override + public void beforeCheckpoint(Context context) throws Exception { + try { + clean(); + } catch(Exception e) { + e.printStackTrace(System.out); + }; + } + + @Override + public void afterRestore(Context context) throws Exception { + set(); + stop = true; + } + + @Override + public void test() throws Exception { + new CracBuilder().doCheckpointAndRestore(); + } + + @Override + public void exec() throws Exception { + sr = SecureRandom.getInstance(algName); + Core.getGlobalContext().register(this); + + Thread[] threads = new Thread[numThreads]; + for(int i = 0; i < numThreads; i++) { + threads[i] = (i % 2 == 0) ? + new TestThread1(): + new TestThread2(); + threads[i].start(); + }; + Thread.sleep(MIN_TIMEOUT); + set(); + Thread.sleep(MIN_TIMEOUT); + + Object checkpointLock = new Object(); + Thread checkpointThread = new Thread("checkpointThread") { + public void run() { + synchronized (checkpointLock) { + try { + jdk.crac.Core.checkpointRestore(); + } catch (CheckpointException e) { + throw new RuntimeException("Checkpoint ERROR " + e); + } catch (RestoreException e) { + throw new RuntimeException("Restore ERROR " + e); + } + checkpointLock.notify(); + } + } + }; + synchronized (checkpointLock) { + try { + checkpointThread.start(); + checkpointLock.wait(MAX_TIMEOUT * 2); + } catch(Exception e){ + throw new RuntimeException("Checkpoint/Restore ERROR " + e); + } + } + Thread.sleep(MAX_TIMEOUT); + } +} diff --git a/test/jdk/jdk/crac/SecureRandom/ReseedTest.java b/test/jdk/jdk/crac/SecureRandom/ReseedTest.java new file mode 100644 index 00000000000..7ef34ee8666 --- /dev/null +++ b/test/jdk/jdk/crac/SecureRandom/ReseedTest.java @@ -0,0 +1,77 @@ +// Copyright 2019-2021 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 Azul Systems, 385 Moffett Park Drive, Suite 115, Sunnyvale, +// CA 94089 USA or visit www.azul.com if you need additional information or +// have any questions. + +import jdk.crac.*; +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracTest; +import jdk.test.lib.crac.CracLogger; +import jdk.test.lib.crac.CracTestArg; + +import java.security.SecureRandom; + +import static jdk.test.lib.Asserts.assertEquals; +import static jdk.test.lib.Asserts.assertNotEquals; + +/* + * @test + * @summary Verify that SHA1PRNG secure random is reseeded after restore if initialized with default seed. + * @library /test/lib + * @build ReseedTest + * @run driver/timeout=60 jdk.test.lib.crac.CracTest true + * @run driver/timeout=60 jdk.test.lib.crac.CracTest false + */ +public class ReseedTest extends CracLogger implements CracTest { + @CracTestArg + boolean reseed; + + @Override + public void test() throws Exception { + CracBuilder builder = new CracBuilder(); + builder.doCheckpoint(); + builder.logToFile(true); + String e1 = builder.doRestore().fileOutputAnalyser().getStdout(); + String e2 = builder.doRestore().fileOutputAnalyser().getStdout(); + if (reseed) { + assertEquals(e1, e2); + } else { + assertNotEquals(e1, e2); + } + } + + @Override + public void exec() throws Exception { + SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); + if (reseed) { + sr.setSeed(sr.generateSeed(10)); + } + sr.nextInt(); + + try { + jdk.crac.Core.checkpointRestore(); + } catch (CheckpointException e) { + e.printStackTrace(System.out); + throw new RuntimeException("Checkpoint ERROR " + e); + } catch (RestoreException e) { + throw new RuntimeException("Restore ERROR " + e); + } + + writeLog(String.valueOf(sr.nextInt())); + } +} diff --git a/test/jdk/jdk/crac/Selector/Test970/ChannelResource.java b/test/jdk/jdk/crac/Selector/Test970/ChannelResource.java new file mode 100644 index 00000000000..f9fdc49f423 --- /dev/null +++ b/test/jdk/jdk/crac/Selector/Test970/ChannelResource.java @@ -0,0 +1,89 @@ +// Copyright 2019-2020 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 Azul Systems, 385 Moffett Park Drive, Suite 115, Sunnyvale, +// CA 94089 USA or visit www.azul.com if you need additional information or +// have any questions. + +import jdk.crac.Context; +import jdk.crac.Core; +import jdk.crac.Resource; + +import java.io.IOException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; + +class ChannelResource implements Resource { + + public enum SelectionType { + SELECT, + SELECT_TIMEOUT, + SELECT_NOW + }; + + private SocketChannel channel; + private SelectionKey key; + private Selector selector; + + private final SelectionType selType; + + public ChannelResource(SelectionType selType) { + this.selType = selType; + Core.getGlobalContext().register(this); + } + + public void open() throws IOException { + channel = SocketChannel.open(); + channel.configureBlocking(false); + } + + public void register(Selector selector) throws IOException { + key = channel.register(selector, SelectionKey.OP_READ); + this.selector = selector; + } + + @Override + public void beforeCheckpoint(Context context) throws IOException { + + channel.socket().close(); + + // causes the channel deregistration + if (selType == SelectionType.SELECT_NOW) { + selector.selectNow(); + } else if (selType == SelectionType.SELECT_TIMEOUT) { + selector.select(500); + } else { + new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(1000); + selector.wakeup(); + } catch (InterruptedException ie) { + throw new RuntimeException(ie); + } + } + }).start(); + + selector.select(); + } + } + + @Override + public void afterRestore(Context context) { + } +} diff --git a/test/jdk/jdk/crac/Selector/Test970/Test.java b/test/jdk/jdk/crac/Selector/Test970/Test.java new file mode 100644 index 00000000000..d6946c675c9 --- /dev/null +++ b/test/jdk/jdk/crac/Selector/Test970/Test.java @@ -0,0 +1,80 @@ +// Copyright 2019-2020 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 Azul Systems, 385 Moffett Park Drive, Suite 115, Sunnyvale, +// CA 94089 USA or visit www.azul.com if you need additional information or +// have any questions. + +import java.nio.channels.*; +import java.io.IOException; +import jdk.crac.*; +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracTest; +import jdk.test.lib.crac.CracTestArg; + +/* + * @test Selector/Test970 + * @summary a regression test for ZE-970 ("a channel deregistration + * is locked depending on mutual order of selector and channel creation") + * @library /test/lib + * @build ChannelResource + * @build Test + * @run driver jdk.test.lib.crac.CracTest SELECT_NOW true + * @run driver jdk.test.lib.crac.CracTest SELECT_NOW false + * @run driver jdk.test.lib.crac.CracTest SELECT true + * @run driver jdk.test.lib.crac.CracTest SELECT false + * @run driver jdk.test.lib.crac.CracTest SELECT_TIMEOUT true + * @run driver jdk.test.lib.crac.CracTest SELECT_TIMEOUT false + */ +public class Test implements CracTest { + @CracTestArg(0) + ChannelResource.SelectionType selType; + + @CracTestArg(1) + boolean openSelectorAtFirst; + + @Override + public void test() throws Exception { + new CracBuilder().doCheckpointAndRestore(); + } + + @Override + public void exec() throws Exception { + + if (openSelectorAtFirst) { + + Selector selector = Selector.open(); + ChannelResource ch = new ChannelResource(selType); + ch.open(); + ch.register(selector); + + Core.checkpointRestore(); + + selector.close(); + + } else { // try in other order (see ZE-970) + + ChannelResource ch = new ChannelResource(selType); + ch.open(); + Selector selector = Selector.open(); + ch.register(selector); + + Core.checkpointRestore(); + + selector.close(); + } + } +} diff --git a/test/jdk/jdk/crac/Selector/interruptedSelection/Test.java b/test/jdk/jdk/crac/Selector/interruptedSelection/Test.java new file mode 100644 index 00000000000..25329409641 --- /dev/null +++ b/test/jdk/jdk/crac/Selector/interruptedSelection/Test.java @@ -0,0 +1,106 @@ +// Copyright 2019-2020 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 Azul Systems, 385 Moffett Park Drive, Suite 115, Sunnyvale, +// CA 94089 USA or visit www.azul.com if you need additional information or +// have any questions. + +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracTest; +import jdk.test.lib.crac.CracTestArg; + +import java.nio.channels.Selector; +import java.io.IOException; + +/* + * @test Selector/interruptedSelection + * @summary check that the thread blocked by Selector.select() could be properly woken up by an interruption + * @library /test/lib + * @build Test + * @run driver/timeout=30 jdk.test.lib.crac.CracTest true true false + * @run driver/timeout=30 jdk.test.lib.crac.CracTest true false false + * @run driver/timeout=30 jdk.test.lib.crac.CracTest false true false + * @run driver/timeout=30 jdk.test.lib.crac.CracTest false false false + * @run driver/timeout=30 jdk.test.lib.crac.CracTest true true true + * @run driver/timeout=30 jdk.test.lib.crac.CracTest false true true + */ +public class Test implements CracTest { + @CracTestArg(0) + boolean setTimeout; + + @CracTestArg(1) + boolean interruptBeforeCheckpoint; + + @CracTestArg(2) + boolean skipCR; + + @Override + public void test() throws Exception { + CracBuilder builder = new CracBuilder(); + if (skipCR) { + builder.doPlain(); + } else { + builder.doCheckpointAndRestore(); + } + } + + // select(): interrupt before the checkpoint + @Override + public void exec() throws Exception { + Selector selector = Selector.open(); + Runnable r = new Runnable() { + @Override + public void run() { try { + if (setTimeout) { selector.select(3600_000); } + else { selector.select(); } + } catch (IOException e) { throw new RuntimeException(e); } } + }; + Thread t = new Thread(r); + t.start(); + + Thread.sleep(1000); + + if (interruptBeforeCheckpoint) { + t.interrupt(); + t.join(); + System.out.println(">> interrupt before checkpoint"); + } + + if (!skipCR) { + jdk.crac.Core.checkpointRestore(); + } + + Thread.sleep(1000); + + if (!interruptBeforeCheckpoint) { + t.interrupt(); + t.join(); + System.out.println(">>> interrupt after restore"); + } + + // just in case, check that the selector works as expected + + if (!selector.isOpen()) { throw new RuntimeException("the selector must be open"); } + + selector.wakeup(); + selector.select(); + + selector.selectNow(); + selector.select(200); + selector.close(); + } +} + diff --git a/test/jdk/jdk/crac/Selector/keyAfterRestore/ChannelResource.java b/test/jdk/jdk/crac/Selector/keyAfterRestore/ChannelResource.java new file mode 100644 index 00000000000..f38a2314a2d --- /dev/null +++ b/test/jdk/jdk/crac/Selector/keyAfterRestore/ChannelResource.java @@ -0,0 +1,176 @@ +// Copyright 2019-2020 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 Azul Systems, 385 Moffett Park Drive, Suite 115, Sunnyvale, +// CA 94089 USA or visit www.azul.com if you need additional information or +// have any questions. + +import jdk.crac.Context; +import jdk.crac.Core; +import jdk.crac.Resource; + +import java.io.IOException; +import java.nio.channels.CancelledKeyException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; + +class ChannelResource implements Resource { + + private SocketChannel channel; + private SelectionKey key; + private Selector selector; + + private Object att = new Integer(123); + + public ChannelResource() { + Core.getGlobalContext().register(this); + } + + public void open() throws IOException { + channel = SocketChannel.open(); + channel.configureBlocking(false); + } + + public void register(Selector selector) throws IOException { + key = channel.register(selector, SelectionKey.OP_CONNECT); + key.attach(att); + this.selector = selector; + } + + @Override + public void beforeCheckpoint(Context context) throws IOException { + + channel.socket().close(); // close the channel => cancel the key + check(!channel.isOpen(), "the channel should not be open"); + selector.select(100); // causes the channel deregistration + } + + @Override + public void afterRestore(Context context) { + + check(key.selector().equals(selector), "invalid key.selector()"); + check(key.channel().equals(channel), "invalid key.channel()"); + + // the key is cancelled + check(!key.isValid(), "expected: key.isValid() == false"); + + boolean caught = false; + try { + key.readyOps(); + } catch (CancelledKeyException e) { + caught = true; + } + check(caught, "expected CancelledKeyException is missing"); + + caught = false; + try { + key.interestOps(); + } catch (CancelledKeyException e) { + caught = true; + } + check(caught, "expected CancelledKeyException is missing"); + + caught = false; + try { + key.interestOps(SelectionKey.OP_CONNECT); + } catch (CancelledKeyException e) { + caught = true; + } + check(caught, "expected CancelledKeyException is missing"); + + caught = false; + try { + key.readyOps(); + } catch (CancelledKeyException e) { + caught = true; + } + check(caught, "expected CancelledKeyException is missing"); + + caught = false; + try { + key.isReadable(); + } catch (CancelledKeyException e) { + caught = true; + } + check(caught, "expected CancelledKeyException is missing"); + + caught = false; + try { + key.isWritable(); + } catch (CancelledKeyException e) { + caught = true; + } + check(caught, "expected CancelledKeyException is missing"); + + caught = false; + try { + key.isConnectable(); + } catch (CancelledKeyException e) { + caught = true; + } + check(caught, "expected CancelledKeyException is missing"); + + caught = false; + try { + key.isAcceptable(); + } catch (CancelledKeyException e) { + caught = true; + } + check(caught, "expected CancelledKeyException is missing"); + + check(att.equals(key.attachment()), "invalid key.attachment()"); + + key.cancel(); // try just in case + + // register again + try { + channel = SocketChannel.open(); + channel.configureBlocking(false); + key = channel.register(selector, SelectionKey.OP_READ); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + // to check after restore + public void checkKey() { + + check(key.isValid(), "key must be valid"); + + check(key.selector().equals(selector), "invalid key.selector()"); + check(key.channel().equals(channel), "invalid key.channel()"); + + key.isReadable(); // just call, cannot set "ready" state manually + check(!key.isWritable(), "invalid key.isWritable()"); + check(!key.isConnectable(), "invalid key.isConnectable()"); + check(!key.isAcceptable(), "invalid key.isAcceptable()"); + + check(key.interestOps() == SelectionKey.OP_READ, "invalid key.interestOps()"); + + System.out.println(">> ready >> " + key.readyOps()); + + check(key.attachment() == null, "key.attachment() expected to be null"); + + key.cancel(); // try just in case + } + + private void check(boolean b, String msg) { + if (!b) { + throw new RuntimeException(msg); + } + } +} diff --git a/test/jdk/jdk/crac/Selector/keyAfterRestore/Test.java b/test/jdk/jdk/crac/Selector/keyAfterRestore/Test.java new file mode 100644 index 00000000000..9a01b7af10f --- /dev/null +++ b/test/jdk/jdk/crac/Selector/keyAfterRestore/Test.java @@ -0,0 +1,76 @@ +// Copyright 2019-2020 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 Azul Systems, 385 Moffett Park Drive, Suite 115, Sunnyvale, +// CA 94089 USA or visit www.azul.com if you need additional information or +// have any questions. + + +import java.nio.channels.*; +import java.io.IOException; +import jdk.crac.*; +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracTest; +import jdk.test.lib.crac.CracTestArg; + +/* + * @test Selector/keyAfterRestore + * @summary a trivial test for SelectionKey's state after restore + * @library /test/lib + * @build ChannelResource + * @build Test + * @run driver/timeout=30 jdk.test.lib.crac.CracTest true + * @run driver/timeout=30 jdk.test.lib.crac.CracTest false + */ +public class Test implements CracTest { + @CracTestArg + boolean openSelectorAtFirst; + + @Override + public void test() throws Exception { + new CracBuilder().doCheckpointAndRestore(); + } + + @Override + public void exec() throws Exception { + ChannelResource ch; + Selector selector = null; + + // check various order (see ZE-970) + if (openSelectorAtFirst) { selector = Selector.open(); } + + ch = new ChannelResource(); + ch.open(); + + if (!openSelectorAtFirst) { selector = Selector.open(); } + + ch.register(selector); + + try { + Core.checkpointRestore(); + } catch (CheckpointException | RestoreException e) { + e.printStackTrace(); + throw e; + } + + Thread.sleep(200); + + ch.checkKey(); + + selector.close(); + } +} + diff --git a/test/jdk/jdk/crac/Selector/multipleSelect/Test.java b/test/jdk/jdk/crac/Selector/multipleSelect/Test.java new file mode 100644 index 00000000000..17edf35e837 --- /dev/null +++ b/test/jdk/jdk/crac/Selector/multipleSelect/Test.java @@ -0,0 +1,172 @@ +// Copyright 2019-2020 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 Azul Systems, 385 Moffett Park Drive, Suite 115, Sunnyvale, +// CA 94089 USA or visit www.azul.com if you need additional information or +// have any questions. + +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracTest; +import jdk.test.lib.crac.CracTestArg; + +import java.nio.channels.Selector; +import java.io.IOException; +import java.util.Random; + +import java.util.concurrent.atomic.AtomicInteger; + +/* + * @test Selector/multipleSelect + * @summary check work of multiple select() + wakeup() + C/R + * @library /test/lib + * @build Test + * @run driver/timeout=30 jdk.test.lib.crac.CracTest ONLY_TIMEOUTS false + * @run driver/timeout=30 jdk.test.lib.crac.CracTest NO_TIMEOUTS false + * @run driver/timeout=30 jdk.test.lib.crac.CracTest MIXED false + * @run driver/timeout=30 jdk.test.lib.crac.CracTest ONLY_TIMEOUTS true + * @run driver/timeout=30 jdk.test.lib.crac.CracTest NO_TIMEOUTS true + * @run driver/timeout=30 jdk.test.lib.crac.CracTest MIXED true + */ +public class Test implements CracTest { + + private final static Random RND = new Random(); + + private final static long LONG_TIMEOUT = 3600_000; + private final static long SHORT_TIMEOUT = 3_000; + + public enum TestType { + NO_TIMEOUTS, // test only select(), wakeup + ONLY_TIMEOUTS, // test only select(timeout), do not call wakeup() + MIXED}; + + @CracTestArg(0) + TestType type; + + @CracTestArg(1) + boolean skipCR; + + @Override + public void test() throws Exception { + CracBuilder builder = new CracBuilder(); + if (skipCR) { + builder.doPlain(); + } else { + builder.doCheckpointAndRestore(); + } + } + + @Override + public void exec() throws Exception { + + long dt = (type == TestType.ONLY_TIMEOUTS) ? SHORT_TIMEOUT : LONG_TIMEOUT; + + int nThreads = (type == TestType.ONLY_TIMEOUTS) ? 5 : 20; + + AtomicInteger nSelected = new AtomicInteger(0); + + Selector selector = Selector.open(); + + Thread selectThreads[] = new Thread[nThreads]; + + boolean setTimeout[] = new boolean[nThreads]; + for (int i = 0; i < nThreads; ++i) { + boolean t = false; // NO_TIMEOUTS + if (type == TestType.ONLY_TIMEOUTS) { t = true; } + else if (type == TestType.MIXED) { t = RND.nextBoolean(); } + setTimeout[i] = t; + } + + Runnable rStart = new Runnable() { + @Override + public void run() { + + for (int i = 0; i < nThreads; ++i) { + + boolean timeout = setTimeout[i]; + + selectThreads[i] = new Thread(new Runnable() { + @Override + public void run() { + try { + int n = nSelected.incrementAndGet(); + System.out.println(">> select" + (timeout ? " (t)" : "") + " " + n); + if (timeout) { selector.select(dt); } + else { selector.select(); } + nSelected.decrementAndGet(); + System.out.println(">> done" + (timeout ? " (t)" : "") + " " + n); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }); + selectThreads[i].start(); + try { Thread.sleep(200); } catch (InterruptedException ie) {} + } + } + }; + Thread t = new Thread(rStart); + t.start(); + Thread.sleep(500); + + if (!skipCR) { + jdk.crac.Core.checkpointRestore(); + } + + t.join(); + Thread.sleep(1000); + + if (type == TestType.ONLY_TIMEOUTS) { // do not wake threads up, the timeouts must work + + while (nSelected.get() > 0) { Thread.sleep(1000); } + + } else { + + int nWakeups = 0; + while (true) { + + int nBefore = nSelected.get(); + if (nBefore == 0) { break; } + + System.out.println(">> wakeup() #" + (nWakeups + 1)); + ++nWakeups; + + selector.wakeup(); + while (nSelected.get() == nBefore) { // wait until any select() would be woken up + Thread.sleep(500); + } + } + + if (nWakeups > nThreads) { + selector.close(); + throw new RuntimeException("invalid number of wakeups"); + } + } + + // just in case... + for (Thread st: selectThreads) { st.join(); } + + // === check that the selector works as expected === + if (!selector.isOpen()) { throw new RuntimeException("the selector must be open"); } + + selector.wakeup(); + selector.select(); + + selector.selectNow(); + selector.select(200); + + selector.close(); + } +} diff --git a/test/jdk/jdk/crac/Selector/multipleSelectNow/Test.java b/test/jdk/jdk/crac/Selector/multipleSelectNow/Test.java new file mode 100644 index 00000000000..96d2adcd62c --- /dev/null +++ b/test/jdk/jdk/crac/Selector/multipleSelectNow/Test.java @@ -0,0 +1,111 @@ +// Copyright 2019-2020 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 Azul Systems, 385 Moffett Park Drive, Suite 115, Sunnyvale, +// CA 94089 USA or visit www.azul.com if you need additional information or +// have any questions. + +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracTest; +import jdk.test.lib.crac.CracTestArg; + +import java.nio.channels.Selector; +import java.io.IOException; + +import java.util.concurrent.atomic.AtomicInteger; + +/* + * @test Selector/multipleSelectNow + * @summary check work of multiple selectNow() + C/R peaceful coexistence + * @library /test/lib + * @build Test + * @run driver jdk.test.lib.crac.CracTest false + * @run driver jdk.test.lib.crac.CracTest true + */ +public class Test implements CracTest { + + @CracTestArg + boolean skipCR; + + @Override + public void test() throws Exception { + CracBuilder builder = new CracBuilder(); + if (skipCR) { + builder.doPlain(); + } else { + builder.doCheckpointAndRestore(); + } + } + + @Override + public void exec() throws Exception { + AtomicInteger nSelected = new AtomicInteger(0); + + Selector selector = Selector.open(); + + int nThreads = skipCR ? 30 : 150; // some selectNow() calls should occur at the same time with C/R + Thread threads[] = new Thread[nThreads]; + + Runnable rStart = new Runnable() { + @Override + public void run() { + + for (int i = 0; i < threads.length; ++i) { + + threads[i] = new Thread(new Runnable() { + @Override + public void run() { + try { + System.out.println("selectNow"); + nSelected.incrementAndGet(); + selector.selectNow(); + System.out.println("done"); + nSelected.decrementAndGet(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }); + threads[i].start(); + try { Thread.sleep(5); } catch (InterruptedException ie) {} + } + } + }; + Thread tStart = new Thread(rStart); + tStart.start(); + Thread.sleep(500); + + if (!skipCR) { + jdk.crac.Core.checkpointRestore(); + } + + tStart.join(); + + do { Thread.sleep(2000); } while (nSelected.get() > 0); + for (Thread t: threads) { t.join(); } // just in case... + + // === check that the selector works as expected === + if (!selector.isOpen()) { throw new RuntimeException("the selector must be open"); } + + selector.wakeup(); + selector.select(); + + selector.selectNow(); + selector.select(200); + + selector.close(); + } +} diff --git a/test/jdk/jdk/crac/Selector/multipleSelectSingleClose/Test.java b/test/jdk/jdk/crac/Selector/multipleSelectSingleClose/Test.java new file mode 100644 index 00000000000..ac9daaa9622 --- /dev/null +++ b/test/jdk/jdk/crac/Selector/multipleSelectSingleClose/Test.java @@ -0,0 +1,127 @@ +// Copyright 2019-2020 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 Azul Systems, 385 Moffett Park Drive, Suite 115, Sunnyvale, +// CA 94089 USA or visit www.azul.com if you need additional information or +// have any questions. + +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracTest; +import jdk.test.lib.crac.CracTestArg; + +import java.nio.channels.Selector; +import java.nio.channels.ClosedSelectorException; +import java.io.IOException; +import java.util.Random; + +import java.util.concurrent.atomic.AtomicInteger; + +/* + * @test Selector/multipleSelectSingleClose + * @summary check a coexistence of multiple select() + C/R in case when the selector is finally closed + * @library /test/lib + * @build Test + * @run driver jdk.test.lib.crac.CracTest false false + * @run driver jdk.test.lib.crac.CracTest false true + * @run driver jdk.test.lib.crac.CracTest true true + */ +public class Test implements CracTest { + private final static Random RND = new Random(); + + @CracTestArg(0) + boolean skipCR; + + @CracTestArg(1) + boolean closeBeforeCheckpoint; + + @Override + public void test() throws Exception { + CracBuilder builder = new CracBuilder(); + if (skipCR) { + builder.doPlain(); + } else { + builder.doCheckpointAndRestore(); + } + } + + @Override + public void exec() throws Exception { + int nThreads = 20; + + AtomicInteger nSelected = new AtomicInteger(0); + + Selector selector = Selector.open(); + + Thread selectThreads[] = new Thread[nThreads]; + + Runnable rStart = new Runnable() { + @Override + public void run() { + + for (int i = 0; i < nThreads; ++i) { + + selectThreads[i] = new Thread(new Runnable() { + @Override + public void run() { + try { + boolean timeout = RND.nextBoolean(); + int n = nSelected.incrementAndGet(); + System.out.println(">> select" + (timeout ? " (t)" : "") + " " + n); + if (timeout) { selector.select(10 + RND.nextInt(7_000)); } + else { selector.select(); } + nSelected.decrementAndGet(); + System.out.println(">> done" + (timeout ? " (t)" : "") + " " + n); + } catch (ClosedSelectorException e) { + System.out.println(">> ClosedSelectorException"); // expected when the selector is closed + nSelected.decrementAndGet(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }); + selectThreads[i].start(); + try { Thread.sleep(50); } catch (InterruptedException ie) {} + } + } + }; + Thread tStart = new Thread(rStart); + tStart.start(); + Thread.sleep(500); + + if (closeBeforeCheckpoint) { + tStart.join(); + Thread.sleep(1000); + selector.close(); + } + + if (!skipCR) { + jdk.crac.Core.checkpointRestore(); + } + + if (!closeBeforeCheckpoint) { + tStart.join(); + Thread.sleep(1000); + selector.close(); + } + + do { Thread.sleep(2000); } while (nSelected.get() > 0); + + if (nSelected.get() < 0) { throw new RuntimeException("negative nSelected??"); } + + // just in case... + for (Thread t: selectThreads) { t.join(); } + } +} diff --git a/test/jdk/jdk/crac/Selector/selectAfterWakeup/Test.java b/test/jdk/jdk/crac/Selector/selectAfterWakeup/Test.java new file mode 100644 index 00000000000..7623b4638d6 --- /dev/null +++ b/test/jdk/jdk/crac/Selector/selectAfterWakeup/Test.java @@ -0,0 +1,80 @@ +// Copyright 2019-2020 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 Azul Systems, 385 Moffett Park Drive, Suite 115, Sunnyvale, +// CA 94089 USA or visit www.azul.com if you need additional information or +// have any questions. + +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracTest; +import jdk.test.lib.crac.CracTestArg; + +import java.io.IOException; +import java.nio.channels.Selector; + +/* + * @test Selector/selectAfterWakeup + * @summary check that the Selector's wakeup() makes the subsequent select() call to return immediately + * (see also jdk/test/java/nio/channels/Selector/WakeupSpeed.java); + * covers ZE-983 + * @library /test/lib + * @build Test + * @run driver jdk.test.lib.crac.CracTest true false false + * @run driver jdk.test.lib.crac.CracTest true false true + * @run driver jdk.test.lib.crac.CracTest true true false + * @run driver jdk.test.lib.crac.CracTest true true true + * @run driver jdk.test.lib.crac.CracTest false true false + * @run driver jdk.test.lib.crac.CracTest false true true + */ +public class Test implements CracTest { + @CracTestArg(0) + boolean wakeupBeforeCheckpoint; + + @CracTestArg(1) + boolean wakeupAfterRestore; + + @CracTestArg(2) + boolean setSelectTimeout; + + @Override + public void test() throws Exception { + new CracBuilder().doCheckpointAndRestore(); + } + + @Override + public void exec() throws Exception { + + Selector selector = Selector.open(); + + // do this just in case + selector.wakeup(); + selector.select(); + + if (wakeupBeforeCheckpoint) { + selector.wakeup(); + } + + jdk.crac.Core.checkpointRestore(); + + if (wakeupAfterRestore) { + selector.wakeup(); + } + if (setSelectTimeout) { selector.select(3600_000); } + else { selector.select(); } + + selector.close(); + } +} diff --git a/test/jdk/jdk/crac/Selector/selectAndWakeupAfterRestore/Test.java b/test/jdk/jdk/crac/Selector/selectAndWakeupAfterRestore/Test.java new file mode 100644 index 00000000000..d6bac95ae1c --- /dev/null +++ b/test/jdk/jdk/crac/Selector/selectAndWakeupAfterRestore/Test.java @@ -0,0 +1,70 @@ +// Copyright 2019-2020 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 Azul Systems, 385 Moffett Park Drive, Suite 115, Sunnyvale, +// CA 94089 USA or visit www.azul.com if you need additional information or +// have any questions. + +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracTest; + +import java.io.IOException; +import java.nio.channels.Selector; + +/* + * @test Selector/selectAndWakeupAfterRestore + * @summary a trivial check that Selector.wakeup() after restore behaves as expected + * @library /test/lib + * @build Test + * @run driver jdk.test.lib.crac.CracTest + */ +public class Test implements CracTest { + @Override + public void test() throws Exception { + new CracBuilder().doCheckpointAndRestore(); + } + + private static void selectAndWakeup(Selector selector) throws java.io.IOException { + + new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(7000); + System.out.println(">> waking up"); + selector.wakeup(); + } catch (InterruptedException ie) { throw new RuntimeException(ie); } + } + }).start(); + + System.out.println(">> selecting"); + selector.select(); + } + + @Override + public void exec() throws Exception { + + Selector selector = Selector.open(); + + selectAndWakeup(selector); // just in case + + jdk.crac.Core.checkpointRestore(); + + selectAndWakeup(selector); + + selector.close(); + } +} diff --git a/test/jdk/jdk/crac/Selector/wakeupAfterRestore/Test.java b/test/jdk/jdk/crac/Selector/wakeupAfterRestore/Test.java new file mode 100644 index 00000000000..730683b153f --- /dev/null +++ b/test/jdk/jdk/crac/Selector/wakeupAfterRestore/Test.java @@ -0,0 +1,89 @@ +// Copyright 2019-2020 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 Azul Systems, 385 Moffett Park Drive, Suite 115, Sunnyvale, +// CA 94089 USA or visit www.azul.com if you need additional information or +// have any questions. + +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracTest; +import jdk.test.lib.crac.CracTestArg; + +import java.nio.channels.Selector; +import java.io.IOException; + +/* + * @test Selector/wakeupAfterRestore + * @summary check that the thread blocked by Selector.select() on checkpoint could be properly woken up after restore + * @library /test/lib + * @build Test + * @run driver jdk.test.lib.crac.CracTest true + * @run driver jdk.test.lib.crac.CracTest false + */ +public class Test implements CracTest { + + private final static long TIMEOUT = 3600_000; // looong timeout + + static boolean awakened; + + @CracTestArg + boolean setTimeout; + + @Override + public void test() throws Exception { + new CracBuilder().doCheckpointAndRestore(); + } + + @Override + public void exec() throws Exception { + Selector selector = Selector.open(); + Runnable r = new Runnable() { + @Override + public void run() { + System.out.println(">> select, setTimeout = " + setTimeout); + try { + awakened = false; + if (setTimeout) { selector.select(TIMEOUT); } + else { selector.select(); } + awakened = true; + } catch (IOException e) { throw new RuntimeException(e); } + } + }; + Thread t = new Thread(r); + t.start(); + Thread.sleep(1000); + + jdk.crac.Core.checkpointRestore(); + + System.out.print(">> waking up: "); + selector.wakeup(); + t.join(); + System.out.println("done"); + + if (!awakened) { throw new RuntimeException("not awakened!"); } + + // check that the selector works as expected + + if (!selector.isOpen()) { throw new RuntimeException("the selector must be open"); } + + selector.wakeup(); + selector.select(); + + selector.selectNow(); + selector.select(200); + selector.close(); + } +} diff --git a/test/jdk/jdk/crac/Selector/wakeupByClose/Test.java b/test/jdk/jdk/crac/Selector/wakeupByClose/Test.java new file mode 100644 index 00000000000..38a755e7d72 --- /dev/null +++ b/test/jdk/jdk/crac/Selector/wakeupByClose/Test.java @@ -0,0 +1,106 @@ +// Copyright 2019-2020 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 Azul Systems, 385 Moffett Park Drive, Suite 115, Sunnyvale, +// CA 94089 USA or visit www.azul.com if you need additional information or +// have any questions. + +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracTest; +import jdk.test.lib.crac.CracTestArg; + +import java.nio.channels.Selector; +import java.io.IOException; + +/* + * @test Selector/wakeupByClose + * @summary check that the Selector's close() wakes it up after restore + * @library /test/lib + * @build Test + * @run driver jdk.test.lib.crac.CracTest true false + * @run driver jdk.test.lib.crac.CracTest false false + * @run driver jdk.test.lib.crac.CracTest true true + * @run driver jdk.test.lib.crac.CracTest false true + */ +public class Test implements CracTest { + + static boolean awakened, closed; + + @CracTestArg(0) + boolean setTimeout; + + @CracTestArg(1) + boolean skipCR; + + @Override + public void test() throws Exception { + CracBuilder builder = new CracBuilder(); + if (skipCR) { + builder.doPlain(); + } else { + builder.doCheckpointAndRestore(); + } + } + + @Override + public void exec() throws Exception { + Selector selector = Selector.open(); + + Thread tSelect = new Thread(new Runnable() { + @Override + public void run() { + try { + awakened = false; + if (setTimeout) { selector.select(3600_000); } + else { selector.select(); } + awakened = true; + } catch (IOException e) { throw new RuntimeException(e); } + } + }); + tSelect.start(); + + Thread.sleep(3000); + + if (!skipCR) { jdk.crac.Core.checkpointRestore(); } + + // close() must wakeup the selector + Thread tClose = new Thread(new Runnable() { + + @Override + public void run() { + try { + closed = false; + selector.close(); + closed = true; + } catch (IOException e) { throw new RuntimeException(e); } + } + }); + tClose.start(); + tClose.join(); + tSelect.join(); + + if (!awakened) { + selector.wakeup(); + throw new RuntimeException("selector did not wake up"); + } + + if (!closed) { + selector.close(); + throw new RuntimeException("selector did not close"); + } + } +} + diff --git a/test/jdk/jdk/crac/Selector/wakeupByTimeoutAfterRestore/Test.java b/test/jdk/jdk/crac/Selector/wakeupByTimeoutAfterRestore/Test.java new file mode 100644 index 00000000000..5b3d8915f05 --- /dev/null +++ b/test/jdk/jdk/crac/Selector/wakeupByTimeoutAfterRestore/Test.java @@ -0,0 +1,78 @@ +// Copyright 2019-2020 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 Azul Systems, 385 Moffett Park Drive, Suite 115, Sunnyvale, +// CA 94089 USA or visit www.azul.com if you need additional information or +// have any questions. + +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracTest; + +import java.nio.channels.Selector; +import java.io.IOException; + +/* + * @test Selector/wakeupByTimeoutAfterRestore + * @summary check that the Selector selected before the checkpoint, + * will wake up by timeout after the restore + * @library /test/lib + * @build Test + * @run driver jdk.test.lib.crac.CracTest + */ +public class Test implements CracTest { + + private final static long TIMEOUT = 40_000; // 40 seconds + + static boolean awakened = false; + + @Override + public void test() throws Exception { + new CracBuilder().doCheckpointAndRestore(); + } + + @Override + public void exec() throws Exception { + Selector selector = Selector.open(); + Runnable r = new Runnable() { + @Override + public void run() { + try { + selector.select(TIMEOUT); + awakened = true; + } catch (IOException e) { throw new RuntimeException(e); } + } + }; + Thread t = new Thread(r); + t.start(); + Thread.sleep(1000); + + jdk.crac.Core.checkpointRestore(); + + t.join(); + if (!awakened) { throw new RuntimeException("not awakened!"); } + + // check that the selector works as expected + + if (!selector.isOpen()) { throw new RuntimeException("the selector must be open"); } + + selector.wakeup(); + selector.select(); + + selector.selectNow(); + selector.select(200); + selector.close(); + } +} diff --git a/test/jdk/jdk/crac/TestHello.java b/test/jdk/jdk/crac/TestHello.java new file mode 100644 index 00000000000..690361d8406 --- /dev/null +++ b/test/jdk/jdk/crac/TestHello.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024, Alibaba Group Holding Limited. 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. + */ +public class TestHello { + public static void main(String[] args) { + System.out.println("hello"); + } +} diff --git a/test/jdk/jdk/crac/fileDescriptors/CheckpointWithOpenFdsTest.java b/test/jdk/jdk/crac/fileDescriptors/CheckpointWithOpenFdsTest.java new file mode 100644 index 00000000000..14634b081e7 --- /dev/null +++ b/test/jdk/jdk/crac/fileDescriptors/CheckpointWithOpenFdsTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023, 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.Utils; +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracLogger; +import jdk.test.lib.crac.CracTest; +import java.nio.file.Path; +import java.util.*; + +/** + * @test + * @library /test/lib + * @build CheckpointWithOpenFdsTest + * @run driver jdk.test.lib.crac.CracTest + */ +public class CheckpointWithOpenFdsTest extends CracLogger implements CracTest { + private static final String EXTRA_FD_WRAPPER = Path.of(Utils.TEST_SRC, "extra_fd_wrapper.sh").toString(); + + @Override + public void test() throws Exception { + CracBuilder builder = new CracBuilder(); + builder.startCheckpoint(Arrays.asList(EXTRA_FD_WRAPPER, CracBuilder.JAVA)).waitForCheckpointed(); + builder.logToFile(true).doRestore().fileOutputAnalyser().shouldContain(RESTORED_MESSAGE); + } + + @Override + public void exec() throws Exception { + Core.checkpointRestore(); + writeLog(RESTORED_MESSAGE); + } +} diff --git a/test/jdk/jdk/crac/fileDescriptors/IgnoredFileDescriptorsTest.java b/test/jdk/jdk/crac/fileDescriptors/IgnoredFileDescriptorsTest.java new file mode 100644 index 00000000000..08b815d7055 --- /dev/null +++ b/test/jdk/jdk/crac/fileDescriptors/IgnoredFileDescriptorsTest.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2023, 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.Utils; +import jdk.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracLogger; +import jdk.test.lib.crac.CracTest; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @test + * @library /test/lib + * @build IgnoredFileDescriptorsTest + * @run driver jdk.test.lib.crac.CracTest + */ +public class IgnoredFileDescriptorsTest extends CracLogger implements CracTest { + private static final String EXTRA_FD_WRAPPER = Path.of(Utils.TEST_SRC, "extra_fd_wrapper.sh").toString(); + + @Override + public void test() throws Exception { + List prefix = new ArrayList<>(); + prefix.add(EXTRA_FD_WRAPPER); + prefix.addAll(Arrays.asList("-o", "43", "/dev/stdout")); + prefix.addAll(Arrays.asList("-o", "45", "/dev/urandom")); + prefix.add(CracBuilder.JAVA); + prefix.add("-XX:CRaCIgnoredFileDescriptors=43,/dev/null,44,/dev/urandom"); + + CracBuilder builder = new CracBuilder(); + builder.startCheckpoint(prefix).waitForCheckpointed(); + builder.logToFile(true).doRestore().fileOutputAnalyser().shouldContain(RESTORED_MESSAGE); + } + + @Override + public void exec() throws Exception { + try (var stream = Files.list(Path.of("/proc/self/fd"))) { + Map fds = stream.filter(Files::isSymbolicLink) + .collect(Collectors.toMap( + f -> Integer.parseInt(f.toFile().getName()), + f -> { + try { + return Files.readSymbolicLink(f).toFile().getAbsoluteFile().toString(); + } catch (IOException e) { + throw new RuntimeException(e); + } + })); + if (fds.containsKey(42)) { + throw new IllegalStateException("Oh no, 42 was not supposed to be ignored"); + } else if (!fds.containsKey(0) || !fds.containsKey(1) || !fds.containsKey(2)) { + throw new IllegalStateException("Missing standard I/O? Available: " + fds); + } else if (!fds.containsKey(43)) { + throw new IllegalStateException("Missing FD 43"); + } else if (!fds.containsValue("/dev/urandom")) { + throw new IllegalStateException("Missing /dev/urandom"); + } + } + Core.checkpointRestore(); + writeLog(RESTORED_MESSAGE); + } +} diff --git a/test/jdk/jdk/crac/fileDescriptors/extra_fd_wrapper.sh b/test/jdk/jdk/crac/fileDescriptors/extra_fd_wrapper.sh new file mode 100755 index 00000000000..1942a1efac9 --- /dev/null +++ b/test/jdk/jdk/crac/fileDescriptors/extra_fd_wrapper.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# Java opens all files with O_CLOEXEC (or calls fcntl(FD_CLOEXEC)) so we cannot trigger this behaviour from Java code; +# this opens a file descriptor and executes subprocess based on its arguments. +FILE=$(mktemp -p /dev/shm) +exec 42<>$FILE +# criu uses DEFAULT_GHOST_LIMIT 1M - let's create a file bigger than that +dd if=/dev/urandom bs=4096 count=257 >&42 2>/dev/null +rm $FILE +# Open some extra files +while [ $1 = "-o" ]; do + eval "exec $2<>$3" + shift 3 +done +exec "$@" diff --git a/test/jdk/jdk/crac/java/lang/Thread/JoinSleepWaitOnCRPauseTest.java b/test/jdk/jdk/crac/java/lang/Thread/JoinSleepWaitOnCRPauseTest.java new file mode 100644 index 00000000000..f369f8fb1de --- /dev/null +++ b/test/jdk/jdk/crac/java/lang/Thread/JoinSleepWaitOnCRPauseTest.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2022, 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.test.lib.crac.CracBuilder; +import jdk.test.lib.crac.CracTest; +import jdk.test.lib.crac.CracTestArg; + +import java.util.concurrent.CountDownLatch; +/* + * @test JoinSleepWaitOnCRPauseTest.java + * @requires (os.family == "linux") + * @library /test/lib + * @summary check if Thread.join(timeout), Thread.sleep(timeout) + * and Object.wait(timeout) + * will be completed on restore immediately + * if their end time fell on the CRaC pause period + * (i.e. between the checkpoint and restore) + * + * @build JoinSleepWaitOnCRPauseTest + * @run driver jdk.test.lib.crac.CracTest join_ms + * @run driver jdk.test.lib.crac.CracTest join_ns + * @run driver jdk.test.lib.crac.CracTest sleep_ms + * @run driver jdk.test.lib.crac.CracTest sleep_ns + * @run driver jdk.test.lib.crac.CracTest wait_ms + * @run driver jdk.test.lib.crac.CracTest wait_ns + */ +public class JoinSleepWaitOnCRPauseTest implements CracTest { + private enum TestType { + join_ms, join_ns, sleep_ms, sleep_ns, wait_ms, wait_ns + } + + @CracTestArg + private TestType testType; + + private final static long EPS_MS = Long.parseLong(System.getProperty( + "test.jdk.jdk.crac.java.lang.Thread.crac.JoinSleepWaitOnCRPauseTest.eps", + "100")); // default: 0.1s + + private static final long CRPAUSE_MS = 4000; + + private static final long T_MS = CRPAUSE_MS / 2; + private static final int T_NS = 100; + + private volatile long tDone = -1; + + private final CountDownLatch checkpointLatch = new CountDownLatch(1); + + @Override + public void exec() throws Exception { + + String op = testType.name(); + op = op.substring(0, op.length() - 3); // remove suffix + + Thread mainThread = Thread.currentThread(); + + Runnable r = () -> { + + try { + + checkpointLatch.countDown(); + + switch (testType) { + + case join_ms: + mainThread.join(T_MS); + break; + + case join_ns: + mainThread.join(T_MS, T_NS); + break; + + case sleep_ms: + Thread.sleep(T_MS); + break; + + case sleep_ns: + Thread.sleep(T_MS, T_NS); + break; + + case wait_ms: + synchronized(this) { wait(T_MS); } + break; + + case wait_ns: + synchronized(this) { wait(T_MS, T_NS); } + break; + + default: + throw new IllegalArgumentException("unknown test type"); + } + + } catch (InterruptedException ie) { + throw new RuntimeException(ie); + } + + tDone = System.currentTimeMillis(); + }; + + Thread t = new Thread(r); + t.start(); + + // this additional synchronization is probably redundant; + // adding it to ensure we get the expected TIMED_WAITING state + // from "our" join or sleep + checkpointLatch.await(); + + // it is expected that EPS_MS is enough to complete the join/sleep + // on restore => expecting that 5 * EPS_MS is enough to enter them + long dt = 5 * EPS_MS; + Thread.sleep(dt); + + if (t.getState() != Thread.State.TIMED_WAITING) { + throw new AssertionError("was not able to enter " + op + + " in " + dt + " ns"); + } + + long tBeforeCheckpoint = System.currentTimeMillis(); + + jdk.crac.Core.checkpointRestore(); + + long tAfterRestore = System.currentTimeMillis(); + + t.join(); + + long pause = tAfterRestore - tBeforeCheckpoint; + if (pause < CRPAUSE_MS - EPS_MS) { + throw new AssertionError( + "the CR pause was less than " + CRPAUSE_MS + " ms"); + } + + if (tDone < tBeforeCheckpoint + EPS_MS) { + throw new AssertionError( + op + " has finished before the checkpoint"); + } + + long eps = Math.abs(tAfterRestore - tDone); + + if (eps > T_MS) { + throw new RuntimeException( + "the " + op + "ing thread has finished in " + eps + " ms " + + "after the restore (expected: " + T_MS + " ms)"); + } + } + + @Override + public void test() throws Exception { + CracBuilder builder = new CracBuilder() + .imageDir("cr_" + testType.name()); + builder.doCheckpoint(); + + // sleep a few seconds to ensure the task execution time + // falls within this pause period + Thread.sleep(CRPAUSE_MS); + + builder.doRestore(); + } +} diff --git a/test/jdk/jdk/crac/java/net/InetAddress/ResolveTest.java b/test/jdk/jdk/crac/java/net/InetAddress/ResolveTest.java new file mode 100644 index 00000000000..82d95995972 --- /dev/null +++ b/test/jdk/jdk/crac/java/net/InetAddress/ResolveTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2023, 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.test.lib.Utils; +import jdk.test.lib.containers.docker.Common; +import jdk.test.lib.containers.docker.DockerTestUtils; +import jdk.test.lib.crac.*; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Date; +import java.util.concurrent.*; + +import static jdk.test.lib.Asserts.fail; + +/* + * @test + * @summary Test if InetAddress cache is flushed after checkpoint/restore + * @requires docker.support + * @library /test/lib + * @build ResolveTest + * @run driver jdk.test.lib.crac.CracTest + */ +public class ResolveTest extends CracLogger implements CracTest { + private static final String imageName = Common.imageName("inet-address"); + public static final String TEST_HOSTNAME = "some.test.hostname.example.com"; + private static final long WAIT_TIMEOUT = 10 * 1000L; + + @CracTestArg(value = 0, optional = true) + String ip; + + @CracTestArg(value = 1, optional = true) + String checkFile; + + @Override + public void test() throws Exception { + if (!DockerTestUtils.canTestDocker()) { + return; + } + //current ubuntu latest glibc version is 2.35, and the host glibc version is 2.32 + //there is a crash when restore VMA. The root cause is unknown. + //Here low the ubuntu with 20.04 so that glibc version is 2.31, the crash disappeared. + //There also other solution: change the docker image with alinux3 that same as the host. + // + System.setProperty("jdk.test.docker.image.version", "20.04"); + + CracBuilder builder = new CracBuilder() + .inDockerImage(imageName).dockerOptions("--add-host", TEST_HOSTNAME + ":192.168.12.34") + .logToFile(true) + .oneStopDockerRun(true) + .bumpPid(true) + .args(CracTest.args(TEST_HOSTNAME, "/second-run")); + + try { + CracProcess crProcess = builder.startCheckpoint(); + crProcess.watchFile(WAIT_TIMEOUT, "192.168.12.34"); + builder.checkpointViaJcmd(); + //builder.checkpointViaJcmd kill the process that run in container,but + //the container not exit immediately. There is an error that container + // "xxxx" exists if run immediately. + builder.waitUntilContainerExit(WAIT_TIMEOUT); + CracBuilder builderRestore = new CracBuilder() + .inDockerImage(imageName).dockerOptions( + "--add-host", TEST_HOSTNAME + ":192.168.56.78", + "--volume", Utils.TEST_CLASSES + ":/second-run") + .logToFile(true) + .oneStopDockerRun(true); + CracProcess restoreProcess = builderRestore.startRestore(); + restoreProcess.watchFile(WAIT_TIMEOUT, "192.168.56.78"); + } catch (Exception e) { + e.printStackTrace(); + fail("Throw exception :" + e); + } finally { + builder.deepClearContainer(); + } + } + + @Override + public void exec() throws Exception { + if (ip == null || checkFile == null) { + System.err.println("Args: "); + return; + } + printAddress(ip, this); + while (!Files.exists(Path.of(checkFile))) { + try { + //noinspection BusyWait + Thread.sleep(100); + } catch (InterruptedException e) { + System.err.println("Interrupted!"); + return; + } + } + printAddress(ip, this); + } + + private static void printAddress(String hostname, CracLogger logger) throws IOException, InterruptedException { + StringBuilder sb = new StringBuilder(); + try { + InetAddress address = InetAddress.getByName(hostname); + // we will assume IPv4 address + byte[] bytes = address.getAddress(); + sb.append(bytes[0] & 0xFF); + for (int i = 1; i < bytes.length; ++i) { + sb.append('.'); + sb.append(bytes[i] & 0xFF); + } + sb.append("\n"); + } catch (UnknownHostException e) { + sb.append("\n"); + } + logger.writeLog(sb.toString()); + } +} diff --git a/test/jdk/jdk/crac/java/net/InetAddress/bump_pid_run.sh b/test/jdk/jdk/crac/java/net/InetAddress/bump_pid_run.sh new file mode 100644 index 00000000000..bdfa5e68cae --- /dev/null +++ b/test/jdk/jdk/crac/java/net/InetAddress/bump_pid_run.sh @@ -0,0 +1,4 @@ +#! /bin/sh +minPid=100 +while [ $(cat /proc/sys/kernel/ns_last_pid) -le ${minPid} ]; do cat /dev/null; done +"$@" \ No newline at end of file diff --git a/test/jdk/jdk/crac/recursiveCheckpoint/Test.java b/test/jdk/jdk/crac/recursiveCheckpoint/Test.java new file mode 100644 index 00000000000..90a3ae00103 --- /dev/null +++ b/test/jdk/jdk/crac/recursiveCheckpoint/Test.java @@ -0,0 +1,145 @@ +// Copyright 2019-2021 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 Azul Systems, 385 Moffett Park Drive, Suite 115, Sunnyvale, +// CA 94089 USA or visit www.azul.com if you need additional information or +// have any questions. + +import jdk.crac.*; +import jdk.test.lib.crac.*; + +import java.util.concurrent.atomic.AtomicInteger; + +/* + * @test + * @summary check that the recursive checkpoint is not allowed + * @library /test/lib + * @build Test + * @run driver/timeout=60 jdk.test.lib.crac.CracTest 10 + */ +public class Test implements Resource, CracTest { + private static final AtomicInteger counter = new AtomicInteger(0); + private static Exception exception = null; + + @CracTestArg + int numThreads; + + @Override + public void test() throws Exception { + CracBuilder builder = new CracBuilder().engine(CracEngine.PAUSE); + CracProcess process = builder.startCheckpoint(); + //If image dir not exist, process.waitForPausePid() can throw an java.nio.file.NoSuchFileException. + //This can happen when run this testcase on host with high load. + process.ensureFileIntegrityForPausePid(); + process.waitForPausePid(); + for (int i = 1; i <= numThreads + 1; ++i) { + System.err.printf("Restore #%d%n", i); + process.ensureFileIntegrityForPausePid(); + builder.doRestore(); + } + process.waitForSuccess(); + } + + private static class TestThread extends Thread { + + @Override + public void run() { + try { + jdk.crac.Core.checkpointRestore(); + } catch (CheckpointException e) { + if (exception == null) + exception = new RuntimeException("Checkpoint in thread ERROR " + e); + } catch (RestoreException e) { + if (exception == null) + exception = new RuntimeException("Restore in thread ERROR " + e); + } + } + }; + + @Override + public void beforeCheckpoint(Context context) throws Exception { + try { + int c = counter.incrementAndGet(); + if (c > 1) { + if (exception == null) + exception = new RuntimeException("Parallel checkpoint"); + } + Thread.sleep(100); + jdk.crac.Core.checkpointRestore(); + if (exception != null) + exception = new RuntimeException("Checkpoint Exception should be thrown"); + } catch (CheckpointException e) { + // Expected Exception + } catch (RestoreException e) { + if (exception == null) + exception = new RuntimeException("Restore ERROR " + e); + } + } + + @Override + public void afterRestore(Context context) throws Exception { + try { + int c = counter.get(); + if (c > 1) { + if (exception == null) + exception = new RuntimeException("Parallel checkpoint"); + } + Thread.sleep(100); + jdk.crac.Core.checkpointRestore(); + if (exception == null) + exception = new RuntimeException("Checkpoint Exception should be thrown"); + } catch (CheckpointException e) { + // Expected Exception + } catch (RestoreException e) { + if (exception == null) + exception = new RuntimeException("Restore ERROR " + e); + } finally { + counter.decrementAndGet(); + } + } + + @Override + public void exec() throws Exception { + Core.getGlobalContext().register(new Test()); + + TestThread[] threads = new TestThread[numThreads]; + for (int i = 0; i < numThreads; i++) { + threads[i] = new TestThread(); + threads[i].start(); + }; + + Thread.sleep(100); + try { + jdk.crac.Core.checkpointRestore(); + } catch (CheckpointException e) { + throw new RuntimeException("Checkpoint ERROR " + e); + } catch (RestoreException e) { + throw new RuntimeException("Restore ERROR " + e); + } + + for (int i = 0; i < numThreads; i++) { + threads[i].join(); + }; + + long ccounter = counter.get(); + if (ccounter != 0) + throw new RuntimeException("Incorrect counter after restore: " + ccounter + " instead of 0"); + if (exception != null) { + throw exception; + } + System.out.println("PASSED"); + } +} diff --git a/test/jdk/jdk/crac/stdoutInDocker/Dockerfile b/test/jdk/jdk/crac/stdoutInDocker/Dockerfile new file mode 100644 index 00000000000..41893e188e7 --- /dev/null +++ b/test/jdk/jdk/crac/stdoutInDocker/Dockerfile @@ -0,0 +1,8 @@ +FROM alibaba-cloud-linux-3-registry.cn-hangzhou.cr.aliyuncs.com/alinux3/alinux3:latest +ADD jdk.tar.gz / +RUN setcap 'cap_checkpoint_restore+eip cap_setpcap+eip' /jdk/lib/criu +RUN groupadd -g 1000 app && useradd -u 1000 -g app -s /bin/sh -d /home/app -m app +USER app:app +ENV JAVA_HOME=/jdk +ADD classes.tar.gz takepid.sh runapp.sh /home/app +CMD /home/app/runapp.sh \ No newline at end of file diff --git a/test/jdk/jdk/crac/stdoutInDocker/TestStdoutInDocker.java b/test/jdk/jdk/crac/stdoutInDocker/TestStdoutInDocker.java new file mode 100644 index 00000000000..a3e26a27396 --- /dev/null +++ b/test/jdk/jdk/crac/stdoutInDocker/TestStdoutInDocker.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024, Alibaba Group Holding Limited. 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.Context; +import jdk.crac.Core; +import jdk.crac.Resource; + +/** + * @summary Test run "docker logs" can get output from stdout/stderr correctly. + */ + +public class TestStdoutInDocker { + public static void main(String[] args) throws Exception { + System.out.println("Message from stdout before doing checkpoint"); + System.err.println("Message from stderr before doing checkpoint"); + Core.getGlobalContext().register(new Resource() { + @Override + public void beforeCheckpoint(Context context) throws Exception { + } + + @Override + public void afterRestore(Context context) throws Exception { + System.out.println("Message from stdout in afterRestore callback"); + System.err.println("Message from stderr in afterRestore callback"); + } + }); + Core.checkpointRestore(); + System.out.println("Message from stdout afterRestore"); + System.err.println("Message from stderr afterRestore"); + int sleepTime = Integer.parseInt(System.getenv("SLEEP_TIME")); + System.out.println("Sleep time is : " + sleepTime); + Thread.sleep(sleepTime); + } +} diff --git a/test/jdk/jdk/crac/stdoutInDocker/TestStdoutInDocker.sh b/test/jdk/jdk/crac/stdoutInDocker/TestStdoutInDocker.sh new file mode 100644 index 00000000000..32ca431f527 --- /dev/null +++ b/test/jdk/jdk/crac/stdoutInDocker/TestStdoutInDocker.sh @@ -0,0 +1,131 @@ +#! /bin/sh -x +# +# Copyright (c) 2024, Alibaba Group Holding Limited. 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. +# + + +# @test +# @summary Test "docker logs" can get stdout/stderr after restoring. +# Test docker run with the combination of (privileged/unprivileged) and (interactive/detach) modes. +# @requires docker.support +# @requires os.family == "linux" +# @requires os.arch=="amd64" | os.arch=="aarch64" +# @build TestStdoutInDocker +# @run shell TestStdoutInDocker.sh + +TEST_IMAGE="crac-stdout-err-testimage:latest" +PRIV_PARAM="--privileged --user root" +NON_PRIV_PARAM="--security-opt seccomp=unconfined --cap-add CHECKPOINT_RESTORE --cap-add CAP_SETPCAP --env CRAC_UNPRIV_OPT=-XX:+CRaCUnprivileged" +CHECKPOINT_PARAM="--env CRAC_INHERIT_OPT=-XX:CRaCRestoreInheritPipeFds=1,2 --env DO_CHECKPOINT=true --env CRAC_IMAGE_DIR=/cr" +RESTORE_PARAM="--env DO_RESTORE=true --env CRAC_IMAGE_DIR=/cr" +CR_DIR="/tmp/cr" + +resetDir() { + rm -rf $1 + mkdir -p $1 +} + +assertExist() { + grep "$1" $2 + if [ $? -ne 0 ]; then + echo "$? not found $1 in file $2" + exit 1 + fi +} + +checkOutput() { + assertExist 'Message from stderr in afterRestore callback' $1 + assertExist 'Message from stdout in afterRestore callback' $1 + assertExist 'Message from stderr afterRestore' $1 + assertExist 'Message from stdout afterRestore' $1 +} + +killDockerPs() { + docker ps | grep ${TEST_IMAGE} | awk '{print $1}' | xargs docker kill &> /dev/null +} + +executCmd() { + echo "Run command: [$1]" + eval $1 +} + +# cr dir is used to save image files, it need to mount as volume when run docker +# So avoid the permission denied error, use a directory in /tmp and grant with 0777 +resetCrDir() { + rm -rf ${CR_DIR} + mkdir ${CR_DIR} + chmod 0777 ${CR_DIR} +} + +runInteractive() { + resetCrDir + executCmd "docker run $1 ${CHECKPOINT_PARAM} -v ${CR_DIR}:/cr ${TEST_IMAGE}" + executCmd "docker run $1 ${RESTORE_PARAM} --env SLEEP_TIME=3000 -v ${CR_DIR}:/cr ${TEST_IMAGE} &> ${CR_DIR}/docker.log" + checkOutput ${CR_DIR}/docker.log +} + +runDetach() { + resetCrDir + executCmd "docker run $1 -d ${CHECKPOINT_PARAM} -v ${CR_DIR}:/cr ${TEST_IMAGE}" + sleep 3 + killDockerPs + + executCmd "docker run $1 -d ${RESTORE_PARAM} --env SLEEP_TIME=30000 -v ${CR_DIR}:/cr ${TEST_IMAGE}" + sleep 3 + cid=$(docker ps | grep ${TEST_IMAGE} | awk '{print $1}') + docker logs ${cid} &> ${CR_DIR}/docker.log + killDockerPs + checkOutput ${CR_DIR}/docker.log +} + +preCheck() { + match=$(uname -r | awk -F'.' '{print $1>=5 && $2>=9; }') + if [ $match -ne 1 ]; then + echo "OS version should >= 5.9" + exit 0 + fi +} + +buildTestImage() { + docker rmi -f ${TEST_IMAGE} + resetDir ${WORK_DIR}/baseimage-tmp + pushd baseimage-tmp + mkdir jdk classes + cp -R ${TESTJAVA}/* jdk + cp -R ${TESTCLASSES}/* classes + cp ${TESTSRCPATH}/*.sh ${TESTSRCPATH}/Dockerfile . + chmod +x *.sh + tar zcvf jdk.tar.gz jdk + tar zcvf classes.tar.gz classes + rm -rf jdk classes + docker build -t ${TEST_IMAGE} . + popd + rm -rf baseimage-tmp +} + +WORK_DIR=$(pwd) +preCheck +buildTestImage +runDetach "$PRIV_PARAM" +runDetach "$NON_PRIV_PARAM" +runInteractive "$PRIV_PARAM" +runInteractive "$NON_PRIV_PARAM" \ No newline at end of file diff --git a/test/jdk/jdk/crac/stdoutInDocker/runapp.sh b/test/jdk/jdk/crac/stdoutInDocker/runapp.sh new file mode 100644 index 00000000000..0f2bbba5441 --- /dev/null +++ b/test/jdk/jdk/crac/stdoutInDocker/runapp.sh @@ -0,0 +1,17 @@ +#! /bin/sh +APP_DIR=$(dirname "$0") +JAVA=${JAVA_HOME}/bin/java +if [[ "${DO_CHECKPOINT}" = "true" ]]; +then + for i in {1..100} + do + sh ${APP_DIR}/takepid.sh + done + ${JAVA} -XX:CRaCCheckpointTo=${CRAC_IMAGE_DIR} ${CRAC_UNPRIV_OPT} ${CRAC_INHERIT_OPT} -cp ${APP_DIR}/classes TestStdoutInDocker +elif [[ "${DO_RESTORE}" = "true" ]]; +then + export CRAC_CRIU_OPTS="-o ${CRAC_IMAGE_DIR}/restore.log -vvvv" + ${JAVA} -XX:CRaCRestoreFrom=${CRAC_IMAGE_DIR} ${CRAC_UNPRIV_OPT} -cp ${APP_DIR}/classes TestStdoutInDocker +else + ${JAVA} -cp ${APP_DIR}/classes TestStdoutInDocker +fi \ No newline at end of file diff --git a/test/jdk/jdk/crac/stdoutInDocker/takepid.sh b/test/jdk/jdk/crac/stdoutInDocker/takepid.sh new file mode 100644 index 00000000000..9068bd6c5e7 --- /dev/null +++ b/test/jdk/jdk/crac/stdoutInDocker/takepid.sh @@ -0,0 +1,2 @@ +#! /bin/sh +echo "nothing" > /dev/null \ No newline at end of file diff --git a/test/lib/jdk/test/lib/containers/docker/DockerTestUtils.java b/test/lib/jdk/test/lib/containers/docker/DockerTestUtils.java index 5d642aa11c4..cd1a817b53d 100644 --- a/test/lib/jdk/test/lib/containers/docker/DockerTestUtils.java +++ b/test/lib/jdk/test/lib/containers/docker/DockerTestUtils.java @@ -127,10 +127,12 @@ private static boolean isDockerEngineAvailableCheck() throws Exception { * The jdk will be placed under the "/jdk/" folder inside the image/container file system. * * @param imageName name of the image to be created, including version tag + *@param buildDirName name of the docker build/staging directory, which will + * be created in the jtreg's scratch folder * @throws Exception */ - public static void buildJdkContainerImage(String imageName) throws Exception { - buildJdkContainerImage(imageName, null); + public static void buildJdkContainerImage(String imageName, String buildDirName) throws Exception { + buildJdkContainerImage(imageName, null, buildDirName); } /** @@ -139,14 +141,13 @@ public static void buildJdkContainerImage(String imageName) throws Exception { * * @param imageName name of the image to be created, including version tag * @param dockerfileContent content of the Dockerfile; use null to generate default content + * @param buildDirName name of the docker build/staging directory, which will + * be created in the jtreg's scratch folder * @throws Exception */ - public static void buildJdkContainerImage(String imageName, String dockerfileContent) throws Exception { - // image name may contain tag, hence replace ':' - String imageDirName = imageName.replace(":", "-"); + public static void buildJdkContainerImage(String imageName, String dockerfileContent, String buildDirName) throws Exception { - // Create an image build/staging directory - Path buildDir = Paths.get(imageDirName); + Path buildDir = Paths.get(".", buildDirName); if (Files.exists(buildDir)) { throw new RuntimeException("The docker build directory already exists: " + buildDir); } diff --git a/test/lib/jdk/test/lib/crac/CracBuilder.java b/test/lib/jdk/test/lib/crac/CracBuilder.java new file mode 100644 index 00000000000..95c91ff3c62 --- /dev/null +++ b/test/lib/jdk/test/lib/crac/CracBuilder.java @@ -0,0 +1,487 @@ +package jdk.test.lib.crac; + +import jdk.test.lib.Container; +import jdk.test.lib.Utils; +import jdk.test.lib.containers.docker.DockerTestUtils; +import jdk.test.lib.util.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.*; + +import static jdk.test.lib.Asserts.*; + +public class CracBuilder { + private static final String DEFAULT_IMAGE_DIR = "cr"; + public static final String CONTAINER_NAME = "crac-test"; + public static final String JAVA = Utils.TEST_JDK + "/bin/java"; + public static final String DOCKER_JAVA = "/jdk/bin/java"; + private static final List CRIU_CANDIDATES = Arrays.asList(Utils.TEST_JDK + "/lib/criu", "/usr/sbin/criu", "/sbin/criu"); + private static final String CRIU_PATH; + public static final String ENV_LOG_FILE = "CRAC_LOG_FILE"; + + // This dummy field is here as workaround for (possibly) a JTReg bug; + // some tests don't build CracTestArg into their Test.d/ directory + // (not all classes from /test/lib are built!) and the tests would fail. + // This does not always happen when the test is run individually but breaks + // when the whole suite is executed. + private static final Class dummyWorkaround = CracTestArg.class; + + boolean verbose = true; + boolean debug = false; + final List classpathEntries = new ArrayList<>(); + final Map env = new HashMap<>(); + String imageDir = DEFAULT_IMAGE_DIR; + CracEngine engine; + boolean printResources; + Class main; + String[] args; + boolean captureOutput; + String dockerImageName; + private String[] dockerOptions; + + boolean containerStarted; + boolean logToFile; + String cracLogFile; + //As opposite to one-stop mode is two steps mode that run application in docker + // 1. docker run sleep 3600 + // 2. docker exec java xxxx + // This mode has a flaw when checkpoint failed with message: "Error (criu/files-reg.c:1816): Can't lookup mount=24 for fd=0 path=/dev/null" + // If run application with docker exec, the mnt_id of some fds are not in the /proc/$pid/mountinfo. + // If run the one-stop mode, this problem is disappeared. + boolean oneStopDockerRun; + boolean allowSelfAttach; + boolean restorePipeStdOutErr; + + boolean bumpPid; + + String appendOnlyFilesParam; + + static { + String path = System.getenv("CRAC_CRIU_PATH"); + if (path == null) { + for (String candidate : CRIU_CANDIDATES) { + if (new File(candidate).exists()) { + path = candidate; + break; + } + } + } + CRIU_PATH = path; + } + + public CracBuilder() { + } + + public CracBuilder verbose(boolean verbose) { + this.verbose = verbose; + return this; + } + + public CracBuilder debug(boolean debug) { + this.debug = debug; + return this; + } + + public CracBuilder restorePipeStdOutErr(boolean restorePipeStdOutErr) { + this.restorePipeStdOutErr = restorePipeStdOutErr; + return this; + } + + public CracBuilder classpathEntry(String cp) { + classpathEntries.add(cp); + return this; + } + + public CracBuilder engine(CracEngine engine) { + assertNull(this.engine); // set once + this.engine = engine; + return this; + } + + public Path imageDir() { + return Path.of(imageDir); + } + + public CracBuilder imageDir(String imageDir) { + assertEquals(DEFAULT_IMAGE_DIR, this.imageDir); // set once + this.imageDir = imageDir; + return this; + } + + public CracBuilder printResources(boolean print) { + this.printResources = print; + return this; + } + + public CracBuilder env(String name, String value) { + env.put(name, value); + return this; + } + + public CracBuilder main(Class mainClass) { + assertNull(this.main); // set once + this.main = mainClass; + return this; + } + + public Class main() { + return main != null ? main : CracTest.class; + } + + public CracBuilder args(String... args) { + assertNull(this.args); // set once + this.args = args; + return this; + } + + public String[] args() { + return args != null ? args : CracTest.args(); + } + + public CracBuilder captureOutput(boolean captureOutput) { + this.captureOutput = captureOutput; + return this; + } + + public CracBuilder logToFile(boolean logToFile) { + this.logToFile = logToFile; + assertNull(this.cracLogFile, "logToFile only can be set once!"); + this.cracLogFile = "crac-" + System.currentTimeMillis() + ".txt"; + return this; + } + + public CracBuilder oneStopDockerRun(boolean oneStopDockerRun) { + this.oneStopDockerRun = oneStopDockerRun; + return this; + } + + public CracBuilder bumpPid(boolean bumpPid) { + this.bumpPid = bumpPid; + return this; + } + + public CracBuilder allowSelfAttach(boolean allow) { + this.allowSelfAttach = allow; + return this; + } + + public CracBuilder inDockerImage(String imageName) { + assertNull(dockerImageName); + this.dockerImageName = imageName; + return this; + } + + public CracBuilder dockerOptions(String... options) { + assertNull(dockerOptions); + this.dockerOptions = options; + return this; + } + + public CracBuilder appendOnlyFiles(String param) { + assertNull(appendOnlyFilesParam); + this.appendOnlyFilesParam = param; + return this; + } + + public void doCheckpoint() throws Exception { + startCheckpoint().waitForCheckpointed(); + } + + public CracProcess startCheckpoint() throws Exception { + return startCheckpoint(null); + } + + public CracProcess startCheckpoint(List javaPrefix) throws Exception { + if (oneStopDockerRun) { + return startOneStopCheckpoint(javaPrefix); + } + ensureContainerStarted(); + List cmd = prepareCommand(javaPrefix); + cmd.add("-XX:CRaCCheckpointTo=" + imageDir); + cmd.add(main().getName()); + cmd.addAll(Arrays.asList(args())); + log("Starting process to be checkpointed:"); + log(String.join(" ", cmd)); + return new CracProcess(this, cmd); + } + + private CracProcess startOneStopCheckpoint(List javaPrefix) throws Exception { + assertNotNull(dockerImageName); + assertNotNull(CRIU_PATH); + ensureContainerKilled(); + DockerTestUtils.buildJdkContainerImage(dockerImageName, null, "jdk-docker"); + FileUtils.deleteFileTreeWithRetry(Path.of(".", "jdk-docker")); + // Make sure we start with a clean image directory + DockerTestUtils.execute(Container.ENGINE_COMMAND, "volume", "rm", "cr"); + + List runCmds = prepareCommonDockerRunCommands(javaPrefix); + runCmds.add("-XX:CRaCCheckpointTo=" + imageDir); + runCmds.add(main().getName()); + runCmds.addAll(Arrays.asList(args())); + List cmd = prepareContainerCommand(dockerImageName, dockerOptions, + bumpPid ? wrapWithBumpPid(runCmds) : runCmds); + log("Starting docker container to be checkpointed:\n" + String.join(" ", cmd)); + + return new CracProcess(this, cmd); + } + + private List wrapWithBumpPid(List runCmds) { + runCmds.addAll(0, Arrays.asList("sh", "/test_src/bump_pid_run.sh")); + return runCmds; + } + + private List prepareCommonDockerRunCommands(List javaPrefix) { + List runCmds = new ArrayList<>(); + if (javaPrefix != null) { + runCmds.addAll(javaPrefix); + } else { + runCmds.add(DOCKER_JAVA); + } + runCmds.add("-ea"); + runCmds.add("-cp"); + runCmds.add(getClassPath()); + if (engine != null) { + runCmds.add("-XX:CREngine=" + engine.engine); + } + if (printResources) { + runCmds.add("-XX:+UnlockDiagnosticVMOptions"); + runCmds.add("-XX:+CRPrintResourcesOnCheckpoint"); + } + if (debug) { + runCmds.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=0.0.0.0:5005"); + runCmds.add("-XX:-CRDoThrowCheckpointException"); + } + return runCmds; + } + + void log(String fmt, Object... args) { + if (verbose) { + if (args.length == 0) { + System.err.println(fmt); + } else { + System.err.printf(fmt, args); + } + } + } + + private void ensureContainerStarted() throws Exception { + if (dockerImageName == null) { + return; + } + if (CRIU_PATH == null) { + fail("CRAC_CRIU_PATH is not set and cannot find criu executable in any of: " + CRIU_CANDIDATES); + } + if (!containerStarted) { + ensureContainerKilled(); + DockerTestUtils.buildJdkContainerImage(dockerImageName, null, "jdk-docker"); + FileUtils.deleteFileTreeWithRetry(Path.of(".", "jdk-docker")); + // Make sure we start with a clean image directory + DockerTestUtils.execute(Container.ENGINE_COMMAND, "volume", "rm", "cr"); + List cmd = prepareDefaultContainerCommand(dockerImageName, dockerOptions); + log("Starting docker container:\n" + String.join(" ", cmd)); + assertEquals(0, new ProcessBuilder().inheritIO().command(cmd).start().waitFor()); + containerStarted = true; + } + } + + private List prepareDefaultContainerCommand(String imageName, String[] options) { + return prepareContainerCommand(imageName, options, Arrays.asList("sleep", "3600")); + } + + private List prepareContainerCommand(String imageName, String[] options, List runCommands) { + List cmd = new ArrayList<>(); + cmd.add(Container.ENGINE_COMMAND); + cmd.addAll(Arrays.asList("run", "--rm", "-d")); + cmd.add("--privileged"); // required to give CRIU sufficient permissions + cmd.add("--init"); // otherwise the checkpointed process would not be reaped (by sleep with PID 1) + int entryCounter = 0; + for (var entry : Utils.TEST_CLASS_PATH.split(File.pathSeparator)) { + cmd.addAll(Arrays.asList("--volume", entry + ":/cp/" + (entryCounter++))); + } + //bumpPid need bump_pid_run.sh,so need mount the test.src + if (bumpPid) { + cmd.addAll(Arrays.asList("--volume", Path.of(Utils.TEST_SRC).toString() + ":/test_src")); + } + cmd.addAll(Arrays.asList("--volume", "cr:/cr")); + cmd.addAll(Arrays.asList("--volume", CRIU_PATH + ":/criu")); + cmd.addAll(Arrays.asList("--env", "CRAC_CRIU_PATH=/criu")); + cmd.addAll(Arrays.asList("--name", CONTAINER_NAME)); + if (logToFile) { + cmd.addAll(Arrays.asList("--env", "CRAC_LOG_FILE=" + Path.of(System.getProperty("user.dir"), cracLogFile))); + cmd.addAll(Arrays.asList("--volume", System.getProperty("user.dir") + ":" + System.getProperty("user.dir"))); + } + if (debug) { + cmd.addAll(Arrays.asList("--publish", "5005:5005")); + } + if (options != null) { + cmd.addAll(Arrays.asList(options)); + } + cmd.add(imageName); + cmd.addAll(runCommands); + return cmd; + } + + public void ensureContainerKilled() throws Exception { + DockerTestUtils.execute(Container.ENGINE_COMMAND, "kill", CONTAINER_NAME).getExitValue(); + DockerTestUtils.removeDockerImage(dockerImageName); + } + + public void deepClearContainer() throws Exception { + DockerTestUtils.execute(Container.ENGINE_COMMAND, "kill", CONTAINER_NAME).getExitValue(); + DockerTestUtils.execute(Container.ENGINE_COMMAND, "rm", CONTAINER_NAME).getExitValue(); + DockerTestUtils.removeDockerImage(dockerImageName); + DockerTestUtils.execute(Container.ENGINE_COMMAND, "volume", "rm", "cr"); + //if don't execute prune command , there lots of files under overlay2. + DockerTestUtils.execute(Container.ENGINE_COMMAND, "system", "prune", "-f"); + } + + public void waitUntilContainerExit(long timeout) throws Exception { + long start = System.currentTimeMillis(); + while ((System.currentTimeMillis() - start) < timeout) { + String output = DockerTestUtils.execute(Container.ENGINE_COMMAND, "ps", "-f", "name=" + CONTAINER_NAME).getOutput(); + if (!output.contains(CONTAINER_NAME)) { + return; + } + Thread.sleep(100); + } + fail("waitUntilContainerExit timeout for " + timeout + " ms."); + } + + public void recreateContainer(String imageName, String... options) throws Exception { + assertTrue(containerStarted); + String minPid = DockerTestUtils.execute(Container.ENGINE_COMMAND, "exec", CONTAINER_NAME, + "cat", "/proc/sys/kernel/ns_last_pid").getStdout().trim(); + DockerTestUtils.execute(Container.ENGINE_COMMAND, "kill", CONTAINER_NAME).getExitValue(); + List cmd = prepareDefaultContainerCommand(imageName, options); + log("Recreating docker container:\n" + String.join(" ", cmd)); + assertEquals(0, new ProcessBuilder().inheritIO().command(cmd).start().waitFor()); + // We need to cycle PIDs; had we tried to restore right away the exec would get the + // same PIDs and restore would fail. + log("Cycling PIDs until %s%n", minPid); + DockerTestUtils.execute(Container.ENGINE_COMMAND, "exec", + CONTAINER_NAME, "bash", "-c", + "while [ $(cat /proc/sys/kernel/ns_last_pid) -le " + minPid + " ]; do cat /dev/null; done"); + } + + public CracProcess doRestore() throws Exception { + return startRestore().waitForSuccess(); + } + + public CracProcess startRestore() throws Exception { + return startRestore(null); + } + public CracProcess startRestore(List prefixJava) throws Exception { + if (oneStopDockerRun) { + return startOneStopRestore(prefixJava); + } + ensureContainerStarted(); + List cmd = prepareCommand(prefixJava); + cmd.add("-XX:CRaCRestoreFrom=" + imageDir); + log("Starting restored process:"); + log(String.join(" ", cmd)); + return new CracProcess(this, cmd); + } + + private CracProcess startOneStopRestore(List prefixJava) throws Exception { + assertNotNull(dockerImageName); + assertNotNull(CRIU_PATH); + + List runCmds = prepareCommonDockerRunCommands(prefixJava); + runCmds.add("-XX:CRaCRestoreFrom=" + imageDir); + List cmd = prepareContainerCommand(dockerImageName, dockerOptions, runCmds); + log("Starting docker container to be restore:\n" + String.join(" ", cmd)); + return new CracProcess(this, cmd); + } + + public CracProcess startPlain() throws IOException { + List cmd = new ArrayList<>(); + if (dockerImageName != null) { + cmd.addAll(Arrays.asList(Container.ENGINE_COMMAND, "exec", CONTAINER_NAME)); + } + cmd.add(JAVA); + cmd.add("-ea"); + cmd.add("-cp"); + cmd.add(getClassPath()); + if (debug) { + cmd.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=0.0.0.0:5005"); + } + cmd.add(main().getName()); + cmd.addAll(Arrays.asList(args())); + log("Starting process without CRaC:"); + log(String.join(" ", cmd)); + return new CracProcess(this, cmd); + } + + private String getClassPath() { + String classPath = classpathEntries.isEmpty() ? "" : String.join(File.pathSeparator, classpathEntries) + File.pathSeparator; + if (dockerImageName == null) { + classPath += Utils.TEST_CLASS_PATH; + } else { + int numEntries = Utils.TEST_CLASS_PATH.split(File.pathSeparator).length; + for (int i = 0; i < numEntries; ++i) { + classPath += "/cp/" + i + File.pathSeparator; + } + } + return classPath; + } + + public CracProcess doPlain() throws IOException, InterruptedException { + return startPlain().waitForSuccess(); + } + + private List prepareCommand(List javaPrefix) { + List cmd = new ArrayList<>(); + if (javaPrefix != null) { + cmd.addAll(javaPrefix); + } else if (dockerImageName != null) { + cmd.addAll(Arrays.asList(Container.ENGINE_COMMAND, "exec", CONTAINER_NAME)); + cmd.add(DOCKER_JAVA); + } else { + cmd.add(JAVA); + } + cmd.add("-ea"); + cmd.add("-cp"); + cmd.add(getClassPath()); + if (engine != null) { + cmd.add("-XX:CREngine=" + engine.engine); + } + if (printResources) { + cmd.add("-XX:+UnlockDiagnosticVMOptions"); + cmd.add("-XX:+CRPrintResourcesOnCheckpoint"); + } + if (debug) { + cmd.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=0.0.0.0:5005"); + cmd.add("-XX:-CRDoThrowCheckpointException"); + } + if (allowSelfAttach) { + cmd.add("-Djdk.attach.allowAttachSelf=true"); + } + if (restorePipeStdOutErr) { + cmd.add("-XX:CRaCRestoreInheritPipeFds=1,2"); + } + if (appendOnlyFilesParam != null) { + cmd.add("-XX:CRaCAppendOnlyLogFiles=" + appendOnlyFilesParam); + } + return cmd; + } + + public void doCheckpointAndRestore() throws Exception { + doCheckpoint(); + doRestore(); + } + + public void checkpointViaJcmd() throws Exception { + List cmd = new ArrayList<>(); + if (dockerImageName != null) { + cmd.addAll(Arrays.asList(Container.ENGINE_COMMAND, "exec", CONTAINER_NAME, "/jdk/bin/jcmd")); + } else { + cmd.add(Utils.TEST_JDK + "/bin/jcmd"); + } + cmd.addAll(Arrays.asList(main().getName(), "JDK.checkpoint")); + // This works for non-docker commands, too + DockerTestUtils.execute(cmd).shouldHaveExitValue(0); + } + +} diff --git a/test/lib/jdk/test/lib/crac/CracEngine.java b/test/lib/jdk/test/lib/crac/CracEngine.java new file mode 100644 index 00000000000..79caef8ff2a --- /dev/null +++ b/test/lib/jdk/test/lib/crac/CracEngine.java @@ -0,0 +1,13 @@ +package jdk.test.lib.crac; + +public enum CracEngine { + CRIU("criuengine"), + PAUSE("pauseengine"), + SIMULATE("simengine"); + + public final String engine; + + CracEngine(String engine) { + this.engine = engine; + } +} diff --git a/test/lib/jdk/test/lib/crac/CracLogger.java b/test/lib/jdk/test/lib/crac/CracLogger.java new file mode 100644 index 00000000000..cd4228d8b14 --- /dev/null +++ b/test/lib/jdk/test/lib/crac/CracLogger.java @@ -0,0 +1,81 @@ +package jdk.test.lib.crac; + +import jdk.crac.*; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.concurrent.CountDownLatch; + +import static jdk.test.lib.Asserts.*; + +/** + * capture output from Process is not work when restore from criu. + * Process use pipe facility to capture the output,but the pipe is broken when restore. + * CracLogger write output to a file, and this file will close before checkpoint,and open after restore. + * The correct way use CracLogger is : + * 1. Extend with CracLogger + * 2. Enable logToFile with : CracBuilder.logToFile(true) + * 3. Read the output with : CracProcess#readLogFile() + */ +public class CracLogger implements Resource { + + private volatile FileWriter fileWriter; + private volatile boolean opened; + + public CracLogger() { + Core.getGlobalContext().register(this); + open(); + } + + public boolean isOpened() { + return opened; + } + + private synchronized void open() { + String logFile = System.getenv(CracBuilder.ENV_LOG_FILE); + if (logFile != null) { + assertFalse(opened, "file should not open yet!"); + try { + fileWriter = new FileWriter(logFile); + opened = true; + this.notify(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private synchronized void close() throws IOException { + assertTrue(fileWriter != null, "file must open yet!"); + fileWriter.close(); + fileWriter = null; + opened = false; + } + + public synchronized void writeLog(String msg) throws IOException, InterruptedException { + while (!isOpened()) { + this.wait(); + } + assertTrue(fileWriter != null, "file must open yet!"); + fileWriter.write(msg); + fileWriter.flush(); + } + + public void beforeCheckpoint(Context context) throws Exception { + if (isOpened()) { + close(); + } + } + + /** + * Invoked by a {@code Context} as a notification about restore. + * + * @param context {@code Context} providing notification + * @throws Exception if the method have failed + */ + public void afterRestore(Context context) throws Exception { + open(); + } +} diff --git a/test/lib/jdk/test/lib/crac/CracProcess.java b/test/lib/jdk/test/lib/crac/CracProcess.java new file mode 100644 index 00000000000..b26e2861097 --- /dev/null +++ b/test/lib/jdk/test/lib/crac/CracProcess.java @@ -0,0 +1,166 @@ +package jdk.test.lib.crac; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.StreamPumper; + +import java.io.*; +import java.nio.file.*; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import static jdk.test.lib.Asserts.*; + +public class CracProcess { + private final CracBuilder builder; + private final Process process; + private static final long WAIT_IMAGE_DIR_CREATED_TIMEOUT = 30 * 1000L; + private static final long POLL_INTERVAL = 100L; + + public CracProcess(CracBuilder builder, List cmd) throws IOException { + this.builder = builder; + ProcessBuilder pb = new ProcessBuilder().inheritIO(); + if (builder.captureOutput) { + pb.redirectOutput(ProcessBuilder.Redirect.PIPE); + pb.redirectError(ProcessBuilder.Redirect.PIPE); + } + pb.environment().putAll(builder.env); + if (builder.logToFile) { + pb.environment().put("CRAC_LOG_FILE", builder.cracLogFile); + } + this.process = pb.command(cmd).start(); + } + + public int waitFor() throws InterruptedException { + return process.waitFor(); + } + + public void waitForCheckpointed() throws InterruptedException { + if (builder.engine == null || builder.engine == CracEngine.CRIU) { + assertEquals(137, process.waitFor(), "Checkpointed process was not killed as expected."); + // TODO: we could check that "CR: Checkpoint" was written out + } else { + fail("With engine " + builder.engine.engine + " use the async version."); + } + } + + public void waitForPausePid() throws IOException, InterruptedException { + assertEquals(CracEngine.PAUSE, builder.engine, "Pause PID file created only with pauseengine"); + try (WatchService watcher = FileSystems.getDefault().newWatchService()) { + Path imageDir = builder.imageDir().toAbsolutePath(); + waitForFileCreated(watcher, imageDir.getParent(), path -> "cr".equals(path.toFile().getName())); + waitForFileCreated(watcher, imageDir, path -> "pid".equals(path.toFile().getName())); + } + } + + /** + * wait until image dir exists and non-empty pid file until {@code WAIT_IMAGE_DIR_CREATED_TIMEOUT} milliseconds. + * + * @throws InterruptedException Interrupt by other threads. + * @throws RuntimeException When reach {@code WAIT_IMAGE_DIR_CREATED_TIMEOUT}, but {@code dir} not exist. + */ + public void ensureFileIntegrityForPausePid() throws InterruptedException { + Path imageDir = builder.imageDir().toAbsolutePath(); + Path pidFile = imageDir.resolve("pid"); + long start = System.currentTimeMillis(); + while (true) { + if (Files.exists(imageDir) && Files.exists(pidFile) && pidFile.toFile().length() > 0) { + return; + } + if (System.currentTimeMillis() - start > WAIT_IMAGE_DIR_CREATED_TIMEOUT) { + throw new RuntimeException("Check image dir and pid file failed after " + WAIT_IMAGE_DIR_CREATED_TIMEOUT + "ms."); + } + Thread.sleep(POLL_INTERVAL); + } + } + + private void waitForFileCreated(WatchService watcher, Path dir, Predicate predicate) throws IOException, InterruptedException { + WatchKey key = dir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE); + assertTrue(key.isValid()); + try { + try (Stream dirContents = Files.list(dir)) { + if (dirContents.anyMatch(predicate)) { + // file already present + return; + } + } + for (; ; ) { + WatchKey key2 = watcher.take(); + for (WatchEvent event : key2.pollEvents()) { + if (event.kind() != StandardWatchEventKinds.ENTRY_CREATE) { + continue; + } + if (predicate.test((Path) event.context())) { + return; + } + } + key2.reset(); + } + } finally { + key.cancel(); + } + } + + public CracProcess waitForSuccess() throws InterruptedException { + int exitValue = process.waitFor(); + assertEquals(0, exitValue, "Process returned unexpected exit code: " + exitValue); + builder.log("Process %d completed with exit value %d%n", process.pid(), exitValue); + return this; + } + + public OutputAnalyzer outputAnalyzer() throws IOException { + assertTrue(builder.captureOutput, "Output must be captured with .captureOutput(true)"); + return new OutputAnalyzer(process); + } + + public OutputAnalyzer fileOutputAnalyser() throws IOException { + return new OutputAnalyzer(readFile()); + } + + private String readFile() throws IOException { + assertTrue(builder.logToFile, "logToFile must be true."); + assertNotNull(builder.cracLogFile, "cracLogFile should be not null.Maybe the process may not started!"); + StringBuilder sb = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new FileReader(builder.cracLogFile))) { + String line = reader.readLine(); + while (line != null) { + sb.append(line); + line = reader.readLine(); + } + } + return sb.toString(); + } + + public void watchFile(long timeout, String expectedString) throws RuntimeException{ + try { + long start = System.currentTimeMillis(); + while ((System.currentTimeMillis() - start) < timeout) { + if (new File(builder.cracLogFile).exists() && readFile().contains(expectedString)) { + return; + } + Thread.sleep(100); + } + fail("Not found " + expectedString + " in file : " + builder.cracLogFile); + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + } + + + public CracProcess watch(Consumer outputConsumer, Consumer errorConsumer) { + assertTrue(builder.captureOutput, "Output must be captured with .captureOutput(true)"); + pump(process.getInputStream(), outputConsumer); + pump(process.getErrorStream(), errorConsumer); + return this; + } + + private static void pump(InputStream stream, Consumer consumer) { + new StreamPumper(stream).addPump(new StreamPumper.LinePump() { + @Override + protected void processLine(String line) { + consumer.accept(line); + } + }).process(); + } +} diff --git a/test/lib/jdk/test/lib/crac/CracTest.java b/test/lib/jdk/test/lib/crac/CracTest.java new file mode 100644 index 00000000000..a4b01ec6f7d --- /dev/null +++ b/test/lib/jdk/test/lib/crac/CracTest.java @@ -0,0 +1,210 @@ +package jdk.test.lib.crac; + +import jdk.crac.Core; + +import java.lang.reflect.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import static jdk.test.lib.Asserts.*; + +/** + * CRaC tests usually consists of two parts; the test started by JTreg through the 'run' tag + * and subprocesses started by the test with various VM options. These are represented by the + * {@link #test()} and {@link #exec()} methods. + * CracTest use '@run driver jdk.test.crac.lib.CracTest' as the executable command; the main + * method in this class discovers the executed class from system properties passed by JTReg, + * instantiates the test (public no-arg constructor is needed), populates fields annotated + * with {@link CracTestArg} and executes the {@link #test()} method. + * The test method is expected to use {@link CracBuilder} to start another process. By default, + * CracBuilder invokes the test with arguments that will again instantiate and fill the instance + * and invoke the {@link #exec()} method. + */ +public interface CracTest { + + String RESTORED_MESSAGE = "Restored"; + + /** + * This method is called when JTReg invokes the test; it is supposed to start + * another process (most often using CRaC VM options) and validate its behaviour. + * + * @throws Exception + */ + void test() throws Exception; + + /** + * This method is invoked in the subprocess; this is where you're likely to call + * {@link Core#checkpointRestore()}. + * + * @throws Exception + */ + void exec() throws Exception; + + class ArgsHolder { + private static final String RUN_TEST = "__run_test__"; + private static Class testClass; + private static String[] args; + // This field is present as workaround for @build somehow missing + // the annotation when + private static final Class dummyField = CracTestArg.class; + } + + /** + * Main method for orchestrating the test. This should be called directly by JTReg. + */ + static void main(String[] args) throws Exception { + String testClassName; + if (args.length == 0 || !ArgsHolder.RUN_TEST.equals(args[0])) { + // We will look up the class name (and package) to avoid boilerplate in any @run invocation + String testFile = System.getProperty("test.file"); + String source = Files.readString(Path.of(testFile)).replace('\n', ' '); + Matcher clsMatcher = Pattern.compile("class\\s+(\\S+)\\s+(extends\\s+\\S+\\s+)?implements\\s+(\\S+\\s*,\\s*)*CracTest").matcher(source); + if (!clsMatcher.find()) { + fail("Cannot find test class in " + testFile + ", does it look like class implements CracTest?"); + } + testClassName = clsMatcher.group(1); + Matcher pkgMatcher = Pattern.compile("package\\s+([^;]+);").matcher(source); + if (pkgMatcher.find()) { + testClassName = pkgMatcher.group(1) + "." + testClassName; + } + } else { + testClassName = args[1]; + } + + // When we use CracTest as driver the file with test is not compiled without a @build tag. + // We could compile the class here and load it from a new classloader but since the test library + // is not compiled completely we could be missing some dependencies - this would be just too fragile. + Class testClass; + try { + testClass = Class.forName(testClassName); + } catch (ClassNotFoundException e) { + throw new ClassNotFoundException("Test class " + testClassName + " not found, add jtreg tag @build " + args[0], e); + } + if (CracTest.class.isAssignableFrom(testClass)) { + //noinspection unchecked + run((Class) testClass, args); + } else { + throw new IllegalArgumentException("Class " + testClass.getName() + " does not implement CracTest!"); + } + } + + /** + * This method should be invoked from the public static void main(String[]) method. + * + * @param testClass Class implementing the test. + * @param args Arguments received in the main method. + * @throws Exception + */ + static void run(Class testClass, String[] args) throws Exception { + assertNotNull(args); + ArgsHolder.testClass = testClass; + int argsOffset = 0; + if (args.length == 0 || !args[0].equals(ArgsHolder.RUN_TEST)) { + String[] newArgs = new String[args.length + 2]; + newArgs[0] = ArgsHolder.RUN_TEST; + newArgs[1] = testClass.getName(); + System.arraycopy(args, 0, newArgs, 2, args.length); + ArgsHolder.args = newArgs; + } else { + argsOffset = 2; + } + + try { + Constructor ctor = testClass.getConstructor(); + CracTest testInstance = ctor.newInstance(); + Field[] argFields = getArgFields(testClass); + for (int index = 0; index < argFields.length; index++) { + Field f = argFields[index]; + assertFalse(Modifier.isFinal(f.getModifiers()), "@CracTestArg fields must not be final!"); + Class t = f.getType(); + if (index + argsOffset >= args.length) { + if (f.getAnnotation(CracTestArg.class).optional()) { + break; + } else { + fail("Not enough args for field " + f.getName() + "(" + index + "): have " + (args.length - argsOffset)); + } + } + String arg = args[index + argsOffset]; + Object value = arg; + if (t == boolean.class || t == Boolean.class) { + assertTrue("true".equals(arg) || "false".equals(arg), "Argument " + index + "Boolean arg should be either 'true' or 'false', was: " + arg); + value = Boolean.parseBoolean(arg); + } else if (t == int.class || t == Integer.class) { + try { + value = Integer.parseInt(arg); + } catch (NumberFormatException e) { + fail("Cannot parse argument '" + arg + "' as integer for @CracTestArg(" + index + ") " + f.getName()); + } + } else if (t == long.class || t == Long.class) { + try { + value = Long.parseLong(arg); + } catch (NumberFormatException e) { + fail("Cannot parse argument '" + arg + "' as long for @CracTestArg(" + index + ") " + f.getName()); + } + } else if (t.isEnum()) { + value = Enum.valueOf((Class) t, arg); + } + f.setAccessible(true); + f.set(testInstance, value); + } + if (argsOffset == 0) { + testInstance.test(); + } else { + testInstance.exec(); + } + } catch (NoSuchMethodException e) { + fail("Test class " + testClass.getName() + " is expected to have a public no-arg constructor"); + } + } + + private static Field[] getArgFields(Class testClass) { + // TODO: check superclasses + Field[] sortedFields = Stream.of(testClass.getDeclaredFields()).filter(f -> f.isAnnotationPresent(CracTestArg.class)) + .sorted(Comparator.comparingInt(f -> f.getAnnotation(CracTestArg.class).value())) + .toArray(Field[]::new); + if (sortedFields.length == 0) { + return sortedFields; + } + int firstOptional = -1; + for (int i = 0; i < sortedFields.length; ++i) { + CracTestArg annotation = sortedFields[i].getAnnotation(CracTestArg.class); + int index = annotation.value(); + assertGreaterThanOrEqual(index, 0); + if (i == 0) { + assertEquals(0, index, "@CracTestArg numbers should start with 0"); + } + if (index < i) { + fail("Duplicate @CracTestArg(" + index + "): both fields " + sortedFields[i - 1].getName() + " and " + sortedFields[i].getName()); + } else if (index > i) { + fail("Gap in @CracTestArg indices: missing " + i + ", next is " + index); + } + if (annotation.optional()) { + firstOptional = index; + } else if (firstOptional >= 0) { + fail("Argument " + firstOptional + " is optional; all subsequent arguments must be optional, too."); + } + } + return sortedFields; + } + + /** + * Used as argument for {@link CracBuilder#args(String...)}. + */ + static String[] args(String... extraArgs) { + assertNotNull(ArgsHolder.args, "Args are null; are you trying to access them from test method?"); + if (extraArgs == null || extraArgs.length == 0) { + return ArgsHolder.args; + } else { + return Stream.concat(Stream.of(ArgsHolder.args), Stream.of(extraArgs)).toArray(String[]::new); + } + } + + static Class testClass() { + return ArgsHolder.testClass; + } +} + diff --git a/test/lib/jdk/test/lib/crac/CracTestArg.java b/test/lib/jdk/test/lib/crac/CracTestArg.java new file mode 100644 index 00000000000..158b93dcc88 --- /dev/null +++ b/test/lib/jdk/test/lib/crac/CracTestArg.java @@ -0,0 +1,23 @@ +package jdk.test.lib.crac; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Used to mark fields in {@link CracTest} that should be populated from main method arguments. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface CracTestArg { + /** + * @return The (zero-based) index of the argument used as source of the data. + */ + int value() default 0; + + /** + * @return Can this argument be omitted? + */ + boolean optional() default false; +}