1 /*
  2  * Copyright (c) 2021, 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 jdk.internal.vm;
 26 
 27 import java.lang.invoke.MethodHandles;
 28 import java.lang.invoke.VarHandle;
 29 import java.util.Objects;
 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 
 37 /**
 38  * A "shared" thread container. A shared thread container doesn't have an owner
 39  * and is intended for unstructured uses, e.g. thread pools.
 40  */
 41 public class SharedThreadContainer extends ThreadContainer implements AutoCloseable {
 42     private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
 43     private static final VarHandle CLOSED;
 44     private static final VarHandle VIRTUAL_THREADS;
 45     static {
 46         try {
 47             MethodHandles.Lookup l = MethodHandles.lookup();
 48             CLOSED = l.findVarHandle(SharedThreadContainer.class,
 49                     "closed", boolean.class);
 50             VIRTUAL_THREADS = l.findVarHandle(SharedThreadContainer.class,
 51                     "virtualThreads", Set.class);
 52         } catch (Exception e) {
 53             throw new InternalError(e);
 54         }
 55     }
 56 
 57     // name of container, used by toString
 58     private final String name;
 59 
 60     // the number of threads in the container
 61     private final LongAdder threadCount;
 62 
 63     // the virtual threads in the container, created lazily
 64     private volatile Set<Thread> virtualThreads;
 65 
 66     // the key for this container in the registry
 67     private volatile Object key;
 68 
 69     // set to true when the container is closed
 70     private volatile boolean closed;
 71 
 72     /**
 73      * Initialize a new SharedThreadContainer.
 74      * @param name the container name, can be null
 75      */
 76     private SharedThreadContainer(String name) {
 77         super(/*shared*/ true);
 78         this.name = name;
 79         this.threadCount = new LongAdder();
 80     }
 81 
 82     /**
 83      * Creates a shared thread container with the given parent and name.
 84      * @throws IllegalArgumentException if the parent has an owner.
 85      */
 86     public static SharedThreadContainer create(ThreadContainer parent, String name) {
 87         if (parent.owner() != null)
 88             throw new IllegalArgumentException("parent has owner");
 89         var container = new SharedThreadContainer(name);
 90         // register the container to allow discovery by serviceability tools
 91         container.key = ThreadContainers.registerContainer(container);
 92         return container;
 93     }
 94 
 95     /**
 96      * Creates a shared thread container with the given name. Its parent will be
 97      * the root thread container.
 98      */
 99     public static SharedThreadContainer create(String name) {
100         return create(ThreadContainers.root(), name);
101     }
102 
103     @Override
104     public Thread owner() {
105         return null;
106     }
107 
108     @Override
109     public void onStart(Thread thread) {
110         // virtual threads needs to be tracked
111         if (thread.isVirtual()) {
112             Set<Thread> vthreads = this.virtualThreads;
113             if (vthreads == null) {
114                 vthreads = ConcurrentHashMap.newKeySet();
115                 if (!VIRTUAL_THREADS.compareAndSet(this, null, vthreads)) {
116                     // lost the race
117                     vthreads = this.virtualThreads;
118                 }
119             }
120             vthreads.add(thread);
121         }
122         threadCount.add(1L);
123     }
124 
125     @Override
126     public void onExit(Thread thread) {
127         threadCount.add(-1L);
128         if (thread.isVirtual())
129             virtualThreads.remove(thread);
130     }
131 
132     @Override
133     public long threadCount() {
134         return threadCount.sum();
135     }
136 
137     @Override
138     public Stream<Thread> threads() {
139         // live platform threads in this container
140         Stream<Thread> platformThreads = Stream.of(JLA.getAllThreads())
141                 .filter(t -> JLA.threadContainer(t) == this);
142         Set<Thread> vthreads = this.virtualThreads;
143         if (vthreads == null) {
144             // live platform threads only, no virtual threads
145             return platformThreads;
146         } else {
147             // all live threads in this container
148             return Stream.concat(platformThreads,
149                                  vthreads.stream().filter(Thread::isAlive));
150         }
151     }
152 
153     /**
154      * Starts a thread in this container.
155      * @throws IllegalStateException if the container is closed
156      */
157     public void start(Thread thread) {
158         if (closed)
159             throw new IllegalStateException();
160         JLA.start(thread, this);
161     }
162 
163     /**
164      * Closes this container. Further attempts to start a thread in this container
165      * throw IllegalStateException. This method has no impact on threads that are
166      * still running or starting around the time that this method is invoked.
167      */
168     @Override
169     public void close() {
170         if (!closed && CLOSED.compareAndSet(this, false, true)) {
171             ThreadContainers.deregisterContainer(key);
172         }
173     }
174 
175     @Override
176     public String toString() {
177         String id = Objects.toIdentityString(this);
178         if (name != null) {
179             return name + "/" + id;
180         } else {
181             return id;
182         }
183     }
184 }