Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Scala Native multithreading #861

Merged
merged 3 commits into from
Dec 17, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 65 additions & 5 deletions munit/native/src/main/scala/munit/internal/PlatformCompat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,35 @@ import sbt.testing.EventHandler
import sbt.testing.Logger
import scala.concurrent.Await
import scala.concurrent.Awaitable
import scala.concurrent.Promise
import scala.concurrent.duration.Duration
import scala.concurrent.ExecutionContext
import java.util.concurrent.{Executors, ThreadFactory, TimeoutException, TimeUnit}
import java.util.concurrent.atomic.AtomicInteger
import scala.scalanative.meta.LinktimeInfo.isMultithreadingEnabled

// Delay reachability of multithreading capability
// Would not force multithreading support unless explicitly configured by the user
// or if using threads in the tests
private object LazyMultithreadingSupport {
val sh = Executors.newSingleThreadScheduledExecutor(
new ThreadFactory {
val counter = new AtomicInteger
def threadNumber() = counter.incrementAndGet()
def newThread(r: Runnable) =
new Thread(r, s"munit-scheduler-${threadNumber()}") {
setDaemon(true)
setPriority(Thread.NORM_PRIORITY)
}
}
)
}
object PlatformCompat {
import LazyMultithreadingSupport._

def awaitResult[T](awaitable: Awaitable[T]): T = {
scalanative.runtime.loop()
if (!isMultithreadingEnabled)
Thread.`yield`() // invokes SN 0.4 scalanative.runtime.loop()
Await.result(awaitable, Duration.Inf)
}

Expand All @@ -26,18 +49,55 @@ object PlatformCompat {
task.execute(eventHandler, loggers)
Future.successful(())
}

def waitAtMost[T](
startFuture: () => Future[T],
duration: Duration,
ec: ExecutionContext
): Future[T] = {
startFuture()
if (!isMultithreadingEnabled) startFuture()
else {
val onComplete = Promise[T]()
val timeout =
if (duration.isFinite)
Some(
sh.schedule[Unit](
() =>
onComplete.tryFailure(
new TimeoutException(s"test timed out after $duration")
),
duration.toMillis,
TimeUnit.MILLISECONDS
)
)
else
None
ec.execute(new Runnable {
def run(): Unit = {
startFuture().onComplete { result =>
onComplete.tryComplete(result)
timeout.foreach(_.cancel(false))
}(ec)
}
})
onComplete.future
}
}

def setTimeout(ms: Int)(body: => Unit): () => Unit = {
Thread.sleep(ms)
body
if (!isMultithreadingEnabled) {
// Thread.sleep(ms)
Copy link
Collaborator

@valencik valencik Jan 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WojciechMazur
Why was this added but commented out?
Now it looks like if multithreading is disabled we ignore timeout durations and run body immediately?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what's the story of original Thread.sleep call but it makes no sense to have it. The goal of this method was to introduce timeouts, instead it was only slowing the execution, while there was nothing else running in the background.

body
() => ()
} else {
val scheduled = sh.schedule[Unit](
() => body,
ms,
TimeUnit.MILLISECONDS
)

() => ()
() => scheduled.cancel(false)
}
}

// Scala Native does not support looking up annotations at runtime.
Expand Down