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