8335779: JFR: Hide sleep events

Reviewed-by: mgronlun
This commit is contained in:
Erik Gahlin 2024-07-10 14:28:20 +00:00
parent 537d20afbf
commit e0fb949460
14 changed files with 196 additions and 104 deletions

View File

@ -47,7 +47,7 @@ class JfrIntrinsicSupport : AllStatic {
#define JFR_HAVE_INTRINSICS #define JFR_HAVE_INTRINSICS
#define JFR_TEMPLATES(template) \ #define JFR_TEMPLATES(template) \
template(jdk_jfr_internal_HiddenWait, "jdk/jfr/internal/HiddenWait") \ template(jdk_jfr_internal_management_HiddenWait, "jdk/jfr/internal/management/HiddenWait") \
template(jdk_jfr_internal_JVM, "jdk/jfr/internal/JVM") \ template(jdk_jfr_internal_JVM, "jdk/jfr/internal/JVM") \
template(jdk_jfr_internal_event_EventWriterFactory, "jdk/jfr/internal/event/EventWriterFactory") \ template(jdk_jfr_internal_event_EventWriterFactory, "jdk/jfr/internal/event/EventWriterFactory") \
template(jdk_jfr_internal_event_EventConfiguration_signature, "Ljdk/jfr/internal/event/EventConfiguration;") \ template(jdk_jfr_internal_event_EventConfiguration_signature, "Ljdk/jfr/internal/event/EventConfiguration;") \

View File

@ -1442,7 +1442,7 @@ bool ObjectMonitor::check_owner(TRAPS) {
static inline bool is_excluded(const Klass* monitor_klass) { static inline bool is_excluded(const Klass* monitor_klass) {
assert(monitor_klass != nullptr, "invariant"); assert(monitor_klass != nullptr, "invariant");
NOT_JFR_RETURN_(false); NOT_JFR_RETURN_(false);
JFR_ONLY(return vmSymbols::jdk_jfr_internal_HiddenWait() == monitor_klass->name();) JFR_ONLY(return vmSymbols::jdk_jfr_internal_management_HiddenWait() == monitor_klass->name();)
} }
static void post_monitor_wait_event(EventJavaMonitorWait* event, static void post_monitor_wait_event(EventJavaMonitorWait* event,

View File

@ -31,6 +31,7 @@ import jdk.internal.vm.annotation.IntrinsicCandidate;
import jdk.jfr.Event; import jdk.jfr.Event;
import jdk.jfr.internal.event.EventConfiguration; import jdk.jfr.internal.event.EventConfiguration;
import jdk.jfr.internal.event.EventWriter; import jdk.jfr.internal.event.EventWriter;
import jdk.jfr.internal.management.HiddenWait;
/** /**
* Interface against the JVM. * Interface against the JVM.

View File

@ -30,6 +30,7 @@ import java.time.LocalDateTime;
import jdk.jfr.Recording; import jdk.jfr.Recording;
import jdk.jfr.internal.event.EventConfiguration; import jdk.jfr.internal.event.EventConfiguration;
import jdk.jfr.internal.management.HiddenWait;
import jdk.jfr.internal.util.Utils; import jdk.jfr.internal.util.Utils;
import jdk.jfr.internal.util.ValueFormatter; import jdk.jfr.internal.util.ValueFormatter;
@ -118,7 +119,8 @@ public final class JVMSupport {
lastTimestamp = time; lastTimestamp = time;
return; return;
} }
Utils.takeNap(1); HiddenWait hiddenWait = new HiddenWait();
hiddenWait.takeNap(1);
} }
} }

View File

@ -47,6 +47,7 @@ import jdk.jfr.Period;
import jdk.jfr.ValueDescriptor; import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.consumer.RepositoryFiles; import jdk.jfr.internal.consumer.RepositoryFiles;
import jdk.jfr.internal.event.EventConfiguration; import jdk.jfr.internal.event.EventConfiguration;
import jdk.jfr.internal.management.HiddenWait;
import jdk.jfr.internal.periodic.PeriodicEvents; import jdk.jfr.internal.periodic.PeriodicEvents;
import jdk.jfr.internal.util.Utils; import jdk.jfr.internal.util.Utils;
@ -57,6 +58,7 @@ public final class MetadataRepository {
private final Map<String, EventType> nativeEventTypes = LinkedHashMap.newHashMap(150); private final Map<String, EventType> nativeEventTypes = LinkedHashMap.newHashMap(150);
private final Map<String, EventControl> nativeControls = LinkedHashMap.newHashMap(150); private final Map<String, EventControl> nativeControls = LinkedHashMap.newHashMap(150);
private final SettingsManager settingsManager = new SettingsManager(); private final SettingsManager settingsManager = new SettingsManager();
private final HiddenWait threadSleeper = new HiddenWait();
private Constructor<EventConfiguration> cachedEventConfigurationConstructor; private Constructor<EventConfiguration> cachedEventConfigurationConstructor;
private boolean staleMetadata = true; private boolean staleMetadata = true;
private boolean unregistered; private boolean unregistered;
@ -341,7 +343,7 @@ public final class MetadataRepository {
lastMillis = millis; lastMillis = millis;
return; return;
} }
Utils.takeNap(1); threadSleeper.takeNap(1);
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -33,6 +33,7 @@ import jdk.jfr.RecordingState;
import jdk.jfr.internal.SecuritySupport; import jdk.jfr.internal.SecuritySupport;
import jdk.jfr.internal.SecuritySupport.SafePath; import jdk.jfr.internal.SecuritySupport.SafePath;
import jdk.jfr.internal.management.EventByteStream; import jdk.jfr.internal.management.EventByteStream;
import jdk.jfr.internal.management.HiddenWait;
import jdk.jfr.internal.management.ManagementSupport; import jdk.jfr.internal.management.ManagementSupport;
public final class OngoingStream extends EventByteStream { public final class OngoingStream extends EventByteStream {
@ -44,6 +45,7 @@ public final class OngoingStream extends EventByteStream {
private final RepositoryFiles repositoryFiles; private final RepositoryFiles repositoryFiles;
private final Recording recording; private final Recording recording;
private final HiddenWait threadSleeper = new HiddenWait();
private final int blockSize; private final int blockSize;
private final long endTimeNanos; private final long endTimeNanos;
private final byte[] headerBytes = new byte[HEADER_SIZE]; private final byte[] headerBytes = new byte[HEADER_SIZE];
@ -195,19 +197,13 @@ public final class OngoingStream extends EventByteStream {
return bytes; return bytes;
} }
} }
takeNap(); if (!threadSleeper.takeNap(10)) {
throw new IOException("Read operation interrupted");
}
} }
return EMPTY_ARRAY; return EMPTY_ARRAY;
} }
private void takeNap() throws IOException {
try {
Thread.sleep(10);
} catch (InterruptedException ie) {
throw new IOException("Read operation interrupted", ie);
}
}
private boolean ensureInput() throws IOException { private boolean ensureInput() throws IOException {
if (input == null) { if (input == null) {
if (SecuritySupport.getFileSize(new SafePath(path)) < HEADER_SIZE) { if (SecuritySupport.getFileSize(new SafePath(path)) < HEADER_SIZE) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -31,6 +31,8 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.nio.file.Path; import java.nio.file.Path;
import jdk.jfr.internal.management.HiddenWait;
import jdk.jfr.internal.util.Utils; import jdk.jfr.internal.util.Utils;
public final class RecordingInput implements DataInput, AutoCloseable { public final class RecordingInput implements DataInput, AutoCloseable {
@ -67,6 +69,7 @@ public final class RecordingInput implements DataInput, AutoCloseable {
} }
private final int blockSize; private final int blockSize;
private final FileAccess fileAccess; private final FileAccess fileAccess;
private final HiddenWait threadSleeper = new HiddenWait();
private long pollCount = 1000; private long pollCount = 1000;
private RandomAccessFile file; private RandomAccessFile file;
private String filename; private String filename;
@ -453,6 +456,6 @@ public final class RecordingInput implements DataInput, AutoCloseable {
if (pollCount < 0) { if (pollCount < 0) {
throw new IOException("Recording file is stuck in locked stream state."); throw new IOException("Recording file is stuck in locked stream state.");
} }
Utils.takeNap(1); threadSleeper.takeNap(1);
} }
} }

View File

@ -46,9 +46,10 @@ import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger; import jdk.jfr.internal.Logger;
import jdk.jfr.internal.Repository; import jdk.jfr.internal.Repository;
import jdk.jfr.internal.SecuritySupport.SafePath; import jdk.jfr.internal.SecuritySupport.SafePath;
import jdk.jfr.internal.management.HiddenWait;;
public final class RepositoryFiles { public final class RepositoryFiles {
private static final Object WAIT_OBJECT = new Object(); private static final HiddenWait WAIT_OBJECT = new HiddenWait();
private static final String DIRECTORY_PATTERN = "DDDD_DD_DD_DD_DD_DD_"; private static final String DIRECTORY_PATTERN = "DDDD_DD_DD_DD_DD_DD_";
public static void notifyNewFile() { public static void notifyNewFile() {
synchronized (WAIT_OBJECT) { synchronized (WAIT_OBJECT) {
@ -59,7 +60,7 @@ public final class RepositoryFiles {
private final FileAccess fileAccess; private final FileAccess fileAccess;
private final NavigableMap<Long, Path> pathSet = new TreeMap<>(); private final NavigableMap<Long, Path> pathSet = new TreeMap<>();
private final Map<Path, Long> pathLookup = new HashMap<>(); private final Map<Path, Long> pathLookup = new HashMap<>();
private final Object waitObject; private final HiddenWait waitObject;
private boolean allowSubDirectory; private boolean allowSubDirectory;
private volatile boolean closed; private volatile boolean closed;
private Path repository; private Path repository;
@ -67,7 +68,7 @@ public final class RepositoryFiles {
public RepositoryFiles(FileAccess fileAccess, Path repository, boolean allowSubDirectory) { public RepositoryFiles(FileAccess fileAccess, Path repository, boolean allowSubDirectory) {
this.repository = repository; this.repository = repository;
this.fileAccess = fileAccess; this.fileAccess = fileAccess;
this.waitObject = repository == null ? WAIT_OBJECT : new Object(); this.waitObject = repository == null ? WAIT_OBJECT : new HiddenWait();
this.allowSubDirectory = allowSubDirectory; this.allowSubDirectory = allowSubDirectory;
} }
@ -108,7 +109,7 @@ public final class RepositoryFiles {
// was accessed. Just ignore, and retry later. // was accessed. Just ignore, and retry later.
} }
if (wait) { if (wait) {
nap(); waitObject.takeNap(1000);
} else { } else {
return pathLookup.size() > beforeSize; return pathLookup.size() > beforeSize;
} }
@ -157,16 +158,6 @@ public final class RepositoryFiles {
} }
} }
private void nap() {
try {
synchronized (waitObject) {
waitObject.wait(1000);
}
} catch (InterruptedException e) {
// ignore
}
}
private boolean updatePaths() throws IOException, DirectoryIteratorException { private boolean updatePaths() throws IOException, DirectoryIteratorException {
boolean foundNew = false; boolean foundNew = false;
Path repoPath = repository; Path repoPath = repository;

View File

@ -22,11 +22,21 @@
* or visit www.oracle.com if you need additional information or have any * or visit www.oracle.com if you need additional information or have any
* questions. * questions.
*/ */
package jdk.jfr.internal; package jdk.jfr.internal.management;
/** /**
* The HiddenWait class is used to exclude jdk.JavaMonitorWait events * The HiddenWait class is used to exclude jdk.JavaMonitorWait events
* from being generated when Object.wait() is called on an object of this type. * from being generated when Object.wait() is called on an object of this type.
*/ */
public final class HiddenWait { public final class HiddenWait {
public synchronized boolean takeNap(long timeoutMillis) {
try {
this.wait(timeoutMillis);
return true;
} catch (InterruptedException e) {
// Ok, ignore
return false;
}
}
} }

View File

@ -39,45 +39,57 @@ import java.io.IOException;
* processing should not continue. * processing should not continue.
*/ */
public final class StreamBarrier implements Closeable { public final class StreamBarrier implements Closeable {
private final HiddenWait lock = new HiddenWait();
private boolean activated = false; private boolean activated = false;
private boolean used = false; private boolean used = false;
private long end = Long.MAX_VALUE; private long end = Long.MAX_VALUE;
// Blocks thread until barrier is deactivated // Blocks thread until barrier is deactivated
public synchronized void check() { public void check() {
while (activated) { synchronized (lock) {
try { while (activated) {
this.wait(); try {
} catch (InterruptedException e) { lock.wait();
Thread.currentThread().interrupt(); } catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
} }
} }
} }
public synchronized void setStreamEnd(long timestamp) { public void setStreamEnd(long timestamp) {
end = timestamp; synchronized(lock) {
end = timestamp;
}
} }
public synchronized long getStreamEnd() { public long getStreamEnd() {
return end; synchronized(lock) {
return end;
}
} }
public synchronized void activate() { public void activate() {
activated = true; synchronized (lock) {
used = true; activated = true;
used = true;
}
} }
@Override @Override
public synchronized void close() throws IOException { public synchronized void close() throws IOException {
activated = false; synchronized (lock) {
this.notifyAll(); activated = false;
lock.notifyAll();
}
} }
/** /**
* Returns {@code true) if barrier is, or has been, in active state, {@code false) otherwise. * Returns {@code true) if barrier is, or has been, in active state, {@code false) otherwise.
*/ */
public synchronized boolean used() { public boolean used() {
return used; synchronized (lock) {
return used;
}
} }
} }

View File

@ -48,19 +48,19 @@ import jdk.internal.module.Checks;
import jdk.jfr.Event; import jdk.jfr.Event;
import jdk.jfr.EventType; import jdk.jfr.EventType;
import jdk.jfr.RecordingState; import jdk.jfr.RecordingState;
import jdk.jfr.internal.HiddenWait;
import jdk.jfr.internal.LogLevel; import jdk.jfr.internal.LogLevel;
import jdk.jfr.internal.LogTag; import jdk.jfr.internal.LogTag;
import jdk.jfr.internal.Logger; import jdk.jfr.internal.Logger;
import jdk.jfr.internal.MirrorEvent; import jdk.jfr.internal.MirrorEvent;
import jdk.jfr.internal.SecuritySupport; import jdk.jfr.internal.SecuritySupport;
import jdk.jfr.internal.Type; import jdk.jfr.internal.Type;
import jdk.jfr.internal.management.HiddenWait;
import jdk.jfr.internal.settings.PeriodSetting; import jdk.jfr.internal.settings.PeriodSetting;
import jdk.jfr.internal.settings.StackTraceSetting; import jdk.jfr.internal.settings.StackTraceSetting;
import jdk.jfr.internal.settings.ThresholdSetting; import jdk.jfr.internal.settings.ThresholdSetting;
public final class Utils { public final class Utils {
private static final Object flushObject = new Object(); private static final HiddenWait flushObject = new HiddenWait();
private static final String LEGACY_EVENT_NAME_PREFIX = "com.oracle.jdk."; private static final String LEGACY_EVENT_NAME_PREFIX = "com.oracle.jdk.";
/** /**
@ -351,17 +351,6 @@ public final class Utils {
return Type.isValidJavaFieldType(type.getName()); return Type.isValidJavaFieldType(type.getName());
} }
public static void takeNap(long millis) {
HiddenWait hiddenWait = new HiddenWait();
try {
synchronized(hiddenWait) {
hiddenWait.wait(millis);
}
} catch (InterruptedException e) {
// ok
}
}
public static void notifyFlush() { public static void notifyFlush() {
synchronized (flushObject) { synchronized (flushObject) {
flushObject.notifyAll(); flushObject.notifyAll();
@ -369,13 +358,7 @@ public final class Utils {
} }
public static void waitFlush(long timeOut) { public static void waitFlush(long timeOut) {
synchronized (flushObject) { flushObject.takeNap(timeOut);
try {
flushObject.wait(timeOut);
} catch (InterruptedException e) {
// OK
}
}
} }
public static Instant epochNanosToInstant(long epochNanos) { public static Instant epochNanosToInstant(long epochNanos) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -30,12 +30,14 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import jdk.jfr.internal.management.ManagementSupport; import jdk.jfr.internal.management.ManagementSupport;
import jdk.jfr.internal.management.HiddenWait;
final class DownLoadThread extends Thread { final class DownLoadThread extends Thread {
private final RemoteRecordingStream stream; private final RemoteRecordingStream stream;
private final Instant startTime; private final Instant startTime;
private final Instant endTime; private final Instant endTime;
private final DiskRepository diskRepository; private final DiskRepository diskRepository;
private final HiddenWait threadSleeper = new HiddenWait();
DownLoadThread(RemoteRecordingStream stream, String name) { DownLoadThread(RemoteRecordingStream stream, String name) {
super(name); super(name);
@ -64,7 +66,7 @@ final class DownLoadThread extends Thread {
if (bytes.length != 0) { if (bytes.length != 0) {
diskRepository.write(bytes); diskRepository.write(bytes);
} else { } else {
takeNap(); threadSleeper.takeNap(1000);
} }
} }
} catch (IOException ioe) { } catch (IOException ioe) {
@ -73,12 +75,4 @@ final class DownLoadThread extends Thread {
diskRepository.complete(); diskRepository.complete();
} }
} }
private void takeNap() {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
// ignore
}
}
} }

View File

@ -32,8 +32,10 @@ import java.util.ArrayDeque;
import java.util.Deque; import java.util.Deque;
import jdk.management.jfr.DiskRepository.DiskChunk; import jdk.management.jfr.DiskRepository.DiskChunk;
import jdk.jfr.internal.management.HiddenWait;
final class FileDump { final class FileDump {
private final HiddenWait lock = new HiddenWait();
private final Deque<DiskChunk> chunks = new ArrayDeque<>(); private final Deque<DiskChunk> chunks = new ArrayDeque<>();
private final long stopTimeMillis; private final long stopTimeMillis;
private boolean complete; private boolean complete;
@ -42,45 +44,53 @@ final class FileDump {
this.stopTimeMillis = stopTimeMillis; this.stopTimeMillis = stopTimeMillis;
} }
public synchronized void add(DiskChunk dc) { public void add(DiskChunk dc) {
if (isComplete()) { synchronized (lock) {
return; if (isComplete()) {
} return;
dc.acquire(); }
chunks.addFirst(dc); dc.acquire();
long endMillis = dc.endTimeNanos / 1_000_000; chunks.addFirst(dc);
if (endMillis >= stopTimeMillis) { long endMillis = dc.endTimeNanos / 1_000_000;
setComplete(); if (endMillis >= stopTimeMillis) {
setComplete();
}
} }
} }
public synchronized boolean isComplete() { public boolean isComplete() {
return complete; synchronized (lock) {
} return complete;
}
public synchronized void setComplete() { }
complete = true;
this.notifyAll(); public void setComplete() {
} synchronized (lock) {
complete = true;
public synchronized void close() { lock.notifyAll();
for (DiskChunk dc : chunks) { }
dc.release(); }
public void close() {
synchronized (lock) {
for (DiskChunk dc : chunks) {
dc.release();
}
chunks.clear();
complete = true;
} }
chunks.clear();
complete = true;
} }
private DiskChunk oldestChunk() throws InterruptedException { private DiskChunk oldestChunk() throws InterruptedException {
while (true) { while (true) {
synchronized (this) { synchronized (lock) {
if (!chunks.isEmpty()) { if (!chunks.isEmpty()) {
return chunks.pollLast(); return chunks.pollLast();
} }
if (complete) { if (complete) {
return null; return null;
} }
this.wait(); lock.wait();
} }
} }
} }

View File

@ -0,0 +1,88 @@
/*
* Copyright (c) 2024, 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.
*/
package jdk.jfr.jvm;
import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import jdk.jfr.Recording;
import jdk.jfr.Name;
import jdk.jfr.Event;
import jdk.jfr.FlightRecorder;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordingStream;
import jdk.test.lib.jfr.Events;
/**
* @test TestHiddenWait
* @key jfr
* @summary Checks that JFR code don't emit noise in the form of ThreadSleep and JavaMonitorWait events.
* @requires vm.hasJFR
* @library /test/lib
* @run main/othervm jdk.jfr.jvm.TestHiddenWait
*/
public class TestHiddenWait {
static final String PERIODIC_EVENT_NAME = "test.Periodic";
@Name(PERIODIC_EVENT_NAME)
public static class PeriodicEvent extends Event {
}
public static void main(String... args) throws Exception {
FlightRecorder.addPeriodicEvent(PeriodicEvent.class, () -> {
PeriodicEvent event = new PeriodicEvent();
event.commit();
});
try (Recording r = new Recording()) {
AtomicLong counter = new AtomicLong();
r.enable("jdk.ThreadSleep").withoutThreshold();
r.enable("jdk.JavaMonitorWait").withoutThreshold();
r.enable(PERIODIC_EVENT_NAME).withPeriod(Duration.ofMillis(100));
r.start();
// Triggers Object.wait() in stream barrier
try (RecordingStream b = new RecordingStream()) {
b.startAsync();
b.stop();
}
// Wait for for periodic events
try (RecordingStream s = new RecordingStream()) {
s.onEvent(PERIODIC_EVENT_NAME, e -> {
if (counter.incrementAndGet() >= 2) {
s.close();
}
});
s.start();
}
List<RecordedEvent> events = Events.fromRecording(r);
for (RecordedEvent event : events) {
if (!event.getEventType().getName().equals(PERIODIC_EVENT_NAME)) {
System.out.println(event);
throw new Exception("Didn't expect ThreadSleep or JavaMonitorWait events");
}
}
}
}
}