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

Print warning when we detect a non-main-thread main #3774

Merged
merged 3 commits into from
Sep 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
27 changes: 27 additions & 0 deletions core/jvm/src/main/scala/cats/effect/IOApp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,32 @@ trait IOApp {
protected def onCpuStarvationWarn(metrics: CpuStarvationWarningMetrics): IO[Unit] =
CpuStarvationCheck.logWarning(metrics)

/**
* Defines what to do when IOApp detects that `main` is being invoked on a `Thread` which
* isn't the main process thread. This condition can happen when we are running inside of an
* `sbt run` with `fork := false`
*/
private def onNonMainThreadDetected(): Unit = {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This can probably become protected in the next API break, though honestly I'm skeptical that it's valuable to expose at all

Copy link
Member

Choose a reason for hiding this comment

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

Yeah I'd rather not expose an API like this in IOApp. :-) Better to keep it private.

val shouldPrint =
Option(System.getProperty("cats.effect.warnOnNonMainThreadDetected"))
.map(_.equalsIgnoreCase("true"))
.getOrElse(true)
if (shouldPrint)
System
.err
.println(
"""|[WARNING] IOApp `main` is running on a thread other than the main thread.
|This may prevent correct resource cleanup after `main` completes.
|This condition could be caused by executing `run` in an interactive sbt session with `fork := false`.
|Set `Compile / run / fork := true` in this project to resolve this.
Daenyth marked this conversation as resolved.
Show resolved Hide resolved
|
|To silence this warning set the system property:
|`-Dcats.effect.warnOnNonMainThreadDetected=false`.
djspiewak marked this conversation as resolved.
Show resolved Hide resolved
|""".stripMargin
)
else ()
}

/**
* The entry point for your application. Will be called by the runtime when the process is
* started. If the underlying runtime supports it, any arguments passed to the process will be
Expand All @@ -333,6 +359,7 @@ trait IOApp {
final def main(args: Array[String]): Unit = {
// checked in openjdk 8-17; this attempts to detect when we're running under artificial environments, like sbt
val isForked = Thread.currentThread().getId() == 1
if (!isForked) onNonMainThreadDetected()

val installed = if (runtime == null) {
import unsafe.IORuntime
Expand Down
3 changes: 2 additions & 1 deletion docs/core/io-runtime-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ This can be done for example with the [EnvironmentPlugin for Webpack](https://we
| `cats.effect.detectBlockedThreads` <br/> N/A | `Boolean` (`false`) | Whether or not we should detect blocked threads. |
| `cats.effect.logNonDaemonThreadsOnExit` <br/> N/A | `Boolean` (`true`) | Whether or not we should check for non-daemon threads on JVM exit. |
| `cats.effect.logNonDaemonThreads.sleepIntervalMillis` <br/> N/A | `Long` (`10000L`) | Time to sleep between checking for presence of non-daemon threads. |
| `cats.effect.cancelation.check.threshold ` <br/> `CATS_EFFECT_CANCELATION_CHECK_THRESHOLD` | `Int` (`512`) | Configure how often cancellation is checked. By default, every 512 iterations of the run loop. |
| `cats.effect.warnOnNonMainThreadDetected` <br/> N/A | `Boolean` (`true`) | Print a warning message when IOApp `main` runs on a non-main thread |
| `cats.effect.cancelation.check.threshold` <br/> `CATS_EFFECT_CANCELATION_CHECK_THRESHOLD` | `Int` (`512`) | Configure how often cancellation is checked. By default, every 512 iterations of the run loop. |
| `cats.effect.auto.yield.threshold.multiplier` <br/> `CATS_EFFECT_AUTO_YIELD_THRESHOLD_MULTIPLIER` | `Int` (`2`) | `autoYieldThreshold = autoYieldThresholdMultiplier x cancelationCheckThreshold`. See [thread model](../thread-model.md). |
| `cats.effect.tracing.exceptions.enhanced` <br/> `CATS_EFFECT_TRACING_EXCEPTIONS_ENHANCED` | `Boolean` (`true`) | Augment the stack traces of caught exceptions to include frames from the asynchronous stack traces. See [tracing](../tracing.md). |
| `cats.effect.tracing.buffer.size` <br/> `CATS_EFFECT_TRACING_BUFFER_SIZE` | `Int` (`16`) | Number of stack frames retained in the tracing buffer. Will be rounded up to next power of two. |
Expand Down