/
niceSOFT
/
openjdk21
Обзор
Документация
Войти
/
niceSOFT
/
openjdk21
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/java.base/share/classes/jdk/internal/misc/ThreadFlock.java
591 строка
22 KB
Alan Bateman
8311867: StructuredTaskScope.shutdown does not interrupt newly started threads
14 июл 2023, 08:13
14 июл 2023, 08:13
281aee9
Код
Авторство
О чём код?
/* * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.internal.misc; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.time.Duration; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeoutException; import java.util.concurrent.StructureViolationException; import java.util.concurrent.locks.LockSupport; import java.util.stream.Stream; import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.vm.ScopedValueContainer; import jdk.internal.vm.ThreadContainer; import jdk.internal.vm.ThreadContainers; import static java.util.concurrent.TimeUnit.NANOSECONDS; /** * A grouping of threads that typically run closely related tasks. Threads started * in a flock remain in the flock until they terminate. * * <p> ThreadFlock defines the {@link #open(String) open} method to open a new flock, * the {@link #start(Thread) start} method to start a thread in the flock, and the * {@link #close() close} method to close the flock. The {@code close} waits for all * threads in the flock to finish. The {@link #awaitAll() awaitAll} method may be used * to wait for all threads to finish without closing the flock. The {@link #wakeup()} * method will cause {@code awaitAll} method to complete early, which can be used to * support cancellation in higher-level APIs. ThreadFlock also defines the {@link * #shutdown() shutdown} method to prevent new threads from starting while allowing * existing threads in the flock to continue. * * <p> Thread flocks are intended to be used in a <em>structured manner</em>. The * thread that opens a new flock is the {@link #owner() owner}. The owner closes the * flock when done, failure to do so may result in a resource leak. The {@code open} * and {@code close} should be matched to avoid closing an <em>enclosing</em> flock * while a <em>nested</em> flock is open. A ThreadFlock can be used with the * try-with-resources construct if required but more likely, the close method of a * higher-level API that implements {@link AutoCloseable} will close the flock. * * <p> Thread flocks are conceptually nodes in a tree. A thread {@code T} started in * flock "A" may itself open a new flock "B", implicitly forming a tree where flock * "A" is the parent of flock "B". When nested, say where thread {@code T} opens * flock "B" and then invokes a method that opens flock "C", then the enclosing * flock "B" is conceptually the parent of the nested flock "C". ThreadFlock does * not define APIs that exposes the tree structure. It does define the {@link * #containsThread(Thread) containsThread} method to test if a flock contains a * thread, a test that is equivalent to testing membership of flocks in the tree. * The {@code start} and {@code shutdown} methods are confined to the flock * owner or threads contained in the flock. The confinement check is equivalent to * invoking the {@code containsThread} method to test if the caller is contained * in the flock. * * <p> Unless otherwise specified, passing a {@code null} argument to a method * in this class will cause a {@link NullPointerException} to be thrown. */ public class ThreadFlock implements AutoCloseable { private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); private static final VarHandle THREAD_COUNT; private static final VarHandle PERMIT; static { try { MethodHandles.Lookup l = MethodHandles.lookup(); THREAD_COUNT = l.findVarHandle(ThreadFlock.class, "threadCount", int.class); PERMIT = l.findVarHandle(ThreadFlock.class, "permit", boolean.class); } catch (Exception e) { throw new InternalError(e); } } private final Set<Thread> threads = ConcurrentHashMap.newKeySet(); // thread count, need to re-examine contention once API is stable private volatile int threadCount; private final String name; private final ScopedValueContainer.BindingsSnapshot scopedValueBindings; private final ThreadContainerImpl container; // encapsulate for now // state private volatile boolean shutdown; private volatile boolean closed; // set by wakeup, cleared by awaitAll private volatile boolean permit; ThreadFlock(String name) { this.name = name; this.scopedValueBindings = ScopedValueContainer.captureBindings(); this.container = new ThreadContainerImpl(this); } private long threadCount() { return threadCount; } private ScopedValueContainer.BindingsSnapshot scopedValueBindings() { return scopedValueBindings; } private void incrementThreadCount() { THREAD_COUNT.getAndAdd(this, 1); } /** * Decrement the thread count. Unpark the owner if the count goes to zero. */ private void decrementThreadCount() { int count = (int) THREAD_COUNT.getAndAdd(this, -1) - 1; // signal owner when the count goes to zero if (count == 0) { LockSupport.unpark(owner()); } } /** * Invoked on the parent thread when starting {@code thread}. */ private void onStart(Thread thread) { incrementThreadCount(); boolean done = false; try { boolean added = threads.add(thread); assert added; if (shutdown) throw new IllegalStateException("Shutdown"); done = true; } finally { if (!done) { threads.remove(thread); decrementThreadCount(); } } } /** * Invoked on the terminating thread or the parent thread when starting * {@code thread} failed. This method is only called if onStart succeeded. */ private void onExit(Thread thread) { boolean removed = threads.remove(thread); assert removed; decrementThreadCount(); } /** * Clear wakeup permit. */ private void clearPermit() { if (permit) permit = false; } /** * Sets the wakeup permit to the given value, returning the previous value. */ private boolean getAndSetPermit(boolean newValue) { if (permit != newValue) { return (boolean) PERMIT.getAndSet(this, newValue); } else { return newValue; } } /** * Throws WrongThreadException if the current thread is not the owner. */ private void ensureOwner() { if (Thread.currentThread() != owner()) throw new WrongThreadException("Current thread not owner"); } /** * Throws WrongThreadException if the current thread is not the owner * or a thread contained in the flock. */ private void ensureOwnerOrContainsThread() { Thread currentThread = Thread.currentThread(); if (currentThread != owner() && !containsThread(currentThread)) throw new WrongThreadException("Current thread not owner or thread in flock"); } /** * Opens a new thread flock. The flock is owned by the current thread. It can be * named to aid debugging. * * <p> This method captures the current thread's {@linkplain ScopedValue scoped value} * bindings for inheritance by threads created in the flock. * * <p> For the purposes of containment, monitoring, and debugging, the parent * of the new flock is determined as follows: * <ul> * <li> If the current thread is the owner of open flocks then the most recently * created, and open, flock is the parent of the new flock. In other words, the * <em>enclosing flock</em> is the parent. * <li> If the current thread is not the owner of any open flocks then the * parent of the new flock is the current thread's flock. If the current thread * was not started in a flock then the new flock does not have a parent. * </ul> * * @param name the name of the flock, can be null * @return a new thread flock */ public static ThreadFlock open(String name) { var flock = new ThreadFlock(name); flock.container.push(); return flock; } /** * {@return the name of this flock or {@code null} if unnamed} */ public String name() { return name; } /** * {@return the owner of this flock} */ public Thread owner() { return container.owner(); } /** * Starts the given unstarted thread in this flock. * * <p> The thread is started with the scoped value bindings that were captured * when opening the flock. The bindings must match the current thread's bindings. * * <p> This method may only be invoked by the flock owner or threads {@linkplain * #containsThread(Thread) contained} in the flock. * * @param thread the unstarted thread * @return the thread, started * @throws IllegalStateException if this flock is shutdown or closed * @throws IllegalThreadStateException if the given thread was already started * @throws WrongThreadException if the current thread is not the owner or a thread * contained in the flock * @throws StructureViolationException if the current scoped value bindings are * not the same as when the flock was created */ public Thread start(Thread thread) { ensureOwnerOrContainsThread(); JLA.start(thread, container); return thread; } /** * Shutdown this flock so that no new threads can be started, existing threads * in the flock will continue to run. This method is a no-op if the flock is * already shutdown or closed. * * <p> This method may only be invoked by the flock owner or threads {@linkplain * #containsThread(Thread) contained} in the flock. * * @throws WrongThreadException if the current thread is not the owner or a thread * contained in the flock */ public void shutdown() { ensureOwnerOrContainsThread(); if (!shutdown) { shutdown = true; } } /** * Wait for all threads in the flock to finish executing their tasks. This method * waits until all threads finish, the {@link #wakeup() wakeup} method is invoked, * or the current thread is interrupted. * * <p> This method may only be invoked by the flock owner. The method trivially * returns true when the flock is closed. * * <p> This method clears the effect of any previous invocations of the * {@code wakeup} method. * * @return true if there are no threads in the flock, false if wakeup was invoked * and there are unfinished threads * @throws InterruptedException if interrupted while waiting * @throws WrongThreadException if invoked by a thread that is not the owner */ public boolean awaitAll() throws InterruptedException { ensureOwner(); if (getAndSetPermit(false)) return (threadCount == 0); while (threadCount > 0 && !permit) { LockSupport.park(); if (Thread.interrupted()) throw new InterruptedException(); } clearPermit(); return (threadCount == 0); } /** * Wait, up to the given waiting timeout, for all threads in the flock to finish * executing their tasks. This method waits until all threads finish, the {@link * #wakeup() wakeup} method is invoked, the current thread is interrupted, or * the timeout expires. * * <p> This method may only be invoked by the flock owner. The method trivially * returns true when the flock is closed. * * <p> This method clears the effect of any previous invocations of the {@code wakeup} * method. * * @param timeout the maximum duration to wait * @return true if there are no threads in the flock, false if wakeup was invoked * and there are unfinished threads * @throws InterruptedException if interrupted while waiting * @throws TimeoutException if the wait timed out * @throws WrongThreadException if invoked by a thread that is not the owner */ public boolean awaitAll(Duration timeout) throws InterruptedException, TimeoutException { Objects.requireNonNull(timeout); ensureOwner(); if (getAndSetPermit(false)) return (threadCount == 0); long startNanos = System.nanoTime(); long nanos = NANOSECONDS.convert(timeout); long remainingNanos = nanos; while (threadCount > 0 && remainingNanos > 0 && !permit) { LockSupport.parkNanos(remainingNanos); if (Thread.interrupted()) throw new InterruptedException(); remainingNanos = nanos - (System.nanoTime() - startNanos); } boolean done = (threadCount == 0); if (!done && remainingNanos <= 0 && !permit) { throw new TimeoutException(); } else { clearPermit(); return done; } } /** * Causes the call to {@link #awaitAll()} or {@link #awaitAll(Duration)} by the * {@linkplain #owner() owner} to return immediately. * * <p> If the owner is blocked in {@code awaitAll} then it will return immediately. * If the owner is not blocked in {@code awaitAll} then its next call to wait * will return immediately. The method does nothing when the flock is closed. * * @throws WrongThreadException if the current thread is not the owner or a thread * contained in the flock */ public void wakeup() { ensureOwnerOrContainsThread(); if (!getAndSetPermit(true) && Thread.currentThread() != owner()) { LockSupport.unpark(owner()); } } /** * Closes this flock. This method first shuts down the flock to prevent * new threads from starting. It then waits for the threads in the flock * to finish executing their tasks. In other words, this method blocks until * all threads in the flock finish. * * <p> This method may only be invoked by the flock owner. * * <p> If interrupted then this method continues to wait until all threads * finish, before completing with the interrupt status set. * * <p> A ThreadFlock is intended to be used in a <em>structured manner</em>. If * this method is called to close a flock before nested flocks are closed then it * closes the nested flocks (in the reverse order that they were created in), * closes this flock, and then throws {@code StructureViolationException}. * Similarly, if this method is called to close a thread flock while executing with * scoped value bindings, and the thread flock was created before the scoped values * were bound, then {@code StructureViolationException} is thrown after closing the * thread flock. * * @throws WrongThreadException if invoked by a thread that is not the owner * @throws StructureViolationException if a structure violation was detected */ public void close() { ensureOwner(); if (closed) return; // shutdown, if not already shutdown if (!shutdown) shutdown = true; // wait for threads to finish boolean interrupted = false; try { while (threadCount > 0) { LockSupport.park(); if (Thread.interrupted()) { interrupted = true; } } } finally { try { container.close(); // may throw } finally { closed = true; if (interrupted) Thread.currentThread().interrupt(); } } } /** * {@return true if the flock has been {@linkplain #shutdown() shut down}} */ public boolean isShutdown() { return shutdown; } /** * {@return true if the flock has been {@linkplain #close() closed}} */ public boolean isClosed() { return closed; } /** * {@return a stream of the threads in this flock} * The elements of the stream are threads that were started in this flock * but have not terminated. The stream will reflect the set of threads in the * flock at some point at or since the creation of the stream. It may or may * not reflect changes to the set of threads subsequent to creation of the * stream. */ public Stream<Thread> threads() { return threads.stream(); } /** * Tests if this flock contains the given thread. This method returns {@code true} * if the thread was started in this flock and has not finished. If the thread * is not in this flock then it tests if the thread is in flocks owned by threads * in this flock, essentially equivalent to invoking {@code containsThread} method * on all flocks owned by the threads in this flock. * * @param thread the thread * @return true if this flock contains the thread */ public boolean containsThread(Thread thread) { var c = JLA.threadContainer(thread); if (c == this.container) return true; if (c != null && c != ThreadContainers.root()) { var parent = c.parent(); while (parent != null) { if (parent == this.container) return true; parent = parent.parent(); } } return false; } @Override public String toString() { String id = Objects.toIdentityString(this); if (name != null) { return name + "/" + id; } else { return id; } } /** * A ThreadContainer backed by a ThreadFlock. */ private static class ThreadContainerImpl extends ThreadContainer { private final ThreadFlock flock; private volatile Object key; private boolean closing; ThreadContainerImpl(ThreadFlock flock) { super(/*shared*/ false); this.flock = flock; } @Override public ThreadContainerImpl push() { // Virtual threads in the root containers may not be tracked so need // to register container to ensure that it is found if (!ThreadContainers.trackAllThreads()) { Thread thread = Thread.currentThread(); if (thread.isVirtual() && JLA.threadContainer(thread) == ThreadContainers.root()) { this.key = ThreadContainers.registerContainer(this); } } super.push(); return this; } /** * Invoked by ThreadFlock.close when closing the flock. This method pops the * container from the current thread's scope stack. */ void close() { assert Thread.currentThread() == owner(); if (!closing) { closing = true; boolean atTop = popForcefully(); // may block Object key = this.key; if (key != null) ThreadContainers.deregisterContainer(key); if (!atTop) throw new StructureViolationException(); } } /** * Invoked when an enclosing scope is closing. Invokes ThreadFlock.close to * close the flock. This method does not pop the container from the current * thread's scope stack. */ @Override protected boolean tryClose() { assert Thread.currentThread() == owner(); if (!closing) { closing = true; flock.close(); Object key = this.key; if (key != null) ThreadContainers.deregisterContainer(key); return true; } else { assert false : "Should not get there"; return false; } } @Override public String name() { return flock.name(); } @Override public long threadCount() { return flock.threadCount(); } @Override public Stream<Thread> threads() { return flock.threads().filter(Thread::isAlive); } @Override public void onStart(Thread thread) { flock.onStart(thread); } @Override public void onExit(Thread thread) { flock.onExit(thread); } @Override public ScopedValueContainer.BindingsSnapshot scopedValueBindings() { return flock.scopedValueBindings(); } } }