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