8233700: EventStream not closed
authoregahlin
Fri, 22 Nov 2019 17:20:43 +0100
changeset 59226 a0f39cc47387
parent 59225 80e1201f6c9a
child 59227 46084917fde7
8233700: EventStream not closed Reviewed-by: mgronlun, mseledtsov
src/hotspot/share/jfr/jni/jfrJniMethod.cpp
src/hotspot/share/jfr/jni/jfrJniMethod.hpp
src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp
src/hotspot/share/jfr/recorder/repository/jfrChunk.cpp
src/hotspot/share/jfr/recorder/repository/jfrChunk.hpp
src/hotspot/share/jfr/recorder/repository/jfrChunkWriter.cpp
src/hotspot/share/jfr/recorder/repository/jfrChunkWriter.hpp
src/hotspot/share/jfr/recorder/repository/jfrRepository.cpp
src/hotspot/share/jfr/recorder/repository/jfrRepository.hpp
src/hotspot/share/jfr/recorder/service/jfrRecorderService.cpp
src/jdk.jfr/share/classes/jdk/jfr/consumer/EventStream.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingStream.java
src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java
src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java
src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java
src/jdk.jfr/share/classes/jdk/jfr/internal/Repository.java
src/jdk.jfr/share/classes/jdk/jfr/internal/ShutdownHook.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkHeader.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkParser.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventFileStream.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RepositoryFiles.java
src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java
test/jdk/jdk/jfr/api/consumer/recordingstream/TestStoppedRecording.java
test/jdk/jdk/jfr/api/consumer/streaming/TestInProcessMigration.java
test/jdk/jdk/jfr/api/consumer/streaming/TestJVMCrash.java
test/jdk/jdk/jfr/api/consumer/streaming/TestJVMExit.java
test/jdk/jdk/jfr/api/consumer/streaming/TestOutOfProcessMigration.java
test/jdk/jdk/jfr/api/consumer/streaming/TestProcess.java
test/jdk/jdk/jfr/api/consumer/streaming/TestRepositoryMigration.java
--- a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp	Fri Nov 22 17:20:43 2019 +0100
@@ -210,6 +210,10 @@
   JfrRecorder::start_recording();
 JVM_END
 
+JVM_ENTRY_NO_ENV(jboolean, jfr_is_recording(JNIEnv * env, jobject jvm))
+  return JfrRecorder::is_recording() ? JNI_TRUE : JNI_FALSE;
+JVM_END
+
 JVM_ENTRY_NO_ENV(void, jfr_end_recording(JNIEnv* env, jobject jvm))
   if (!JfrRecorder::is_recording()) {
     return;
@@ -217,6 +221,9 @@
   JfrRecorder::stop_recording();
 JVM_END
 
+JVM_ENTRY_NO_ENV(void, jfr_mark_chunk_final(JNIEnv * env, jobject jvm))
+  JfrRepository::mark_chunk_final();
+JVM_END
 
 JVM_ENTRY_NO_ENV(jboolean, jfr_emit_event(JNIEnv* env, jobject jvm, jlong eventTypeId, jlong timeStamp, jlong when))
   JfrPeriodicEventSet::requestEvent((JfrEventId)eventTypeId);
--- a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp	Fri Nov 22 17:20:43 2019 +0100
@@ -49,8 +49,12 @@
 
 void JNICALL jfr_begin_recording(JNIEnv* env, jobject jvm);
 
+jboolean JNICALL jfr_is_recording(JNIEnv* env, jobject jvm);
+
 void JNICALL jfr_end_recording(JNIEnv* env, jobject jvm);
 
+void JNICALL jfr_mark_chunk_final(JNIEnv* env, jobject jvm);
+
 jboolean JNICALL jfr_emit_event(JNIEnv* env, jobject jvm, jlong eventTypeId, jlong timeStamp, jlong when);
 
 jobject JNICALL jfr_get_all_event_classes(JNIEnv* env, jobject jvm);
--- a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp	Fri Nov 22 17:20:43 2019 +0100
@@ -36,7 +36,9 @@
   if (jfr_clz != NULL) {
     JNINativeMethod method[] = {
       (char*)"beginRecording", (char*)"()V", (void*)jfr_begin_recording,
+      (char*)"isRecording", (char*)"()Z", (void*)jfr_is_recording,
       (char*)"endRecording", (char*)"()V", (void*)jfr_end_recording,
+      (char*)"markChunkFinal", (char*)"()V", (void*)jfr_mark_chunk_final,
       (char*)"counterTime", (char*)"()J", (void*)jfr_elapsed_counter,
       (char*)"createJFR", (char*)"(Z)Z", (void*)jfr_create_jfr,
       (char*)"destroyJFR", (char*)"()Z", (void*)jfr_destroy_jfr,
--- a/src/hotspot/share/jfr/recorder/repository/jfrChunk.cpp	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/hotspot/share/jfr/recorder/repository/jfrChunk.cpp	Fri Nov 22 17:20:43 2019 +0100
@@ -59,7 +59,8 @@
   _last_update_nanos(0),
   _last_checkpoint_offset(0),
   _last_metadata_offset(0),
-  _generation(1) {}
+  _generation(1),
+  _final(false) {}
 
 JfrChunk::~JfrChunk() {
   reset();
@@ -86,10 +87,20 @@
   return JFR_VERSION_MINOR;
 }
 
-u2 JfrChunk::capabilities() const {
+void JfrChunk::mark_final() {
+  _final = true;
+}
+
+u2 JfrChunk::flags() const {
   // chunk capabilities, CompressedIntegers etc
-  static bool compressed_integers = JfrOptionSet::compressed_integers();
-  return compressed_integers;
+  u2 flags = 0;
+  if (JfrOptionSet::compressed_integers()) {
+    flags |= 1 << 0;
+  }
+  if (_final) {
+    flags |= 1 << 1;
+  }
+  return flags;
 }
 
 int64_t JfrChunk::cpu_frequency() const {
--- a/src/hotspot/share/jfr/recorder/repository/jfrChunk.hpp	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/hotspot/share/jfr/recorder/repository/jfrChunk.hpp	Fri Nov 22 17:20:43 2019 +0100
@@ -44,6 +44,7 @@
   int64_t _last_checkpoint_offset;
   int64_t _last_metadata_offset;
   mutable u1 _generation;
+  bool _final;
 
   JfrChunk();
   ~JfrChunk();
@@ -53,7 +54,9 @@
   u2 major_version() const;
   u2 minor_version() const;
   int64_t cpu_frequency() const;
-  u2 capabilities() const;
+  u2 flags() const;
+
+  void mark_final();
 
   void update_start_ticks();
   void update_start_nanos();
--- a/src/hotspot/share/jfr/recorder/repository/jfrChunkWriter.cpp	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/hotspot/share/jfr/recorder/repository/jfrChunkWriter.cpp	Fri Nov 22 17:20:43 2019 +0100
@@ -41,8 +41,8 @@
 static const int64_t START_TICKS_OFFSET = DURATION_NANOS_OFFSET + SLOT_SIZE;
 static const int64_t CPU_FREQUENCY_OFFSET = START_TICKS_OFFSET + SLOT_SIZE;
 static const int64_t GENERATION_OFFSET = CPU_FREQUENCY_OFFSET + SLOT_SIZE;
-static const int64_t CAPABILITY_OFFSET = GENERATION_OFFSET + 2;
-static const int64_t HEADER_SIZE = CAPABILITY_OFFSET + 2;
+static const int64_t FLAG_OFFSET = GENERATION_OFFSET + 2;
+static const int64_t HEADER_SIZE = FLAG_OFFSET + 2;
 
 static fio_fd open_chunk(const char* path) {
   return path != NULL ? os::open(path, O_CREAT | O_RDWR, S_IREAD | S_IWRITE) : invalid_fd;
@@ -117,8 +117,8 @@
     _writer->flush();
   }
 
-  void write_capabilities() {
-    _writer->be_write(_chunk->capabilities());
+  void write_flags() {
+    _writer->be_write(_chunk->flags());
   }
 
   void write_size_to_generation(int64_t size, bool finalize) {
@@ -135,7 +135,7 @@
     assert(_chunk != NULL, "invariant");
     DEBUG_ONLY(assert_writer_position(_writer, SIZE_OFFSET);)
     write_size_to_generation(size, finalize);
-    // no need to write capabilities
+    write_flags();
     _writer->seek(size); // implicit flush
   }
 
@@ -146,7 +146,7 @@
     write_magic();
     write_version();
     write_size_to_generation(HEADER_SIZE, false);
-    write_capabilities();
+    write_flags();
     DEBUG_ONLY(assert_writer_position(_writer, HEADER_SIZE);)
     _writer->flush();
   }
@@ -201,7 +201,7 @@
   head.write_time(false);
   head.write_cpu_frequency();
   head.write_next_generation();
-  head.write_capabilities();
+  head.write_flags();
   assert(current_offset() - header_content_pos == HEADER_SIZE, "invariant");
   const u4 checkpoint_size = current_offset() - event_size_offset;
   write_padded_at_offset<u4>(checkpoint_size, event_size_offset);
@@ -211,6 +211,11 @@
   return sz_written;
 }
 
+void JfrChunkWriter::mark_chunk_final() {
+  assert(_chunk != NULL, "invariant");
+  _chunk->mark_final();
+}
+
 int64_t JfrChunkWriter::flush_chunk(bool flushpoint) {
   assert(_chunk != NULL, "invariant");
   const int64_t sz_written = write_chunk_header_checkpoint(flushpoint);
--- a/src/hotspot/share/jfr/recorder/repository/jfrChunkWriter.hpp	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/hotspot/share/jfr/recorder/repository/jfrChunkWriter.hpp	Fri Nov 22 17:20:43 2019 +0100
@@ -59,6 +59,7 @@
 
   bool has_metadata() const;
   void set_time_stamp();
+  void mark_chunk_final();
 };
 
 #endif // SHARE_JFR_RECORDER_REPOSITORY_JFRCHUNKWRITER_HPP
--- a/src/hotspot/share/jfr/recorder/repository/jfrRepository.cpp	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/hotspot/share/jfr/recorder/repository/jfrRepository.cpp	Fri Nov 22 17:20:43 2019 +0100
@@ -115,6 +115,10 @@
   chunkwriter().set_path(path);
 }
 
+void JfrRepository::mark_chunk_final() {
+  chunkwriter().mark_chunk_final();
+}
+
 jlong JfrRepository::current_chunk_start_nanos() {
   return chunkwriter().current_chunk_start_nanos();
 }
--- a/src/hotspot/share/jfr/recorder/repository/jfrRepository.hpp	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/hotspot/share/jfr/recorder/repository/jfrRepository.hpp	Fri Nov 22 17:20:43 2019 +0100
@@ -70,6 +70,7 @@
  public:
   static void set_path(jstring location, JavaThread* jt);
   static void set_chunk_path(jstring path, JavaThread* jt);
+  static void mark_chunk_final();
   static void flush(JavaThread* jt);
   static jlong current_chunk_start_nanos();
 };
--- a/src/hotspot/share/jfr/recorder/service/jfrRecorderService.cpp	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/hotspot/share/jfr/recorder/service/jfrRecorderService.cpp	Fri Nov 22 17:20:43 2019 +0100
@@ -432,6 +432,7 @@
   if (_chunkwriter.is_valid()) {
     Thread* const t = Thread::current();
     _storage.flush_regular_buffer(t->jfr_thread_local()->native_buffer(), t);
+    _chunkwriter.mark_chunk_final();
     invoke_flush();
     _chunkwriter.set_time_stamp();
     _repository.close_chunk();
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventStream.java	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventStream.java	Fri Nov 22 17:20:43 2019 +0100
@@ -139,7 +139,7 @@
      */
     public static EventStream openRepository() throws IOException {
         Utils.checkAccessFlightRecorder();
-        return new EventDirectoryStream(AccessController.getContext(), null, SecuritySupport.PRIVILIGED, false);
+        return new EventDirectoryStream(AccessController.getContext(), null, SecuritySupport.PRIVILIGED, null);
     }
 
     /**
@@ -162,7 +162,7 @@
     public static EventStream openRepository(Path directory) throws IOException {
         Objects.nonNull(directory);
         AccessControlContext acc = AccessController.getContext();
-        return new EventDirectoryStream(acc, directory, FileAccess.UNPRIVILIGED, false);
+        return new EventDirectoryStream(acc, directory, FileAccess.UNPRIVILIGED, null);
     }
 
     /**
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingStream.java	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingStream.java	Fri Nov 22 17:20:43 2019 +0100
@@ -88,7 +88,8 @@
         this.recording = new Recording();
         this.recording.setFlushInterval(Duration.ofMillis(1000));
         try {
-            this.directoryStream = new EventDirectoryStream(acc, null, SecuritySupport.PRIVILIGED, true);
+            PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording);
+            this.directoryStream = new EventDirectoryStream(acc, null, SecuritySupport.PRIVILIGED, pr);
         } catch (IOException ioe) {
             this.recording.close();
             throw new IllegalStateException(ioe.getMessage());
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java	Fri Nov 22 17:20:43 2019 +0100
@@ -43,7 +43,6 @@
 
     static final long RESERVED_CLASS_ID_LIMIT = 400;
 
-    private volatile boolean recording;
     private volatile boolean nativeOK;
 
     private static native void registerNatives();
@@ -69,6 +68,15 @@
     }
 
     /**
+     * Marks current chunk as final
+     * <p>
+     * This allows streaming clients to read the chunk header and
+     * close the stream when no more data will be written into
+     * the current repository.
+     */
+    public native void markChunkFinal();
+
+    /**
      * Begin recording events
      *
      * Requires that JFR has been started with {@link #createNativeJFR()}
@@ -76,6 +84,19 @@
     public native void beginRecording();
 
     /**
+     * Return true if the JVM is recording
+     */
+    public native boolean isRecording();
+
+    /**
+     * End recording events, which includes flushing data in thread buffers
+     *
+     * Requires that JFR has been started with {@link #createNativeJFR()}
+     *
+     */
+    public native void endRecording();
+
+    /**
      * Return ticks
      *
      * @return the time, in ticks
@@ -97,13 +118,7 @@
      */
     public native boolean emitEvent(long eventTypeId, long timestamp, long when);
 
-    /**
-     * End recording events, which includes flushing data in thread buffers
-     *
-     * Requires that JFR has been started with {@link #createNativeJFR()}
-     *
-     */
-    public native void endRecording();
+
 
     /**
      * Return a list of all classes deriving from {@link jdk.internal.event.Event}
@@ -354,20 +369,6 @@
      */
     public native void storeMetadataDescriptor(byte[] bytes);
 
-    public void endRecording_() {
-        endRecording();
-        recording = false;
-    }
-
-    public void beginRecording_() {
-        beginRecording();
-        recording = true;
-    }
-
-    public boolean isRecording() {
-        return recording;
-    }
-
     /**
      * If the JVM supports JVM TI and retransformation has not been disabled this
      * method will return true. This flag can not change during the lifetime of
@@ -558,4 +559,5 @@
      *@return start time of the recording in nanos, -1 in case of in-memory
      */
     public native long getChunkStartNanos();
+
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java	Fri Nov 22 17:20:43 2019 +0100
@@ -31,6 +31,7 @@
 import static jdk.jfr.internal.LogTag.JFR;
 import static jdk.jfr.internal.LogTag.JFR_SYSTEM;
 
+import java.io.IOException;
 import java.security.AccessControlContext;
 import java.security.AccessController;
 import java.time.Duration;
@@ -53,6 +54,7 @@
 import jdk.jfr.RecordingState;
 import jdk.jfr.events.ActiveRecordingEvent;
 import jdk.jfr.events.ActiveSettingEvent;
+import jdk.jfr.internal.SecuritySupport.SafePath;
 import jdk.jfr.internal.SecuritySupport.SecureRecorderListener;
 import jdk.jfr.internal.instrument.JDKEvents;
 
@@ -70,6 +72,7 @@
 
     private long recordingCounter = 0;
     private RepositoryChunk currentChunk;
+    private boolean inShutdown;
 
     public PlatformRecorder() throws Exception {
         repository = Repository.getRepository();
@@ -176,6 +179,10 @@
         }
     }
 
+    synchronized void setInShutDown() {
+        this.inShutdown = true;
+    }
+
     // called by shutdown hook
     synchronized void destroy() {
         try {
@@ -198,7 +205,7 @@
 
         if (jvm.hasNativeJFR()) {
             if (jvm.isRecording()) {
-                jvm.endRecording_();
+                jvm.endRecording();
             }
             jvm.destroyNativeJFR();
         }
@@ -236,7 +243,7 @@
                 MetadataRepository.getInstance().setOutput(null);
             }
             currentChunk = newChunk;
-            jvm.beginRecording_();
+            jvm.beginRecording();
             startNanos = jvm.getChunkStartNanos();
             recording.setState(RecordingState.RUNNING);
             updateSettings();
@@ -289,11 +296,15 @@
             }
         }
         OldObjectSample.emit(recording);
+        recording.setFinalStartnanos(jvm.getChunkStartNanos());
 
         if (endPhysical) {
             RequestEngine.doChunkEnd();
             if (recording.isToDisk()) {
                 if (currentChunk != null) {
+                    if (inShutdown) {
+                        jvm.markChunkFinal();
+                    }
                     MetadataRepository.getInstance().setOutput(null);
                     finishChunk(currentChunk, now, null);
                     currentChunk = null;
@@ -302,7 +313,7 @@
                 // last memory
                 dumpMemoryToDestination(recording);
             }
-            jvm.endRecording_();
+            jvm.endRecording();
             disableEvents();
         } else {
             RepositoryChunk newChunk = null;
@@ -327,7 +338,6 @@
         } else {
             RequestEngine.setFlushInterval(Long.MAX_VALUE);
         }
-
         recording.setState(RecordingState.STOPPED);
     }
 
@@ -357,17 +367,7 @@
         MetadataRepository.getInstance().setSettings(list);
     }
 
-    public synchronized void rotateIfRecordingToDisk() {
-        boolean disk = false;
-        for (PlatformRecording s : getRecordings()) {
-            if (RecordingState.RUNNING == s.getState() && s.isToDisk()) {
-                disk = true;
-            }
-        }
-        if (disk) {
-            rotateDisk();
-        }
-    }
+
 
     synchronized void rotateDisk() {
         Instant now = Instant.now();
@@ -584,6 +584,19 @@
         target.setInternalDuration(Duration.between(startTime, endTime));
     }
 
-
-
+    public synchronized void migrate(SafePath repo) throws IOException {
+        // Must set repository while holding recorder lock so
+        // the final chunk in repository gets marked correctly
+        Repository.getRepository().setBasePath(repo);
+        boolean disk = false;
+        for (PlatformRecording s : getRecordings()) {
+            if (RecordingState.RUNNING == s.getState() && s.isToDisk()) {
+                disk = true;
+            }
+        }
+        if (disk) {
+            jvm.markChunkFinal();
+            rotateDisk();
+        }
+    }
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java	Fri Nov 22 17:20:43 2019 +0100
@@ -85,6 +85,7 @@
     private AccessControlContext noDestinationDumpOnExitAccessControlContext;
     private boolean shuoldWriteActiveRecordingEvent = true;
     private Duration flushInterval = Duration.ofSeconds(1);
+    private long finalStartChunkNanos = Long.MIN_VALUE;
 
     PlatformRecording(PlatformRecorder recorder, long id) {
         // Typically the access control context is taken
@@ -811,4 +812,12 @@
             return Long.MAX_VALUE;
         }
     }
+
+    public long getFinalChunkStartNanos() {
+        return finalStartChunkNanos;
+    }
+
+    public void setFinalStartnanos(long chunkStartNanos) {
+       this.finalStartChunkNanos = chunkStartNanos;
+    }
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Repository.java	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Repository.java	Fri Nov 22 17:20:43 2019 +0100
@@ -85,6 +85,7 @@
             if (!SecuritySupport.existDirectory(repository)) {
                 this.repository = createRepository(baseLocation);
                 jvm.setRepositoryLocation(repository.toString());
+                SecuritySupport.setProperty(JFR_REPOSITORY_LOCATION_PROPERTY, repository.toString());
                 cleanupDirectories.add(repository);
             }
             return new RepositoryChunk(repository, timestamp);
@@ -115,9 +116,7 @@
         if (i == MAX_REPO_CREATION_RETRIES) {
             throw new IOException("Unable to create JFR repository directory using base location (" + basePath + ")");
         }
-        SafePath canonicalRepositoryPath = SecuritySupport.toRealPath(f);
-        SecuritySupport.setProperty(JFR_REPOSITORY_LOCATION_PROPERTY, canonicalRepositoryPath.toString());
-        return canonicalRepositoryPath;
+        return SecuritySupport.toRealPath(f);
     }
 
     private static SafePath createRealBasePath(SafePath safePath) throws IOException {
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/ShutdownHook.java	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/ShutdownHook.java	Fri Nov 22 17:20:43 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2019, 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
@@ -51,7 +51,7 @@
         // starting any "real" operations. In low memory situations,
         // we would like to take an OOM as early as possible.
         tlabDummyObject = new Object();
-
+        recorder.setInShutDown();
         for (PlatformRecording recording : recorder.getRecordings()) {
             if (recording.getDumpOnExit() && recording.getState() == RecordingState.RUNNING) {
                 dump(recording);
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java	Fri Nov 22 17:20:43 2019 +0100
@@ -40,6 +40,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.SecuritySupport;
 
 /*
@@ -50,19 +51,19 @@
     private final static AtomicLong counter = new AtomicLong(1);
 
     private final Object terminated = new Object();
-    private final boolean active;
     private final Runnable flushOperation = () -> dispatcher().runFlushActions();
     private final AccessControlContext accessControllerContext;
     private final StreamConfiguration configuration = new StreamConfiguration();
+    private final PlatformRecording recording;
 
     private volatile Thread thread;
     private Dispatcher dispatcher;
 
     private volatile boolean closed;
 
-    AbstractEventStream(AccessControlContext acc, boolean active) throws IOException {
+    AbstractEventStream(AccessControlContext acc, PlatformRecording recording) throws IOException {
         this.accessControllerContext = Objects.requireNonNull(acc);
-        this.active = active;
+        this.recording = recording;
     }
 
     @Override
@@ -229,7 +230,7 @@
             if (configuration.started) {
                 throw new IllegalStateException("Event stream can only be started once");
             }
-            if (active && configuration.startTime == null) {
+            if (recording != null && configuration.startTime == null) {
                 configuration.setStartNanos(startNanos);
             }
             configuration.setStarted(true);
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkHeader.java	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkHeader.java	Fri Nov 22 17:20:43 2019 +0100
@@ -39,8 +39,10 @@
     private static final long CHUNK_SIZE_POSITION = 8;
     private static final long DURATION_NANOS_POSITION = 40;
     private static final long FILE_STATE_POSITION = 64;
+    private static final long FLAG_BYTE_POSITION = 67;
     private static final long METADATA_TYPE_ID = 0;
     private static final byte[] FILE_MAGIC = { 'F', 'L', 'R', '\0' };
+    private static final int MASK_FINAL_CHUNK = 1 << 1;
 
     private final short major;
     private final short minor;
@@ -58,6 +60,7 @@
     private long absoluteChunkEnd;
     private boolean isFinished;
     private boolean finished;
+    private boolean finalChunk;
 
     public ChunkHeader(RecordingInput input) throws IOException {
         this(input, 0, 0);
@@ -101,8 +104,7 @@
         Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: startTicks=" + chunkStartTicks);
         ticksPerSecond = input.readRawLong();
         Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: ticksPerSecond=" + ticksPerSecond);
-        input.readRawInt(); // features, not used
-
+        input.readRawInt(); // ignore file state and flag bits
         refresh();
         input.position(absoluteEventStart);
     }
@@ -123,6 +125,8 @@
             long durationNanos = input.readPhysicalLong();
             input.positionPhysical(absoluteChunkStart + FILE_STATE_POSITION);
             byte fileState2 =  input.readPhysicalByte();
+            input.positionPhysical(absoluteChunkStart + FLAG_BYTE_POSITION);
+            int flagByte = input.readPhysicalByte();
             if (fileState1 == fileState2) { // valid header
                 finished = fileState1 == 0;
                 if (metadataPosition != 0) {
@@ -150,6 +154,8 @@
                     Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: generation=" + fileState2);
                     Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: finished=" + isFinished);
                     Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: fileSize=" + input.size());
+                    this.finalChunk = (flagByte & MASK_FINAL_CHUNK) != 0;
+                    Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: finalChunk=" + finalChunk);
                     absoluteChunkEnd = absoluteChunkStart + chunkSize;
                     return;
                 }
@@ -183,6 +189,10 @@
         return input.getFileSize() == absoluteChunkEnd;
    }
 
+    public boolean isFinalChunk() {
+        return finalChunk;
+    }
+
     public boolean isFinished() throws IOException {
         return isFinished;
     }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkParser.java	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkParser.java	Fri Nov 22 17:20:43 2019 +0100
@@ -448,4 +448,8 @@
         return chunkHeader.getStartNanos();
     }
 
+    public boolean isFinalChunk() {
+        return chunkHeader.isFinalChunk();
+    }
+
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java	Fri Nov 22 17:20:43 2019 +0100
@@ -35,6 +35,7 @@
 
 import jdk.jfr.consumer.RecordedEvent;
 import jdk.jfr.internal.JVM;
+import jdk.jfr.internal.PlatformRecording;
 import jdk.jfr.internal.Utils;
 import jdk.jfr.internal.consumer.ChunkParser.ParserConfiguration;
 
@@ -43,12 +44,12 @@
  * with chunk files.
  *
  */
-public final class EventDirectoryStream extends AbstractEventStream {
+public class EventDirectoryStream extends AbstractEventStream {
 
     private final static Comparator<? super RecordedEvent> EVENT_COMPARATOR = JdkJfrConsumer.instance().eventComparator();
 
     private final RepositoryFiles repositoryFiles;
-    private final boolean active;
+    private final PlatformRecording recording;
     private final FileAccess fileAccess;
 
     private ChunkParser currentParser;
@@ -56,10 +57,10 @@
     private RecordedEvent[] sortedCache;
     private int threadExclusionLevel = 0;
 
-    public EventDirectoryStream(AccessControlContext acc, Path p, FileAccess fileAccess, boolean active) throws IOException {
-        super(acc, active);
+    public EventDirectoryStream(AccessControlContext acc, Path p, FileAccess fileAccess, PlatformRecording recording) throws IOException {
+        super(acc, recording);
         this.fileAccess = Objects.requireNonNull(fileAccess);
-        this.active = active;
+        this.recording = recording;
         this.repositoryFiles = new RepositoryFiles(fileAccess, p);
     }
 
@@ -104,7 +105,7 @@
         Dispatcher disp = dispatcher();
 
         Path path;
-        boolean validStartTime = active || disp.startTime != null;
+        boolean validStartTime = recording != null || disp.startTime != null;
         if (validStartTime) {
             path = repositoryFiles.firstPath(disp.startNanos);
         } else {
@@ -139,8 +140,17 @@
                         return;
                     }
                 }
+                if (isLastChunk()) {
+                    // Recording was stopped/closed externally, and no more data to process.
+                    return;
+                }
 
+                if (repositoryFiles.hasFixedPath() && currentParser.isFinalChunk()) {
+                    // JVM process exited/crashed, or repository migrated to an unknown location
+                    return;
+                }
                 if (isClosed()) {
+                    // Stream was closed
                     return;
                 }
                 long durationNanos = currentParser.getChunkDuration();
@@ -162,6 +172,13 @@
         }
     }
 
+    private boolean isLastChunk() {
+        if (recording == null) {
+            return false;
+        }
+        return recording.getFinalChunkStartNanos() >= currentParser.getStartNanos();
+    }
+
     private boolean processOrdered(Dispatcher c, boolean awaitNewEvents) throws IOException {
         if (sortedCache == null) {
             sortedCache = new RecordedEvent[100_000];
@@ -206,4 +223,5 @@
             }
         }
     }
+
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventFileStream.java	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventFileStream.java	Fri Nov 22 17:20:43 2019 +0100
@@ -50,7 +50,7 @@
     private RecordedEvent[] cacheSorted;
 
     public EventFileStream(AccessControlContext acc, Path path) throws IOException {
-        super(acc, false);
+        super(acc, null);
         Objects.requireNonNull(path);
         this.input = new RecordingInput(path.toFile(), FileAccess.UNPRIVILIGED);
     }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RepositoryFiles.java	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RepositoryFiles.java	Fri Nov 22 17:20:43 2019 +0100
@@ -227,4 +227,8 @@
             waitObject.notify();
         }
     }
+
+    public boolean hasFixedPath() {
+        return repository != null;
+    }
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java	Fri Nov 22 09:06:35 2019 -0500
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java	Fri Nov 22 17:20:43 2019 +0100
@@ -32,6 +32,7 @@
 import jdk.jfr.internal.LogTag;
 import jdk.jfr.internal.Logger;
 import jdk.jfr.internal.Options;
+import jdk.jfr.internal.PlatformRecorder;
 import jdk.jfr.internal.PrivateAccess;
 import jdk.jfr.internal.Repository;
 import jdk.jfr.internal.SecuritySupport.SafePath;
@@ -89,11 +90,12 @@
         if (repositoryPath != null) {
             try {
                 SafePath s = new SafePath(repositoryPath);
-                Repository.getRepository().setBasePath(s);
+                if (FlightRecorder.isInitialized()) {
+                    PrivateAccess.getInstance().getPlatformRecorder().migrate(s);
+                } else {
+                    Repository.getRepository().setBasePath(s);
+                }
                 Logger.log(LogTag.JFR, LogLevel.INFO, "Base repository path set to " + repositoryPath);
-                if (FlightRecorder.isInitialized()) {
-                    PrivateAccess.getInstance().getPlatformRecorder().rotateIfRecordingToDisk();;
-                }
             } catch (Exception e) {
                 throw new DCmdException("Could not use " + repositoryPath + " as repository. " + e.getMessage(), e);
             }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestStoppedRecording.java	Fri Nov 22 17:20:43 2019 +0100
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.recordingstream;
+
+import java.util.concurrent.CountDownLatch;
+
+import jdk.jfr.Event;
+import jdk.jfr.FlightRecorder;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Tests that a RecordingStream is closed if the underlying Recording
+ *          is stopped.
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestStoppedRecording
+ */
+public class TestStoppedRecording {
+
+    private static final class StopEvent extends Event {
+    }
+
+    public static void main(String... args) throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        try (RecordingStream rs = new RecordingStream()) {
+            rs.onEvent(e -> {
+                FlightRecorder.getFlightRecorder().getRecordings().get(0).stop();
+            });
+            rs.onClose(() -> {
+                latch.countDown();
+            });
+            rs.startAsync();
+            StopEvent stop = new StopEvent();
+            stop.commit();
+            latch.await();
+        }
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestInProcessMigration.java	Fri Nov 22 17:20:43 2019 +0100
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.streaming;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.concurrent.CountDownLatch;
+
+import jdk.jfr.Event;
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.EventStream;
+import jdk.jfr.jcmd.JcmdHelper;
+
+/**
+ * @test
+ * @summary Verifies that is possible to stream from an in-process repository
+ *          that is being moved.
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib /test/jdk
+ * @run main/othervm jdk.jfr.api.consumer.streaming.TestInProcessMigration
+ */
+public class TestInProcessMigration {
+    static class MigrationEvent extends Event {
+        int id;
+    }
+
+    public static void main(String... args) throws Exception {
+        Path newRepository = Paths.get("new-repository");
+        CountDownLatch event1 = new CountDownLatch(1);
+        CountDownLatch event2 = new CountDownLatch(1);
+
+        try (EventStream es = EventStream.openRepository()) {
+            es.setStartTime(Instant.EPOCH);
+            es.onEvent(e -> {
+                System.out.println(e);
+                if (e.getInt("id") == 1) {
+                    event1.countDown();
+                }
+                if (e.getInt("id") == 2) {
+                    event2.countDown();
+                }
+            });
+            es.startAsync();
+            System.out.println("Started es.startAsync()");
+
+            try (Recording r = new Recording()) {
+                r.setFlushInterval(Duration.ofSeconds(1));
+                r.start();
+                // Chunk in default repository
+                MigrationEvent e1 = new MigrationEvent();
+                e1.id = 1;
+                e1.commit();
+                event1.await();
+                System.out.println("Passed the event1.await()");
+                JcmdHelper.jcmd("JFR.configure", "repositorypath=" + newRepository.toAbsolutePath());
+                // Chunk in new repository
+                MigrationEvent e2 = new MigrationEvent();
+                e2.id = 2;
+                e2.commit();
+                r.stop();
+                event2.await();
+                System.out.println("Passed the event2.await()");
+                // Verify that it happened in new repository
+                if (!Files.exists(newRepository)) {
+                    throw new AssertionError("Could not find repository " + newRepository);
+                }
+                System.out.println("Listing contents in new repository:");
+                boolean empty = true;
+                for (Path p : Files.newDirectoryStream(newRepository)) {
+                    System.out.println(p.toAbsolutePath());
+                    empty = false;
+                }
+                System.out.println();
+                if (empty) {
+                    throw new AssertionError("Could not find contents in new repository location " + newRepository);
+                }
+            }
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestJVMCrash.java	Fri Nov 22 17:20:43 2019 +0100
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.streaming;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import jdk.jfr.consumer.EventStream;
+
+/**
+ * @test
+ * @summary Test that a stream ends/closes when an application crashes.
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib /test/jdk
+ * @modules jdk.jfr jdk.attach java.base/jdk.internal.misc
+ *
+ * @run main/othervm jdk.jfr.api.consumer.streaming.TestJVMCrash
+ */
+public class TestJVMCrash {
+
+    public static void main(String... args) throws Exception {
+        int id = 1;
+        while (true) {
+            TestProcess process = new TestProcess("crash-application-" + id++);
+            AtomicInteger eventCounter = new AtomicInteger();
+            try (EventStream es = EventStream.openRepository(process.getRepository())) {
+                // Start from first event in repository
+                es.setStartTime(Instant.EPOCH);
+                es.onEvent(e -> {
+                    if (eventCounter.incrementAndGet() == TestProcess.NUMBER_OF_EVENTS) {
+                        process.crash();
+                    }
+                });
+                es.startAsync();
+                // If crash corrupts chunk in repository, retry in 30 seconds
+                es.awaitTermination(Duration.ofSeconds(30));
+                if (eventCounter.get() == TestProcess.NUMBER_OF_EVENTS) {
+                    return;
+                }
+                System.out.println("Incorrect event count. Retrying...");
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestJVMExit.java	Fri Nov 22 17:20:43 2019 +0100
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.streaming;
+
+import java.time.Instant;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import jdk.jfr.consumer.EventStream;
+
+/**
+ * @test
+ * @summary Test that a stream ends/closes when an application exists.
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib /test/jdk
+ * @modules jdk.jfr jdk.attach java.base/jdk.internal.misc
+ *
+ * @run main/othervm jdk.jfr.api.consumer.streaming.TestJVMExit
+ */
+public class TestJVMExit {
+
+    public static void main(String... args) throws Exception {
+        TestProcess process = new TestProcess("exit-application");
+        AtomicInteger eventCounter = new AtomicInteger();
+        try (EventStream es = EventStream.openRepository(process.getRepository())) {
+            // Start from first event in repository
+            es.setStartTime(Instant.EPOCH);
+            es.onEvent(e -> {
+                if (eventCounter.incrementAndGet() == TestProcess.NUMBER_OF_EVENTS) {
+                    process.exit();
+                }
+            });
+            es.start();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestOutOfProcessMigration.java	Fri Nov 22 17:20:43 2019 +0100
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.streaming;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Instant;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import jdk.jfr.consumer.EventStream;
+import jdk.test.lib.dcmd.CommandExecutor;
+import jdk.test.lib.dcmd.PidJcmdExecutor;
+import jdk.test.lib.process.OutputAnalyzer;
+
+/**
+ * @test
+ * @summary Verifies that a out-of-process stream is closed when the repository
+ *          is changed.
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib /test/jdk
+ * @modules jdk.jfr jdk.attach java.base/jdk.internal.misc
+ * @run main/othervm jdk.jfr.api.consumer.streaming.TestOutOfProcessMigration
+ */
+public class TestOutOfProcessMigration {
+    public static void main(String... args) throws Exception {
+        Path newRepo = Paths.get("new-repository").toAbsolutePath();
+
+        TestProcess process = new TestProcess("application");
+        AtomicInteger eventCounter = new AtomicInteger();
+        try (EventStream es = EventStream.openRepository(process.getRepository())) {
+            // Start from first event in repository
+            es.setStartTime(Instant.EPOCH);
+            es.onEvent(e -> {
+                if (eventCounter.incrementAndGet() == TestProcess.NUMBER_OF_EVENTS) {
+                    System.out.println("Changing repository to " + newRepo + " ...");
+                    CommandExecutor executor = new PidJcmdExecutor(String.valueOf(process.pid()));
+                    // This should close stream
+                    OutputAnalyzer oa = executor.execute("JFR.configure repositorypath=" + newRepo);
+                    System.out.println(oa);
+                }
+            });
+            es.start();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestProcess.java	Fri Nov 22 17:20:43 2019 +0100
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.streaming;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Properties;
+
+import jdk.internal.misc.Unsafe;
+import jdk.jfr.Event;
+import jdk.test.lib.process.ProcessTools;
+
+import com.sun.tools.attach.VirtualMachine;
+
+/**
+ * Class that emits a NUMBER_OF_EVENTS and then awaits crash or exit
+ *
+ * Requires jdk.attach module.
+ *
+ */
+public final class TestProcess {
+
+    private static class TestEvent extends Event {
+    }
+
+    public final static int NUMBER_OF_EVENTS = 10;
+
+    private final Process process;
+    private final Path path;
+
+    public TestProcess(String name) throws IOException {
+        this.path = Paths.get("action-" + System.currentTimeMillis()).toAbsolutePath();
+        String[] args = {
+                "--add-exports",
+                "java.base/jdk.internal.misc=ALL-UNNAMED",
+                "-XX:StartFlightRecording:settings=none",
+                TestProcess.class.getName(), path.toString()
+            };
+        ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(false, args);
+        process = ProcessTools.startProcess(name, pb);
+    }
+
+    public static void main(String... args) throws Exception {
+        for (int i = 0; i < NUMBER_OF_EVENTS; i++) {
+            TestEvent e = new TestEvent();
+            e.commit();
+        }
+
+        Path path = Paths.get(args[0]);
+        while (true) {
+            try {
+                String action = Files.readString(path);
+                if ("crash".equals(action)) {
+                    System.out.println("About to crash...");
+                    Unsafe.getUnsafe().putInt(0L, 0);
+                }
+                if ("exit".equals(action)) {
+                    System.out.println("About to exit...");
+                    System.exit(0);
+                }
+            } catch (Exception ioe) {
+                // Ignore
+            }
+            takeNap();
+        }
+    }
+
+    public Path getRepository() {
+        while (true) {
+            try {
+                VirtualMachine vm = VirtualMachine.attach(String.valueOf(process.pid()));
+                Properties p = vm.getSystemProperties();
+                vm.detach();
+                String repo = (String) p.get("jdk.jfr.repository");
+                if (repo != null) {
+                    return Paths.get(repo);
+                }
+            } catch (Exception e) {
+                System.out.println("Attach failed: " + e.getMessage());
+                System.out.println("Retrying...");
+            }
+            takeNap();
+        }
+    }
+
+    private static void takeNap() {
+        try {
+            Thread.sleep(10);
+        } catch (InterruptedException ie) {
+            // ignore
+        }
+    }
+
+    public void crash() {
+        try {
+            Files.writeString(path, "crash");
+        } catch (IOException ioe) {
+            ioe.printStackTrace();
+        }
+    }
+
+    public void exit() {
+        try {
+            Files.writeString(path, "exit");
+        } catch (IOException ioe) {
+            ioe.printStackTrace();
+        }
+    }
+
+    public long pid() {
+        return process.pid();
+    }
+}
--- a/test/jdk/jdk/jfr/api/consumer/streaming/TestRepositoryMigration.java	Fri Nov 22 09:06:35 2019 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,108 +0,0 @@
-/*
- * Copyright (c) 2019, 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.api.consumer.streaming;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.concurrent.CountDownLatch;
-
-import jdk.jfr.Event;
-import jdk.jfr.Recording;
-import jdk.jfr.consumer.EventStream;
-import jdk.jfr.jcmd.JcmdHelper;
-
-/**
- * @test
- * @summary Verifies that is possible to stream from a repository that is being
- *          moved.
- * @key jfr
- * @requires vm.hasJFR
- * @library /test/lib /test/jdk
- * @run main/othervm jdk.jfr.api.consumer.streaming.TestRepositoryMigration
- */
-public class TestRepositoryMigration {
-    static class MigrationEvent extends Event {
-        int id;
-    }
-
-    public static void main(String... args) throws Exception {
-        Path newRepository = Paths.get("new-repository");
-        CountDownLatch event1 = new CountDownLatch(1);
-        CountDownLatch event2 = new CountDownLatch(1);
-
-        try (EventStream es = EventStream.openRepository()) {
-            es.setStartTime(Instant.EPOCH);
-            es.onEvent(e -> {
-                System.out.println(e);
-                if (e.getInt("id") == 1) {
-                    event1.countDown();
-                }
-                if (e.getInt("id") == 2) {
-                    event2.countDown();
-                }
-            });
-            es.startAsync();
-            System.out.println("Started es.startAsync()");
-
-            try (Recording r = new Recording()) {
-                r.setFlushInterval(Duration.ofSeconds(1));
-                r.start();
-                // Chunk in default repository
-                MigrationEvent e1 = new MigrationEvent();
-                e1.id = 1;
-                e1.commit();
-                event1.await();
-                System.out.println("Passed the event1.await()");
-                JcmdHelper.jcmd("JFR.configure", "repositorypath=" + newRepository.toAbsolutePath());
-                // Chunk in new repository
-                MigrationEvent e2 = new MigrationEvent();
-                e2.id = 2;
-                e2.commit();
-                r.stop();
-                event2.await();
-                System.out.println("Passed the event2.await()");
-                // Verify that it happened in new repository
-                if (!Files.exists(newRepository)) {
-                    throw new AssertionError("Could not find repository " + newRepository);
-                }
-                System.out.println("Listing contents in new repository:");
-                boolean empty = true;
-                for (Path p : Files.newDirectoryStream(newRepository)) {
-                    System.out.println(p.toAbsolutePath());
-                    empty = false;
-                }
-                System.out.println();
-                if (empty) {
-                    throw new AssertionError("Could not find contents in new repository location " + newRepository);
-                }
-            }
-        }
-    }
-
-}