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.createJavaProcessBuilder(true, 95 "-XX:-CreateCoredumpOnCrash", 96 "--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED", 97 "-XX:StartFlightRecording=filename=./dumped.jfr,dumponexit=true,settings=default", 98 "jdk.jfr.event.runtime.TestShutdownEvent$TestMain", 99 String.valueOf(subTestIndex)); 100 OutputAnalyzer output = ProcessTools.executeProcess(pb); 101 System.out.println(output.getOutput()); 102 int exitCode = output.getExitValue(); 103 System.out.println("Exit code: " + exitCode); 104 105 String recordingName = output.firstMatch("JFR recording file will be written. Location: (.*.jfr)", 1); 106 if (recordingName == null) { 107 recordingName = "./dumped.jfr"; 108 } 109 110 List<RecordedEvent> events = RecordingFile.readAllEvents(Paths.get(recordingName)); 111 List<RecordedEvent> filteredEvents = events.stream() 112 .filter(e -> e.getEventType().getName().equals(EventNames.Shutdown)) 113 .sorted(Comparator.comparing(RecordedEvent::getStartTime)) 114 .collect(Collectors.toList()); 115 116 Asserts.assertEquals(filteredEvents.size(), 1); 117 RecordedEvent event = filteredEvents.get(0); 118 subTests[subTestIndex].verifyEvents(event, exitCode); 119 } 120 121 @SuppressWarnings("unused") 122 private static class TestMain { 123 public static void main(String[] args) throws Exception { 124 ShutdownEventSubTest subTest = subTests[Integer.parseInt(args[0])]; 125 System.out.println("Running subtest " + args[0] + " (" + subTest.getClass().getName() + ")"); 126 subTest.runTest(); 127 } 128 } 129 130 private interface ShutdownEventSubTest { 131 default int attempts() { 132 return 1; 133 } 134 void runTest(); 135 void verifyEvents(RecordedEvent event, int exitCode); 136 } 137 138 // Basic stack trace validation, checking that the runTest method is part of the stack 139 static void validateStackTrace(RecordedStackTrace stackTrace) { 140 List<RecordedFrame> frames = stackTrace.getFrames(); 141 Asserts.assertFalse(frames.isEmpty()); 142 Asserts.assertTrue(frames.stream() 143 .anyMatch(t -> t.getMethod().getName().equals("runTest"))); 144 } 145 146 147 // ========================================================================= 148 private static class TestLastNonDaemon implements ShutdownEventSubTest { 149 @Override 150 public void runTest() { 151 // Do nothing - this is the default exit reason 152 } 153 154 @Override 155 public void verifyEvents(RecordedEvent event, int exitCode) { 156 Events.assertField(event, "reason").equal("No remaining non-daemon Java threads"); 157 } 158 } 159 160 private static class TestSystemExit implements ShutdownEventSubTest { 161 @Override 162 public void runTest() { 163 System.out.println("Running System.exit"); 164 System.exit(42); 165 } 166 167 @Override 168 public void verifyEvents(RecordedEvent event, int exitCode) { 169 Events.assertField(event, "reason").equal("Shutdown requested from Java"); 170 validateStackTrace(event.getStackTrace()); 171 } 172 } 173 174 private static class TestVMCrash implements ShutdownEventSubTest { 175 176 @Override 177 public void runTest() { 178 System.out.println("Attempting to crash"); 179 Unsafe.getUnsafe().putInt(0L, 0); 180 } 181 182 @Override 183 public void verifyEvents(RecordedEvent event, int exitCode) { 184 Events.assertField(event, "reason").equal("VM Error"); 185 // for now avoid validating the stack trace, in case of compiled code 186 // the vframeStream based solution will not work in this special VMCrash case 187 // see 8219082 for details (running the crashed VM with -Xint would solve the issue too) 188 //validateStackTrace(event.getStackTrace()); 189 } 190 191 @Override 192 public int attempts() { 193 return 3; 194 } 195 } 196 197 private static class TestUnhandledException implements ShutdownEventSubTest { 198 @Override 199 public void runTest() { 200 throw new RuntimeException("Unhandled"); 201 } 202 203 @Override 204 public void verifyEvents(RecordedEvent event, int exitCode) { 205 Events.assertField(event, "reason").equal("No remaining non-daemon Java threads"); 206 } 207 } 208 209 private static class TestRuntimeHalt implements ShutdownEventSubTest { 210 @Override 211 public void runTest() { 212 System.out.println("Running Runtime.getRuntime.halt"); 213 Runtime.getRuntime().halt(17); 214 } 215 216 @Override 217 public void verifyEvents(RecordedEvent event, int exitCode) { 218 Events.assertField(event, "reason").equal("Shutdown requested from Java"); 219 validateStackTrace(event.getStackTrace()); 220 } 221 } 222 223 private static class TestSig implements ShutdownEventSubTest { 224 225 private final String signalName; 226 227 @Override 228 public int attempts() { 229 if (Platform.isWindows()) { 230 return 0; 231 } 232 if (signalName.equals("HUP") && Platform.isSolaris()) { 233 return 0; 234 } 235 return 1; 236 } 237 238 public TestSig(String signalName) { 239 this.signalName = signalName; 240 } 241 242 @Override 243 public void runTest() { 244 System.out.println("Sending SIG" + signalName + " to process " + ProcessHandle.current().pid()); 245 try { 246 Runtime.getRuntime().exec("kill -" + signalName + " " + ProcessHandle.current().pid()).waitFor(); 247 Thread.sleep(60_1000); 248 } catch (InterruptedException e) { 249 e.printStackTrace(); 250 } catch (IOException e) { 251 e.printStackTrace(); 252 } 253 System.out.println("Process survived the SIG" + signalName + " signal!"); 254 } 255 256 @Override 257 public void verifyEvents(RecordedEvent event, int exitCode) { 258 if (exitCode == 0) { 259 System.out.println("Process exited normally with exit code 0, skipping the verification"); 260 return; 261 } 262 Events.assertField(event, "reason").equal("Shutdown requested from Java"); 263 Events.assertEventThread(event); 264 Asserts.assertEquals(event.getThread().getJavaName(), "SIG" + signalName + " handler"); 265 } 266 } 267 }