Skip to content

Commit e9f9eba

Browse files
committed
added TimeLimit test facility
1 parent 5900c63 commit e9f9eba

File tree

2 files changed

+183
-1
lines changed

2 files changed

+183
-1
lines changed
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. 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. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package jdk.graal.compiler.core.test;
26+
27+
import jdk.graal.compiler.test.GraalTest;
28+
29+
import java.lang.management.LockInfo;
30+
import java.lang.management.ManagementFactory;
31+
import java.lang.management.MonitorInfo;
32+
import java.lang.management.ThreadInfo;
33+
import java.util.Date;
34+
import java.util.Formatter;
35+
import java.util.concurrent.TimeUnit;
36+
37+
/**
38+
* A try-with-resources scope for raising an error if the time spent executing in the scope exceeds
39+
* some limit.
40+
*
41+
* {@snippet lang = java :
42+
* try (var scope = TimeLimit.create(1000, "MyTask")) {
43+
* // MyTask must complete in under 1000 milliseconds to
44+
* // avoid AssertionError being thrown when scope is closed.
45+
* work();
46+
* }
47+
* }
48+
*/
49+
public final class TimeLimit implements AutoCloseable {
50+
51+
private volatile boolean closed;
52+
private final Thread watchdog;
53+
54+
/**
55+
* Describes error to thrown when {@link #close()} is called.
56+
*/
57+
private String error;
58+
59+
/**
60+
* Enters a scope for timing execution within the scope.
61+
*
62+
* @param limit a time limit in milliseconds. If the execution exceeds this limit, an
63+
* {@link AssertionError} is thrown when the scope is {@linkplain #close() closed}.
64+
* The error will include a dump of all threads and other info that should provide
65+
* insight as to why the execution ran too long.
66+
* @param label a descriptive name of the execution
67+
* @return a TimeLimit scope object or null if JMX is not available
68+
*/
69+
public static TimeLimit create(long limit, String label) {
70+
if (GraalTest.isManagementLibraryIsLoadable() != null) {
71+
return null;
72+
}
73+
return new TimeLimit(limit, label);
74+
}
75+
76+
private TimeLimit(long limit, String label) {
77+
watchdog = new Thread("TimeLimitWatchDog") {
78+
@Override
79+
public void run() {
80+
long elapsed = 0;
81+
while (elapsed < limit && !closed) {
82+
long start = System.nanoTime();
83+
try {
84+
Thread.sleep(limit - elapsed);
85+
} catch (InterruptedException e) {
86+
// ignore
87+
}
88+
elapsed += TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
89+
}
90+
if (!closed) {
91+
setError(limit, label);
92+
}
93+
}
94+
};
95+
watchdog.setDaemon(true);
96+
watchdog.start();
97+
98+
}
99+
100+
@Override
101+
public void close() {
102+
closed = true;
103+
watchdog.interrupt();
104+
if (error != null) {
105+
throw new AssertionError(error);
106+
}
107+
}
108+
109+
private void setError(long limit, String label) {
110+
Formatter buf = new Formatter();
111+
buf.format("Task '%s' failed to complete within %d milleseconds%n", label, limit);
112+
buf.format("Dumping all stack traces. Current time: %s%n", new Date());
113+
threadDump(buf);
114+
Runtime runtime = Runtime.getRuntime();
115+
final long heapSizeUnit = 1024 * 1024;
116+
long usedHeapSize = runtime.totalMemory() / heapSizeUnit;
117+
long freeHeapSize = runtime.freeMemory() / heapSizeUnit;
118+
long maximumHeapSize = runtime.maxMemory() / heapSizeUnit;
119+
buf.format("=== Memory statistics (in MB):%n=== Used heap size: %d%n=== Free heap size: %d%n=== Maximum heap size: %d%n", usedHeapSize, freeHeapSize, maximumHeapSize);
120+
this.error = buf.toString();
121+
}
122+
123+
private static void threadDump(Formatter buf) {
124+
for (ThreadInfo ti : ManagementFactory.getThreadMXBean().dumpAllThreads(true, true)) {
125+
printThreadInfo(buf, ti);
126+
printLockInfo(buf, ti.getLockedSynchronizers());
127+
}
128+
buf.format("%n");
129+
}
130+
131+
private static void printThreadInfo(Formatter buf, ThreadInfo ti) {
132+
buf.format("\"%s\" Id=%d in %s", ti.getThreadName(), ti.getThreadId(), ti.getThreadState());
133+
if (ti.getLockName() != null) {
134+
buf.format(" on lock=%s", ti.getLockName());
135+
}
136+
if (ti.isSuspended()) {
137+
buf.format(" (suspended)");
138+
}
139+
if (ti.isInNative()) {
140+
buf.format(" (running in native)");
141+
}
142+
buf.format("%n");
143+
144+
if (ti.getLockOwnerName() != null) {
145+
buf.format(" owned by %s Id=%d%n", ti.getLockOwnerName(), ti.getLockOwnerId());
146+
}
147+
148+
StackTraceElement[] stacktrace = ti.getStackTrace();
149+
MonitorInfo[] monitors = ti.getLockedMonitors();
150+
for (int i = 0; i < stacktrace.length; i++) {
151+
StackTraceElement ste = stacktrace[i];
152+
buf.format(" at %s%n", ste);
153+
for (MonitorInfo mi : monitors) {
154+
if (mi.getLockedStackDepth() == i) {
155+
buf.format(" - locked %s%n", mi);
156+
}
157+
}
158+
}
159+
buf.format("%n");
160+
}
161+
162+
private static void printLockInfo(Formatter buf, LockInfo[] locks) {
163+
if (locks.length > 0) {
164+
buf.format(" Locked synchronizers: count = %d%n", locks.length);
165+
for (LockInfo li : locks) {
166+
buf.format(" - %s%n", li);
167+
}
168+
buf.format("%n");
169+
}
170+
}
171+
}

compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/test/GraalTest.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,12 +318,23 @@ protected static StructuredGraph assertInGraph(StructuredGraph graph, Class<?>..
318318
* @see "https://bugs.openjdk.java.net/browse/JDK-8076557"
319319
*/
320320
public static void assumeManagementLibraryIsLoadable() {
321+
Throwable unloadableReason = isManagementLibraryIsLoadable();
322+
if (unloadableReason != null) {
323+
throw new AssumptionViolatedException("Management interface is unavailable: " + unloadableReason);
324+
}
325+
}
326+
327+
/**
328+
* @see "https://bugs.openjdk.java.net/browse/JDK-8076557"
329+
*/
330+
public static Throwable isManagementLibraryIsLoadable() {
321331
try {
322332
/* Trigger loading of the management library using the bootstrap class loader. */
323333
GraalServices.getCurrentThreadAllocatedBytes();
324334
} catch (UnsatisfiedLinkError | NoClassDefFoundError | UnsupportedOperationException e) {
325-
throw new AssumptionViolatedException("Management interface is unavailable: " + e);
335+
return e;
326336
}
337+
return null;
327338
}
328339

329340
/**

0 commit comments

Comments
 (0)