78b80150e0
Co-authored-by: Patricio Chilano Mateo <pchilanomate@openjdk.org> Co-authored-by: Alan Bateman <alanb@openjdk.org> Co-authored-by: Andrew Haley <aph@openjdk.org> Co-authored-by: Fei Yang <fyang@openjdk.org> Co-authored-by: Coleen Phillimore <coleenp@openjdk.org> Co-authored-by: Richard Reingruber <rrich@openjdk.org> Co-authored-by: Martin Doerr <mdoerr@openjdk.org> Reviewed-by: aboldtch, dholmes, coleenp, fbredberg, dlong, sspitsyn
2659 lines
82 KiB
Java
2659 lines
82 KiB
Java
/*
|
|
* Copyright (c) 2019, 2024, 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.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
/*
|
|
* @test id=default
|
|
* @bug 8284161 8286788 8321270
|
|
* @summary Test Thread API with virtual threads
|
|
* @modules java.base/java.lang:+open jdk.management
|
|
* @library /test/lib
|
|
* @build LockingMode
|
|
* @run junit/othervm --enable-native-access=ALL-UNNAMED ThreadAPI
|
|
*/
|
|
|
|
/*
|
|
* @test id=no-vmcontinuations
|
|
* @requires vm.continuations
|
|
* @modules java.base/java.lang:+open jdk.management
|
|
* @library /test/lib
|
|
* @build LockingMode
|
|
* @run junit/othervm -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations
|
|
* --enable-native-access=ALL-UNNAMED ThreadAPI
|
|
*/
|
|
|
|
import java.time.Duration;
|
|
import java.util.Arrays;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.concurrent.CopyOnWriteArrayList;
|
|
import java.util.concurrent.CountDownLatch;
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.concurrent.Executor;
|
|
import java.util.concurrent.Executors;
|
|
import java.util.concurrent.ForkJoinPool;
|
|
import java.util.concurrent.ScheduledExecutorService;
|
|
import java.util.concurrent.ThreadFactory;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
import java.util.concurrent.locks.LockSupport;
|
|
import java.util.concurrent.locks.ReentrantLock;
|
|
import java.util.stream.Stream;
|
|
import java.nio.channels.Selector;
|
|
|
|
import jdk.test.lib.thread.VThreadPinner;
|
|
import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management
|
|
import jdk.test.lib.thread.VThreadScheduler;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.junit.jupiter.api.BeforeAll;
|
|
import org.junit.jupiter.api.AfterAll;
|
|
import org.junit.jupiter.api.Disabled;
|
|
import org.junit.jupiter.api.condition.DisabledIf;
|
|
import org.junit.jupiter.params.ParameterizedTest;
|
|
import org.junit.jupiter.params.provider.MethodSource;
|
|
import org.junit.jupiter.params.provider.ValueSource;
|
|
import static org.junit.jupiter.api.Assertions.*;
|
|
import static org.junit.jupiter.api.Assumptions.*;
|
|
|
|
class ThreadAPI {
|
|
private static final Object lock = new Object();
|
|
|
|
// used for scheduling thread interrupt
|
|
private static ScheduledExecutorService scheduler;
|
|
|
|
@BeforeAll
|
|
static void setup() {
|
|
ThreadFactory factory = Executors.defaultThreadFactory();
|
|
scheduler = Executors.newSingleThreadScheduledExecutor(factory);
|
|
|
|
// need >=2 carriers for testing pinning
|
|
VThreadRunner.ensureParallelism(2);
|
|
}
|
|
|
|
@AfterAll
|
|
static void finish() {
|
|
scheduler.shutdown();
|
|
}
|
|
|
|
/**
|
|
* An operation that does not return a result but may throw an exception.
|
|
*/
|
|
@FunctionalInterface
|
|
interface ThrowingRunnable {
|
|
void run() throws Exception;
|
|
}
|
|
|
|
/**
|
|
* Test Thread.currentThread before/after park.
|
|
*/
|
|
@Test
|
|
void testCurrentThread1() throws Exception {
|
|
var before = new AtomicReference<Thread>();
|
|
var after = new AtomicReference<Thread>();
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
before.set(Thread.currentThread());
|
|
LockSupport.park();
|
|
after.set(Thread.currentThread());
|
|
});
|
|
await(thread, Thread.State.WAITING);
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
assertTrue(before.get() == thread);
|
|
assertTrue(after.get() == thread);
|
|
}
|
|
|
|
/**
|
|
* Test Thread.currentThread before/after entering synchronized block.
|
|
*/
|
|
@Test
|
|
void testCurrentThread2() throws Exception {
|
|
var ref1 = new AtomicReference<Thread>();
|
|
var ref2 = new AtomicReference<Thread>();
|
|
var ref3 = new AtomicReference<Thread>();
|
|
var thread = Thread.ofVirtual().unstarted(() -> {
|
|
ref1.set(Thread.currentThread());
|
|
synchronized (lock) {
|
|
ref2.set(Thread.currentThread());
|
|
}
|
|
ref3.set(Thread.currentThread());
|
|
});
|
|
synchronized (lock) {
|
|
thread.start();
|
|
await(thread, Thread.State.BLOCKED);
|
|
}
|
|
thread.join();
|
|
assertTrue(ref1.get() == thread);
|
|
assertTrue(ref2.get() == thread);
|
|
assertTrue(ref3.get() == thread);
|
|
}
|
|
|
|
/**
|
|
* Test Thread.currentThread before/after acquiring lock.
|
|
*/
|
|
@Test
|
|
void testCurrentThread3() throws Exception {
|
|
var ref1 = new AtomicReference<Thread>();
|
|
var ref2 = new AtomicReference<Thread>();
|
|
var ref3 = new AtomicReference<Thread>();
|
|
var lock = new ReentrantLock();
|
|
var thread = Thread.ofVirtual().unstarted(() -> {
|
|
ref1.set(Thread.currentThread());
|
|
lock.lock();
|
|
try {
|
|
ref2.set(Thread.currentThread());
|
|
} finally {
|
|
lock.unlock();
|
|
}
|
|
ref3.set(Thread.currentThread());
|
|
});
|
|
lock.lock();
|
|
try {
|
|
thread.start();
|
|
await(thread, Thread.State.WAITING);
|
|
} finally {
|
|
lock.unlock();
|
|
}
|
|
thread.join();
|
|
assertTrue(ref1.get() == thread);
|
|
assertTrue(ref2.get() == thread);
|
|
assertTrue(ref3.get() == thread);
|
|
}
|
|
|
|
/**
|
|
* Test Thread::run.
|
|
*/
|
|
@Test
|
|
void testRun1() throws Exception {
|
|
var ref = new AtomicBoolean();
|
|
var thread = Thread.ofVirtual().unstarted(() -> ref.set(true));
|
|
thread.run();
|
|
assertFalse(ref.get());
|
|
}
|
|
|
|
/**
|
|
* Test Thread::start.
|
|
*/
|
|
@Test
|
|
void testStart1() throws Exception {
|
|
var ref = new AtomicBoolean();
|
|
var thread = Thread.ofVirtual().unstarted(() -> ref.set(true));
|
|
assertFalse(ref.get());
|
|
thread.start();
|
|
thread.join();
|
|
assertTrue(ref.get());
|
|
}
|
|
|
|
/**
|
|
* Test Thread::start, thread already started.
|
|
*/
|
|
@Test
|
|
void testStart2() throws Exception {
|
|
var thread = Thread.ofVirtual().start(LockSupport::park);
|
|
try {
|
|
assertThrows(IllegalThreadStateException.class, thread::start);
|
|
} finally {
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Thread::start, thread already terminated.
|
|
*/
|
|
@Test
|
|
void testStart3() throws Exception {
|
|
var thread = Thread.ofVirtual().start(() -> { });
|
|
thread.join();
|
|
assertThrows(IllegalThreadStateException.class, thread::start);
|
|
}
|
|
|
|
/**
|
|
* Test Thread.startVirtualThread.
|
|
*/
|
|
@Test
|
|
void testStartVirtualThread() throws Exception {
|
|
var ref = new AtomicReference<Thread>();
|
|
Thread vthread = Thread.startVirtualThread(() -> {
|
|
ref.set(Thread.currentThread());
|
|
LockSupport.park();
|
|
});
|
|
try {
|
|
assertTrue(vthread.isVirtual());
|
|
|
|
// Thread.currentThread() returned by the virtual thread
|
|
Thread current;
|
|
while ((current = ref.get()) == null) {
|
|
Thread.sleep(10);
|
|
}
|
|
assertTrue(current == vthread);
|
|
} finally {
|
|
LockSupport.unpark(vthread);
|
|
}
|
|
|
|
assertThrows(NullPointerException.class, () -> Thread.startVirtualThread(null));
|
|
}
|
|
|
|
/**
|
|
* Test Thread::stop from current thread.
|
|
*/
|
|
@Test
|
|
void testStop1() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
Thread t = Thread.currentThread();
|
|
assertThrows(UnsupportedOperationException.class, t::stop);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test Thread::stop from another thread.
|
|
*/
|
|
@Test
|
|
void testStop2() throws Exception {
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
try {
|
|
Thread.sleep(20*1000);
|
|
} catch (InterruptedException e) { }
|
|
});
|
|
try {
|
|
assertThrows(UnsupportedOperationException.class, thread::stop);
|
|
} finally {
|
|
thread.interrupt();
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Thread.join before thread starts, platform thread invokes join.
|
|
*/
|
|
@Test
|
|
void testJoin1() throws Exception {
|
|
var thread = Thread.ofVirtual().unstarted(() -> { });
|
|
|
|
thread.join();
|
|
thread.join(0);
|
|
thread.join(0, 0);
|
|
thread.join(100);
|
|
thread.join(100, 0);
|
|
assertThrows(IllegalThreadStateException.class,
|
|
() -> thread.join(Duration.ofMillis(-100)));
|
|
assertThrows(IllegalThreadStateException.class,
|
|
() -> thread.join(Duration.ofMillis(0)));
|
|
assertThrows(IllegalThreadStateException.class,
|
|
() -> thread.join(Duration.ofMillis(100)));
|
|
}
|
|
|
|
/**
|
|
* Test Thread.join before thread starts, virtual thread invokes join.
|
|
*/
|
|
@Test
|
|
void testJoin2() throws Exception {
|
|
VThreadRunner.run(this::testJoin1);
|
|
}
|
|
|
|
/**
|
|
* Test Thread.join where thread does not terminate, platform thread invokes join.
|
|
*/
|
|
@Test
|
|
void testJoin3() throws Exception {
|
|
var thread = Thread.ofVirtual().start(LockSupport::park);
|
|
try {
|
|
thread.join(20);
|
|
thread.join(20, 0);
|
|
thread.join(20, 20);
|
|
thread.join(0, 20);
|
|
assertFalse(thread.join(Duration.ofMillis(-20)));
|
|
assertFalse(thread.join(Duration.ofMillis(0)));
|
|
assertFalse(thread.join(Duration.ofMillis(20)));
|
|
assertTrue(thread.isAlive());
|
|
} finally {
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Thread.join where thread does not terminate, virtual thread invokes join.
|
|
*/
|
|
@Test
|
|
void testJoin4() throws Exception {
|
|
VThreadRunner.run(this::testJoin3);
|
|
}
|
|
|
|
/**
|
|
* Test Thread.join where thread terminates, platform thread invokes join.
|
|
*/
|
|
@Test
|
|
void testJoin5() throws Exception {
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
try {
|
|
Thread.sleep(50);
|
|
} catch (InterruptedException e) { }
|
|
});
|
|
thread.join();
|
|
assertFalse(thread.isAlive());
|
|
}
|
|
|
|
/**
|
|
* Test Thread.join where thread terminates, virtual thread invokes join.
|
|
*/
|
|
@Test
|
|
void testJoin6() throws Exception {
|
|
VThreadRunner.run(this::testJoin5);
|
|
}
|
|
|
|
/**
|
|
* Test Thread.join where thread terminates, platform thread invokes timed-join.
|
|
*/
|
|
@Test
|
|
void testJoin7() throws Exception {
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
try {
|
|
Thread.sleep(50);
|
|
} catch (InterruptedException e) { }
|
|
});
|
|
thread.join(10*1000);
|
|
assertFalse(thread.isAlive());
|
|
}
|
|
|
|
/**
|
|
* Test Thread.join where thread terminates, virtual thread invokes timed-join.
|
|
*/
|
|
@Test
|
|
void testJoin8() throws Exception {
|
|
VThreadRunner.run(this::testJoin7);
|
|
}
|
|
|
|
/**
|
|
* Test Thread.join where thread terminates, platform thread invokes timed-join.
|
|
*/
|
|
@Test
|
|
void testJoin11() throws Exception {
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
try {
|
|
Thread.sleep(50);
|
|
} catch (InterruptedException e) { }
|
|
});
|
|
assertTrue(thread.join(Duration.ofSeconds(10)));
|
|
assertFalse(thread.isAlive());
|
|
}
|
|
|
|
/**
|
|
* Test Thread.join where thread terminates, virtual thread invokes timed-join.
|
|
*/
|
|
@Test
|
|
void testJoin12() throws Exception {
|
|
VThreadRunner.run(this::testJoin11);
|
|
}
|
|
|
|
/**
|
|
* Test Thread.join where thread already terminated, platform thread invokes join.
|
|
*/
|
|
@Test
|
|
void testJoin13() throws Exception {
|
|
var thread = Thread.ofVirtual().start(() -> { });
|
|
while (thread.isAlive()) {
|
|
Thread.sleep(10);
|
|
}
|
|
thread.join();
|
|
thread.join(0);
|
|
thread.join(0, 0);
|
|
thread.join(100);
|
|
thread.join(100, 0);
|
|
assertTrue(thread.join(Duration.ofMillis(-100)));
|
|
assertTrue(thread.join(Duration.ofMillis(0)));
|
|
assertTrue(thread.join(Duration.ofMillis(100)));
|
|
}
|
|
|
|
/**
|
|
* Test Thread.join where thread already terminated, virtual thread invokes join.
|
|
*/
|
|
@Test
|
|
void testJoin14() throws Exception {
|
|
VThreadRunner.run(this::testJoin13);
|
|
}
|
|
|
|
/**
|
|
* Test platform thread invoking Thread.join with interrupt status set.
|
|
*/
|
|
@Test
|
|
void testJoin15() throws Exception {
|
|
var thread = Thread.ofVirtual().start(LockSupport::park);
|
|
Thread.currentThread().interrupt();
|
|
try {
|
|
assertThrows(InterruptedException.class, thread::join);
|
|
} finally {
|
|
Thread.interrupted();
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test virtual thread invoking Thread.join with interrupt status set.
|
|
*/
|
|
@Test
|
|
void testJoin16() throws Exception {
|
|
VThreadRunner.run(this::testJoin15);
|
|
}
|
|
|
|
/**
|
|
* Test platform thread invoking timed-Thread.join with interrupt status set.
|
|
*/
|
|
@Test
|
|
void testJoin17() throws Exception {
|
|
var thread = Thread.ofVirtual().start(LockSupport::park);
|
|
Thread.currentThread().interrupt();
|
|
try {
|
|
assertThrows(InterruptedException.class, () -> thread.join(100));
|
|
} finally {
|
|
Thread.interrupted();
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test virtual thread invoking timed-Thread.join with interrupt status set.
|
|
*/
|
|
@Test
|
|
void testJoin18() throws Exception {
|
|
VThreadRunner.run(this::testJoin17);
|
|
}
|
|
|
|
/**
|
|
* Test platform thread invoking timed-Thread.join with interrupt status set.
|
|
*/
|
|
@Test
|
|
void testJoin19() throws Exception {
|
|
var thread = Thread.ofVirtual().start(LockSupport::park);
|
|
Thread.currentThread().interrupt();
|
|
try {
|
|
assertThrows(InterruptedException.class,
|
|
() -> thread.join(Duration.ofMillis(100)));
|
|
} finally {
|
|
Thread.interrupted();
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test virtual thread invoking timed-Thread.join with interrupt status set.
|
|
*/
|
|
@Test
|
|
void testJoin20() throws Exception {
|
|
VThreadRunner.run(this::testJoin19);
|
|
}
|
|
|
|
/**
|
|
* Test interrupt of platform thread blocked in Thread.join.
|
|
*/
|
|
@Test
|
|
void testJoin21() throws Exception {
|
|
var thread = Thread.ofVirtual().start(LockSupport::park);
|
|
scheduleInterrupt(Thread.currentThread(), 100);
|
|
try {
|
|
assertThrows(InterruptedException.class, thread::join);
|
|
} finally {
|
|
Thread.interrupted();
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test interrupt of virtual thread blocked in Thread.join.
|
|
*/
|
|
@Test
|
|
void testJoin22() throws Exception {
|
|
VThreadRunner.run(this::testJoin17);
|
|
}
|
|
|
|
/**
|
|
* Test interrupt of platform thread blocked in timed-Thread.join.
|
|
*/
|
|
@Test
|
|
void testJoin23() throws Exception {
|
|
var thread = Thread.ofVirtual().start(LockSupport::park);
|
|
scheduleInterrupt(Thread.currentThread(), 100);
|
|
try {
|
|
assertThrows(InterruptedException.class, () -> thread.join(10*1000));
|
|
} finally {
|
|
Thread.interrupted();
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test interrupt of virtual thread blocked in Thread.join.
|
|
*/
|
|
@Test
|
|
void testJoin24() throws Exception {
|
|
VThreadRunner.run(this::testJoin23);
|
|
}
|
|
|
|
/**
|
|
* Test interrupt of platform thread blocked in Thread.join.
|
|
*/
|
|
@Test
|
|
void testJoin25() throws Exception {
|
|
var thread = Thread.ofVirtual().start(LockSupport::park);
|
|
scheduleInterrupt(Thread.currentThread(), 100);
|
|
try {
|
|
assertThrows(InterruptedException.class,
|
|
() -> thread.join(Duration.ofSeconds(10)));
|
|
} finally {
|
|
Thread.interrupted();
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test interrupt of virtual thread blocked in Thread.join.
|
|
*/
|
|
@Test
|
|
void testJoin26() throws Exception {
|
|
VThreadRunner.run(this::testJoin25);
|
|
}
|
|
|
|
/**
|
|
* Test virtual thread calling Thread.join to wait for platform thread to terminate.
|
|
*/
|
|
@Test
|
|
void testJoin27() throws Exception {
|
|
AtomicBoolean done = new AtomicBoolean();
|
|
VThreadRunner.run(() -> {
|
|
var thread = new Thread(() -> {
|
|
while (!done.get()) {
|
|
LockSupport.park();
|
|
}
|
|
});
|
|
thread.start();
|
|
try {
|
|
assertFalse(thread.join(Duration.ofMillis(-100)));
|
|
assertFalse(thread.join(Duration.ofMillis(0)));
|
|
assertFalse(thread.join(Duration.ofMillis(100)));
|
|
} finally {
|
|
done.set(true);
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test virtual thread calling Thread.join to wait for platform thread to terminate.
|
|
*/
|
|
@Test
|
|
void testJoin28() throws Exception {
|
|
long nanos = TimeUnit.NANOSECONDS.convert(100, TimeUnit.MILLISECONDS);
|
|
VThreadRunner.run(() -> {
|
|
var thread = new Thread(() -> LockSupport.parkNanos(nanos));
|
|
thread.start();
|
|
try {
|
|
assertTrue(thread.join(Duration.ofSeconds(Integer.MAX_VALUE)));
|
|
assertFalse(thread.isAlive());
|
|
} finally {
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test virtual thread with interrupt status set calling Thread.join to wait
|
|
* for platform thread to terminate.
|
|
*/
|
|
@Test
|
|
void testJoin29() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
var thread = new Thread(LockSupport::park);
|
|
thread.start();
|
|
Thread.currentThread().interrupt();
|
|
try {
|
|
thread.join(Duration.ofSeconds(Integer.MAX_VALUE));
|
|
fail("join not interrupted");
|
|
} catch (InterruptedException expected) {
|
|
assertFalse(Thread.interrupted());
|
|
} finally {
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test interrupting virtual thread that is waiting in Thread.join for
|
|
* platform thread to terminate.
|
|
*/
|
|
@Test
|
|
void testJoin30() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
AtomicBoolean done = new AtomicBoolean();
|
|
var thread = new Thread(() -> {
|
|
while (!done.get()) {
|
|
LockSupport.park();
|
|
}
|
|
});
|
|
thread.start();
|
|
scheduleInterrupt(Thread.currentThread(), 100);
|
|
try {
|
|
thread.join(Duration.ofSeconds(Integer.MAX_VALUE));
|
|
fail("join not interrupted");
|
|
} catch (InterruptedException expected) {
|
|
assertFalse(Thread.interrupted());
|
|
} finally {
|
|
done.set(true);
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test platform thread invoking Thread.join on a thread that is parking
|
|
* and unparking.
|
|
*/
|
|
@Test
|
|
void testJoin31() throws Exception {
|
|
Thread thread = Thread.ofVirtual().start(() -> {
|
|
synchronized (lock) {
|
|
for (int i = 0; i < 10; i++) {
|
|
LockSupport.parkNanos(Duration.ofMillis(20).toNanos());
|
|
}
|
|
}
|
|
});
|
|
thread.join();
|
|
assertFalse(thread.isAlive());
|
|
}
|
|
|
|
/**
|
|
* Test virtual thread invoking Thread.join on a thread that is parking
|
|
* and unparking.
|
|
*/
|
|
@Test
|
|
void testJoin32() throws Exception {
|
|
VThreadRunner.run(this::testJoin31);
|
|
}
|
|
|
|
/**
|
|
* Test platform thread invoking timed-Thread.join on a thread that is parking
|
|
* and unparking while pinned.
|
|
*/
|
|
@Test
|
|
void testJoin33() throws Exception {
|
|
AtomicBoolean done = new AtomicBoolean();
|
|
Thread thread = Thread.ofVirtual().start(() -> {
|
|
VThreadPinner.runPinned(() -> {
|
|
while (!done.get()) {
|
|
LockSupport.parkNanos(Duration.ofMillis(20).toNanos());
|
|
}
|
|
});
|
|
});
|
|
try {
|
|
assertFalse(thread.join(Duration.ofMillis(100)));
|
|
} finally {
|
|
done.set(true);
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test virtual thread invoking timed-Thread.join on a thread that is parking
|
|
* and unparking while pinned.
|
|
*/
|
|
@Test
|
|
void testJoin34() throws Exception {
|
|
VThreadRunner.run(this::testJoin33);
|
|
}
|
|
|
|
/**
|
|
* Test Thread.join(null).
|
|
*/
|
|
@Test
|
|
void testJoin35() throws Exception {
|
|
var thread = Thread.ofVirtual().unstarted(LockSupport::park);
|
|
|
|
// unstarted
|
|
assertThrows(NullPointerException.class, () -> thread.join(null));
|
|
|
|
// started
|
|
thread.start();
|
|
try {
|
|
assertThrows(NullPointerException.class, () -> thread.join(null));
|
|
} finally {
|
|
LockSupport.unpark(thread);
|
|
}
|
|
thread.join();
|
|
|
|
// terminated
|
|
assertThrows(NullPointerException.class, () -> thread.join(null));
|
|
}
|
|
|
|
/**
|
|
* Test Thread.interrupt on current thread.
|
|
*/
|
|
@Test
|
|
void testInterrupt1() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
Thread me = Thread.currentThread();
|
|
assertFalse(me.isInterrupted());
|
|
me.interrupt();
|
|
assertTrue(me.isInterrupted());
|
|
Thread.interrupted(); // clear interrupt status
|
|
assertFalse(me.isInterrupted());
|
|
me.interrupt();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test Thread.interrupt before thread started.
|
|
*/
|
|
@Test
|
|
void testInterrupt2() throws Exception {
|
|
var thread = Thread.ofVirtual().unstarted(() -> { });
|
|
thread.interrupt();
|
|
assertTrue(thread.isInterrupted());
|
|
}
|
|
|
|
/**
|
|
* Test Thread.interrupt after thread started.
|
|
*/
|
|
@Test
|
|
void testInterrupt3() throws Exception {
|
|
var thread = Thread.ofVirtual().start(() -> { });
|
|
thread.join();
|
|
thread.interrupt();
|
|
assertTrue(thread.isInterrupted());
|
|
}
|
|
|
|
/**
|
|
* Test termination with interrupt status set.
|
|
*/
|
|
@Test
|
|
void testInterrupt4() throws Exception {
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
Thread.currentThread().interrupt();
|
|
});
|
|
thread.join();
|
|
assertTrue(thread.isInterrupted());
|
|
}
|
|
|
|
/**
|
|
* Test Thread.interrupt of thread blocked in Selector.select.
|
|
*/
|
|
@Test
|
|
void testInterrupt5() throws Exception {
|
|
var exception = new AtomicReference<Exception>();
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
try {
|
|
try (var sel = Selector.open()) {
|
|
sel.select();
|
|
assertTrue(Thread.currentThread().isInterrupted());
|
|
}
|
|
} catch (Exception e) {
|
|
exception.set(e);
|
|
}
|
|
});
|
|
Thread.sleep(100); // give time for thread to block
|
|
thread.interrupt();
|
|
thread.join();
|
|
assertNull(exception.get());
|
|
}
|
|
|
|
/**
|
|
* Test Thread.interrupt of thread parked in sleep.
|
|
*/
|
|
@Test
|
|
void testInterrupt6() throws Exception {
|
|
var exception = new AtomicReference<Exception>();
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
try {
|
|
try {
|
|
Thread.sleep(60*1000);
|
|
fail("sleep not interrupted");
|
|
} catch (InterruptedException e) {
|
|
// interrupt status should be reset
|
|
assertFalse(Thread.interrupted());
|
|
}
|
|
} catch (Exception e) {
|
|
exception.set(e);
|
|
}
|
|
});
|
|
await(thread, Thread.State.TIMED_WAITING);
|
|
thread.interrupt();
|
|
thread.join();
|
|
assertNull(exception.get());
|
|
}
|
|
|
|
/**
|
|
* Test Thread.interrupt of parked thread.
|
|
*/
|
|
@Test
|
|
void testInterrupt7() throws Exception {
|
|
var exception = new AtomicReference<Exception>();
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
try {
|
|
LockSupport.park();
|
|
assertTrue(Thread.currentThread().isInterrupted());
|
|
} catch (Exception e) {
|
|
exception.set(e);
|
|
}
|
|
});
|
|
await(thread, Thread.State.WAITING);
|
|
thread.interrupt();
|
|
thread.join();
|
|
assertNull(exception.get());
|
|
}
|
|
|
|
/**
|
|
* Test trying to park with interrupt status set.
|
|
*/
|
|
@Test
|
|
void testInterrupt8() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
Thread me = Thread.currentThread();
|
|
me.interrupt();
|
|
LockSupport.park();
|
|
assertTrue(Thread.interrupted());
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test trying to wait with interrupt status set.
|
|
*/
|
|
@Test
|
|
void testInterrupt9() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
Thread me = Thread.currentThread();
|
|
me.interrupt();
|
|
synchronized (lock) {
|
|
try {
|
|
lock.wait();
|
|
fail("wait not interrupted");
|
|
} catch (InterruptedException expected) {
|
|
assertFalse(Thread.interrupted());
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test trying to block with interrupt status set.
|
|
*/
|
|
@Test
|
|
void testInterrupt10() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
Thread me = Thread.currentThread();
|
|
me.interrupt();
|
|
try (Selector sel = Selector.open()) {
|
|
sel.select();
|
|
assertTrue(Thread.interrupted());
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test Thread.getName and setName from current thread, started without name.
|
|
*/
|
|
@Test
|
|
void testSetName1() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
Thread me = Thread.currentThread();
|
|
assertTrue(me.getName().isEmpty());
|
|
me.setName("fred");
|
|
assertEquals("fred", me.getName());
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test Thread.getName and setName from current thread, started with name.
|
|
*/
|
|
@Test
|
|
void testSetName2() throws Exception {
|
|
VThreadRunner.run("fred", () -> {
|
|
Thread me = Thread.currentThread();
|
|
assertEquals("fred", me.getName());
|
|
me.setName("joe");
|
|
assertEquals("joe", me.getName());
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test Thread.getName and setName from another thread.
|
|
*/
|
|
@Test
|
|
void testSetName3() throws Exception {
|
|
var thread = Thread.ofVirtual().unstarted(LockSupport::park);
|
|
assertTrue(thread.getName().isEmpty());
|
|
|
|
// not started
|
|
thread.setName("fred1");
|
|
assertEquals("fred1", thread.getName());
|
|
|
|
// started
|
|
thread.start();
|
|
try {
|
|
assertEquals("fred1", thread.getName());
|
|
thread.setName("fred2");
|
|
assertEquals("fred2", thread.getName());
|
|
} finally {
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
|
|
// terminated
|
|
assertEquals("fred2", thread.getName());
|
|
thread.setName("fred3");
|
|
assertEquals("fred3", thread.getName());
|
|
}
|
|
|
|
/**
|
|
* Test Thread.getPriority and setPriority from current thread.
|
|
*/
|
|
@Test
|
|
void testSetPriority1() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
Thread me = Thread.currentThread();
|
|
assertEquals(Thread.NORM_PRIORITY, me.getPriority());
|
|
|
|
me.setPriority(Thread.MAX_PRIORITY);
|
|
assertEquals(Thread.NORM_PRIORITY, me.getPriority());
|
|
|
|
me.setPriority(Thread.NORM_PRIORITY);
|
|
assertEquals(Thread.NORM_PRIORITY, me.getPriority());
|
|
|
|
me.setPriority(Thread.MIN_PRIORITY);
|
|
assertEquals(Thread.NORM_PRIORITY, me.getPriority());
|
|
|
|
assertThrows(IllegalArgumentException.class, () -> me.setPriority(-1));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test Thread.getPriority and setPriority from another thread.
|
|
*/
|
|
@Test
|
|
void testSetPriority2() throws Exception {
|
|
var thread = Thread.ofVirtual().unstarted(LockSupport::park);
|
|
|
|
// not started
|
|
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
|
|
|
thread.setPriority(Thread.MAX_PRIORITY);
|
|
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
|
|
|
thread.setPriority(Thread.NORM_PRIORITY);
|
|
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
|
|
|
thread.setPriority(Thread.MIN_PRIORITY);
|
|
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
|
|
|
assertThrows(IllegalArgumentException.class, () -> thread.setPriority(-1));
|
|
|
|
// running
|
|
thread.start();
|
|
try {
|
|
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
|
thread.setPriority(Thread.NORM_PRIORITY);
|
|
|
|
thread.setPriority(Thread.MAX_PRIORITY);
|
|
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
|
|
|
thread.setPriority(Thread.NORM_PRIORITY);
|
|
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
|
|
|
thread.setPriority(Thread.MIN_PRIORITY);
|
|
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
|
|
|
assertThrows(IllegalArgumentException.class, () -> thread.setPriority(-1));
|
|
|
|
} finally {
|
|
LockSupport.unpark(thread);
|
|
}
|
|
thread.join();
|
|
|
|
// terminated
|
|
assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
|
|
}
|
|
|
|
/**
|
|
* Test Thread.isDaemon and setDaemon from current thread.
|
|
*/
|
|
@Test
|
|
void testSetDaemon1() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
Thread me = Thread.currentThread();
|
|
assertTrue(me.isDaemon());
|
|
assertThrows(IllegalThreadStateException.class, () -> me.setDaemon(true));
|
|
assertThrows(IllegalArgumentException.class, () -> me.setDaemon(false));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test Thread.isDaemon and setDaemon from another thread.
|
|
*/
|
|
@Test
|
|
void testSetDaemon2() throws Exception {
|
|
var thread = Thread.ofVirtual().unstarted(LockSupport::park);
|
|
|
|
// not started
|
|
assertTrue(thread.isDaemon());
|
|
thread.setDaemon(true);
|
|
assertThrows(IllegalArgumentException.class, () -> thread.setDaemon(false));
|
|
|
|
// running
|
|
thread.start();
|
|
try {
|
|
assertTrue(thread.isDaemon());
|
|
assertThrows(IllegalThreadStateException.class, () -> thread.setDaemon(true));
|
|
assertThrows(IllegalArgumentException.class, () -> thread.setDaemon(false));
|
|
} finally {
|
|
LockSupport.unpark(thread);
|
|
}
|
|
thread.join();
|
|
|
|
// terminated
|
|
assertTrue(thread.isDaemon());
|
|
}
|
|
|
|
/**
|
|
* Test Thread.yield releases carrier thread.
|
|
*/
|
|
@Test
|
|
void testYieldReleasesCarrier() throws Exception {
|
|
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
|
var list = new CopyOnWriteArrayList<String>();
|
|
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
|
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
|
var thread = factory.newThread(() -> {
|
|
list.add("A");
|
|
var child = factory.newThread(() -> {
|
|
list.add("B");
|
|
Thread.yield();
|
|
list.add("B");
|
|
});
|
|
child.start();
|
|
Thread.yield();
|
|
list.add("A");
|
|
try { child.join(); } catch (InterruptedException e) { }
|
|
});
|
|
thread.start();
|
|
thread.join();
|
|
}
|
|
assertEquals(List.of("A", "B", "A", "B"), list);
|
|
}
|
|
|
|
/**
|
|
* Test Thread.yield releases carrier thread when virtual thread holds a monitor.
|
|
*/
|
|
@Test
|
|
@DisabledIf("LockingMode#isLegacy")
|
|
void testYieldReleasesCarrierWhenHoldingMonitor() throws Exception {
|
|
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
|
var list = new CopyOnWriteArrayList<String>();
|
|
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
|
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
|
var lock = new Object();
|
|
var thread = factory.newThread(() -> {
|
|
list.add("A");
|
|
var child = factory.newThread(() -> {
|
|
list.add("B");
|
|
synchronized (lock) {
|
|
Thread.yield();
|
|
}
|
|
list.add("B");
|
|
});
|
|
child.start();
|
|
Thread.yield();
|
|
list.add("A");
|
|
try { child.join(); } catch (InterruptedException e) { }
|
|
});
|
|
thread.start();
|
|
thread.join();
|
|
}
|
|
assertEquals(List.of("A", "B", "A", "B"), list);
|
|
}
|
|
|
|
/**
|
|
* Test Thread.yield when thread is pinned.
|
|
*/
|
|
@Test
|
|
void testYieldWhenPinned() throws Exception {
|
|
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
|
var list = new CopyOnWriteArrayList<String>();
|
|
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
|
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
|
var thread = factory.newThread(() -> {
|
|
list.add("A");
|
|
var child = factory.newThread(() -> {
|
|
list.add("B");
|
|
});
|
|
child.start();
|
|
VThreadPinner.runPinned(() -> {
|
|
Thread.yield(); // pinned so will be a no-op
|
|
list.add("A");
|
|
});
|
|
try { child.join(); } catch (InterruptedException e) { }
|
|
});
|
|
thread.start();
|
|
thread.join();
|
|
}
|
|
assertEquals(List.of("A", "A", "B"), list);
|
|
}
|
|
|
|
/**
|
|
* Test Thread.yield does not consume the thread's parking permit.
|
|
*/
|
|
@Test
|
|
void testYieldDoesNotConsumParkingPermit() throws Exception {
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
LockSupport.unpark(Thread.currentThread());
|
|
Thread.yield();
|
|
LockSupport.park(); // should not park
|
|
});
|
|
thread.join();
|
|
}
|
|
|
|
/**
|
|
* Test Thread.yield does not make available the thread's parking permit.
|
|
*/
|
|
@Test
|
|
void testYieldDoesNotOfferParkingPermit() throws Exception {
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
Thread.yield();
|
|
LockSupport.park(); // should park
|
|
});
|
|
try {
|
|
await(thread, Thread.State.WAITING);
|
|
} finally {
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Thread.onSpinWait.
|
|
*/
|
|
@Test
|
|
void testOnSpinWait() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
Thread me = Thread.currentThread();
|
|
Thread.onSpinWait();
|
|
assertTrue(Thread.currentThread() == me);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test Thread.sleep(-1).
|
|
*/
|
|
@Test
|
|
void testSleep1() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
assertThrows(IllegalArgumentException.class, () -> Thread.sleep(-1));
|
|
assertThrows(IllegalArgumentException.class, () -> Thread.sleep(-1, 0));
|
|
assertThrows(IllegalArgumentException.class, () -> Thread.sleep(0, -1));
|
|
assertThrows(IllegalArgumentException.class, () -> Thread.sleep(0, 1_000_000));
|
|
});
|
|
VThreadRunner.run(() -> Thread.sleep(Duration.ofMillis(-1)));
|
|
}
|
|
|
|
/**
|
|
* Test Thread.sleep(0).
|
|
*/
|
|
@Test
|
|
void testSleep2() throws Exception {
|
|
VThreadRunner.run(() -> Thread.sleep(0));
|
|
VThreadRunner.run(() -> Thread.sleep(0, 0));
|
|
VThreadRunner.run(() -> Thread.sleep(Duration.ofMillis(0)));
|
|
}
|
|
|
|
/**
|
|
* Tasks that sleep for 1 second.
|
|
*/
|
|
static Stream<ThrowingRunnable> oneSecondSleepers() {
|
|
return Stream.of(
|
|
() -> Thread.sleep(1000),
|
|
() -> Thread.sleep(Duration.ofSeconds(1))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Test Thread.sleep duration.
|
|
*/
|
|
@ParameterizedTest
|
|
@MethodSource("oneSecondSleepers")
|
|
void testSleep3(ThrowingRunnable sleeper) throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
long start = millisTime();
|
|
sleeper.run();
|
|
expectDuration(start, /*min*/900, /*max*/20_000);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Tasks that sleep for zero or longer duration.
|
|
*/
|
|
static Stream<ThrowingRunnable> sleepers() {
|
|
return Stream.of(
|
|
() -> Thread.sleep(0),
|
|
() -> Thread.sleep(0, 0),
|
|
() -> Thread.sleep(1000),
|
|
() -> Thread.sleep(1000, 0),
|
|
() -> Thread.sleep(Duration.ofMillis(0)),
|
|
() -> Thread.sleep(Duration.ofMillis(1000))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Test Thread.sleep with interrupt status set.
|
|
*/
|
|
@ParameterizedTest
|
|
@MethodSource("sleepers")
|
|
void testSleep4(ThrowingRunnable sleeper) throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
Thread me = Thread.currentThread();
|
|
me.interrupt();
|
|
try {
|
|
sleeper.run();
|
|
fail("sleep was not interrupted");
|
|
} catch (InterruptedException e) {
|
|
// expected
|
|
assertFalse(me.isInterrupted());
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test Thread.sleep with interrupt status set and a negative duration.
|
|
*/
|
|
@Test
|
|
void testSleep4() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
Thread me = Thread.currentThread();
|
|
me.interrupt();
|
|
Thread.sleep(Duration.ofMillis(-1000)); // does nothing
|
|
assertTrue(me.isInterrupted());
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Tasks that sleep for a long time.
|
|
*/
|
|
static Stream<ThrowingRunnable> longSleepers() {
|
|
return Stream.of(
|
|
() -> Thread.sleep(20_000),
|
|
() -> Thread.sleep(20_000, 0),
|
|
() -> Thread.sleep(Duration.ofSeconds(20))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Test interrupting Thread.sleep.
|
|
*/
|
|
@ParameterizedTest
|
|
@MethodSource("longSleepers")
|
|
void testSleep5(ThrowingRunnable sleeper) throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
Thread t = Thread.currentThread();
|
|
scheduleInterrupt(t, 100);
|
|
try {
|
|
sleeper.run();
|
|
fail("sleep was not interrupted");
|
|
} catch (InterruptedException e) {
|
|
// interrupt status should be cleared
|
|
assertFalse(t.isInterrupted());
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test that Thread.sleep does not disrupt parking permit.
|
|
*/
|
|
@Test
|
|
void testSleep6() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
LockSupport.unpark(Thread.currentThread());
|
|
|
|
long start = millisTime();
|
|
Thread.sleep(1000);
|
|
expectDuration(start, /*min*/900, /*max*/20_000);
|
|
|
|
// check that parking permit was not consumed
|
|
LockSupport.park();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test that Thread.sleep is not disrupted by unparking thread.
|
|
*/
|
|
@Test
|
|
void testSleep7() throws Exception {
|
|
AtomicReference<Exception> exc = new AtomicReference<>();
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
try {
|
|
long start = millisTime();
|
|
Thread.sleep(1000);
|
|
expectDuration(start, /*min*/900, /*max*/20_000);
|
|
} catch (Exception e) {
|
|
exc.set(e);
|
|
}
|
|
|
|
});
|
|
// attempt to disrupt sleep
|
|
for (int i = 0; i < 5; i++) {
|
|
Thread.sleep(20);
|
|
LockSupport.unpark(thread);
|
|
}
|
|
thread.join();
|
|
Exception e = exc.get();
|
|
if (e != null) {
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Thread.sleep when pinned.
|
|
*/
|
|
@Test
|
|
void testSleep8() throws Exception {
|
|
VThreadPinner.runPinned(() -> {
|
|
long start = millisTime();
|
|
Thread.sleep(1000);
|
|
expectDuration(start, /*min*/900, /*max*/20_000);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test Thread.sleep when pinned and with interrupt status set.
|
|
*/
|
|
@Test
|
|
void testSleep9() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
Thread me = Thread.currentThread();
|
|
me.interrupt();
|
|
try {
|
|
VThreadPinner.runPinned(() -> {
|
|
Thread.sleep(2000);
|
|
});
|
|
fail("sleep not interrupted");
|
|
} catch (InterruptedException e) {
|
|
// expected
|
|
assertFalse(me.isInterrupted());
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test interrupting Thread.sleep when pinned.
|
|
*/
|
|
@Test
|
|
void testSleep10() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
Thread t = Thread.currentThread();
|
|
scheduleInterrupt(t, 100);
|
|
try {
|
|
VThreadPinner.runPinned(() -> {
|
|
Thread.sleep(20 * 1000);
|
|
});
|
|
fail("sleep not interrupted");
|
|
} catch (InterruptedException e) {
|
|
// interrupt status should be cleared
|
|
assertFalse(t.isInterrupted());
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test Thread.sleep(null).
|
|
*/
|
|
@Test
|
|
void testSleep11() throws Exception {
|
|
assertThrows(NullPointerException.class, () -> Thread.sleep(null));
|
|
VThreadRunner.run(() -> {
|
|
assertThrows(NullPointerException.class, () -> Thread.sleep(null));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns the current time in milliseconds.
|
|
*/
|
|
private static long millisTime() {
|
|
long now = System.nanoTime();
|
|
return TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS);
|
|
}
|
|
|
|
/**
|
|
* Check the duration of a task
|
|
* @param start start time, in milliseconds
|
|
* @param min minimum expected duration, in milliseconds
|
|
* @param max maximum expected duration, in milliseconds
|
|
* @return the duration (now - start), in milliseconds
|
|
*/
|
|
private static void expectDuration(long start, long min, long max) {
|
|
long duration = millisTime() - start;
|
|
assertTrue(duration >= min,
|
|
"Duration " + duration + "ms, expected >= " + min + "ms");
|
|
assertTrue(duration <= max,
|
|
"Duration " + duration + "ms, expected <= " + max + "ms");
|
|
}
|
|
|
|
/**
|
|
* Test Thread.xxxContextClassLoader from the current thread.
|
|
*/
|
|
@Test
|
|
void testContextClassLoader1() throws Exception {
|
|
ClassLoader loader = new ClassLoader() { };
|
|
VThreadRunner.run(() -> {
|
|
Thread t = Thread.currentThread();
|
|
t.setContextClassLoader(loader);
|
|
assertTrue(t.getContextClassLoader() == loader);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test inheriting initial value of TCCL from platform thread.
|
|
*/
|
|
@Test
|
|
void testContextClassLoader2() throws Exception {
|
|
ClassLoader loader = new ClassLoader() { };
|
|
Thread t = Thread.currentThread();
|
|
ClassLoader savedLoader = t.getContextClassLoader();
|
|
t.setContextClassLoader(loader);
|
|
try {
|
|
VThreadRunner.run(() -> {
|
|
assertTrue(Thread.currentThread().getContextClassLoader() == loader);
|
|
});
|
|
} finally {
|
|
t.setContextClassLoader(savedLoader);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test inheriting initial value of TCCL from virtual thread.
|
|
*/
|
|
@Test
|
|
void testContextClassLoader3() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
ClassLoader loader = new ClassLoader() { };
|
|
Thread.currentThread().setContextClassLoader(loader);
|
|
VThreadRunner.run(() -> {
|
|
assertTrue(Thread.currentThread().getContextClassLoader() == loader);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test inheriting initial value of TCCL through an intermediate virtual thread.
|
|
*/
|
|
@Test
|
|
void testContextClassLoader4() throws Exception {
|
|
ClassLoader loader = new ClassLoader() { };
|
|
Thread t = Thread.currentThread();
|
|
ClassLoader savedLoader = t.getContextClassLoader();
|
|
t.setContextClassLoader(loader);
|
|
try {
|
|
VThreadRunner.run(() -> {
|
|
VThreadRunner.run(() -> {
|
|
assertTrue(Thread.currentThread().getContextClassLoader() == loader);
|
|
});
|
|
});
|
|
} finally {
|
|
t.setContextClassLoader(savedLoader);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Thread.xxxContextClassLoader when thread does not inherit the
|
|
* initial value of inheritable thread locals.
|
|
*/
|
|
@Test
|
|
void testContextClassLoader5() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
ClassLoader loader = new ClassLoader() { };
|
|
Thread.currentThread().setContextClassLoader(loader);
|
|
int characteristics = VThreadRunner.NO_INHERIT_THREAD_LOCALS;
|
|
VThreadRunner.run(characteristics, () -> {
|
|
Thread t = Thread.currentThread();
|
|
assertTrue(t.getContextClassLoader() == ClassLoader.getSystemClassLoader());
|
|
t.setContextClassLoader(loader);
|
|
assertTrue(t.getContextClassLoader() == loader);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test Thread.setUncaughtExceptionHandler.
|
|
*/
|
|
@Test
|
|
void testUncaughtExceptionHandler1() throws Exception {
|
|
class FooException extends RuntimeException { }
|
|
var handler = new CapturingUHE();
|
|
Thread thread = Thread.ofVirtual().start(() -> {
|
|
Thread me = Thread.currentThread();
|
|
assertTrue(me.getUncaughtExceptionHandler() == me.getThreadGroup());
|
|
me.setUncaughtExceptionHandler(handler);
|
|
assertTrue(me.getUncaughtExceptionHandler() == handler);
|
|
throw new FooException();
|
|
});
|
|
thread.join();
|
|
assertInstanceOf(FooException.class, handler.exception());
|
|
assertEquals(thread, handler.thread());
|
|
assertNull(thread.getUncaughtExceptionHandler());
|
|
}
|
|
|
|
/**
|
|
* Test default UncaughtExceptionHandler.
|
|
*/
|
|
@Test
|
|
void testUncaughtExceptionHandler2() throws Exception {
|
|
class FooException extends RuntimeException { }
|
|
var handler = new CapturingUHE();
|
|
Thread.UncaughtExceptionHandler savedHandler = Thread.getDefaultUncaughtExceptionHandler();
|
|
Thread.setDefaultUncaughtExceptionHandler(handler);
|
|
Thread thread;
|
|
try {
|
|
thread = Thread.ofVirtual().start(() -> {
|
|
Thread me = Thread.currentThread();
|
|
throw new FooException();
|
|
});
|
|
thread.join();
|
|
} finally {
|
|
Thread.setDefaultUncaughtExceptionHandler(savedHandler); // restore
|
|
}
|
|
assertInstanceOf(FooException.class, handler.exception());
|
|
assertEquals(thread, handler.thread());
|
|
assertNull(thread.getUncaughtExceptionHandler());
|
|
}
|
|
|
|
/**
|
|
* Test Thread and default UncaughtExceptionHandler set.
|
|
*/
|
|
@Test
|
|
void testUncaughtExceptionHandler3() throws Exception {
|
|
class FooException extends RuntimeException { }
|
|
var defaultHandler = new CapturingUHE();
|
|
var threadHandler = new CapturingUHE();
|
|
Thread.UncaughtExceptionHandler savedHandler = Thread.getDefaultUncaughtExceptionHandler();
|
|
Thread.setDefaultUncaughtExceptionHandler(defaultHandler);
|
|
Thread thread;
|
|
try {
|
|
thread = Thread.ofVirtual().start(() -> {
|
|
Thread me = Thread.currentThread();
|
|
assertTrue(me.getUncaughtExceptionHandler() == me.getThreadGroup());
|
|
me.setUncaughtExceptionHandler(threadHandler);
|
|
assertTrue(me.getUncaughtExceptionHandler() == threadHandler);
|
|
throw new FooException();
|
|
});
|
|
thread.join();
|
|
} finally {
|
|
Thread.setDefaultUncaughtExceptionHandler(savedHandler); // restore
|
|
}
|
|
assertInstanceOf(FooException.class, threadHandler.exception());
|
|
assertNull(defaultHandler.exception());
|
|
assertEquals(thread, threadHandler.thread());
|
|
assertNull(thread.getUncaughtExceptionHandler());
|
|
}
|
|
|
|
/**
|
|
* Test no Thread or default UncaughtExceptionHandler set.
|
|
*/
|
|
@Test
|
|
void testUncaughtExceptionHandler4() throws Exception {
|
|
Thread.UncaughtExceptionHandler savedHandler = Thread.getDefaultUncaughtExceptionHandler();
|
|
Thread.setDefaultUncaughtExceptionHandler(null);
|
|
try {
|
|
class FooException extends RuntimeException { }
|
|
Thread thread = Thread.ofVirtual().start(() -> {
|
|
throw new FooException();
|
|
});
|
|
thread.join();
|
|
assertNull(thread.getUncaughtExceptionHandler());
|
|
} finally {
|
|
Thread.setDefaultUncaughtExceptionHandler(savedHandler);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Thread::threadId and getId.
|
|
*/
|
|
@Test
|
|
void testThreadId1() throws Exception {
|
|
record ThreadIds(long threadId, long id) { }
|
|
var ref = new AtomicReference<ThreadIds>();
|
|
|
|
Thread vthread = Thread.ofVirtual().unstarted(() -> {
|
|
Thread thread = Thread.currentThread();
|
|
ref.set(new ThreadIds(thread.threadId(), thread.getId()));
|
|
LockSupport.park();
|
|
});
|
|
|
|
// unstarted
|
|
long tid = vthread.threadId();
|
|
|
|
// running
|
|
ThreadIds tids;
|
|
vthread.start();
|
|
try {
|
|
while ((tids = ref.get()) == null) {
|
|
Thread.sleep(10);
|
|
}
|
|
assertTrue(tids.threadId() == tid);
|
|
assertTrue(tids.id() == tid);
|
|
} finally {
|
|
LockSupport.unpark(vthread);
|
|
vthread.join();
|
|
}
|
|
|
|
// terminated
|
|
assertTrue(vthread.threadId() == tid);
|
|
assertTrue(vthread.getId() == tid);
|
|
}
|
|
|
|
/**
|
|
* Test that each Thread has a unique ID
|
|
*/
|
|
@Test
|
|
void testThreadId2() throws Exception {
|
|
// thread ID should be unique
|
|
long tid1 = Thread.ofVirtual().unstarted(() -> { }).threadId();
|
|
long tid2 = Thread.ofVirtual().unstarted(() -> { }).threadId();
|
|
long tid3 = Thread.currentThread().threadId();
|
|
assertFalse(tid1 == tid2);
|
|
assertFalse(tid1 == tid3);
|
|
assertFalse(tid2 == tid3);
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getState when thread is new/unstarted.
|
|
*/
|
|
@Test
|
|
void testGetState1() {
|
|
var thread = Thread.ofVirtual().unstarted(() -> { });
|
|
assertEquals(Thread.State.NEW, thread.getState());
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getState when thread is terminated.
|
|
*/
|
|
@Test
|
|
void testGetState2() throws Exception {
|
|
var thread = Thread.ofVirtual().start(() -> { });
|
|
thread.join();
|
|
assertEquals(Thread.State.TERMINATED, thread.getState());
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getState when thread is runnable (mounted).
|
|
*/
|
|
@Test
|
|
void testGetState3() throws Exception {
|
|
var started = new CountDownLatch(1);
|
|
var done = new AtomicBoolean();
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
started.countDown();
|
|
|
|
// spin until done
|
|
while (!done.get()) {
|
|
Thread.onSpinWait();
|
|
}
|
|
});
|
|
try {
|
|
// wait for thread to start
|
|
started.await();
|
|
|
|
// thread should be runnable
|
|
assertEquals(Thread.State.RUNNABLE, thread.getState());
|
|
} finally {
|
|
done.set(true);
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getState when thread is runnable (not mounted).
|
|
*/
|
|
@Test
|
|
void testGetState4() throws Exception {
|
|
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
|
AtomicBoolean completed = new AtomicBoolean();
|
|
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
|
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
|
Thread thread1 = factory.newThread(() -> {
|
|
Thread thread2 = factory.newThread(LockSupport::park);
|
|
assertEquals(Thread.State.NEW, thread2.getState());
|
|
|
|
// start t2 to make it runnable
|
|
thread2.start();
|
|
try {
|
|
assertEquals(Thread.State.RUNNABLE, thread2.getState());
|
|
|
|
// yield to allow t2 to run and park
|
|
Thread.yield();
|
|
assertEquals(Thread.State.WAITING, thread2.getState());
|
|
} finally {
|
|
// unpark t2 to make it runnable again
|
|
LockSupport.unpark(thread2);
|
|
}
|
|
|
|
// t2 should be runnable (not mounted)
|
|
assertEquals(Thread.State.RUNNABLE, thread2.getState());
|
|
|
|
completed.set(true);
|
|
});
|
|
thread1.start();
|
|
thread1.join();
|
|
}
|
|
assertTrue(completed.get() == true);
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getState when thread is blocked waiting to enter a monitor.
|
|
*/
|
|
@ParameterizedTest
|
|
@ValueSource(booleans = { true, false })
|
|
void testGetState5(boolean pinned) throws Exception {
|
|
var ready = new AtomicBoolean();
|
|
var thread = Thread.ofVirtual().unstarted(() -> {
|
|
if (pinned) {
|
|
VThreadPinner.runPinned(() -> {
|
|
ready.set(true);
|
|
synchronized (lock) { }
|
|
});
|
|
} else {
|
|
ready.set(true);
|
|
synchronized (lock) { }
|
|
}
|
|
});
|
|
synchronized (lock) {
|
|
thread.start();
|
|
awaitTrue(ready);
|
|
|
|
// wait for thread to block
|
|
await(thread, Thread.State.BLOCKED);
|
|
}
|
|
thread.join();
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getState when thread is waiting in Object.wait.
|
|
*/
|
|
@ParameterizedTest
|
|
@ValueSource(booleans = { true, false })
|
|
void testGetState6(boolean pinned) throws Exception {
|
|
var ready = new AtomicBoolean();
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
synchronized (lock) {
|
|
try {
|
|
if (pinned) {
|
|
VThreadPinner.runPinned(() -> {
|
|
ready.set(true);
|
|
lock.wait();
|
|
});
|
|
} else {
|
|
ready.set(true);
|
|
lock.wait();
|
|
}
|
|
} catch (InterruptedException e) { }
|
|
}
|
|
});
|
|
try {
|
|
// wait for thread to wait
|
|
awaitTrue(ready);
|
|
await(thread, Thread.State.WAITING);
|
|
|
|
// notify, thread should block trying to reenter
|
|
synchronized (lock) {
|
|
lock.notifyAll();
|
|
await(thread, Thread.State.BLOCKED);
|
|
}
|
|
} finally {
|
|
thread.interrupt();
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getState when thread is waiting in Object.wait(millis).
|
|
*/
|
|
@ParameterizedTest
|
|
@ValueSource(booleans = { true, false })
|
|
void testGetState7(boolean pinned) throws Exception {
|
|
var ready = new AtomicBoolean();
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
synchronized (lock) {
|
|
try {
|
|
if (pinned) {
|
|
VThreadPinner.runPinned(() -> {
|
|
ready.set(true);
|
|
lock.wait(Long.MAX_VALUE);
|
|
});
|
|
} else {
|
|
ready.set(true);
|
|
lock.wait(Long.MAX_VALUE);
|
|
}
|
|
} catch (InterruptedException e) { }
|
|
}
|
|
});
|
|
try {
|
|
// wait for thread to timed-wait
|
|
awaitTrue(ready);
|
|
await(thread, Thread.State.TIMED_WAITING);
|
|
|
|
// notify, thread should block trying to reenter
|
|
synchronized (lock) {
|
|
lock.notifyAll();
|
|
await(thread, Thread.State.BLOCKED);
|
|
}
|
|
} finally {
|
|
thread.interrupt();
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getState when thread is parked.
|
|
*/
|
|
@Test
|
|
void testGetState8() throws Exception {
|
|
var thread = Thread.ofVirtual().start(LockSupport::park);
|
|
try {
|
|
await(thread, Thread.State.WAITING);
|
|
} finally {
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getState when thread is timed parked.
|
|
*/
|
|
@Test
|
|
void testGetState9() throws Exception {
|
|
var thread = Thread.ofVirtual().start(() -> LockSupport.parkNanos(Long.MAX_VALUE));
|
|
try {
|
|
await(thread, Thread.State.TIMED_WAITING);
|
|
} finally {
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getState when thread is parked while holding a monitor.
|
|
*/
|
|
@Test
|
|
void testGetState10() throws Exception {
|
|
var started = new CountDownLatch(1);
|
|
var done = new AtomicBoolean();
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
started.countDown();
|
|
synchronized (lock) {
|
|
while (!done.get()) {
|
|
LockSupport.park();
|
|
}
|
|
}
|
|
});
|
|
try {
|
|
// wait for thread to start
|
|
started.await();
|
|
|
|
// wait for thread to park
|
|
await(thread, Thread.State.WAITING);
|
|
} finally {
|
|
done.set(true);
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getState when thread is timed parked while holding a monitor.
|
|
*/
|
|
@Test
|
|
void testGetState11() throws Exception {
|
|
var started = new CountDownLatch(1);
|
|
var done = new AtomicBoolean();
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
started.countDown();
|
|
synchronized (lock) {
|
|
while (!done.get()) {
|
|
LockSupport.parkNanos(Long.MAX_VALUE);
|
|
}
|
|
}
|
|
});
|
|
try {
|
|
// wait for thread to start
|
|
started.await();
|
|
|
|
// wait for thread to park
|
|
await(thread, Thread.State.TIMED_WAITING);
|
|
} finally {
|
|
done.set(true);
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Thread::isAlive.
|
|
*/
|
|
@Test
|
|
void testIsAlive1() throws Exception {
|
|
// unstarted
|
|
var thread = Thread.ofVirtual().unstarted(LockSupport::park);
|
|
assertFalse(thread.isAlive());
|
|
|
|
// started
|
|
thread.start();
|
|
try {
|
|
assertTrue(thread.isAlive());
|
|
} finally {
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
|
|
// terminated
|
|
assertFalse(thread.isAlive());
|
|
}
|
|
|
|
/**
|
|
* Test Thread.holdsLock when lock not held.
|
|
*/
|
|
@Test
|
|
void testHoldsLock1() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
var lock = new Object();
|
|
assertFalse(Thread.holdsLock(lock));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test Thread.holdsLock when lock held.
|
|
*/
|
|
@Test
|
|
void testHoldsLock2() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
var lock = new Object();
|
|
synchronized (lock) {
|
|
assertTrue(Thread.holdsLock(lock));
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test Thread.holdsLock when lock held by carrier thread.
|
|
*/
|
|
@Disabled
|
|
@Test
|
|
void testHoldsLock3() throws Exception {
|
|
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
|
|
|
Object lock = new Object();
|
|
|
|
// carrier thread runs all tasks while holding the lock
|
|
ThreadFactory carrierThreadFactory = task -> Thread.ofPlatform().unstarted(() -> {
|
|
synchronized (lock) {
|
|
task.run();
|
|
}
|
|
});
|
|
try (ExecutorService pool = Executors.newSingleThreadExecutor(carrierThreadFactory)) {
|
|
Executor scheduler = task -> pool.submit(task::run);
|
|
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
|
|
|
// start virtual that tests if it holds the lock
|
|
var result = new AtomicReference<Boolean>();
|
|
Thread vthread = factory.newThread(() -> {
|
|
result.set(Thread.holdsLock(lock));
|
|
});
|
|
vthread.start();
|
|
vthread.join();
|
|
boolean holdsLock = result.get();
|
|
assertFalse(holdsLock, "Thread.holdsLock should return false");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getStackTrace on unstarted thread.
|
|
*/
|
|
@Test
|
|
void testGetStackTraceUnstarted() {
|
|
var thread = Thread.ofVirtual().unstarted(() -> { });
|
|
StackTraceElement[] stack = thread.getStackTrace();
|
|
assertTrue(stack.length == 0);
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getStackTrace on thread that has been started but has not run.
|
|
*/
|
|
@Test
|
|
void testGetStackTraceStarted() throws Exception {
|
|
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
|
Executor scheduler = task -> { };
|
|
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
|
Thread thread = factory.newThread(() -> { });
|
|
thread.start();
|
|
StackTraceElement[] stack = thread.getStackTrace();
|
|
assertTrue(stack.length == 0);
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getStackTrace on thread that is runnable-mounted.
|
|
*/
|
|
@Test
|
|
void testGetStackTraceRunnableMounted() throws Exception {
|
|
var ready = new AtomicBoolean();
|
|
var done = new AtomicBoolean();
|
|
|
|
class Foo {
|
|
void spinUntilDone() {
|
|
ready.set(true);
|
|
while (!done.get()) {
|
|
Thread.onSpinWait();
|
|
}
|
|
}
|
|
}
|
|
|
|
Foo foo = new Foo();
|
|
var thread = Thread.ofVirtual().start(foo::spinUntilDone);
|
|
try {
|
|
awaitTrue(ready);
|
|
StackTraceElement[] stack = thread.getStackTrace();
|
|
assertTrue(contains(stack, Foo.class.getName() + ".spinUntilDone"));
|
|
} finally {
|
|
done.set(true);
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getStackTrace on thread that is runnable-unmounted.
|
|
*/
|
|
@Test
|
|
void testGetStackTraceRunnableUnmounted() throws Exception {
|
|
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
|
|
|
// custom scheduler with one carrier thread
|
|
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
|
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
|
|
|
// start thread1 to park
|
|
Thread thread1 = factory.newThread(LockSupport::park);
|
|
thread1.start();
|
|
await(thread1, Thread.State.WAITING);
|
|
|
|
// start thread2 to spin and pin the carrier thread
|
|
var started = new AtomicBoolean();
|
|
var done = new AtomicBoolean();
|
|
Thread thread2 = factory.newThread(() -> {
|
|
started.set(true);
|
|
while (!done.get()) {
|
|
Thread.onSpinWait();
|
|
}
|
|
});
|
|
thread2.start();
|
|
awaitTrue(started);
|
|
|
|
try {
|
|
// unpark thread1, it should be "stuck" in runnable state
|
|
// (the carrier thread is pinned, no other virtual thread can run)
|
|
LockSupport.unpark(thread1);
|
|
assertEquals(Thread.State.RUNNABLE, thread1.getState());
|
|
|
|
// print thread1's stack trace
|
|
StackTraceElement[] stack = thread1.getStackTrace();
|
|
assertTrue(contains(stack, "LockSupport.park"));
|
|
|
|
} finally {
|
|
done.set(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getStackTrace on thread blocked on monitor enter.
|
|
*/
|
|
@ParameterizedTest
|
|
@ValueSource(booleans = { true, false })
|
|
void testGetStackTraceBlocked(boolean pinned) throws Exception {
|
|
class Foo {
|
|
void enter() {
|
|
synchronized (this) { }
|
|
}
|
|
}
|
|
Foo foo = new Foo();
|
|
var ready = new AtomicBoolean();
|
|
var thread = Thread.ofVirtual().unstarted(() -> {
|
|
if (pinned) {
|
|
VThreadPinner.runPinned(() -> {
|
|
ready.set(true);
|
|
foo.enter();
|
|
});
|
|
} else {
|
|
ready.set(true);
|
|
foo.enter();
|
|
}
|
|
});
|
|
synchronized (foo) {
|
|
thread.start();
|
|
awaitTrue(ready);
|
|
|
|
// wait for thread to block
|
|
await(thread, Thread.State.BLOCKED);
|
|
|
|
StackTraceElement[] stack = thread.getStackTrace();
|
|
assertTrue(contains(stack, Foo.class.getName() + ".enter"));
|
|
}
|
|
thread.join();
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getStackTrace when thread is waiting in Object.wait.
|
|
*/
|
|
@ParameterizedTest
|
|
@ValueSource(booleans = { true, false })
|
|
void testGetStackTraceWaiting(boolean pinned) throws Exception {
|
|
var ready = new AtomicBoolean();
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
synchronized (lock) {
|
|
try {
|
|
if (pinned) {
|
|
VThreadPinner.runPinned(() -> {
|
|
ready.set(true);
|
|
lock.wait();
|
|
});
|
|
} else {
|
|
ready.set(true);
|
|
lock.wait();
|
|
}
|
|
} catch (InterruptedException e) { }
|
|
}
|
|
});
|
|
try {
|
|
// wait for thread to wait
|
|
awaitTrue(ready);
|
|
await(thread, Thread.State.WAITING);
|
|
|
|
StackTraceElement[] stack = thread.getStackTrace();
|
|
assertTrue(contains(stack, "Object.wait"));
|
|
} finally {
|
|
thread.interrupt();
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getStackTrace when thread is waiting in timed-Object.wait.
|
|
*/
|
|
@ParameterizedTest
|
|
@ValueSource(booleans = { true, false })
|
|
void testGetStackTraceTimedWaiting(boolean pinned) throws Exception {
|
|
var ready = new AtomicBoolean();
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
synchronized (lock) {
|
|
try {
|
|
if (pinned) {
|
|
VThreadPinner.runPinned(() -> {
|
|
ready.set(true);
|
|
lock.wait(Long.MAX_VALUE);
|
|
});
|
|
} else {
|
|
ready.set(true);
|
|
lock.wait(Long.MAX_VALUE);
|
|
}
|
|
} catch (InterruptedException e) { }
|
|
}
|
|
});
|
|
try {
|
|
// wait for thread to wait
|
|
awaitTrue(ready);
|
|
await(thread, Thread.State.TIMED_WAITING);
|
|
|
|
StackTraceElement[] stack = thread.getStackTrace();
|
|
assertTrue(contains(stack, "Object.wait"));
|
|
} finally {
|
|
thread.interrupt();
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getStackTrace when thread in park.
|
|
*/
|
|
@ParameterizedTest
|
|
@ValueSource(booleans = { true, false })
|
|
void testGetStackTraceParked(boolean pinned) throws Exception {
|
|
var ready = new AtomicBoolean();
|
|
var done = new AtomicBoolean();
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
if (pinned) {
|
|
VThreadPinner.runPinned(() -> {
|
|
ready.set(true);
|
|
while (!done.get()) {
|
|
LockSupport.park();
|
|
}
|
|
});
|
|
} else {
|
|
ready.set(true);
|
|
while (!done.get()) {
|
|
LockSupport.park();
|
|
}
|
|
}
|
|
});
|
|
try {
|
|
// wait for thread to park
|
|
awaitTrue(ready);
|
|
await(thread, Thread.State.WAITING);
|
|
|
|
StackTraceElement[] stack = thread.getStackTrace();
|
|
assertTrue(contains(stack, "LockSupport.park"));
|
|
} finally {
|
|
done.set(true);
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getStackTrace when thread in timed-park.
|
|
*/
|
|
@ParameterizedTest
|
|
@ValueSource(booleans = { true, false })
|
|
void testGetStackTraceTimedPark(boolean pinned) throws Exception {
|
|
var ready = new AtomicBoolean();
|
|
var done = new AtomicBoolean();
|
|
var thread = Thread.ofVirtual().start(() -> {
|
|
if (pinned) {
|
|
ready.set(true);
|
|
VThreadPinner.runPinned(() -> {
|
|
while (!done.get()) {
|
|
LockSupport.parkNanos(Long.MAX_VALUE);
|
|
}
|
|
});
|
|
} else {
|
|
ready.set(true);
|
|
while (!done.get()) {
|
|
LockSupport.parkNanos(Long.MAX_VALUE);
|
|
}
|
|
}
|
|
});
|
|
try {
|
|
// wait for thread to park
|
|
awaitTrue(ready);
|
|
await(thread, Thread.State.TIMED_WAITING);
|
|
|
|
StackTraceElement[] stack = thread.getStackTrace();
|
|
assertTrue(contains(stack, "LockSupport.parkNanos"));
|
|
} finally {
|
|
done.set(true);
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getStackTrace on terminated thread.
|
|
*/
|
|
@Test
|
|
void testGetStackTraceTerminated() throws Exception {
|
|
var thread = Thread.ofVirtual().start(() -> { });
|
|
thread.join();
|
|
StackTraceElement[] stack = thread.getStackTrace();
|
|
assertTrue(stack.length == 0);
|
|
}
|
|
|
|
/**
|
|
* Test that Thread.getAllStackTraces does not include virtual threads.
|
|
*/
|
|
@Test
|
|
void testGetAllStackTraces1() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
|
assertFalse(threads.stream().anyMatch(Thread::isVirtual));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test that Thread.getAllStackTraces includes carrier threads.
|
|
*/
|
|
@Test
|
|
void testGetAllStackTraces2() throws Exception {
|
|
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
|
try (ForkJoinPool pool = new ForkJoinPool(1)) {
|
|
AtomicReference<Thread> ref = new AtomicReference<>();
|
|
Executor scheduler = task -> {
|
|
pool.submit(() -> {
|
|
ref.set(Thread.currentThread());
|
|
task.run();
|
|
});
|
|
};
|
|
|
|
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
|
Thread vthread = factory.newThread(() -> {
|
|
synchronized (lock) {
|
|
try {
|
|
lock.wait();
|
|
} catch (Exception e) { }
|
|
}
|
|
});
|
|
vthread.start();
|
|
|
|
// get carrier Thread
|
|
Thread carrier;
|
|
while ((carrier = ref.get()) == null) {
|
|
Thread.sleep(20);
|
|
}
|
|
|
|
// wait for virtual thread to block in wait
|
|
await(vthread, Thread.State.WAITING);
|
|
|
|
// get all stack traces
|
|
Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
|
|
|
|
// allow virtual thread to terminate
|
|
synchronized (lock) {
|
|
lock.notifyAll();
|
|
}
|
|
vthread.join();
|
|
|
|
// stack trace for the carrier thread
|
|
StackTraceElement[] stackTrace = map.get(carrier);
|
|
assertNotNull(stackTrace);
|
|
assertTrue(contains(stackTrace, "java.util.concurrent.ForkJoinPool"));
|
|
assertFalse(contains(stackTrace, "java.lang.Object.wait"));
|
|
|
|
// there should be no stack trace for the virtual thread
|
|
assertNull(map.get(vthread));
|
|
}
|
|
}
|
|
|
|
private boolean contains(StackTraceElement[] stack, String expected) {
|
|
return Stream.of(stack)
|
|
.map(Object::toString)
|
|
.anyMatch(s -> s.contains(expected));
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getThreadGroup on virtual thread created by platform thread.
|
|
*/
|
|
@Test
|
|
void testThreadGroup1() throws Exception {
|
|
var thread = Thread.ofVirtual().unstarted(LockSupport::park);
|
|
var vgroup = thread.getThreadGroup();
|
|
thread.start();
|
|
try {
|
|
assertEquals(vgroup, thread.getThreadGroup());
|
|
} finally {
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
assertNull(thread.getThreadGroup());
|
|
}
|
|
|
|
/**
|
|
* Test Thread::getThreadGroup on platform thread created by virtual thread.
|
|
*/
|
|
@Test
|
|
void testThreadGroup2() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
|
|
Thread child = new Thread(() -> { });
|
|
ThreadGroup group = child.getThreadGroup();
|
|
assertEquals(vgroup, group);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test ThreadGroup returned by Thread::getThreadGroup and subgroup
|
|
* created with 2-arg ThreadGroup constructor.
|
|
*/
|
|
@Test
|
|
void testThreadGroup3() throws Exception {
|
|
var ref = new AtomicReference<ThreadGroup>();
|
|
var thread = Thread.startVirtualThread(() -> {
|
|
ref.set(Thread.currentThread().getThreadGroup());
|
|
});
|
|
thread.join();
|
|
|
|
ThreadGroup vgroup = ref.get();
|
|
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
|
|
|
|
ThreadGroup group = new ThreadGroup(vgroup, "group");
|
|
assertTrue(group.getParent() == vgroup);
|
|
assertEquals(Thread.MAX_PRIORITY, group.getMaxPriority());
|
|
|
|
vgroup.setMaxPriority(Thread.MAX_PRIORITY - 1);
|
|
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
|
|
assertEquals(Thread.MAX_PRIORITY - 1, group.getMaxPriority());
|
|
|
|
vgroup.setMaxPriority(Thread.MIN_PRIORITY);
|
|
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
|
|
assertEquals(Thread.MIN_PRIORITY, group.getMaxPriority());
|
|
}
|
|
|
|
/**
|
|
* Test ThreadGroup returned by Thread::getThreadGroup and subgroup
|
|
* created with 1-arg ThreadGroup constructor.
|
|
*/
|
|
@Test
|
|
void testThreadGroup4() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
|
|
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
|
|
|
|
ThreadGroup group = new ThreadGroup("group");
|
|
assertEquals(vgroup, group.getParent());
|
|
assertEquals(Thread.MAX_PRIORITY, group.getMaxPriority());
|
|
|
|
vgroup.setMaxPriority(Thread.MAX_PRIORITY - 1);
|
|
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
|
|
assertEquals(Thread.MAX_PRIORITY - 1, group.getMaxPriority());
|
|
|
|
vgroup.setMaxPriority(Thread.MIN_PRIORITY);
|
|
assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority());
|
|
assertEquals(Thread.MIN_PRIORITY, group.getMaxPriority());
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test Thread.enumerate(false).
|
|
*/
|
|
@Test
|
|
void testEnumerate1() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
|
|
Thread[] threads = new Thread[100];
|
|
int n = vgroup.enumerate(threads, /*recurse*/false);
|
|
assertFalse(Arrays.stream(threads, 0, n).anyMatch(Thread::isVirtual));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test Thread.enumerate(true).
|
|
*/
|
|
@Test
|
|
void testEnumerate2() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
|
|
Thread[] threads = new Thread[100];
|
|
int n = vgroup.enumerate(threads, /*recurse*/true);
|
|
assertFalse(Arrays.stream(threads, 0, n).anyMatch(Thread::isVirtual));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test equals and hashCode.
|
|
*/
|
|
@Test
|
|
void testEqualsAndHashCode() throws Exception {
|
|
Thread vthread1 = Thread.ofVirtual().unstarted(LockSupport::park);
|
|
Thread vthread2 = Thread.ofVirtual().unstarted(LockSupport::park);
|
|
|
|
// unstarted
|
|
assertTrue(vthread1.equals(vthread1));
|
|
assertTrue(vthread2.equals(vthread2));
|
|
assertFalse(vthread1.equals(vthread2));
|
|
assertFalse(vthread2.equals(vthread1));
|
|
int hc1 = vthread1.hashCode();
|
|
int hc2 = vthread2.hashCode();
|
|
|
|
vthread1.start();
|
|
vthread2.start();
|
|
try {
|
|
// started, maybe running or parked
|
|
assertTrue(vthread1.equals(vthread1));
|
|
assertTrue(vthread2.equals(vthread2));
|
|
assertFalse(vthread1.equals(vthread2));
|
|
assertFalse(vthread2.equals(vthread1));
|
|
assertTrue(vthread1.hashCode() == hc1);
|
|
assertTrue(vthread2.hashCode() == hc2);
|
|
} finally {
|
|
LockSupport.unpark(vthread1);
|
|
LockSupport.unpark(vthread2);
|
|
}
|
|
vthread1.join();
|
|
vthread2.join();
|
|
|
|
// terminated
|
|
assertTrue(vthread1.equals(vthread1));
|
|
assertTrue(vthread2.equals(vthread2));
|
|
assertFalse(vthread1.equals(vthread2));
|
|
assertFalse(vthread2.equals(vthread1));
|
|
assertTrue(vthread1.hashCode() == hc1);
|
|
assertTrue(vthread2.hashCode() == hc2);
|
|
}
|
|
|
|
/**
|
|
* Test toString on unstarted thread.
|
|
*/
|
|
@Test
|
|
void testToString1() {
|
|
Thread thread = Thread.ofVirtual().unstarted(() -> { });
|
|
thread.setName("fred");
|
|
assertTrue(thread.toString().contains("fred"));
|
|
}
|
|
|
|
/**
|
|
* Test toString on running thread.
|
|
*/
|
|
@Test
|
|
void testToString2() throws Exception {
|
|
VThreadRunner.run(() -> {
|
|
Thread me = Thread.currentThread();
|
|
me.setName("fred");
|
|
assertTrue(me.toString().contains("fred"));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test toString on parked thread.
|
|
*/
|
|
@Test
|
|
void testToString3() throws Exception {
|
|
Thread thread = Thread.ofVirtual().start(() -> {
|
|
Thread me = Thread.currentThread();
|
|
me.setName("fred");
|
|
LockSupport.park();
|
|
});
|
|
await(thread, Thread.State.WAITING);
|
|
try {
|
|
assertTrue(thread.toString().contains("fred"));
|
|
} finally {
|
|
LockSupport.unpark(thread);
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test toString on terminated thread.
|
|
*/
|
|
@Test
|
|
void testToString4() throws Exception {
|
|
Thread thread = Thread.ofVirtual().start(() -> {
|
|
Thread me = Thread.currentThread();
|
|
me.setName("fred");
|
|
});
|
|
thread.join();
|
|
assertTrue(thread.toString().contains("fred"));
|
|
}
|
|
|
|
/**
|
|
* Thread.UncaughtExceptionHandler that captures the first exception thrown.
|
|
*/
|
|
private static class CapturingUHE implements Thread.UncaughtExceptionHandler {
|
|
Thread thread;
|
|
Throwable exception;
|
|
@Override
|
|
public void uncaughtException(Thread t, Throwable e) {
|
|
synchronized (this) {
|
|
if (thread == null) {
|
|
this.thread = t;
|
|
this.exception = e;
|
|
}
|
|
}
|
|
}
|
|
Thread thread() {
|
|
synchronized (this) {
|
|
return thread;
|
|
}
|
|
}
|
|
Throwable exception() {
|
|
synchronized (this) {
|
|
return exception;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Waits for the boolean value to become true.
|
|
*/
|
|
private static void awaitTrue(AtomicBoolean ref) throws Exception {
|
|
while (!ref.get()) {
|
|
Thread.sleep(20);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Waits for the given thread to reach a given state.
|
|
*/
|
|
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
|
Thread.State state = thread.getState();
|
|
while (state != expectedState) {
|
|
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
|
|
Thread.sleep(10);
|
|
state = thread.getState();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Schedule a thread to be interrupted after a delay.
|
|
*/
|
|
private void scheduleInterrupt(Thread thread, long delayInMillis) {
|
|
scheduler.schedule(thread::interrupt, delayInMillis, TimeUnit.MILLISECONDS);
|
|
}
|
|
}
|