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