1 /*
  2  * Copyright (c) 2021, 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.internal.vm;
 26 
 27 import java.lang.ref.ReferenceQueue;
 28 import java.lang.ref.WeakReference;
 29 import java.util.Optional;
 30 import java.util.Set;
 31 import java.util.concurrent.ConcurrentHashMap;
 32 import java.util.concurrent.atomic.LongAdder;
 33 import java.util.stream.Stream;
 34 import jdk.internal.access.JavaLangAccess;
 35 import jdk.internal.access.SharedSecrets;
 36 import sun.nio.ch.Poller;
 37 
 38 /**
 39  * This class consists exclusively of static methods to support debugging and
 40  * monitoring of threads.
 41  */
 42 public class ThreadContainers {
 43     private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
 44 
 45     // the set of shared thread containers
 46     private static final Set<WeakReference<ThreadContainer>> SHARED_CONTAINERS = ConcurrentHashMap.newKeySet();
 47     private static final ReferenceQueue<Object> QUEUE = new ReferenceQueue<>();
 48 
 49     private ThreadContainers() { }
 50 
 51     /**
 52      * Expunge stale entries from set of shared thread containers.
 53      */
 54     private static void expungeStaleEntries() {
 55         Object key;
 56         while ((key = QUEUE.poll()) != null) {
 57             SHARED_CONTAINERS.remove(key);
 58         }
 59     }
 60 
 61     /**
 62      * Registers a shared thread container to be tracked this class, returning
 63      * a key that is used to remove it from the registry.
 64      */
 65     public static Object registerSharedContainer(ThreadContainer container) {
 66         assert container.owner() == null;
 67         expungeStaleEntries();
 68         var ref = new WeakReference<>(container);
 69         SHARED_CONTAINERS.add(ref);
 70         return ref;
 71     }
 72 
 73     /**
 74      * Removes a shared thread container from being tracked by specifying the
 75      * key returned when the thread container was registered.
 76      */
 77     public static void deregisterSharedContainer(Object key) {
 78         assert key instanceof WeakReference;
 79         SHARED_CONTAINERS.remove(key);
 80     }
 81 
 82     /**
 83      * Returns the root thread container.
 84      */
 85     public static ThreadContainer root() {
 86         return RootContainer.INSTANCE;
 87     }
 88 
 89     /**
 90      * Returns the "parent" of the given thread container.
 91      *
 92      * The parent of an owned container is the enclosing container when nested,
 93      * otherwise the parent of an owned container is the owner's container.
 94      *
 95      * The root thread container is the parent of all unowned/shared thread
 96      * containers. The parent of the root container is null.
 97      */
 98     public static ThreadContainer parent(ThreadContainer container) {
 99         ThreadContainer parent = container.previous();
100         if (parent != null)
101             return parent;
102         Thread owner = container.owner();
103         if (owner != null && (parent = JLA.threadContainer(owner)) != null)
104             return parent;
105         ThreadContainer root = root();
106         return (container != root) ? root : null;
107     }
108 
109     /**
110      * Returns a stream of the given thread container's "children".
111      *
112      * An owned thread container is the parent of the thread container that is
113      * encloses. The "top most" container owned by threads in the container are also
114      * children.
115      *
116      * Unowned/shared thread containers that are reachable from the root container
117      * are children of the root container.
118      */
119     public static Stream<ThreadContainer> children(ThreadContainer container) {
120         Stream<ThreadContainer> s1 = Stream.empty();
121         Thread owner = container.owner();
122         if (owner != null) {
123             // container may enclose another container
124             ThreadContainer next = next(container);
125             s1 = Stream.ofNullable(next);
126         } else if (container == root()) {
127             // the root container is the parent of all shared containers
128             s1 = SHARED_CONTAINERS.stream()
129                     .map(WeakReference::get)
130                     .filter(c -> c != null);
131         }
132 
133         // the top-most container owned by the threads in the container
134         Stream<ThreadContainer> s2 = container.threads()
135                 .map(t -> Optional.ofNullable(top(t)))
136                 .flatMap(Optional::stream);
137         return Stream.concat(s1, s2);
138     }
139 
140     /**
141      * Returns the thread container that the given Thread is in or the root
142      * container if not started in a container.
143      */
144     public static ThreadContainer container(Thread thread) {
145         ThreadContainer container = JLA.threadContainer(thread);
146         return (container != null) ? container : root();
147     }
148 
149     /**
150      * Returns the top-most thread container owned by the given thread.
151      */
152     private static ThreadContainer top(Thread thread) {
153         ThreadContainer container = JLA.headThreadContainer(thread);
154         while (container != null) {
155             ThreadContainer previous = container.previous();
156             if (previous == null)
157                 return container;
158             container = previous;
159         }
160         return null;
161     }
162 
163     /**
164      * Returns the thread container that the given thread container encloses.
165      */
166     private static ThreadContainer next(ThreadContainer container) {
167         ThreadContainer head = JLA.headThreadContainer(container.owner());
168         if (head != null) {
169             ThreadContainer next = head;
170             ThreadContainer previous = next.previous();
171             while (previous != null) {
172                 if (previous == container) {
173                     return next;
174                 }
175                 next = previous;
176                 previous = next.previous();
177             }
178         }
179         return null;
180     }
181 
182     /**
183      * Root container.
184      */
185     private static class RootContainer implements ThreadContainer {
186         static final RootContainer INSTANCE = new RootContainer();
187         private static final LongAdder VTHREAD_COUNT = new LongAdder();
188         @Override
189         public String name() {
190             return "<root>";
191         }
192         @Override
193         public Thread owner() {
194             return null;
195         }
196         @Override
197         public long threadCount() {
198             // platform threads that are not in a container
199             long platformThreadCount = Stream.of(JLA.getAllThreads())
200                     .filter(t -> JLA.threadContainer(t) == null)
201                     .count();
202             return platformThreadCount + VTHREAD_COUNT.sum();
203         }
204         @Override
205         public Stream<Thread> threads() {
206             // platform threads that are not in a container
207             Stream<Thread> s1 = Stream.of(JLA.getAllThreads())
208                     .filter(t -> JLA.threadContainer(t) == null);
209             // virtual threads in the root container that are blocked on I/O
210             Stream<Thread> s2 = Poller.blockedThreads()
211                     .filter(t -> t.isVirtual()
212                             && JLA.threadContainer(t) == this);
213             return Stream.concat(s1, s2);
214         }
215         @Override
216         public void setPrevious(ThreadContainer container) {
217             throw new UnsupportedOperationException();
218         }
219         @Override
220         public String toString() {
221             return name();
222         }
223         @Override
224         public void onStart(Thread thread) {
225             assert thread.isVirtual();
226             VTHREAD_COUNT.add(1L);
227         }
228         @Override
229         public void onExit(Thread thread) {
230             assert thread.isVirtual();
231             VTHREAD_COUNT.add(-11L);
232         }
233     }
234 }