1 /*
  2  * Copyright (c) 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 java.lang;
 26 
 27 import java.io.PrintStream;
 28 import java.security.AccessController;
 29 import java.security.PrivilegedAction;
 30 import java.util.LinkedHashMap;
 31 import java.util.List;
 32 import java.util.Map;
 33 import java.util.Objects;
 34 import java.util.Set;
 35 import java.util.WeakHashMap;
 36 import java.util.stream.Collectors;
 37 import static java.lang.StackWalker.Option.*;
 38 
 39 /**
 40  * Helper class to print the virtual thread stack trace when its carrier thread is
 41  * pinned.
 42  *
 43  * The class maintains a mapping of Class to the hashes of stack traces that are by
 44  * pinned by code in that Class. This is used to avoid printing the same stack trace
 45  * many times.
 46  */
 47 class PinnedThreadPrinter {
 48     static final StackWalker STACK_WALKER;
 49     static {
 50         var options = Set.of(SHOW_REFLECT_FRAMES, RETAIN_CLASS_REFERENCE);
 51         PrivilegedAction<StackWalker> pa = () ->
 52             LiveStackFrame.getStackWalker(options, VirtualThread.continuationScope());
 53         @SuppressWarnings("removal")
 54         var stackWalker = AccessController.doPrivileged(pa);
 55         STACK_WALKER = stackWalker;
 56     }
 57 
 58     // maps a class to the hashes of stack traces pinned by that code in that class
 59     private static final Map<Class<?>, Hashes> classToHashes = new WeakHashMap<>();
 60 
 61     @SuppressWarnings("serial")
 62     private static class Hashes extends LinkedHashMap<Integer, Boolean> {
 63         boolean add(int hash) {
 64             return (putIfAbsent(hash, Boolean.TRUE) == null);
 65         }
 66         @Override
 67         protected boolean removeEldestEntry(Map.Entry<Integer, Boolean> oldest) {
 68             // limit number of hashes
 69             return size() > 8;
 70         }
 71     }
 72 
 73     /**
 74      * Returns a hash of the given stack trace. The hash is based on the class,
 75      * method and bytecode index.
 76      */
 77     private static int hash(List<LiveStackFrame> stack) {
 78         int hash = 0;
 79         for (LiveStackFrame frame : stack) {
 80             hash = (31 * hash) + Objects.hash(frame.getDeclaringClass(),
 81                     frame.getMethodName(),
 82                     frame.getByteCodeIndex());
 83         }
 84         return hash;
 85     }
 86 
 87     /**
 88      * Prints the stack trace of the current mounted vthread continuation.
 89      *
 90      * @param printAll true to print all stack frames, false to only print the
 91      *        frames that are native or holding a monitor
 92      */
 93     static void printStackTrace(PrintStream out, boolean printAll) {
 94         List<LiveStackFrame> stack = STACK_WALKER.walk(s ->
 95             s.map(f -> (LiveStackFrame) f)
 96                     .filter(f -> f.getDeclaringClass() != PinnedThreadPrinter.class)
 97                     .collect(Collectors.toList())
 98         );
 99 
100         // find the closest frame that is causing the thread to be pinned
101         stack.stream()
102             .filter(f -> (f.isNativeMethod() || f.getMonitors().length > 0))
103             .map(LiveStackFrame::getDeclaringClass)
104             .findFirst()
105             .ifPresent(key -> {
106                 int hash = hash(stack);
107                 synchronized (classToHashes) {
108                     // print the stack trace if not already seen
109                     if (classToHashes.computeIfAbsent(key, k -> new Hashes()).add(hash)) {
110                         printStackTrace(stack, out, printAll);
111                     }
112                 }
113             });
114     }
115 
116     private static void printStackTrace(List<LiveStackFrame> stack,
117                                         PrintStream out,
118                                         boolean printAll) {
119         out.println(Thread.currentThread());
120         for (LiveStackFrame frame : stack) {
121             var ste = frame.toStackTraceElement();
122             int monitorCount = frame.getMonitors().length;
123             if (monitorCount > 0 || frame.isNativeMethod()) {
124                 out.format("    %s <== monitors:%d%n", ste, monitorCount);
125             } else if (printAll) {
126                 out.format("    %s%n", ste);
127             }
128         }
129     }
130 }