Skip to content

Commit 780d889

Browse files
author
Roman Marchenko
committed
Merge branch 'crac' into crac-extract
2 parents e3c180f + a8e69de commit 780d889

File tree

6 files changed

+315
-27
lines changed

6 files changed

+315
-27
lines changed

src/hotspot/os/posix/os_posix.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include "services/memTracker.hpp"
4242
#include "runtime/arguments.hpp"
4343
#include "runtime/atomic.hpp"
44+
#include "runtime/crac.hpp"
4445
#include "runtime/java.hpp"
4546
#include "runtime/orderAccess.hpp"
4647
#include "runtime/perfMemory.hpp"
@@ -1410,7 +1411,7 @@ jlong os::javaTimeNanos() {
14101411
struct timespec tp;
14111412
int status = clock_gettime(CLOCK_MONOTONIC, &tp);
14121413
assert(status == 0, "clock_gettime error: %s", os::strerror(errno));
1413-
jlong result = jlong(tp.tv_sec) * NANOSECS_PER_SEC + jlong(tp.tv_nsec);
1414+
jlong result = jlong(tp.tv_sec) * NANOSECS_PER_SEC + jlong(tp.tv_nsec) + crac::monotonic_time_offset();
14141415
return result;
14151416
}
14161417

src/hotspot/share/runtime/crac.cpp

Lines changed: 97 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ class CracRestoreParameters : public CHeapObj<mtInternal> {
147147

148148
struct header {
149149
jlong _restore_time;
150-
jlong _restore_counter;
150+
jlong _restore_nanos;
151151
int _nflags;
152152
int _nprops;
153153
int _env_memory_size;
@@ -205,10 +205,10 @@ class CracRestoreParameters : public CHeapObj<mtInternal> {
205205
const SystemProperty* props,
206206
const char *args,
207207
jlong restore_time,
208-
jlong restore_counter) {
208+
jlong restore_nanos) {
209209
header hdr = {
210210
restore_time,
211-
restore_counter,
211+
restore_nanos,
212212
num_flags,
213213
system_props_length(props),
214214
env_vars_size(environ)
@@ -295,9 +295,17 @@ static char* _crengine_arg_str = NULL;
295295
static unsigned int _crengine_argc = 0;
296296
static const char* _crengine_args[32];
297297
static jlong _restore_start_time;
298-
static jlong _restore_start_counter;
298+
static jlong _restore_start_nanos;
299299
static FdsInfo _vm_inited_fds(false);
300300

301+
// Timestamps recorded before checkpoint
302+
jlong crac::checkpoint_millis;
303+
jlong crac::checkpoint_nanos;
304+
char crac::checkpoint_bootid[UUID_LENGTH];
305+
// Value based on wall clock time difference that will guarantee monotonic
306+
// System.nanoTime() close to actual wall-clock time difference.
307+
jlong crac::javaTimeNanos_offset = 0;
308+
301309
jlong crac::restore_start_time() {
302310
if (!_restore_start_time) {
303311
return -1;
@@ -306,10 +314,10 @@ jlong crac::restore_start_time() {
306314
}
307315

308316
jlong crac::uptime_since_restore() {
309-
if (!_restore_start_counter) {
317+
if (!_restore_start_nanos) {
310318
return -1;
311319
}
312-
return os::javaTimeNanos() - _restore_start_counter;
320+
return os::javaTimeNanos() - _restore_start_nanos;
313321
}
314322

315323
void VM_Crac::trace_cr(const char* msg, ...) {
@@ -600,6 +608,7 @@ class CracSHM {
600608
};
601609

602610
static int checkpoint_restore(int *shmid) {
611+
crac::record_time_before_checkpoint();
603612

604613
int cres = call_crengine();
605614
if (cres < 0) {
@@ -617,6 +626,8 @@ static int checkpoint_restore(int *shmid) {
617626
} while (sig == -1 && errno == EINTR);
618627
assert(sig == RESTORE_SIGNAL, "got what requested");
619628

629+
crac::update_javaTimeNanos_offset();
630+
620631
if (CRTraceStartupTime) {
621632
tty->print_cr("STARTUPTIME " JLONG_FORMAT " restore-native", os::javaTimeNanos());
622633
}
@@ -788,7 +799,9 @@ void VM_Crac::doit() {
788799

789800
if (shmid <= 0 || !VM_Crac::read_shm(shmid)) {
790801
_restore_start_time = os::javaTimeMillis();
791-
_restore_start_counter = os::javaTimeNanos();
802+
_restore_start_nanos = os::javaTimeNanos();
803+
} else {
804+
_restore_start_nanos += crac::monotonic_time_offset();
792805
}
793806
PerfMemoryLinux::restore();
794807

@@ -892,7 +905,7 @@ void crac::restore() {
892905
struct stat st;
893906

894907
jlong restore_time = os::javaTimeMillis();
895-
jlong restore_counter = os::javaTimeNanos();
908+
jlong restore_nanos = os::javaTimeNanos();
896909

897910
compute_crengine();
898911

@@ -906,7 +919,7 @@ void crac::restore() {
906919
Arguments::system_properties(),
907920
Arguments::java_command() ? Arguments::java_command() : "",
908921
restore_time,
909-
restore_counter)) {
922+
restore_nanos)) {
910923
char strid[32];
911924
snprintf(strid, sizeof(strid), "%d", id);
912925
setenv("CRAC_NEW_ARGS_ID", strid, true);
@@ -1008,7 +1021,7 @@ bool CracRestoreParameters::read_from(int fd) {
10081021
char* cursor = _raw_content + sizeof(header);
10091022

10101023
::_restore_start_time = hdr->_restore_time;
1011-
::_restore_start_counter = hdr->_restore_counter;
1024+
::_restore_start_nanos = hdr->_restore_nanos;
10121025

10131026
for (int i = 0; i < hdr->_nflags; i++) {
10141027
FormatBuffer<80> err_msg("%s", "");
@@ -1057,3 +1070,77 @@ bool CracRestoreParameters::read_from(int fd) {
10571070
_args = cursor;
10581071
return true;
10591072
}
1073+
1074+
void crac::record_time_before_checkpoint() {
1075+
checkpoint_millis = os::javaTimeMillis();
1076+
checkpoint_nanos = os::javaTimeNanos();
1077+
memset(checkpoint_bootid, 0, UUID_LENGTH);
1078+
read_bootid(checkpoint_bootid);
1079+
}
1080+
1081+
void crac::update_javaTimeNanos_offset() {
1082+
char buf[UUID_LENGTH];
1083+
// We will change the nanotime offset only if this is not the same boot
1084+
// to prevent reducing the accuracy of System.nanoTime() unnecessarily.
1085+
// It is possible that in a real-world case the boot_id does not change
1086+
// (containers keep the boot_id) - but the monotonic time changes. We will
1087+
// only guarantee that the nanotime does not go backwards in that case but
1088+
// won't offset the time based on wall-clock time as this change in monotonic
1089+
// time is likely intentional.
1090+
if (!read_bootid(buf) || memcmp(buf, checkpoint_bootid, UUID_LENGTH) != 0) {
1091+
assert(checkpoint_millis >= 0, "Restore without a checkpoint?");
1092+
long diff_millis = os::javaTimeMillis() - checkpoint_millis;
1093+
// If the wall clock has gone backwards we won't add it to the offset
1094+
if (diff_millis < 0) {
1095+
diff_millis = 0;
1096+
}
1097+
// javaTimeNanos() call on the second line below uses the *_offset, so we will zero
1098+
// it to make the call return true monotonic time rather than the adjusted value.
1099+
javaTimeNanos_offset = 0;
1100+
javaTimeNanos_offset = checkpoint_nanos - os::javaTimeNanos() + diff_millis * 1000000L;
1101+
} else {
1102+
// ensure monotonicity even if this looks like the same boot
1103+
jlong diff = os::javaTimeNanos() - checkpoint_nanos;
1104+
if (diff < 0) {
1105+
javaTimeNanos_offset -= diff;
1106+
}
1107+
}
1108+
}
1109+
1110+
static bool read_all(int fd, char *dest, size_t n) {
1111+
size_t rd = 0;
1112+
do {
1113+
ssize_t r = ::read(fd, dest + rd, n - rd);
1114+
if (r == 0) {
1115+
return false;
1116+
} else if (r < 0) {
1117+
if (errno == EINTR) {
1118+
continue;
1119+
}
1120+
return false;
1121+
}
1122+
rd += r;
1123+
} while (rd < n);
1124+
return true;
1125+
}
1126+
1127+
bool crac::read_bootid(char *dest) {
1128+
int fd = ::open("/proc/sys/kernel/random/boot_id", O_RDONLY);
1129+
if (fd < 0 || !read_all(fd, dest, UUID_LENGTH)) {
1130+
perror("CRaC: Cannot read system boot ID");
1131+
return false;
1132+
}
1133+
char c;
1134+
if (!read_all(fd, &c, 1) || c != '\n') {
1135+
perror("CRaC: system boot ID does not end with newline");
1136+
return false;
1137+
}
1138+
if (::read(fd, &c, 1) != 0) {
1139+
perror("CRaC: Unexpected data/error reading system boot ID");
1140+
return false;
1141+
}
1142+
if (::close(fd) != 0) {
1143+
perror("CRaC: Cannot close system boot ID file");
1144+
}
1145+
return true;
1146+
}

src/hotspot/share/runtime/crac.hpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
#include "runtime/handles.hpp"
2929
#include "utilities/macros.hpp"
3030

31+
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
32+
#define UUID_LENGTH 36
33+
3134
class crac: AllStatic {
3235
public:
3336
static void vm_create_start();
@@ -38,6 +41,21 @@ class crac: AllStatic {
3841

3942
static jlong restore_start_time();
4043
static jlong uptime_since_restore();
44+
45+
static void record_time_before_checkpoint();
46+
static void update_javaTimeNanos_offset();
47+
48+
static jlong monotonic_time_offset() {
49+
return javaTimeNanos_offset;
50+
}
51+
52+
private:
53+
static bool read_bootid(char *dest);
54+
55+
static jlong checkpoint_millis;
56+
static jlong checkpoint_nanos;
57+
static char checkpoint_bootid[UUID_LENGTH];
58+
static jlong javaTimeNanos_offset;
4159
};
4260

4361
#endif //SHARE_RUNTIME_CRAC_HPP
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Copyright (c) 2023, Azul Systems, Inc. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
import jdk.crac.*;
25+
import jdk.crac.management.CRaCMXBean;
26+
import jdk.test.lib.Container;
27+
import jdk.test.lib.containers.docker.Common;
28+
import jdk.test.lib.containers.docker.DockerTestUtils;
29+
import jdk.test.lib.crac.CracBuilder;
30+
import jdk.test.lib.crac.CracTest;
31+
import jdk.test.lib.crac.CracTestArg;
32+
33+
import java.io.IOException;
34+
import java.lang.management.ManagementFactory;
35+
import java.lang.management.RuntimeMXBean;
36+
import java.nio.file.Files;
37+
import java.nio.file.Path;
38+
import java.util.Arrays;
39+
import java.util.concurrent.TimeUnit;
40+
41+
import static jdk.test.lib.Asserts.*;
42+
43+
/**
44+
* @test NanoTimeTest
45+
* @requires (os.family == "linux")
46+
* @requires docker.support
47+
* @library /test/lib
48+
* @build NanoTimeTest
49+
* @run driver jdk.test.lib.crac.CracTest 0 true
50+
* @run driver jdk.test.lib.crac.CracTest 86400 true
51+
* @run driver jdk.test.lib.crac.CracTest -86400 true
52+
* @run driver jdk.test.lib.crac.CracTest 86400 false
53+
* @run driver jdk.test.lib.crac.CracTest -86400 false
54+
*/
55+
public class NanoTimeTest implements CracTest {
56+
private static final String imageName = Common.imageName("system-nanotime");
57+
58+
@CracTestArg(0)
59+
long monotonicOffset;
60+
61+
@CracTestArg(1)
62+
boolean changeBootId;
63+
64+
@Override
65+
public void test() throws Exception {
66+
if (!DockerTestUtils.canTestDocker()) {
67+
return;
68+
}
69+
CracBuilder builder = new CracBuilder();
70+
Path bootIdFile = Files.createTempFile("NanoTimeTest-", "-boot_id");
71+
try {
72+
// TODO: use more official image
73+
builder.withBaseImage("ghcr.io/crac/test-base", "latest")
74+
.dockerOptions("-v", bootIdFile + ":/fake_boot_id")
75+
.inDockerImage(imageName);
76+
77+
Files.writeString(bootIdFile, "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\n");
78+
// We need to preload the library before checkpoint
79+
builder.doCheckpoint(Container.ENGINE_COMMAND, "exec",
80+
"-e", "LD_PRELOAD=/opt/path-mapping-quiet.so",
81+
"-e", "PATH_MAPPING=/proc/sys/kernel/random/boot_id:/fake_boot_id",
82+
CracBuilder.CONTAINER_NAME,
83+
// In case we are trying to use negative monotonic offset we could
84+
// run into situation where we'd set it to negative value (prohibited).
85+
// Therefore, we'll rather offset it to the future before checkpoint
86+
// and set to 0 for restore.
87+
"unshare", "--fork", "--time", "--monotonic", String.valueOf(Math.max(-monotonicOffset, 0)),
88+
CracBuilder.DOCKER_JAVA);
89+
90+
if (changeBootId) {
91+
Files.writeString(bootIdFile, "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy\n");
92+
}
93+
94+
builder.doRestore(Container.ENGINE_COMMAND, "exec", CracBuilder.CONTAINER_NAME,
95+
"unshare", "--fork", "--time", "--boottime", "86400", "--monotonic", String.valueOf(Math.max(monotonicOffset, 0)),
96+
CracBuilder.DOCKER_JAVA);
97+
} finally {
98+
builder.ensureContainerKilled();
99+
assertTrue(bootIdFile.toFile().delete());
100+
}
101+
}
102+
103+
@Override
104+
public void exec() throws Exception {
105+
System.out.println("Expected offset: " + monotonicOffset);
106+
// We use uptime to assert that changing the clock worked
107+
long boottimeBefore = readSystemUptime();
108+
109+
long before = System.nanoTime();
110+
Core.checkpointRestore();
111+
long after = System.nanoTime();
112+
System.out.println("Before: " + before);
113+
System.out.println("After: " + after);
114+
assertLTE(before, after, "After < Before");
115+
if (changeBootId || monotonicOffset <= 0) {
116+
// Even though we have shifted the monotic offset by a day the difference
117+
// is adjusted by difference between wall clock time before and after;
118+
// the difference in monotonic time is considered "random"
119+
assertLT(after, before + TimeUnit.HOURS.toNanos(1), "After too late");
120+
} else {
121+
assertGT(after, before + TimeUnit.HOURS.toNanos(1), "After too early");
122+
assertLT(after, before + TimeUnit.HOURS.toNanos(25), "After too late");
123+
}
124+
long boottimeAfter = readSystemUptime();
125+
assertGTE(boottimeAfter, boottimeBefore + 86_400_000, "Boottime was not changed");
126+
RuntimeMXBean runtimeMX = ManagementFactory.getRuntimeMXBean();
127+
assertGTE(runtimeMX.getUptime(), 0L, "VM Uptime is negative!");
128+
CRaCMXBean cracBean = CRaCMXBean.getCRaCMXBean();
129+
assertLT(cracBean.getUptimeSinceRestore(), 60_000L);
130+
assertGTE(cracBean.getUptimeSinceRestore(), 0L);
131+
}
132+
133+
private long readSystemUptime() throws IOException {
134+
String uptimeStr = Files.readString(Path.of("/proc/uptime"));
135+
String[] parts = uptimeStr.split(" ");
136+
return (long)(Double.parseDouble(parts[0]) * 1000);
137+
}
138+
}

test/lib/jdk/test/lib/containers/docker/DockerfileConfig.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,11 @@
3737
// Note: base image version should not be an empty string. Use "latest" to get the latest version.
3838

3939
public class DockerfileConfig {
40-
static String getBaseImageName() {
41-
String name = System.getProperty("jdk.test.docker.image.name");
40+
public static final String BASE_IMAGE_NAME = "jdk.test.docker.image.name";
41+
public static final String BASE_IMAGE_VERSION = "jdk.test.docker.image.version";
42+
43+
public static String getBaseImageName() {
44+
String name = System.getProperty(BASE_IMAGE_NAME);
4245
if (name != null) {
4346
System.out.println("DockerfileConfig: using custom image name: " + name);
4447
return name;
@@ -56,8 +59,8 @@ static String getBaseImageName() {
5659
}
5760
}
5861

59-
static String getBaseImageVersion() {
60-
String version = System.getProperty("jdk.test.docker.image.version");
62+
public static String getBaseImageVersion() {
63+
String version = System.getProperty(BASE_IMAGE_VERSION);
6164
if (version != null) {
6265
System.out.println("DockerfileConfig: using custom image version: " + version);
6366
return version;

0 commit comments

Comments
 (0)