From 27b52e6c2255db010b9cf4c39c35e34c1034750b Mon Sep 17 00:00:00 2001 From: Chihiro Ito Date: Fri, 8 Aug 2025 21:13:57 +0900 Subject: [PATCH] 8365066: RecordingStream and RemoteRecordingStream do not terminate when the associated Recording is stopped or closed externally --- .../classes/jdk/jfr/consumer/EventStream.java | 5 +- .../jdk/jfr/consumer/RecordingStream.java | 10 ++- .../consumer/AbstractEventStream.java | 9 +- .../consumer/EventDirectoryStream.java | 37 +++++--- .../internal/consumer/EventFileStream.java | 7 +- .../internal/consumer/FileEventSource.java | 31 +++++++ .../consumer/LocalRecordingEventSource.java | 60 +++++++++++++ .../jfr/internal/management/EventSource.java | 47 ++++++++++ .../management/ManagementSupport.java | 4 +- .../management/jfr/RemoteRecordingStream.java | 22 ++--- .../internal/RemoteRecordingEventSource.java | 67 ++++++++++++++ .../recordingstream/TestClosedRecording.java | 85 ++++++++++++++++++ .../recordingstream/TestStoppedRecording.java | 44 ++++++--- .../jmx/streaming/TestClosedRecording.java | 89 +++++++++++++++++++ .../jmx/streaming/TestStoppedRecording.java | 89 +++++++++++++++++++ 15 files changed, 555 insertions(+), 51 deletions(-) create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/FileEventSource.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/LocalRecordingEventSource.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/management/EventSource.java create mode 100644 src/jdk.management.jfr/share/classes/jdk/management/jfr/internal/RemoteRecordingEventSource.java create mode 100644 test/jdk/jdk/jfr/api/consumer/recordingstream/TestClosedRecording.java create mode 100644 test/jdk/jdk/jfr/jmx/streaming/TestClosedRecording.java create mode 100644 test/jdk/jdk/jfr/jmx/streaming/TestStoppedRecording.java diff --git a/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventStream.java b/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventStream.java index ac325f19f222d..c69fdd69adb2e 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventStream.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventStream.java @@ -35,6 +35,7 @@ import jdk.jfr.internal.consumer.EventDirectoryStream; import jdk.jfr.internal.consumer.EventFileStream; +import jdk.jfr.internal.consumer.FileEventSource; /** * Represents a stream of events. @@ -112,7 +113,7 @@ public interface EventStream extends AutoCloseable { public static EventStream openRepository() throws IOException { return new EventDirectoryStream( null, - null, + new FileEventSource(), Collections.emptyList(), false ); @@ -137,7 +138,7 @@ public static EventStream openRepository(Path directory) throws IOException { Objects.requireNonNull(directory, "directory"); return new EventDirectoryStream( directory, - null, + new FileEventSource(), Collections.emptyList(), true ); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingStream.java b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingStream.java index 3d6c8de3015a2..b0c2274e29122 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingStream.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingStream.java @@ -43,6 +43,8 @@ import jdk.jfr.RecordingState; import jdk.jfr.internal.PlatformRecording; import jdk.jfr.internal.PrivateAccess; +import jdk.jfr.internal.management.EventSource; +import jdk.jfr.internal.consumer.LocalRecordingEventSource; import jdk.jfr.internal.util.Utils; import jdk.jfr.internal.consumer.EventDirectoryStream; import jdk.jfr.internal.management.StreamBarrier; @@ -82,6 +84,7 @@ public void accept(Long endNanos) { private final EventDirectoryStream directoryStream; private long maxSize; private Duration maxAge; + private final EventSource eventSource; /** * Creates an event stream for the current JVM (Java Virtual Machine). @@ -100,9 +103,10 @@ private RecordingStream(Map settings) { this.recording.setName("Recording Stream: " + creationTime); try { PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording); + this.eventSource = new LocalRecordingEventSource(pr); this.directoryStream = new EventDirectoryStream( null, - pr, + eventSource, configurations(), false ); @@ -392,9 +396,9 @@ public boolean stop() { boolean stopped = false; try { try (StreamBarrier sb = directoryStream.activateStreamBarrier()) { - stopped = recording.stop(); + stopped = eventSource.stop(); directoryStream.setCloseOnComplete(false); - sb.setStreamEnd(recording.getStopTime().toEpochMilli()); + sb.setStreamEnd(eventSource.getStopTime()); } directoryStream.awaitTermination(); } catch (InterruptedException | IOException e) { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java index 141fd58ca808c..92a202b5106c9 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java @@ -43,6 +43,7 @@ import jdk.jfr.internal.LogLevel; import jdk.jfr.internal.LogTag; import jdk.jfr.internal.Logger; +import jdk.jfr.internal.management.EventSource; /* * Purpose of this class is to simplify the implementation of @@ -60,10 +61,12 @@ public abstract class AbstractEventStream implements EventStream { private volatile boolean waitForChunks = true; private Dispatcher dispatcher; private boolean daemon = false; + protected final EventSource eventSource; - AbstractEventStream(List configurations) throws IOException { + AbstractEventStream(List configurations, EventSource eventSource) throws IOException { this.configurations = configurations; + this.eventSource = eventSource; } @Override @@ -207,8 +210,6 @@ public final void awaitTermination(Duration timeout) throws InterruptedException protected abstract void process() throws IOException; - protected abstract boolean isRecordingStream(); - protected final void closeParser() { parserState.close(); } @@ -250,7 +251,7 @@ private void startInternal(long startNanos) { if (streamConfiguration.started) { throw new IllegalStateException("Event stream can only be started once"); } - if (isRecordingStream() && streamConfiguration.startTime == null) { + if (eventSource.requiresStartTime() && streamConfiguration.startTime == null) { streamConfiguration.setStartNanos(startNanos); } streamConfiguration.setStarted(true); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java index 46dab564ac0fb..b6b95e7e27ab6 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java @@ -41,7 +41,7 @@ import jdk.jfr.internal.LogLevel; import jdk.jfr.internal.LogTag; import jdk.jfr.internal.Logger; -import jdk.jfr.internal.PlatformRecording; +import jdk.jfr.internal.management.EventSource; import jdk.jfr.internal.util.Utils; import jdk.jfr.internal.management.StreamBarrier; @@ -55,7 +55,6 @@ public final class EventDirectoryStream extends AbstractEventStream { private static final Comparator EVENT_COMPARATOR = JdkJfrConsumer.instance().eventComparator(); private final RepositoryFiles repositoryFiles; - private final PlatformRecording recording; private final StreamBarrier barrier = new StreamBarrier(); private final AtomicLong streamId = new AtomicLong(); private ChunkParser currentParser; @@ -66,11 +65,10 @@ public final class EventDirectoryStream extends AbstractEventStream { public EventDirectoryStream( Path p, - PlatformRecording recording, + EventSource eventSource, List configurations, boolean allowSubDirectories) throws IOException { - super(configurations); - this.recording = recording; + super(configurations, eventSource); this.repositoryFiles = new RepositoryFiles(p, allowSubDirectories); this.streamId.incrementAndGet(); Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Stream " + streamId + " started."); @@ -131,7 +129,7 @@ protected void processRecursionSafe() throws IOException { Dispatcher lastDisp = null; Dispatcher disp = dispatcher(); Path path; - boolean validStartTime = isRecordingStream() || disp.startTime != null; + boolean validStartTime = eventSource.requiresStartTime() || disp.startTime != null; if (validStartTime) { path = repositoryFiles.firstPath(disp.startNanos, true); } else { @@ -169,6 +167,19 @@ protected void processRecursionSafe() throws IOException { "ns (epoch), parser at " + lastFlush + "ns (epoch)."); return; } + + if(!barrier.used()) { + RecordingState state = eventSource.getState(); + if (state == RecordingState.CLOSED) { + return; + } else if (state == RecordingState.STOPPED) { + long stopTime = eventSource.getStopTime(); + if (lastFlush > stopTime) { + logStreamEnd("stopped at " + stopTime + "ns (epoch), parser at " + lastFlush + "ns (epoch)."); + return; + } + } + } } long endNanos = currentParser.getStartNanos() + currentParser.getChunkDuration(); long endMillis = Instant.ofEpochSecond(0, endNanos).toEpochMilli(); @@ -181,9 +192,13 @@ protected void processRecursionSafe() throws IOException { return; } - if (isRecordingStream()) { - if (recording.getState() == RecordingState.STOPPED && !barrier.used()) { - logStreamEnd("recording stopped externally."); + if(!barrier.used()) { + RecordingState state = eventSource.getState(); + if (state == RecordingState.CLOSED){ + logStreamEnd("Event source is closed externally"); + return; + }else if(state == RecordingState.STOPPED) { + logStreamEnd("Event source is stopped externally"); return; } } @@ -226,10 +241,6 @@ private void logStreamEnd(String text) { Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, msg); } - protected boolean isRecordingStream() { - return recording != null; - } - private void processOrdered(Dispatcher c) throws IOException { if (sortedCache == null) { sortedCache = new RecordedEvent[100_000]; diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventFileStream.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventFileStream.java index 56d9fe01d790c..5155806daccca 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventFileStream.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventFileStream.java @@ -45,7 +45,7 @@ public final class EventFileStream extends AbstractEventStream { private RecordedEvent[] cacheSorted; public EventFileStream(Path file) throws IOException { - super(Collections.emptyList()); + super(Collections.emptyList(), new FileEventSource()); this.input = new RecordingInput(file.toFile()); this.input.setStreamed(); } @@ -71,11 +71,6 @@ public void close() { } } - @Override - protected boolean isRecordingStream() { - return false; - } - @Override protected void process() throws IOException { Dispatcher disp = dispatcher(); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/FileEventSource.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/FileEventSource.java new file mode 100644 index 0000000000000..ee7ca0433d48a --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/FileEventSource.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025, 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.jfr.internal.consumer; + +import jdk.jfr.internal.management.EventSource; + +public class FileEventSource implements EventSource { +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/LocalRecordingEventSource.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/LocalRecordingEventSource.java new file mode 100644 index 0000000000000..ee978af112e8e --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/LocalRecordingEventSource.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025, 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.jfr.internal.consumer; + +import jdk.jfr.RecordingState; + +import jdk.jfr.internal.PlatformRecording; +import jdk.jfr.internal.management.EventSource; + +public class LocalRecordingEventSource implements EventSource { + + private final PlatformRecording recording; + + public LocalRecordingEventSource(PlatformRecording recording) { + this.recording = recording; + } + + @Override + public boolean requiresStartTime() { + return true; + } + + @Override + public long getStopTime() { + return recording.getStopTime().toEpochMilli(); + } + + @Override + public boolean stop() { + return recording.stop("stopped by RecordingStream"); + } + + @Override + public RecordingState getState() { + return recording.getState(); + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/management/EventSource.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/management/EventSource.java new file mode 100644 index 0000000000000..8731c9b1d9e7f --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/management/EventSource.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025, 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.jfr.internal.management; + +import jdk.jfr.RecordingState; + +public interface EventSource { + + default long getStopTime() { + return Long.MAX_VALUE; + } + + default boolean stop() { + return true; + } + + default boolean requiresStartTime(){ + return false; + } + + default RecordingState getState() { + return RecordingState.RUNNING; + } +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/management/ManagementSupport.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/management/ManagementSupport.java index 1f5963d027771..c1699173c55a1 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/management/ManagementSupport.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/management/ManagementSupport.java @@ -163,10 +163,10 @@ public static Configuration newConfiguration(String name, String label, String d // with configuration objects public static EventStream newEventDirectoryStream( Path directory, - List confs) throws IOException { + List confs, EventSource eventSource) throws IOException { return new EventDirectoryStream( directory, - null, + eventSource, confs, false ); diff --git a/src/jdk.management.jfr/share/classes/jdk/management/jfr/RemoteRecordingStream.java b/src/jdk.management.jfr/share/classes/jdk/management/jfr/RemoteRecordingStream.java index a8752e159ad3a..fa10bc2bc631e 100644 --- a/src/jdk.management.jfr/share/classes/jdk/management/jfr/RemoteRecordingStream.java +++ b/src/jdk.management.jfr/share/classes/jdk/management/jfr/RemoteRecordingStream.java @@ -27,11 +27,8 @@ import java.io.IOException; import java.io.RandomAccessFile; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; @@ -40,7 +37,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.concurrent.Future; import java.util.function.Consumer; import javax.management.JMX; import javax.management.MBeanServerConnection; @@ -54,11 +50,11 @@ import jdk.jfr.consumer.EventStream; import jdk.jfr.consumer.MetadataEvent; import jdk.jfr.consumer.RecordedEvent; -import jdk.jfr.consumer.RecordingStream; +import jdk.jfr.internal.management.EventSource; +import jdk.management.jfr.internal.RemoteRecordingEventSource; import jdk.jfr.internal.management.EventSettingsModifier; import jdk.jfr.internal.management.ManagementSupport; import jdk.jfr.internal.management.StreamBarrier; -import jdk.management.jfr.DiskRepository.DiskChunk; import jdk.jfr.internal.management.EventByteStream; /** @@ -158,6 +154,7 @@ public void accept(Long endNanos) { private boolean started; private Duration maxAge; private long maxSize; + private final EventSource eventSource; /** * Creates an event stream that operates against a {@link MBeanServerConnection} @@ -213,7 +210,8 @@ private RemoteRecordingStream(MBeanServerConnection connection, Path directory, creationTime = Instant.now(); mbean = createProxy(connection); recordingId = createRecording(); - stream = ManagementSupport.newEventDirectoryStream(path, configurations(mbean)); + eventSource = new RemoteRecordingEventSource(mbean, recordingId); + stream = ManagementSupport.newEventDirectoryStream(path, configurations(mbean), eventSource); stream.setStartTime(Instant.MIN); repository = new DiskRepository(path, delete); ManagementSupport.setOnChunkCompleteHandler(stream, new ChunkConsumer(repository)); @@ -327,7 +325,7 @@ public void setSettings(Map settings) { ManagementSupport.logDebug(e.getMessage()); close(); } - }; + } /** * Disables event with the specified name. @@ -469,7 +467,9 @@ public void close() { ManagementSupport.setOnChunkCompleteHandler(stream, null); stream.close(); try { - mbean.closeRecording(recordingId); + if(eventSource.getState() != RecordingState.CLOSED) { + mbean.closeRecording(recordingId); + } } catch (IOException e) { ManagementSupport.logDebug(e.getMessage()); } @@ -586,9 +586,9 @@ public boolean stop() { boolean stopped = false; try (StreamBarrier pb = ManagementSupport.activateStreamBarrier(stream)) { try (StreamBarrier rb = repository.activateStreamBarrier()) { - stopped = mbean.stopRecording(recordingId); + stopped = eventSource.stop(); ManagementSupport.setCloseOnComplete(stream, false); - long stopTime = getRecordingInfo(mbean.getRecordings(), recordingId).getStopTime(); + long stopTime = eventSource.getStopTime(); pb.setStreamEnd(stopTime); rb.setStreamEnd(stopTime); } diff --git a/src/jdk.management.jfr/share/classes/jdk/management/jfr/internal/RemoteRecordingEventSource.java b/src/jdk.management.jfr/share/classes/jdk/management/jfr/internal/RemoteRecordingEventSource.java new file mode 100644 index 0000000000000..6993823523b1e --- /dev/null +++ b/src/jdk.management.jfr/share/classes/jdk/management/jfr/internal/RemoteRecordingEventSource.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2025, 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.management.jfr.internal; + +import jdk.jfr.RecordingState; +import jdk.jfr.internal.management.EventSource; +import jdk.management.jfr.FlightRecorderMXBean; +import jdk.management.jfr.RecordingInfo; + +import java.util.Optional; + +public class RemoteRecordingEventSource implements EventSource { + + private final FlightRecorderMXBean mbean; + private final long recordingId; + + public RemoteRecordingEventSource(FlightRecorderMXBean mbean, long recordingId) { + this.mbean = mbean; + this.recordingId = recordingId; + } + + @Override + public long getStopTime() { + Optional recordingInfo = mbean.getRecordings().stream().filter(r -> r.getId() == recordingId).findFirst(); + if(recordingInfo.isEmpty()){ + return Long.MAX_VALUE; + } + return recordingInfo.get().getStopTime(); + } + + @Override + public RecordingState getState() { + Optional recordingInfo = mbean.getRecordings().stream().filter(r -> r.getId() == recordingId).findFirst(); + if(recordingInfo.isEmpty()){ + return RecordingState.CLOSED; + } + return RecordingState.valueOf(recordingInfo.get().getState()); + } + + @Override + public boolean stop() { + return mbean.stopRecording(recordingId); + } +} diff --git a/test/jdk/jdk/jfr/api/consumer/recordingstream/TestClosedRecording.java b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestClosedRecording.java new file mode 100644 index 0000000000000..13050f9c4ea86 --- /dev/null +++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestClosedRecording.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2025, 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.api.consumer.recordingstream; + +import jdk.jfr.*; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordingStream; + +import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; + +/** + * @test + * @summary Tests that a RecordingStream is closed if the underlying Recording + * is closed. + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestClosedRecording + */ +public class TestClosedRecording { + + private static class SendEventListener implements FlightRecorderListener { + @Override + public void recordingStateChanged(Recording recording) { + if(recording.getState() == RecordingState.RUNNING){ + CloseEvent e = new CloseEvent(); + e.commit(); + } + } + } + + private static final class CloseEvent extends Event { + } + + private static final Consumer CLOSE_RECORDING = e -> { + FlightRecorder.getFlightRecorder().getRecordings().getFirst().close(); + }; + + public static void main(String... args) throws Exception { + FlightRecorder.addListener(new SendEventListener()); + sync(); + async(); + } + + private static void sync() throws Exception { + try (RecordingStream rs = new RecordingStream()) { + rs.onEvent(CLOSE_RECORDING); + rs.start(); + } + } + + private static void async() throws Exception { + CountDownLatch latch = new CountDownLatch(1); + try (RecordingStream rs = new RecordingStream()) { + rs.onEvent(CLOSE_RECORDING); + rs.onClose(() -> { + latch.countDown(); + }); + rs.startAsync(); + latch.await(); + } + } +} diff --git a/test/jdk/jdk/jfr/api/consumer/recordingstream/TestStoppedRecording.java b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestStoppedRecording.java index a8bcb7f33687e..44e36521ebd23 100644 --- a/test/jdk/jdk/jfr/api/consumer/recordingstream/TestStoppedRecording.java +++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestStoppedRecording.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 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 @@ -23,12 +23,13 @@ package jdk.jfr.api.consumer.recordingstream; -import java.util.concurrent.CountDownLatch; - -import jdk.jfr.Event; -import jdk.jfr.FlightRecorder; +import jdk.jfr.*; +import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordingStream; +import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; + /** * @test * @summary Tests that a RecordingStream is closed if the underlying Recording @@ -40,21 +41,44 @@ */ public class TestStoppedRecording { + private static class SendEventListener implements FlightRecorderListener{ + @Override + public void recordingStateChanged(Recording recording) { + if(recording.getState() == RecordingState.RUNNING){ + StopEvent e = new StopEvent(); + e.commit(); + } + } + } + private static final class StopEvent extends Event { } + private static final Consumer STOP_RECORDING = e -> { + FlightRecorder.getFlightRecorder().getRecordings().getFirst().stop(); + }; + public static void main(String... args) throws Exception { + FlightRecorder.addListener(new SendEventListener()); + sync(); + async(); + } + + private static void sync() throws Exception { + try (RecordingStream rs = new RecordingStream()) { + rs.onEvent(STOP_RECORDING); + rs.start(); + } + } + + private static void async() throws Exception { CountDownLatch latch = new CountDownLatch(1); try (RecordingStream rs = new RecordingStream()) { - rs.onEvent(e -> { - FlightRecorder.getFlightRecorder().getRecordings().getFirst().stop(); - }); + rs.onEvent(STOP_RECORDING); rs.onClose(() -> { latch.countDown(); }); rs.startAsync(); - StopEvent stop = new StopEvent(); - stop.commit(); latch.await(); } } diff --git a/test/jdk/jdk/jfr/jmx/streaming/TestClosedRecording.java b/test/jdk/jdk/jfr/jmx/streaming/TestClosedRecording.java new file mode 100644 index 0000000000000..3c2aa33361949 --- /dev/null +++ b/test/jdk/jdk/jfr/jmx/streaming/TestClosedRecording.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2025, 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.jmx.streaming; + +import jdk.jfr.*; +import jdk.jfr.consumer.RecordedEvent; +import jdk.management.jfr.RemoteRecordingStream; + +import javax.management.MBeanServerConnection; +import java.lang.management.ManagementFactory; +import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; + +/** + * @test + * @summary Tests that a RemoteRecordingStream is closed if the underlying remote recording is closed. + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib /test/jdk + * @build jdk.jfr.api.consumer.recordingstream.EventProducer + * @run main/othervm jdk.jfr.jmx.streaming.TestClosedRecording + */ +public class TestClosedRecording { + + private static class SendEventListener implements FlightRecorderListener { + @Override + public void recordingStateChanged(Recording recording) { + if(recording.getState() == RecordingState.RUNNING){ + CloseEvent e = new CloseEvent(); + e.commit(); + } + } + } + + private static final class CloseEvent extends Event { + } + + private static final Consumer CLOSE_RECORDING = e -> { + FlightRecorder.getFlightRecorder().getRecordings().getFirst().close(); + }; + + private static final MBeanServerConnection CONNECTION = ManagementFactory.getPlatformMBeanServer(); + + public static void main(String... args) throws Exception { + FlightRecorder.addListener(new SendEventListener()); + sync(); + async(); + } + + private static void sync() throws Exception { + try (RemoteRecordingStream rs = new RemoteRecordingStream(CONNECTION)) { + rs.onEvent(CLOSE_RECORDING); + rs.start(); + } + } + + private static void async() throws Exception { + CountDownLatch latch = new CountDownLatch(1); + try (RemoteRecordingStream rs = new RemoteRecordingStream(CONNECTION)) { + rs.onEvent(CLOSE_RECORDING); + rs.onClose(() -> { + latch.countDown(); + }); + rs.startAsync(); + latch.await(); + } + } +} diff --git a/test/jdk/jdk/jfr/jmx/streaming/TestStoppedRecording.java b/test/jdk/jdk/jfr/jmx/streaming/TestStoppedRecording.java new file mode 100644 index 0000000000000..75cb3078a269a --- /dev/null +++ b/test/jdk/jdk/jfr/jmx/streaming/TestStoppedRecording.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2025, 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.jmx.streaming; + +import jdk.jfr.*; +import jdk.jfr.consumer.RecordedEvent; +import jdk.management.jfr.RemoteRecordingStream; + +import javax.management.MBeanServerConnection; +import java.lang.management.ManagementFactory; +import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; + +/** + * @test + * @summary Tests that a RemoteRecordingStream is closed if the underlying Remote Recording is stopped. + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib /test/jdk + * @build jdk.jfr.api.consumer.recordingstream.EventProducer + * @run main/othervm jdk.jfr.jmx.streaming.TestStoppedRecording + */ +public class TestStoppedRecording { + + private static class SendEventListener implements FlightRecorderListener { + @Override + public void recordingStateChanged(Recording recording) { + if(recording.getState() == RecordingState.RUNNING){ + StopEvent e = new StopEvent(); + e.commit(); + } + } + } + + private static final class StopEvent extends Event { + } + + private static final Consumer STOP_RECORDING = e -> { + FlightRecorder.getFlightRecorder().getRecordings().getFirst().stop(); + }; + + private static final MBeanServerConnection CONNECTION = ManagementFactory.getPlatformMBeanServer(); + + public static void main(String... args) throws Exception { + FlightRecorder.addListener(new SendEventListener()); + sync(); + async(); + } + + private static void sync() throws Exception { + try (RemoteRecordingStream rs = new RemoteRecordingStream(CONNECTION)) { + rs.onEvent(STOP_RECORDING); + rs.start(); + } + } + + private static void async() throws Exception { + CountDownLatch latch = new CountDownLatch(1); + try (RemoteRecordingStream rs = new RemoteRecordingStream(CONNECTION)) { + rs.onEvent(STOP_RECORDING); + rs.onClose(() -> { + latch.countDown(); + }); + rs.startAsync(); + latch.await(); + } + } +}