1 /*
   2  * Copyright (c) 2018, 2019, 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.jfr.consumer.RecordedEvent;
  35 import jdk.jfr.consumer.RecordedFrame;
  36 import jdk.jfr.consumer.RecordedStackTrace;
  37 import jdk.jfr.consumer.RecordingFile;
  38 import jdk.test.lib.Asserts;
  39 import jdk.test.lib.Platform;
  40 import jdk.test.lib.process.OutputAnalyzer;
  41 import jdk.test.lib.process.ProcessTools;
  42 import jdk.test.lib.jfr.EventNames;
  43 import jdk.test.lib.jfr.Events;
  44 import sun.misc.Unsafe;
  45 
  46 /**
  47  * @test
  48  * @summary Test Shutdown event
  49  * @key jfr
  50  * @library /lib /
  51  * @run main/othervm jdk.jfr.event.runtime.TestShutdownEvent
  52  */
  53 public class TestShutdownEvent {
  54     private static ShutdownEventSubTest subTests[] = {
  55              new TestLastNonDaemon(),
  56              new TestSystemExit(),
  57              new TestVMCrash(),
  58              new TestUnhandledException(),
  59              new TestRuntimeHalt(),
  60              new TestSig("TERM"),
  61              new TestSig("HUP"),
  62              new TestSig("INT")
  63     };
  64 
  65     public static void main(String[] args) throws Throwable {
  66         for (int i = 0; i < subTests.length; ++i) {
  67             if (subTests[i].isApplicable()) {
  68                 runSubtest(i);
  69             } else {
  70                 System.out.println("Skipping non-applicable test: " + i);
  71             }
  72         }
  73     }
  74 
  75     private static void runSubtest(int subTestIndex) throws Exception {
  76         ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(true,
  77                                 "-XX:+LogJFR",
  78                                 "-XX:-CreateMinidumpOnCrash",
  79                                 "-XX:StartFlightRecording=filename=./dumped.jfr,dumponexit=true,settings=default",
  80                                 "jdk.jfr.event.runtime.TestShutdownEvent$TestMain",
  81                                 String.valueOf(subTestIndex));
  82         OutputAnalyzer output = ProcessTools.executeProcess(pb);
  83         System.out.println(output.getOutput());
  84         int exitCode = output.getExitValue();
  85         System.out.println("Exit code: " + exitCode);
  86 
  87         String recordingName = output.firstMatch("emergency jfr file: (.*.jfr)", 1);
  88         if (recordingName == null) {
  89             recordingName = "./dumped.jfr";
  90         }
  91 
  92         List<RecordedEvent> events = RecordingFile.readAllEvents(Paths.get(recordingName));
  93         List<RecordedEvent> filteredEvents = events.stream()
  94             .filter(e -> e.getEventType().getName().equals(EventNames.Shutdown))
  95             .sorted(Comparator.comparing(RecordedEvent::getStartTime))
  96             .collect(Collectors.toList());
  97 
  98         Asserts.assertEquals(filteredEvents.size(), 1);
  99         RecordedEvent event = filteredEvents.get(0);
 100         subTests[subTestIndex].verifyEvents(event, exitCode);
 101     }
 102 
 103     @SuppressWarnings("unused")
 104     private static class TestMain {
 105         public static void main(String[] args) throws Exception {
 106             ShutdownEventSubTest subTest = subTests[Integer.parseInt(args[0])];
 107             System.out.println("Running subtest " + args[0] + " (" + subTest.getClass().getName() + ")");
 108             subTest.runTest();
 109         }
 110     }
 111 
 112     private interface ShutdownEventSubTest {
 113         default boolean isApplicable() {
 114             return true;
 115         }
 116         void runTest();
 117         void verifyEvents(RecordedEvent event, int exitCode);
 118     }
 119 
 120     public static Unsafe getUnsafe() {
 121         try {
 122             Field f = Unsafe.class.getDeclaredField("theUnsafe");
 123             f.setAccessible(true);
 124             return (Unsafe)f.get(null);
 125         } catch (Exception e) {
 126             Asserts.fail("Could not access Unsafe");
 127         }
 128         return null;
 129     }
 130 
 131     // Basic stack trace validation, checking that the runTest method is part of the stack
 132     static void validateStackTrace(RecordedStackTrace stackTrace) {
 133         List<RecordedFrame> frames = stackTrace.getFrames();
 134         Asserts.assertFalse(frames.isEmpty());
 135         Asserts.assertTrue(frames.stream()
 136                            .anyMatch(t -> t.getMethod().getName().equals("runTest")));
 137     }
 138 
 139 
 140     // =========================================================================
 141     private static class TestLastNonDaemon implements ShutdownEventSubTest {
 142         @Override
 143         public void runTest() {
 144             // Do nothing - this is the default exit reason
 145         }
 146 
 147         @Override
 148         public void verifyEvents(RecordedEvent event, int exitCode) {
 149             Events.assertField(event, "reason").equal("No remaining non-daemon Java threads");
 150         }
 151     }
 152 
 153     private static class TestSystemExit implements ShutdownEventSubTest {
 154         @Override
 155         public void runTest() {
 156             System.out.println("Running System.exit");
 157             System.exit(42);
 158         }
 159 
 160         @Override
 161         public void verifyEvents(RecordedEvent event, int exitCode) {
 162             Events.assertField(event, "reason").equal("Shutdown requested from Java");
 163             validateStackTrace(event.getStackTrace());
 164         }
 165     }
 166 
 167     private static class TestVMCrash implements ShutdownEventSubTest {
 168 
 169         @Override
 170         public void runTest() {
 171             System.out.println("Attempting to crash");
 172             getUnsafe().putInt(0L, 0);
 173         }
 174 
 175         @Override
 176         public void verifyEvents(RecordedEvent event, int exitCode) {
 177             Events.assertField(event, "reason").equal("VM Error");
 178             validateStackTrace(event.getStackTrace());
 179         }
 180     }
 181 
 182     private static class TestUnhandledException implements ShutdownEventSubTest {
 183         @Override
 184         public void runTest() {
 185             throw new RuntimeException("Unhandled");
 186         }
 187 
 188         @Override
 189         public void verifyEvents(RecordedEvent event, int exitCode) {
 190             Events.assertField(event, "reason").equal("No remaining non-daemon Java threads");
 191         }
 192     }
 193 
 194     private static class TestRuntimeHalt implements ShutdownEventSubTest {
 195         @Override
 196         public void runTest() {
 197             System.out.println("Running Runtime.getRuntime.halt");
 198             Runtime.getRuntime().halt(17);
 199         }
 200 
 201         @Override
 202         public void verifyEvents(RecordedEvent event, int exitCode) {
 203             Events.assertField(event, "reason").equal("Shutdown requested from Java");
 204             validateStackTrace(event.getStackTrace());
 205         }
 206     }
 207 
 208     private static class TestSig implements ShutdownEventSubTest {
 209 
 210         private final String signalName;
 211 
 212         @Override
 213         public boolean isApplicable() {
 214             if (Platform.isWindows()) {
 215                 return false;
 216             }
 217             if (signalName.equals("HUP") && Platform.isSolaris()) {
 218                 return false;
 219             }
 220             return true;
 221         }
 222 
 223         public TestSig(String signalName) {
 224             this.signalName = signalName;
 225         }
 226 
 227         @Override
 228         public void runTest() {
 229             try {
 230                 long pid = ProcessTools.getProcessId();
 231                 System.out.println("Sending SIG" + signalName + " to process " + pid);
 232                 Runtime.getRuntime().exec("kill -" + signalName + " " + pid).waitFor();
 233                 Thread.sleep(60_1000);
 234             } catch (InterruptedException e) {
 235                 e.printStackTrace();
 236             } catch (Exception e) {
 237                 e.printStackTrace();
 238             }
 239             System.out.println("Process survived the SIG" + signalName + " signal!");
 240         }
 241 
 242         @Override
 243         public void verifyEvents(RecordedEvent event, int exitCode) {
 244             if (exitCode == 0) {
 245                 System.out.println("Process exited normally with exit code 0, skipping the verification");
 246                 return;
 247             }
 248             Events.assertField(event, "reason").equal("Shutdown requested from Java");
 249             Events.assertEventThread(event);
 250             Asserts.assertEquals(event.getThread().getJavaName(), "SIG" + signalName + " handler");
 251         }
 252     }
 253 }