Skip to content

Commit

Permalink
Release 0.3.1
Browse files Browse the repository at this point in the history
  • Loading branch information
adamw committed Jul 12, 2024
1 parent cc0892c commit e3c5c8f
Show file tree
Hide file tree
Showing 12 changed files with 271 additions and 91 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ the project!
To test Ox, use the following dependency, using either [sbt](https://www.scala-sbt.org):

```scala
"com.softwaremill.ox" %% "core" % "0.3.0"
"com.softwaremill.ox" %% "core" % "0.3.1"
```

Or [scala-cli](https://scala-cli.virtuslab.org):

```scala
//> using dep "com.softwaremill.ox::core:0.3.0"
//> using dep "com.softwaremill.ox::core:0.3.1"
```

Documentation is available at [https://ox.softwaremill.com](https://ox.softwaremill.com), ScalaDocs can be browsed at [https://javadoc.io](https://www.javadoc.io/doc/com.softwaremill.ox).
Expand Down
18 changes: 18 additions & 0 deletions generated-doc/out/adr/0008-scheduled-repeat-retry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# 8. Retries

Date: 2024-07-09

## Context

How should the [retries](../retries.md) and [repeat](../repeat.md) APIs have the common implementation.

## Decision

We're introducing [scheduled](../scheduled.md) as a common API for both retries and repeats.

In addition, `Schedule` trait and its implementations are decoupled from the retry DSL, so that they can be used for repeating as well.
`retry` API remains unchanged, but it now uses `scheduled` underneath.

Also, `repeat` functions has been added as a sugar for `scheduled` with DSL focused on repeating.

The main difference between `retry` and `repeat` is about interpretation of the duration provided by the `Schedule` (delay vs interval).
3 changes: 2 additions & 1 deletion generated-doc/out/basics/quick-example.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ox.*
import ox.either.ok
import ox.channels.*
import ox.resilience.*
import ox.scheduling.*
import scala.concurrent.duration.*

// run two computations in parallel
Expand Down Expand Up @@ -35,7 +36,7 @@ supervised {

// retry a computation
def computationR: Int = ???
retry(RetryPolicy.backoff(3, 100.millis, 5.minutes, Jitter.Equal))(computationR)
retry(RetryConfig.backoff(3, 100.millis, 5.minutes, Jitter.Equal))(computationR)

// create channels & transform them using high-level operations
supervised {
Expand Down
4 changes: 2 additions & 2 deletions generated-doc/out/basics/start-here.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

```scala
// sbt dependency
"com.softwaremill.ox" %% "core" % "0.3.0"
"com.softwaremill.ox" %% "core" % "0.3.1"

// scala-cli dependency
//> using dep "com.softwaremill.ox::core:0.3.0"
//> using dep "com.softwaremill.ox::core:0.3.1"
```

## Scope of the Ox project
Expand Down
2 changes: 2 additions & 0 deletions generated-doc/out/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ In addition to this documentation, ScalaDocs can be browsed at [https://javadoc.
oxapp
io
retries
repeat
scheduled
resources
control-flow
utility
Expand Down
4 changes: 2 additions & 2 deletions generated-doc/out/io.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,13 @@ To use the plugin, add the following settings to your sbt configuration:

```scala
autoCompilerPlugins := true
addCompilerPlugin("com.softwaremill.ox" %% "plugin" % "0.3.0")
addCompilerPlugin("com.softwaremill.ox" %% "plugin" % "0.3.1")
```

For scala-cli:

```scala
//> using plugin com.softwaremill.ox:::plugin:0.3.0
//> using plugin com.softwaremill.ox:::plugin:0.3.1
```

With the plugin enabled, the following code won't compile:
Expand Down
2 changes: 1 addition & 1 deletion generated-doc/out/kafka.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Dependency:

```scala
"com.softwaremill.ox" %% "kafka" % "0.3.0"
"com.softwaremill.ox" %% "kafka" % "0.3.1"
```

`Source`s which read from a Kafka topic, mapping stages and drains which publish to Kafka topics are available through
Expand Down
47 changes: 33 additions & 14 deletions generated-doc/out/oxapp.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# OxApp

Ox provides a way to define application entry points in the "Ox way" using `OxApp` trait. Starting the app this way comes
with the benefit of the main `run` function being executed on a virtual thread, with a root `Ox` scope provided,
and application interruption handling built-in. The latter is handled using `Runtime.addShutdownHook` and will interrupt
the main virtual thread, should the app receive, for example, a SIGINT due to Ctrl+C being issued by the user.
Here's an example:
To properly handle application interruption and clean shutdown, Ox provides a way to define application entry points
using `OxApp` trait. The application's main `run` function is then executed on a virtual thread, with a root `Ox` scope
provided.

Here's an example:

```scala
import ox.*
Expand All @@ -20,6 +20,23 @@ object MyApp extends OxApp:
ExitCode.Success
```

When the application receives a SIGINT/SIGTERM, e.g. due to a CTRL+C, the root scope (and hence any child scopes and
forks) are interrupted. This allows for a clean shutdown: any resources that are attached to scopes, or managed using
`try-finally` blocks, are released. Application shutdown is handled by adding a `Runtime.addShutdownHook`.

In the code below, the resource is released when the application is interrupted:

```scala
import ox.*

object MyApp extends OxApp:
def run(args: Vector[String])(using Ox): ExitCode =
releaseAfterScope:
println("Releasing ...")
println("Waiting ...")
never
```

The `run` function receives command line arguments as a `Vector` of `String`s, a given `Ox` capability and has to
return an `ox.ExitCode` value which translates to the exit code returned from the program. `ox.ExitCode` is defined as:

Expand Down Expand Up @@ -63,27 +80,29 @@ object MyApp extends OxApp.WithEitherErrors[MyAppError]:
}

def run(args: Vector[String])(using Ox, EitherError[MyAppError]): ExitCode =
doWork().ok() // will close the scope with MyAppError as `doWork` returns a Left
doWork().ok() // will end the scope with MyAppError as `doWork` returns a Left
ExitCode.Success
```

## Additional configuration

All `ox.OxApp` instances can be configured by overriding the `def settings: AppSettings` method. For now `AppSettings`
contains only the `gracefulShutdownExitCode` setting that allows one to decide what exit code should be returned by
the application once it gracefully shutdowns after it was interrupted (for example Ctrl+C was pressed by the user).
All `ox.OxApp` instances can be configured by overriding the `def settings: Settings` method. Settings include:

* `interruptedExitCode`: what exit code should be returned by the application once it gracefully shutdowns after it
was interrupted (for example Ctrl+C was pressed by the user). By default `0` (graceful shutdown)
* `handleException` and `handleInterruptedException`: callbacks for exceptions that occur when evaluating the
application's body, or that are thrown when the application shuts down due to an interruption (SIGINT/SIGTERM).
By default, the stack traces are printed to stderr, unless a default uncaught exception handler is defined.

By default `OxApp` will exit in such scenario with exit code `0` meaning successful graceful shutdown, but it can be
overridden:
Settings can be overridden:

```scala
import ox.*
import scala.concurrent.duration.*
import OxApp.AppSettings

object MyApp extends OxApp:
override def settings: AppSettings = AppSettings(
gracefulShutdownExitCode = ExitCode.Failure(130)
override def settings: OxApp.Settings = OxApp.Settings.Default.copy(
interruptedExitCode = ExitCode.Failure(130)
)

def run(args: Vector[String])(using Ox): ExitCode =
Expand Down
80 changes: 80 additions & 0 deletions generated-doc/out/repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Repeat

The `repeat` functions allow to repeat an operation according to a given schedule (e.g. repeat 3 times with a 100ms
interval and 50ms of initial delay).

## API

The basic syntax for `repeat` is:

```scala
import ox.scheduling.repeat

repeat(config)(operation)
```

The `repeat` API uses `scheduled` underneath with DSL focused on repeats. See [scheduled](scheduled.md) for more details.

## Operation definition

Similarly to the `retry` API, the `operation` can be defined:
* directly using a by-name parameter, i.e. `f: => T`
* using a by-name `Either[E, T]`
* or using an arbitrary [error mode](basics/error-handling.md), accepting the computation in an `F` context: `f: => F[T]`.

## Configuration

The `repeat` config requires a `Schedule`, which indicates how many times and with what interval should the `operation`
be repeated.

In addition, it is possible to define a custom `shouldContinueOnSuccess` strategy for deciding if the operation
should continue to be repeated after a successful result returned by the previous operation (defaults to `_: T => true`).

If an operation returns an error, the repeat loop will always be stopped. If an error handling within the operation
is needed, you can use a `retry` inside it (see an example below) or use `scheduled` instead of `repeat`, which allows
full customization.

### API shorthands

You can use one of the following shorthands to define a `RepeatConfig` with a given schedule with an optional initial delay:
- `RepeatConfig.immediate(maxInvocations: Int, initialDelay: Option[FiniteDuration] = None)`
- `RepeatConfig.immediateForever[E, T](initialDelay: Option[FiniteDuration] = None)`
- `RepeatConfig.fixedRate[E, T](maxInvocations: Int, interval: FiniteDuration, initialDelay: Option[FiniteDuration] = None)`
- `RepeatConfig.fixedRateForever[E, T](interval: FiniteDuration, initialDelay: Option[FiniteDuration] = None)`
- `RepeatConfig.backoff[E, T](maxInvocations: Int, firstInterval: FiniteDuration, maxInterval: FiniteDuration = 1.minute, jitter: Jitter = Jitter.None, initialDelay: Option[FiniteDuration] = None)`
- `RepeatConfig.backoffForever[E, T](firstInterval: FiniteDuration, maxInterval: FiniteDuration = 1.minute, jitter: Jitter = Jitter.None, initialDelay: Option[FiniteDuration] = None)`

See [scheduled](scheduled.md) for details on how to create custom schedules.

## Examples

```scala
import ox.UnionMode
import ox.scheduling.{Jitter, Schedule, repeat, repeatEither, repeatWithErrorMode, RepeatConfig}
import ox.resilience.{retry, RetryConfig}
import scala.concurrent.duration.*

def directOperation: Int = ???
def eitherOperation: Either[String, Int] = ???
def unionOperation: String | Int = ???

// various operation definitions - same syntax
repeat(RepeatConfig.immediate(3))(directOperation)
repeatEither(RepeatConfig.immediate(3))(eitherOperation)

// various schedules
repeat(RepeatConfig.fixedRate(3, 100.millis))(directOperation)
repeat(RepeatConfig.fixedRate(3, 100.millis, Some(50.millis)))(directOperation)

// infinite repeats with a custom strategy
def customStopStrategy: Int => Boolean = ???
repeat(RepeatConfig.fixedRateForever(100.millis).copy(shouldContinueOnResult = customStopStrategy))(directOperation)

// custom error mode
repeatWithErrorMode(UnionMode[String])(RepeatConfig.fixedRate(3, 100.millis))(unionOperation)

// repeat with retry inside
repeat(RepeatConfig.fixedRate(3, 100.millis)) {
retry(RetryConfig.backoff(3, 100.millis))(directOperation)
}
```
5 changes: 5 additions & 0 deletions generated-doc/out/resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ useCloseable(new java.io.PrintWriter("test.txt")) { writer =>
If a concurrency scope is available (e.g. `supervised`), or there are multiple resources to allocate, consider using the
approach described below, to avoid creating an additional syntactical scope.

```{warning}
To properly release resources when the entire application is interrupted, make sure to use [`OxApp`](oxapp.md) as the
application's main entry point.
```

## Within a concurrency scope

Resources can be allocated within a concurrency scope. They will be released in reverse acquisition order, after all
Expand Down
Loading

0 comments on commit e3c5c8f

Please sign in to comment.