Skip to content

Commit bd93f74

Browse files
authored
Merge pull request #3870 from armanbilge/issue/3869
Block in-place if running on a virtual thread
2 parents c78e4ee + bc0dab1 commit bd93f74

5 files changed

Lines changed: 71 additions & 1 deletion

File tree

core/js-native/src/main/scala/cats/effect/IOFiberConstants.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package cats.effect
1818

19+
import org.typelevel.scalaccompat.annotation._
20+
1921
// defined in Java for the JVM, Scala for ScalaJS (where object field access is faster)
2022
private object IOFiberConstants {
2123

@@ -43,4 +45,7 @@ private object IOFiberConstants {
4345
final val CedeR = 6
4446
final val AutoCedeR = 7
4547
final val DoneR = 8
48+
49+
@nowarn212
50+
@inline def isVirtualThread(t: Thread): Boolean = false
4651
}

core/jvm/src/main/java/cats/effect/IOFiberConstants.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616

1717
package cats.effect;
1818

19+
import java.lang.invoke.MethodHandle;
20+
import java.lang.invoke.MethodHandles;
21+
import java.lang.invoke.MethodType;
22+
1923
// defined in Java since Scala doesn't let us define static fields
2024
final class IOFiberConstants {
2125

@@ -43,4 +47,28 @@ final class IOFiberConstants {
4347
static final byte CedeR = 6;
4448
static final byte AutoCedeR = 7;
4549
static final byte DoneR = 8;
50+
51+
static boolean isVirtualThread(final Thread thread) {
52+
try {
53+
return (boolean) THREAD_IS_VIRTUAL_HANDLE.invokeExact(thread);
54+
} catch (Throwable t) {
55+
return false;
56+
}
57+
}
58+
59+
private static final MethodHandle THREAD_IS_VIRTUAL_HANDLE;
60+
61+
static {
62+
final MethodHandles.Lookup lookup = MethodHandles.publicLookup();
63+
final MethodType mt = MethodType.methodType(boolean.class);
64+
MethodHandle mh;
65+
try {
66+
mh = lookup.findVirtual(Thread.class, "isVirtual", mt);
67+
} catch (Throwable t) {
68+
mh =
69+
MethodHandles.dropArguments(
70+
MethodHandles.constant(boolean.class, false), 0, Thread.class);
71+
}
72+
THREAD_IS_VIRTUAL_HANDLE = mh;
73+
}
4674
}

core/shared/src/main/scala/cats/effect/IOFiber.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,20 @@ private final class IOFiber[A](
996996
} else {
997997
blockingFallback(cur)
998998
}
999+
} else if (isVirtualThread(Thread.currentThread())) {
1000+
var error: Throwable = null
1001+
val r =
1002+
try {
1003+
cur.thunk()
1004+
} catch {
1005+
case t if NonFatal(t) =>
1006+
error = t
1007+
case t: Throwable =>
1008+
onFatalFailure(t)
1009+
}
1010+
1011+
val next = if (error eq null) succeeded(r, 0) else failed(error, 0)
1012+
runLoop(next, nextCancelation, nextAutoCede)
9991013
} else {
10001014
blockingFallback(cur)
10011015
}

tests/jvm/src/test/scala/cats/effect/DetectPlatform.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,7 @@ trait DetectPlatform {
2121
def isJS: Boolean = false
2222
def isJVM: Boolean = true
2323
def isNative: Boolean = false
24+
25+
def javaMajorVersion: Int =
26+
System.getProperty("java.version").stripPrefix("1.").takeWhile(_.isDigit).toInt
2427
}

tests/jvm/src/test/scala/cats/effect/IOPlatformSpecification.scala

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,13 @@ import java.util.concurrent.{
3636
CancellationException,
3737
CompletableFuture,
3838
CountDownLatch,
39+
ExecutorService,
3940
Executors,
4041
ThreadLocalRandom
4142
}
4243
import java.util.concurrent.atomic.{AtomicBoolean, AtomicInteger, AtomicLong, AtomicReference}
4344

44-
trait IOPlatformSpecification { self: BaseSpec with ScalaCheck =>
45+
trait IOPlatformSpecification extends DetectPlatform { self: BaseSpec with ScalaCheck =>
4546

4647
def platformSpecs = {
4748
"platform" should {
@@ -531,6 +532,25 @@ trait IOPlatformSpecification { self: BaseSpec with ScalaCheck =>
531532
runtime.shutdown()
532533
}
533534
}
535+
536+
if (javaMajorVersion >= 21)
537+
"block in-place on virtual threads" in real {
538+
val loomExec = classOf[Executors]
539+
.getDeclaredMethod("newVirtualThreadPerTaskExecutor")
540+
.invoke(null)
541+
.asInstanceOf[ExecutorService]
542+
543+
val loomEc = ExecutionContext.fromExecutor(loomExec)
544+
545+
IO.blocking {
546+
classOf[Thread]
547+
.getDeclaredMethod("isVirtual")
548+
.invoke(Thread.currentThread())
549+
.asInstanceOf[Boolean]
550+
}.evalOn(loomEc)
551+
}
552+
else
553+
"block in-place on virtual threads" in skipped("virtual threads not supported")
534554
}
535555
}
536556
}

0 commit comments

Comments
 (0)