Skip to content

Commit

Permalink
CRIU resets j.l.VirtualThread.ForkJoinPool.parallelism after restore
Browse files Browse the repository at this point in the history
If there is no system property jdk.virtualThreadScheduler.parallelism
set at VM startup, j.l.VirtualThread.ForkJoinPool.parallelism value is
reset to match Runtime.getRuntime().availableProcessors() after CRIU
restore;
Added VM_VMHelpers methods
getStaticFieldObject/findinstanceFieldOffset/setObjectFieldI32;
Changed hashClassTableAt to peekClassHashTable;
Minor refactoring;
Added tests.

Signed-off-by: Jason Feng <[email protected]>
  • Loading branch information
JasonFengJ9 committed Aug 29, 2023
1 parent 6256ba4 commit ac50591
Show file tree
Hide file tree
Showing 9 changed files with 485 additions and 26 deletions.
23 changes: 23 additions & 0 deletions runtime/nls/j9vm/j9vm.nls
Original file line number Diff line number Diff line change
Expand Up @@ -2151,4 +2151,27 @@ J9NLS_VM_ILLEGAL_THREAD_STATE_UPCALL=The JVM failed to proceed due to the wrong
J9NLS_VM_ILLEGAL_THREAD_STATE_UPCALL.explanation=An error occurred when the JVM attempted to perform upcall in the trivial downcall
J9NLS_VM_ILLEGAL_THREAD_STATE_UPCALL.system_action=The JVM will throw a IllegalThreadStateException.
J9NLS_VM_ILLEGAL_THREAD_STATE_UPCALL.user_response=Ensure the specified linker options for downcall are valid.

J9NLS_VM_CRIU_RESTORE_RESET_VIRTUALTHREAD_FORKJOINPOOL_PARALLELISM_FORKJOINPOOL_CNF=The JVM could not reset java.lang.VirtualThread.ForkJoinPool.parallelism due to java.util.concurrent.ForkJoinPool class not found
# START NON-TRANSLATABLE
J9NLS_VM_CRIU_RESTORE_RESET_VIRTUALTHREAD_FORKJOINPOOL_PARALLELISM_FORKJOINPOOL_CNF.sample_input_1=1
J9NLS_VM_CRIU_RESTORE_RESET_VIRTUALTHREAD_FORKJOINPOOL_PARALLELISM_FORKJOINPOOL_CNF.explanation=CRIUSupport::checkpointJVM failed.
J9NLS_VM_CRIU_RESTORE_RESET_VIRTUALTHREAD_FORKJOINPOOL_PARALLELISM_FORKJOINPOOL_CNF.system_action=The JVM will throw a JVMRestoreException.
J9NLS_VM_CRIU_RESTORE_RESET_VIRTUALTHREAD_FORKJOINPOOL_PARALLELISM_FORKJOINPOOL_CNF.user_response=View CRIU documentation to determine how to resolve the exception.
# END NON-TRANSLATABLE

J9NLS_VM_CRIU_RESTORE_RESET_VIRTUALTHREAD_FORKJOINPOOL_PARALLELISM_INVALID_DEFAULT_SCHEDULER_ADDRESS=The JVM could not reset java.lang.VirtualThread.ForkJoinPool.parallelism due to invalid DEFAULT_SCHEDULER address
# START NON-TRANSLATABLE
J9NLS_VM_CRIU_RESTORE_RESET_VIRTUALTHREAD_FORKJOINPOOL_PARALLELISM_INVALID_DEFAULT_SCHEDULER_ADDRESS.sample_input_1=1
J9NLS_VM_CRIU_RESTORE_RESET_VIRTUALTHREAD_FORKJOINPOOL_PARALLELISM_INVALID_DEFAULT_SCHEDULER_ADDRESS.explanation=CRIUSupport::checkpointJVM failed.
J9NLS_VM_CRIU_RESTORE_RESET_VIRTUALTHREAD_FORKJOINPOOL_PARALLELISM_INVALID_DEFAULT_SCHEDULER_ADDRESS.system_action=The JVM will throw a JVMRestoreException.
J9NLS_VM_CRIU_RESTORE_RESET_VIRTUALTHREAD_FORKJOINPOOL_PARALLELISM_INVALID_DEFAULT_SCHEDULER_ADDRESS.user_response=View CRIU documentation to determine how to resolve the exception.
# END NON-TRANSLATABLE

J9NLS_VM_CRIU_RESTORE_RESET_VIRTUALTHREAD_FORKJOINPOOL_PARALLELISM_INVALID_PARALLELISM_OFFSET=The JVM could not reset java.lang.VirtualThread.ForkJoinPool.parallelism due to invalid parallelism offset
# START NON-TRANSLATABLE
J9NLS_VM_CRIU_RESTORE_RESET_VIRTUALTHREAD_FORKJOINPOOL_PARALLELISM_INVALID_PARALLELISM_OFFSET.sample_input_1=1
J9NLS_VM_CRIU_RESTORE_RESET_VIRTUALTHREAD_FORKJOINPOOL_PARALLELISM_INVALID_PARALLELISM_OFFSET.explanation=CRIUSupport::checkpointJVM failed.
J9NLS_VM_CRIU_RESTORE_RESET_VIRTUALTHREAD_FORKJOINPOOL_PARALLELISM_INVALID_PARALLELISM_OFFSET.system_action=The JVM will throw a JVMRestoreException.
J9NLS_VM_CRIU_RESTORE_RESET_VIRTUALTHREAD_FORKJOINPOOL_PARALLELISM_INVALID_PARALLELISM_OFFSET.user_response=View CRIU documentation to determine how to resolve the exception.
# END NON-TRANSLATABLE
95 changes: 95 additions & 0 deletions runtime/oti/VMHelpers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include "j9vmconstantpool.h"
#include "j9modifiers_api.h"
#include "j9cp.h"
#include "vm_api.h"
#include "ute.h"
#include "AtomicSupport.hpp"
#include "ObjectAllocationAPI.hpp"
Expand Down Expand Up @@ -2072,6 +2073,100 @@ class VM_VMHelpers
}
}
#endif /* JAVA_SPEC_VERSION >= 20 */

/**
* Get a static field object within a defining class.
*
* Current thread must have VM access.
*
* @param[in] currentThread the current J9VMThread
* @param[in] definingClassName the defining class name
* @param[in] fieldName the field name
* @param[in] signature the field signature
*
* @return the field object if successs, otherwise NULL
*/
static VMINLINE j9object_t
getStaticFieldObject(J9VMThread *currentThread, const char *definingClassName, const char *fieldName, const char *signature)
{
J9JavaVM *vm = currentThread->javaVM;
j9object_t fieldObject = NULL;
J9InternalVMFunctions const *vmFuncs = vm->internalVMFunctions;
J9Class *definingClass = vmFuncs->peekClassHashTable(currentThread, vm->systemClassLoader, (U_8 *)definingClassName, strlen(definingClassName));
void *fieldAddress = vmFuncs->staticFieldAddress(currentThread, definingClass, (U_8*)fieldName, strlen(fieldName), (U_8*)signature, strlen(signature), NULL, NULL, 0, NULL);
if (NULL != fieldAddress) {
MM_ObjectAccessBarrierAPI objectAccessBarrier = MM_ObjectAccessBarrierAPI(currentThread);
fieldObject = objectAccessBarrier.inlineStaticReadObject(currentThread, definingClass, (j9object_t*)fieldAddress, FALSE);
}
return fieldObject;
}

/**
* Find the offset of an instance field.
*
* @param[in] currentThread the current J9VMThread
* @param[in] instanceType the instance class
* @param[in] fieldName the field name
* @param[in] fieldSig the field signature
*
* @return the offset
*/
static VMINLINE IDATA
findinstanceFieldOffset(J9VMThread *currentThread, J9Class *instanceType, const char *fieldName, const char *fieldSig)
{
J9JavaVM *vm = currentThread->javaVM;

IDATA offset = (UDATA)vm->internalVMFunctions->instanceFieldOffset(
currentThread, instanceType,
(U_8 *)fieldName, strlen(fieldName),
(U_8 *)fieldSig, strlen(fieldSig),
NULL, NULL, 0);

if (-1 != offset) {
offset += J9VMTHREAD_OBJECT_HEADER_SIZE(currentThread);
}

return offset;
}

#if defined(J9VM_OPT_CRIU_SUPPORT)
/**
* Reset java.util.concurrent.ForkJoinPool.parallelism with a value supplied.
*
* Current thread must have VM access.
*
* @param[in] currentThread the current J9VMThread
* @param[in] instanceObject a java.util.concurrent.ForkJoinPool instance object
* @param[in] value the I_32 value to be set into the parallelism field
*
* @return true if the value has been set into the field within the instance object, false if not
*/
static VMINLINE bool
resetJUCForkJoinPoolParallelism(J9VMThread *currentThread, j9object_t instanceObject, I_32 value)
{
bool result = false;
J9JavaVM *vm = currentThread->javaVM;
IDATA fieldOffset = vm->checkpointState.jucForkJoinPoolParallelismOffset;

if (0 == fieldOffset) {
#define JUC_FORKJOINPOOL "java/util/concurrent/ForkJoinPool"
J9Class *definingClass = vm->internalVMFunctions->peekClassHashTable(currentThread, vm->systemClassLoader, (U_8 *)JUC_FORKJOINPOOL, LITERAL_STRLEN(JUC_FORKJOINPOOL));
#undef JUC_FORKJOINPOOL
if (NULL != definingClass) {
fieldOffset = findinstanceFieldOffset(currentThread, definingClass, "parallelism", "I");
} else {
fieldOffset = -1;
}
}
if (-1 != fieldOffset) {
MM_ObjectAccessBarrierAPI objectAccessBarrier = MM_ObjectAccessBarrierAPI(currentThread);
objectAccessBarrier.inlineMixedObjectStoreI32(currentThread, instanceObject, fieldOffset, value, false);
vm->checkpointState.jucForkJoinPoolParallelismOffset = fieldOffset;
result = true;
}
return result;
}
#endif /* defined(J9VM_OPT_CRIU_SUPPORT) */
};

#endif /* VMHELPERS_HPP_ */
4 changes: 4 additions & 0 deletions runtime/oti/j9nonbuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -4202,6 +4202,9 @@ typedef struct J9DelayedLockingOpertionsRecord {

typedef struct J9CRIUCheckpointState {
U_32 flags;
#if JAVA_SPEC_VERSION >= 20
UDATA checkpointCPUCount;
#endif /* JAVA_SPEC_VERSION >= 20 */
struct J9DelayedLockingOpertionsRecord *delayedLockingOperationsRoot;
struct J9Pool *hookRecords;
struct J9Pool *classIterationRestoreHookRecords;
Expand Down Expand Up @@ -4238,6 +4241,7 @@ typedef struct J9CRIUCheckpointState {
UDATA libCRIUHandle;
struct J9VMInitArgs *restoreArgsList;
char *restoreArgsChars;
IDATA jucForkJoinPoolParallelismOffset;
} J9CRIUCheckpointState;
#endif /* defined(J9VM_OPT_CRIU_SUPPORT) */

Expand Down
66 changes: 45 additions & 21 deletions runtime/vm/CRIUHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ J9_DECLARE_CONSTANT_UTF8(j9InternalCheckpointHookAPI_name, "org/eclipse/openj9/c
static void addInternalJVMCheckpointHook(J9VMThread *currentThread, BOOLEAN isRestore, J9Class *instanceType, BOOLEAN includeSubClass, hookFunc hookFunc);
static void cleanupCriuHooks(J9VMThread *currentThread);
static BOOLEAN fillinHookRecords(J9VMThread *currentThread, j9object_t object);
static IDATA findinstanceFieldOffsetHelper(J9VMThread *currentThread, J9Class *instanceType, const char *fieldName, const char *fieldSig);
static void initializeCriuHooks(J9VMThread *currentThread);
static BOOLEAN juRandomReseed(J9VMThread *currentThread, void *userData, const char **nlsMsgFormat);
static BOOLEAN criuRestoreInitializeTrace(J9VMThread *currentThread, void *userData, const char **nlsMsgFormat);
Expand Down Expand Up @@ -162,22 +161,6 @@ addInternalJVMCheckpointHook(J9VMThread *currentThread, BOOLEAN isRestore, J9Cla
}
}

static IDATA
findinstanceFieldOffsetHelper(J9VMThread *currentThread, J9Class *instanceType, const char *fieldName, const char *fieldSig)
{
IDATA offset = (UDATA)instanceFieldOffset(
currentThread, instanceType,
(U_8*)fieldName, strlen(fieldName),
(U_8*)fieldSig, strlen(fieldSig),
NULL, NULL, 0);

if (-1 != offset) {
offset += J9VMTHREAD_OBJECT_HEADER_SIZE(currentThread);
}

return offset;
}

/**
* An internal JVM checkpoint hook is to re-seed java.util.Random.seed.value.
*
Expand All @@ -197,13 +180,13 @@ juRandomReseed(J9VMThread *currentThread, void *userData, const char **nlsMsgFor
PORT_ACCESS_FROM_VMC(currentThread);

/* Assuming this hook record is to re-seed java.util.Random.seed.value. */
IDATA seedOffset = findinstanceFieldOffsetHelper(currentThread, hookRecord->instanceType, "seed", "Ljava/util/concurrent/atomic/AtomicLong;");
IDATA seedOffset = VM_VMHelpers::findinstanceFieldOffset(currentThread, hookRecord->instanceType, "seed", "Ljava/util/concurrent/atomic/AtomicLong;");
if (-1 != seedOffset) {
#define JUCA_ATOMICLONG "java/util/concurrent/atomic/AtomicLong"
J9Class *jucaAtomicLongClass = hashClassTableAt(vm->systemClassLoader, (U_8 *)JUCA_ATOMICLONG, LITERAL_STRLEN(JUCA_ATOMICLONG));
J9Class *jucaAtomicLongClass = peekClassHashTable(currentThread, vm->systemClassLoader, (U_8 *)JUCA_ATOMICLONG, LITERAL_STRLEN(JUCA_ATOMICLONG));
#undef JUCA_ATOMICLONG
if (NULL != jucaAtomicLongClass) {
IDATA valueOffset = findinstanceFieldOffsetHelper(currentThread, jucaAtomicLongClass, "value", "J");
IDATA valueOffset = VM_VMHelpers::findinstanceFieldOffset(currentThread, jucaAtomicLongClass, "value", "J");
if (-1 != valueOffset) {
PORT_ACCESS_FROM_JAVAVM(vm);
pool_state walkState = {0};
Expand Down Expand Up @@ -456,13 +439,31 @@ initializeCriuHooks(J9VMThread *currentThread)
}
}

#if JAVA_SPEC_VERSION >= 20
{
J9VMSystemProperty *vtParallelism = NULL;
PORT_ACCESS_FROM_VMC(currentThread);
if (J9SYSPROP_ERROR_NONE != getSystemProperty(vm, "jdk.virtualThreadScheduler.parallelism", &vtParallelism)) {
/* This system property only affects j.l.VirtualThread.ForkJoinPool.parallelism at VM startup. */
UDATA cpuCount = j9sysinfo_get_number_CPUs_by_type(J9PORT_CPU_TARGET);
if (cpuCount < 1) {
cpuCount = 1;
}
vm->checkpointState.checkpointCPUCount = cpuCount;
Trc_VM_criu_initializeCriuHooks_checkpointCPUCount(currentThread, cpuCount);
}
}
#endif /* JAVA_SPEC_VERSION >= 20 */

{
/* Add restore hook to re-seed java.uti.Random.seed.value */
#define JAVA_UTIL_RANDOM "java/util/Random"
J9Class *juRandomClass = peekClassHashTable(currentThread, vm->systemClassLoader, (U_8 *)JAVA_UTIL_RANDOM, LITERAL_STRLEN(JAVA_UTIL_RANDOM));
#undef JAVA_UTIL_RANDOM
if (NULL != juRandomClass) {
addInternalJVMCheckpointHook(currentThread, TRUE, juRandomClass, FALSE, juRandomReseed);
} else {
Trc_VM_criu_initializeCriuHooks_Random_CNF(currentThread);
}
addInternalJVMCheckpointHook(currentThread, TRUE, NULL, FALSE, criuRestoreInitializeTrace);
addInternalJVMCheckpointHook(currentThread, TRUE, NULL, FALSE, criuRestoreInitializeXrs);
Expand Down Expand Up @@ -495,8 +496,8 @@ fillinHookRecords(J9VMThread *currentThread, j9object_t object)
BOOLEAN result = TRUE;

J9InternalHookRecord *hookRecord = (J9InternalHookRecord*)pool_startDo(hookRecords, &walkState);
J9Class *clazz = J9OBJECT_CLAZZ_VM(vm, object);
while (NULL != hookRecord) {
J9Class *clazz = J9OBJECT_CLAZZ_VM(vm, object);
if ((clazz == hookRecord->instanceType)
|| (hookRecord->includeSubClass && isSameOrSuperClassOf(hookRecord->instanceType, clazz))
) {
Expand Down Expand Up @@ -602,6 +603,29 @@ runInternalJVMRestoreHooks(J9VMThread *currentThread, const char **nlsMsgFormat)
}
}

#if JAVA_SPEC_VERSION >= 20
if (0 != vm->checkpointState.checkpointCPUCount) {
/* Only reset j.l.VirtualThread.ForkJoinPool.parallelism if jdk.virtualThreadScheduler.parallelism is not set. */
PORT_ACCESS_FROM_VMC(currentThread);
UDATA cpuCount = j9sysinfo_get_number_CPUs_by_type(J9PORT_CPU_TARGET);
if (cpuCount < 1) {
/* This matches ForkJoinPool default constructor with Runtime.getRuntime().availableProcessors(). */
cpuCount = 1;
}
if (cpuCount != vm->checkpointState.checkpointCPUCount) {
j9object_t fjpObject = VM_VMHelpers::getStaticFieldObject(currentThread, "java/lang/VirtualThread", "DEFAULT_SCHEDULER", "Ljava/util/concurrent/ForkJoinPool;");
if (NULL != fjpObject) {
result = VM_VMHelpers::resetJUCForkJoinPoolParallelism(currentThread, fjpObject, cpuCount);
Trc_VM_criu_jlVirtualThreadForkJoinPoolResetParallelism_reset_parallelism(currentThread, fjpObject, cpuCount);
} else {
Trc_VM_criu_jlVirtualThreadForkJoinPoolResetParallelism_DEFAULT_SCHEDULER_NULL(currentThread);
}
} else {
Trc_VM_criu_jlVirtualThreadForkJoinPoolResetParallelism_same_cpucount(currentThread, cpuCount);
}
}
#endif /* JAVA_SPEC_VERSION >= 20 */

/* Cleanup at restore. */
cleanupCriuHooks(currentThread);
Trc_VM_criu_runRestoreHooks_Exit(currentThread);
Expand Down
6 changes: 6 additions & 0 deletions runtime/vm/j9vm.tdf
Original file line number Diff line number Diff line change
Expand Up @@ -930,3 +930,9 @@ TraceExit=Trc_VM_criu_initHooks_Exit Overhead=1 Level=5 Template="initializeCriu
TraceException=Trc_VM_criu_setSingleThreadModeJVMCRIUException_triggerOneOffJavaDump Overhead=1 Level=1 Template="setCRIUSingleThreadModeJVMCRIUException triggerOneOffDump() returns %d"

TraceException=Trc_VM_checkVisibility_Failed Overhead=1 Level=1 Template="checkVisibility from %p (%.*s) to %p (%.*s) modifiers=%zx lookupOptions=%zx failed"

TraceEvent=Trc_VM_criu_initializeCriuHooks_checkpointCPUCount Overhead=1 Level=3 Template="initializeCriuHooks() checkpoint CPU count (%zu)"
TraceEvent=Trc_VM_criu_jlVirtualThreadForkJoinPoolResetParallelism_same_cpucount Overhead=1 Level=5 Template="Reset j.l.VirtualThread.ForkJoinPool.parallelism: same cpu count (%zu) between C/R"
TraceEvent=Trc_VM_criu_jlVirtualThreadForkJoinPoolResetParallelism_reset_parallelism Overhead=1 Level=5 Template="Reset j.l.VirtualThread.ForkJoinPool.parallelism: fjpObject(0x%p) parallelism(%zu) after restore"
TraceException=Trc_VM_criu_initializeCriuHooks_Random_CNF Overhead=1 Level=3 Template="initializeCriuHooks() j.u.Random class not found"
TraceException=Trc_VM_criu_jlVirtualThreadForkJoinPoolResetParallelism_DEFAULT_SCHEDULER_NULL Overhead=1 Level=3 Template="Reset j.l.VirtualThread.ForkJoinPool.parallelism: DEFAULT_SCHEDULER is NULL"
25 changes: 20 additions & 5 deletions test/functional/cmdLineTests/criu/build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,26 +41,41 @@
</target>

<target name="compile" depends="init" description="Using java ${JDK_VERSION} to compile the source ">
<property name="addExports" value="--add-exports java.base/openj9.internal.criu=ALL-UNNAMED --add-exports java.base/jdk.internal.misc=ALL-UNNAMED" />
<echo>===addExports: ${addExports}</echo>
<echo>Ant version is ${ant.version}</echo>
<echo>============COMPILER SETTINGS============</echo>
<echo>===fork: yes</echo>
<echo>===executable: ${compiler.javac}</echo>
<echo>===debug: on</echo>
<echo>===destdir: ${DEST}</echo>
<if>
<not>
<matches string="${JDK_VERSION}" pattern="^(8|9|1[0-9])$$" />
</not>
<then>
<property name="enablePreview" value="--enable-preview -source ${JDK_VERSION}" />
</then>
<else>
<property name="enablePreview" value="" />
<property name="excludeTestMachineResourceChange" value="org/openj9/criu/TestMachineResourceChange.java" />
</else>
</if>
<if>
<equals arg1="${JDK_VERSION}" arg2="8" />
<then>
<property name="excludeFile" value="org/openj9/criu/JDK11UpTimeoutAdjustmentTest.java" />
<property name="excludeJDK11UpTimeoutAdjustmentTest" value="org/openj9/criu/JDK11UpTimeoutAdjustmentTest.java" />
<javac srcdir="${src}" destdir="${build}" debug="true" fork="true" executable="${compiler.javac}" includeAntRuntime="false" encoding="ISO-8859-1">
<exclude name="${excludeFile}" />
<exclude name="${excludeJDK11UpTimeoutAdjustmentTest}" />
<exclude name="${excludeTestMachineResourceChange}" />
</javac>
</then>
<else>
<property name="addExports" value="--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED --add-exports=java.base/openj9.internal.criu=ALL-UNNAMED" />
<property name="addExports" value="--add-exports java.base/openj9.internal.criu=ALL-UNNAMED --add-exports java.base/jdk.internal.misc=ALL-UNNAMED" />
<echo>===addExports: ${addExports}</echo>
<echo>===enablePreview: ${enablePreview}</echo>
<javac srcdir="${src}" destdir="${build}" debug="true" fork="true" executable="${compiler.javac}" includeAntRuntime="false" encoding="ISO-8859-1">
<compilerarg line="${addExports}" />
<compilerarg line="${enablePreview}" />
<exclude name="${excludeTestMachineResourceChange}" />
</javac>
</else>
</if>
Expand Down
Loading

0 comments on commit ac50591

Please sign in to comment.