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

More scaladoc #2132

Merged
merged 9 commits into from
Jul 24, 2021
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
4 changes: 1 addition & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -312,9 +312,7 @@ lazy val core = crossProject(JSPlatform, JVMPlatform)
ProblemFilters.exclude[MissingClassProblem]("cats.effect.SyncIO$Delay"),
ProblemFilters.exclude[DirectMissingMethodProblem]("cats.effect.IO#IOCont.apply"),
ProblemFilters.exclude[DirectMissingMethodProblem]("cats.effect.IO#IOCont.copy"),
ProblemFilters.exclude[DirectMissingMethodProblem]("cats.effect.IO#IOCont.this")
)
)
ProblemFilters.exclude[DirectMissingMethodProblem]("cats.effect.IO#IOCont.this")))
vasilmkd marked this conversation as resolved.
Show resolved Hide resolved
.jvmSettings(
javacOptions ++= Seq("-source", "1.8", "-target", "1.8")
)
Expand Down
164 changes: 163 additions & 1 deletion core/js/src/main/scala/cats/effect/IOApp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,169 @@ import scala.concurrent.CancellationException
import scala.concurrent.duration._
import scala.scalajs.js

/**
* The primary entry point to a Cats Effect application. Extend this
* trait rather than defining your own `main` method. This avoids the
* need to run [[IO.unsafeRunSync]] (or similar) on your own.
*
* `IOApp` takes care of the messy details of properly setting up
* (and tearing down) the [[unsafe.IORuntime]] needed to run the [[IO]]
* which represents your application. All of the associated thread
* pools (if relevant) will be configured with the assumption that
* your application is fully contained within the `IO` produced by
* the [[run]] method. Note that the exact details of how the runtime
* will be configured are very platform-specific. Part of the point
* of `IOApp` is to insulate users from the details of the underlying
* runtime (whether JVM or JavaScript).
*
* {{{
* object MyApplication extends IOApp {
* def run(args: List[String]) =
* for {
* _ <- IO.print("Enter your name: ")
* name <- IO.readln
* _ <- IO.println("Hello, " + name)
* } yield ExitCode.Success
* }
* }}}
*
* In the above example, `MyApplication` will be a runnable class with
* a `main` method, visible to Sbt, IntelliJ, or plain-old `java`. When
* run externally, it will print, read, and print in the obvious way,
* producing a final process exit code of 0. Any exceptions thrown within
* the `IO` will be printed to standard error and the exit code will be
* set to 1. In the event that the main [[Fiber]] (represented by the `IO`
* returned by `run`) is canceled, the runtime will produce an exit code of 1.
*
* Note that exit codes are an implementation-specific feature of the
* underlying runtime, as are process arguments. Naturally, all JVMs
* support these functions, as does NodeJS, but some JavaScript execution
* environments will be unable to replicate these features (or they simply
* may not make sense). In such cases, exit codes may be ignored and/or
* argument lists may be empty.
*
* Note that in the case of the above example, we would actually be
* better off using [[IOApp.Simple]] rather than `IOApp` directly, since
* we are neither using `args` nor are we explicitly producing a custom
* [[ExitCode]]:
*
* {{{
* object MyApplication extends IOApp.Simple {
* val run =
* for {
* _ <- IO.print("Enter your name: ")
* name <- IO.readln
* _ <- IO.println(s"Hello, " + name)
* } yield ()
* }
* }}}
*
* It is valid to define `val run` rather than `def run` because `IO`'s
* evaluation is lazy: it will only run when the `main` method is
* invoked by the runtime.
*
* In the event that the process receives an interrupt signal (`SIGINT`) due
* to Ctrl-C (or any other mechanism), it will immediately `cancel` the main
* fiber. Assuming this fiber is not within an `uncancelable` region, this
* will result in interrupting any current activities and immediately invoking
* any finalizers (see: [[IO.onCancel]] and [[IO.bracket]]). The process will
* not shut down until the finalizers have completed. For example:
*
* {{{
* object InterruptExample extends IOApp.Simple {
* val run =
* IO.bracket(startServer)(
* _ => IO.never)(
* server => IO.println("shutting down") *> server.close)
* }
* }}}
*
* If we assume the `startServer` function has type `IO[Server]` (or similar),
* this kind of pattern is very common. When this process receives a `SIGINT`,
* it will immediately print "shutting down" and run the `server.close` effect.
*
* One consequence of this design is it is possible to build applications which
* will ignore process interrupts. For example, if `server.close` runs forever,
* the process will ignore interrupts and will need to be cleaned up using
* `SIGKILL` (i.e. `kill -9`). This same phenomenon can be demonstrated by using
* [[IO.uncancelable]] to suppress all interruption within the application
* itself:
*
* {{{
* object Zombie extends IOApp.Simple {
* val run = IO.never.uncancelable
* }
* }}}
*
* The above process will run forever and ignore all interrupts. The only way
* it will shut down is if it receives `SIGKILL`.
*
* It is possible (though not necessary) to override various platform-specific
* runtime configuration options, such as `computeWorkerThreadCount` (which only
* exists on the JVM). Please note that the default configurations have been
* extensively benchmarked and are optimal (or close to it) in most conventional
* scenarios.
*
* However, with that said, there really is no substitute to benchmarking your
* own application. Every application and scenario is unique, and you will
* always get the absolute best results by performing your own tuning rather
* than trusting someone else's defaults. `IOApp`'s defaults are very ''good'',
* but they are not perfect in all cases. One common example of this is
* applications which maintain network or file I/O worker threads which are
* under heavy load in steady-state operations. In such a performance profile,
* it is usually better to reduce the number of compute worker threads to
* "make room" for the I/O workers, such that they all sum to the number of
* physical threads exposed by the kernel.
*
* @see [[IO]]
* @see [[run]]
* @see [[ResourceApp]]
* @see [[IOApp.Simple]]
*/
trait IOApp {

private[this] var _runtime: unsafe.IORuntime = null

/**
* The runtime which will be used by `IOApp` to evaluate the
* [[IO]] produced by the `run` method. This may be overridden
* by `IOApp` implementations which have extremely specialized
* needs, but this is highly unlikely to ever be truly needed.
* As an example, if an application wishes to make use of an
* alternative compute thread pool (such as `Executors.fixedThreadPool`),
* it is almost always better to leverage [[IO.evalOn]] on the value
* produced by the `run` method, rather than directly overriding
* `runtime`.
*
* In other words, this method is made available to users, but its
* use is strongly discouraged in favor of other, more precise
* solutions to specific use-cases.
*
* This value is guaranteed to be equal to [[unsafe.IORuntime.global]].
*/
protected def runtime: unsafe.IORuntime = _runtime

/**
* The configuration used to initialize the [[runtime]] which will
* evaluate the [[IO]] produced by `run`. It is very unlikely that
* users will need to override this method.
*/
protected def runtimeConfig: unsafe.IORuntimeConfig = unsafe.IORuntimeConfig()

/**
* 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 made available in the
* `args` parameter. The numeric value within the resulting [[ExitCode]]
* will be used as the exit code when the process terminates unless
* terminated exceptionally or by interrupt.
*
* @param args The arguments passed to the process, if supported by the
* underlying runtime. For example, `java com.company.MyApp --foo --bar baz`
* or `node com-mycompany-fastopt.js --foo --bar baz` would each
* result in `List("--foo", "--bar", "baz")`.
* @see [[IOApp.Simple!.run:cats\.effect\.IO[Unit]*]]
*/
def run(args: List[String]): IO[ExitCode]

final def main(args: Array[String]): Unit = {
Expand Down Expand Up @@ -92,9 +248,15 @@ trait IOApp {

object IOApp {

/**
* A simplified version of [[IOApp]] for applications which ignore their
* process arguments and always produces [[ExitCode.Success]] (unless
* terminated exceptionally or interrupted).
*
* @see [[IOApp]]
*/
trait Simple extends IOApp {
def run: IO[Unit]
final def run(args: List[String]): IO[ExitCode] = run.as(ExitCode.Success)
}

}
Loading