Skip to content

Instantly share code, notes, and snippets.

@lbialy
Created September 19, 2025 14:13
Show Gist options
  • Save lbialy/9f3732ce9901ba76ee2da9dd86f7df33 to your computer and use it in GitHub Desktop.
Save lbialy/9f3732ce9901ba76ee2da9dd86f7df33 to your computer and use it in GitHub Desktop.
Different thread yielding techniques with Loom
//> using scala 3.7.3
//> using dep com.softwaremill.ox::core:1.0.0
import scala.concurrent.duration.*
import java.time.Instant
import ox.*
import scala.util.boundary
import java.util.ArrayList
import java.util.concurrent.locks.LockSupport
object TestYielding:
inline def nowMillis = System.currentTimeMillis()
inline def busyLoop(howOftenYield: Duration, duration: Duration, inline yieldOp: => Unit): Unit =
val start = Instant.now()
val endMillis = start.plusMillis(duration.toMillis).toEpochMilli()
val howOftenYieldMillis = howOftenYield.toMillis
var lastYield = start.toEpochMilli()
boundary:
while true do
val now = nowMillis
if now - lastYield >= howOftenYieldMillis then
lastYield = now
yieldOp
if now >= endMillis then boundary.break()
inline def test(
howOftenYield: Duration,
howOftenProbeLiveness: Duration,
duration: Duration,
cores: Int,
verbose: Boolean,
inline yieldOp: => Unit
): Unit =
val maxProbes = duration / howOftenProbeLiveness - 1
supervised:
// put busy loops in separate VTs
forkUser:
(1 to cores).mapPar(cores): idx =>
if verbose then println(s"Starting busy loop $idx")
busyLoop(howOftenYield, duration, yieldOp)
if verbose then println(s"Finished busy loop $idx")
// run a single separate VT that probes liveness
val livenessFork = forkUser:
val howOftenProbeLivenessMillis = howOftenProbeLiveness.toMillis
val startMillis = nowMillis
val endMillis = startMillis + duration.toMillis
val sampleTimestamps = ArrayList[Long]()
var lastProbe = startMillis
boundary:
while true do
val now = nowMillis
if now >= endMillis then boundary.break()
if now - lastProbe >= howOftenProbeLivenessMillis then
if verbose then
println(
s"Probed liveness: now ${now}, last probe ${lastProbe}, difference ${now - lastProbe}, how often probe liveness ${howOftenProbeLivenessMillis}"
)
lastProbe = now
sampleTimestamps.add(now)
Thread.sleep(howOftenProbeLivenessMillis)
sampleTimestamps
val livenessRecord = livenessFork.join()
println(s"Max liveness probes possible: ${maxProbes}")
val capturedProbesPercentage = livenessRecord.size() * 100.0 / maxProbes
val capturedProbesPercentageString = capturedProbesPercentage.toInt.toString + "%"
println(s"Captured ${livenessRecord.size()} liveness probes (${capturedProbesPercentageString})")
@main def main(): Unit =
val cores = Runtime.getRuntime().availableProcessors()
println(s"Testing with ${cores} cores")
println("Testing always yielding, 1 core in busy loop, using Thread.yield, should capture 100% of liveness probes.")
TestYielding.test(
howOftenYield = 10.millis,
howOftenProbeLiveness = 100.millis,
duration = 5.seconds,
cores = 1,
verbose = false,
yieldOp = Thread.`yield`
)
println("--------------------------------")
println("Testing never yielding, all cores in busy loop, using Thread.yield, should capture 2% of liveness probes.")
TestYielding.test(
howOftenYield = 1.hour,
howOftenProbeLiveness = 100.millis,
duration = 5.seconds,
cores = cores,
verbose = false,
yieldOp = Thread.`yield`
)
println("--------------------------------")
println("Testing yielding every 10ms, all cores in busy loop, using Thread.yield, should capture 100% of liveness probes.")
TestYielding.test(
howOftenYield = 10.millis,
howOftenProbeLiveness = 100.millis,
duration = 5.seconds,
cores = cores,
verbose = false,
yieldOp = Thread.`yield`
)
println("--------------------------------")
println("Testing yielding every 10ms, all cores in busy loop, using Thread.sleep(0), should capture 100% of liveness probes.")
TestYielding.test(
howOftenYield = 10.millis,
howOftenProbeLiveness = 100.millis,
duration = 5.seconds,
cores = cores,
verbose = false,
yieldOp = Thread.sleep(0)
)
println("--------------------------------")
println("Testing yielding every 10ms, all cores in busy loop, using LockSupport.parkNanos(0), should capture 100% of liveness probes.")
TestYielding.test(
howOftenYield = 10.millis,
howOftenProbeLiveness = 100.millis,
duration = 5.seconds,
cores = cores,
verbose = false,
yieldOp = LockSupport.parkNanos(0)
)
println("--------------------------------")
println("Testing yielding every 10ms, all cores in busy loop, using LockSupport.parkNanos(1), should capture 100% of liveness probes.")
TestYielding.test(
howOftenYield = 10.millis,
howOftenProbeLiveness = 100.millis,
duration = 5.seconds,
cores = cores,
verbose = false,
yieldOp = LockSupport.parkNanos(1)
)
println("--------------------------------")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment