1 /* 2 * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package jdk.jfr.event.runtime; 26 27 import java.io.IOException; 28 import java.lang.reflect.Field; 29 import java.nio.file.Paths; 30 import java.util.Comparator; 31 import java.util.List; 32 import java.util.stream.Collectors; 33 34 import jdk.internal.misc.Unsafe; 35 import jdk.jfr.consumer.RecordedEvent; 36 import jdk.jfr.consumer.RecordedFrame; 37 import jdk.jfr.consumer.RecordedStackTrace; 38 import jdk.jfr.consumer.RecordingFile; 39 import jdk.test.lib.Asserts; 40 import jdk.test.lib.Platform; 41 import jdk.test.lib.process.OutputAnalyzer; 42 import jdk.test.lib.process.ProcessTools; 43 import jdk.test.lib.jfr.EventNames; 44 import jdk.test.lib.jfr.Events; 45 46 47 /** 48 * @test 49 * @summary Test Shutdown event 50 * @key jfr 51 * @requires vm.hasJFR 52 * @library /test/lib 53 * @modules jdk.jfr 54 * java.base/jdk.internal.misc 55 * @run main/othervm jdk.jfr.event.runtime.TestShutdownEvent 56 */ 57 public class TestShutdownEvent { 58 private static ShutdownEventSubTest subTests[] = { 59 new TestLastNonDaemon(), 60 new TestSystemExit(), 61 new TestVMCrash(), 62 new TestUnhandledException(), 63 new TestRuntimeHalt(), 64 new TestSig("TERM"), 65 new TestSig("HUP"), 66 new TestSig("INT") 67 }; 68 69 public static void main(String[] args) throws Throwable { 70 for (int i = 0; i < subTests.length; ++i) { 71 int attempts = subTests[i].attempts(); 72 if (attempts == 0) { 73 System.out.println("Skipping non-applicable test: " + i); 74 } 75 for (int j = 0; j < attempts -1; j++) { 76 try { 77 runSubtest(i); 78 return; 79 } catch (Exception e) { 80 System.out.println("Failed: " + e.getMessage()); 81 System.out.println(); 82 System.out.println("Retry " + i + 1); 83 } catch (OutOfMemoryError | StackOverflowError e) { 84 System.out.println("Error"); 85 // Can happen when parsing corrupt file. Abort test. 86 return; 87 } 88 } 89 runSubtest(i); 90 } 91 } 92 93 private static void runSubtest(int subTestIndex) throws Exception { 94 ProcessBuilder pb = ProcessTools.createTestJvm( 95 "-Xlog:jfr=debug", 96 "-XX:-CreateCoredumpOnCrash", 97 "--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED", 98 "-XX:StartFlightRecording=filename=./dumped.jfr,dumponexit=true,settings=default", 99 "jdk.jfr.event.runtime.TestShutdownEvent$TestMain", 100 String.valueOf(subTestIndex)); 101 OutputAnalyzer output = ProcessTools.executeProcess(pb); 102 System.out.println(output.getOutput()); 103 int exitCode = output.getExitValue(); 104 System.out.println("Exit code: " + exitCode); 105 106 String recordingName = output.firstMatch("JFR recording file will be written. Location: (.*.jfr)", 1); 107 if (recordingName == null) { 108 recordingName = "./dumped.jfr"; 109 } 110 111 List<RecordedEvent> events = RecordingFile.readAllEvents(Paths.get(recordingName)); 112 List<RecordedEvent> filteredEvents = events.stream() 113 .filter(e -> e.getEventType().getName().equals(EventNames.Shutdown)) 114 .sorted(Comparator.comparing(RecordedEvent::getStartTime)) 115 .collect(Collectors.toList()); 116 117 Asserts.assertEquals(filteredEvents.size(), 1); 118 RecordedEvent event = filteredEvents.get(0); 119 subTests[subTestIndex].verifyEvents(event, exitCode); 120 } 121 122 @SuppressWarnings("unused") 123 private static class TestMain { 124 public static void main(String[] args) throws Exception { 125 ShutdownEventSubTest subTest = subTests[Integer.parseInt(args[0])]; 126 System.out.println("Running subtest " + args[0] + " (" + subTest.getClass().getName() + ")"); 127 subTest.runTest(); 128 } 129 } 130 131 private interface ShutdownEventSubTest { 132 default int attempts() { 133 return 1; 134 } 135 void runTest(); 136 void verifyEvents(RecordedEvent event, int exitCode); 137 } 138 139 // Basic stack trace validation, checking that the runTest method is part of the stack 140 static void validateStackTrace(RecordedStackTrace stackTrace) { 141 List<RecordedFrame> frames = stackTrace.getFrames(); 142 Asserts.assertFalse(frames.isEmpty()); 143 Asserts.assertTrue(frames.stream() 144 .anyMatch(t -> t.getMethod().getName().equals("runTest"))); 145 } 146 147 148 // ========================================================================= 149 private static class TestLastNonDaemon implements ShutdownEventSubTest { 150 @Override 151 public void runTest() { 152 // Do nothing - this is the default exit reason 153 } 154 155 @Override 156 public void verifyEvents(RecordedEvent event, int exitCode) { 157 Events.assertField(event, "reason").equal("No remaining non-daemon Java threads"); 158 } 159 } 160 161 private static class TestSystemExit implements ShutdownEventSubTest { 162 @Override 163 public void runTest() { 164 System.out.println("Running System.exit"); 165 System.exit(42); 166 } 167 168 @Override 169 public void verifyEvents(RecordedEvent event, int exitCode) { 170 Events.assertField(event, "reason").equal("Shutdown requested from Java"); 171 validateStackTrace(event.getStackTrace()); 172 } 173 } 174 175 private static class TestVMCrash implements ShutdownEventSubTest { 176 177 @Override 178 public void runTest() { 179 System.out.println("Attempting to crash"); 180 Unsafe.getUnsafe().putInt(0L, 0); 181 } 182 183 @Override 184 public void verifyEvents(RecordedEvent event, int exitCode) { 185 Events.assertField(event, "reason").equal("VM Error"); 186 // for now avoid validating the stack trace, in case of compiled code 187 // the vframeStream based solution will not work in this special VMCrash case 188 // see 8219082 for details (running the crashed VM with -Xint would solve the issue too) 189 //validateStackTrace(event.getStackTrace()); 190 } 191 192 @Override 193 public int attempts() { 194 return 3; 195 } 196 } 197 198 private static class TestUnhandledException implements ShutdownEventSubTest { 199 @Override 200 public void runTest() { 201 throw new RuntimeException("Unhandled"); 202 } 203 204 @Override 205 public void verifyEvents(RecordedEvent event, int exitCode) { 206 Events.assertField(event, "reason").equal("No remaining non-daemon Java threads"); 207 } 208 } 209 210 private static class TestRuntimeHalt implements ShutdownEventSubTest { 211 @Override 212 public void runTest() { 213 System.out.println("Running Runtime.getRuntime.halt"); 214 Runtime.getRuntime().halt(17); 215 } 216 217 @Override 218 public void verifyEvents(RecordedEvent event, int exitCode) { 219 Events.assertField(event, "reason").equal("Shutdown requested from Java"); 220 validateStackTrace(event.getStackTrace()); 221 } 222 } 223 224 private static class TestSig implements ShutdownEventSubTest { 225 226 private final String signalName; 227 228 @Override 229 public int attempts() { 230 if (Platform.isWindows()) { 231 return 0; 232 } 233 if (signalName.equals("HUP") && Platform.isSolaris()) { 234 return 0; 235 } 236 return 1; 237 } 238 239 public TestSig(String signalName) { 240 this.signalName = signalName; 241 } 242 243 @Override 244 public void runTest() { 245 System.out.println("Sending SIG" + signalName + " to process " + ProcessHandle.current().pid()); 246 try { 247 Runtime.getRuntime().exec("kill -" + signalName + " " + ProcessHandle.current().pid()).waitFor(); 248 Thread.sleep(60_1000); 249 } catch (InterruptedException e) { 250 e.printStackTrace(); 251 } catch (IOException e) { 252 e.printStackTrace(); 253 } 254 System.out.println("Process survived the SIG" + signalName + " signal!"); 255 } 256 257 @Override 258 public void verifyEvents(RecordedEvent event, int exitCode) { 259 if (exitCode == 0) { 260 System.out.println("Process exited normally with exit code 0, skipping the verification"); 261 return; 262 } 263 Events.assertField(event, "reason").equal("Shutdown requested from Java"); 264 Events.assertEventThread(event); 265 Asserts.assertEquals(event.getThread().getJavaName(), "SIG" + signalName + " handler"); 266 } 267 } 268 }