-
Notifications
You must be signed in to change notification settings - Fork 30
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
Proposal: provide an OxApp
trait as main entry point for app
#152
Comments
Tbh handling signals with interruption seems kinda easy: //> using scala 3.3.3
//> using dep com.softwaremill.ox::core:0.2.1
import ox.*
import java.lang.Runtime
trait OxApp:
def main(args: Array[String]): Unit =
val rt = Runtime.getRuntime()
unsupervised:
val cancellableMainFork = forkCancellable(supervised(run(args.toVector)))
rt.addShutdownHook:
new Thread(() => {
println()
println("shutting down")
cancellableMainFork.cancel()
println("interrupted the main fork")
})
cancellableMainFork.joinEither() match
case Left(iex: InterruptedException) => System.exit(0)
case Left(err) => throw err
case Right(userEither) =>
userEither match
case Left(err) => throw err
case Right(()) => System.exit(0)
def run(args: Vector[String])(using Ox): Either[Throwable, Unit] = run
def run(using Ox): Either[Throwable, Unit]
object Main extends OxApp:
def run(using Ox): Either[Throwable, Unit] = either:
try
Thread.sleep(20000)
println("what's up?")
catch
case iex: InterruptedException =>
println("main got interrupted")
throw iex |
Ah, this would solve #131 - great idea! Let's do it :) I'm not convinced about the |
I was personally interested in finding a set of idioms that allow me to
program without lying in signatures (so without throwing exceptions other
than flow control). If you make it return Unit or ExitCode user using
either block in main function will be forced to patmat+throw. Maybe
multiple variants should be available? OxEitherApp? Maybe different methods
available for override (this is a bit shitty though because there's no way
to force user to override just one)?
…On Mon 10. Jun 2024 at 10:07, Adam Warski ***@***.***> wrote:
Ah, this would solve #131 <#131>
- great idea! Let's do it :) I'm not convinced about the def run(using
Ox): Either[Throwable, Unit] (specifically, returning an Either - I'd
rather say any logical - type-safe errors - should be handled in the app).
So maybe we can go with simply the shutdown hook + OxApp + docs for now?
—
Reply to this email directly, view it on GitHub
<#152 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ACBVNUQNMSMY23CLEC7P5GLZGVNFBAVCNFSM6AAAAABJAZBGKOVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCNJXGYYTSNJTGM>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
I see, though I don't think we'll be able to escape from exceptions. Any I/O method can throw exceptions, do you have an idea how to handle this? (I'm all for not lying in signatures, though :) And I like ZIO's concept of untyped defects, which in our case are represented as exceptions) But even if we don't lie in signatures, if the main returns an either, this means that some errors are unhandled by the app? So at the top-level, if any error is unhandled - it's a bug - hence should be thrown as an exception, no? |
Hmm, it's the same with IOApp, right? They expect your final IO to be
successful and contain an ExitCode (meaning you translated any logical
expected errors to a meaningful exit code no and handled informing the
user) but if it's a failed IO they just recover to exit code 1 and print
stack trace, I think. Ok, so maybe mirror CE:
trait OxApp:
def run(args: Vector[String]): ExitCode
object OxApp:
trait Simple extends OxApp:
def run(args: Vector[String]): Unit
and then we joinEither anyway, handle any exception thrown on main fiber
and map it to exit code as recovery? For now there's no need to worry about
cross-platform code at least.
I think I'd still prefer my entry point to allow me to use ox.either
combinators but if these traits can be extended, it's not a big deal.
…On Mon 10. Jun 2024 at 10:23, Adam Warski ***@***.***> wrote:
I see, though I don't think we'll be able to escape from exceptions. Any
I/O method can throw exceptions, do you have an idea how to handle this?
(I'm all for not lying in signatures, though :) And I like ZIO's concept
of untyped defects, which in our case are represented as exceptions)
But even if we don't lie in signatures, if the main returns an either,
this means that some errors are unhandled by the app? So at the top-level,
if any error is unhandled - it's a bug - hence should be thrown as an
exception, no?
—
Reply to this email directly, view it on GitHub
<#152 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ACBVNUTZOPUW27AFDHHS7RDZGVO6ZAVCNFSM6AAAAABJAZBGKOVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCNJXGY4DEOJVGI>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
Or going the So this would bring the variants to 3 ("simple", "regular" and "either"). I'd prefer to have a single variant, but maybe that's essential, not accidental complexity. |
Either[Any, Any] sounds like very bad.
trait OxEitherApp[E, A]:
def handleError(e: E): ExitCode
def handleResult(a: A): ExitCode
def run(args: Vector[String])(using Label[Either[E, A]], Ox): Either[E, A]
is an option but it's quite complex. But it's would probably be best for my
needs tbh.
…On Mon 10. Jun 2024 at 10:39, Adam Warski ***@***.***> wrote:
Or going the ZIOApp route you could return an Either[Any, Any] and
printout any error that occurs to stdout - not necessarily exceptions only.
So this would bring the variants to 3 ("simple", "regular" and "either").
I'd prefer to have a single variant, but maybe that's essential, not
accidental complexity.
—
Reply to this email directly, view it on GitHub
<#152 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ACBVNUVGZSYGMTSDE2RSWDLZGVQ25AVCNFSM6AAAAABJAZBGKOVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCNJXG4YTSMBTGE>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
So we could have:
then? |
yeah, this would be very nice! |
Would you like a PR for that or are you already working on it? |
@lbialy a PR would be great, I'm back to "normal" work only next week |
implemented in #157 |
One things that would improve usability of Ox would be a way to define an entry point to the application which would start user's program on a separate virtual thread, block the main thread until user's program finish and translate any errors into app exit with an error code. Moreover, it could handle signals too so that it would be possible to interrupt user's code on SIGTERM as #131 proposes. I guess some inspiration for signal handling could be borrowed from CE:
https://github.com/typelevel/cats-effect/blob/1e445fb1750fdf3878f85f9a1bccab7720ce9817/core/jvm/src/main/java/cats/effect/Signal.java
But on JVM CE seems to just use
addShutdownHook
:https://github.com/typelevel/cats-effect/blob/1e445fb1750fdf3878f85f9a1bccab7720ce9817/core/jvm/src/main/scala/cats/effect/IOApp.scala#L502C1-L511C6
I have built a small draft for myself here:
It's extremely limited, only thing it does is:
a) run user's
run
method on a forked virtual thread with a supervised scope so that user can fork off the main "fiber"b) handle both thrown and returned errors (expected return type from user is
Either[Throwable, Unit]
)It could, for example, open an
either
block for the user in run so thatox.either.ok()
combinator can be used in the main method. It would, however, make therun
signature a bit more cumbersome due to(using Label[Either[Throwable, Unit]], Ox)
.The text was updated successfully, but these errors were encountered: