From 54de5d62ed121d0f3f42f27b8e10105277e4ad04 Mon Sep 17 00:00:00 2001 From: adamw Date: Wed, 3 Apr 2024 16:34:57 +0200 Subject: [PATCH] Release 0.0.25 --- README.md | 4 +- generated-doc/out/channels/actors.md | 5 +- generated-doc/out/collections.md | 8 +-- generated-doc/out/control-flow.md | 5 +- generated-doc/out/error-handling-scopes.md | 3 +- generated-doc/out/fork-join.md | 11 ++-- generated-doc/out/index.md | 5 +- generated-doc/out/kafka.md | 10 +-- generated-doc/out/par.md | 9 ++- generated-doc/out/race.md | 6 +- generated-doc/out/resources.md | 20 +++++- generated-doc/out/retries.md | 28 ++++---- generated-doc/out/utility.md | 75 ++++++++++++++++++++++ 13 files changed, 147 insertions(+), 42 deletions(-) create mode 100644 generated-doc/out/utility.md diff --git a/README.md b/README.md index 85cc057a..da21a28e 100644 --- a/README.md +++ b/README.md @@ -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.0.24" +"com.softwaremill.ox" %% "core" % "0.0.25" ``` Or [scala-cli](https://scala-cli.virtuslab.org): ```scala -//> using dep "com.softwaremill.ox::core:0.0.24" +//> using dep "com.softwaremill.ox::core:0.0.25" ``` 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). diff --git a/generated-doc/out/channels/actors.md b/generated-doc/out/channels/actors.md index a44e8bf7..1775f1ad 100644 --- a/generated-doc/out/channels/actors.md +++ b/generated-doc/out/channels/actors.md @@ -70,7 +70,7 @@ Such a callback can be used to release any resources held by the actor's logic. includes closing of the enclosing scope: ```scala -import ox.supervised +import ox.{never, supervised} import ox.channels.* class Stateful: @@ -82,5 +82,8 @@ supervised { // fire-and-forget, exception causes the scope to close ref.tell(_.work(5)) + + // preventing the scope from closing + never } ``` diff --git a/generated-doc/out/collections.md b/generated-doc/out/collections.md index a4213c65..cdc24646 100644 --- a/generated-doc/out/collections.md +++ b/generated-doc/out/collections.md @@ -3,7 +3,7 @@ ## mapPar ```scala -import ox.syntax.mapPar +import ox.mapPar val input: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) @@ -18,7 +18,7 @@ limits how many concurrent forks are going to process the collection. ## foreachPar ```scala -import ox.syntax.foreachPar +import ox.foreachPar val input: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) @@ -31,7 +31,7 @@ Similar to `mapPar` but doesn't return anything. ## filterPar ```scala -import ox.syntax.filterPar +import ox.filterPar val input: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) @@ -45,7 +45,7 @@ and other forks calculating predicates are interrupted. ## collectPar ```scala -import ox.syntax.collectPar +import ox.collectPar val input: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) diff --git a/generated-doc/out/control-flow.md b/generated-doc/out/control-flow.md index 52d7aa50..1e21b522 100644 --- a/generated-doc/out/control-flow.md +++ b/generated-doc/out/control-flow.md @@ -5,7 +5,8 @@ There are some helper methods which might be useful when writing code using ox's * `forever { ... }` repeatedly evaluates the given code block forever * `repeatWhile { ... }` repeatedly evaluates the given code block, as long as it returns `true` * `repeatUntil { ... }` repeatedly evaluates the given code block, until it returns `true` -* `uninterruptible { ... }` evaluates the given code block making sure it can't be interrupted * `never` blocks the current thread indefinitely, until it is interrupted +* `checkInterrupt()` checks if the current thread is interrupted, and if so, throws an `InterruptedException`. Useful in + compute-intensive code, which wants to cooperate in the cancellation protocol -All of these are `inline` methods. +All of these are `inline` methods, imposing no runtime overhead. diff --git a/generated-doc/out/error-handling-scopes.md b/generated-doc/out/error-handling-scopes.md index 4c0d0864..dbb8980f 100644 --- a/generated-doc/out/error-handling-scopes.md +++ b/generated-doc/out/error-handling-scopes.md @@ -8,7 +8,8 @@ The "default" and recommended scope is created using `supervised`. When this sco `fork` or `forkUser` that fails with an exception, will cause the enclosing scope to end: ```scala -import ox.{forkUser, supervised} +import ox.{forkUser, sleep, supervised} +import scala.concurrent.duration.* supervised { forkUser { diff --git a/generated-doc/out/fork-join.md b/generated-doc/out/fork-join.md index da83a99e..c9e34150 100644 --- a/generated-doc/out/fork-join.md +++ b/generated-doc/out/fork-join.md @@ -16,7 +16,8 @@ exception, or due to an interrupt. For example, the code below is equivalent to `par`: ```scala -import ox.{fork, supervised} +import ox.{fork, sleep, supervised} +import scala.concurrent.duration.* supervised { val f1 = fork { @@ -37,10 +38,11 @@ It is a compile-time error to use `fork`/`forkUser` outside of a `supervised` or require to be run within a scope by requiring the `Ox` capability: ```scala -import ox.{fork, Fork, Ox, supervised} +import ox.{fork, Fork, Ox, sleep, supervised} +import scala.concurrent.duration.* def forkComputation(p: Int)(using Ox): Fork[Int] = fork { - Thread.sleep(p * 1000) + sleep(p.seconds) p + 1 } @@ -68,7 +70,8 @@ are cancelled (using interruption). Once all forks complete, the exception is pr the `supervised` method invocation: ```scala -import ox.{fork, forkUser, Ox, supervised} +import ox.{fork, forkUser, Ox, sleep, supervised} +import scala.concurrent.duration.* supervised { forkUser { diff --git a/generated-doc/out/index.md b/generated-doc/out/index.md index 1391461f..a467d206 100644 --- a/generated-doc/out/index.md +++ b/generated-doc/out/index.md @@ -11,10 +11,10 @@ In addition to this documentation, ScalaDocs can be browsed at [https://javadoc. ```scala // sbt dependency -"com.softwaremill.ox" %% "core" % "0.0.24" +"com.softwaremill.ox" %% "core" % "0.0.25" // scala-cli dependency -//> using dep "com.softwaremill.ox::core:0.0.24" +//> using dep "com.softwaremill.ox::core:0.0.25" ``` ## Scope of the project @@ -86,6 +86,7 @@ We offer commercial support for ox and related technologies, as well as developm interruptions resources control-flow + utility extension dictionary performance diff --git a/generated-doc/out/kafka.md b/generated-doc/out/kafka.md index 8024f64d..74b6658a 100644 --- a/generated-doc/out/kafka.md +++ b/generated-doc/out/kafka.md @@ -3,7 +3,7 @@ Dependency: ```scala -"com.softwaremill.ox" %% "kafka" % "0.0.24" +"com.softwaremill.ox" %% "kafka" % "0.0.25" ``` `Source`s which read from a Kafka topic, mapping stages and drains which publish to Kafka topics are available through @@ -33,7 +33,7 @@ To publish data to a Kafka topic: ```scala import ox.channels.Source import ox.kafka.{ProducerSettings, KafkaDrain} -import ox.supervised +import ox.{pipe, supervised} import org.apache.kafka.clients.producer.ProducerRecord supervised { @@ -41,7 +41,7 @@ supervised { Source .fromIterable(List("a", "b", "c")) .mapAsView(msg => ProducerRecord[String, String]("my_topic", msg)) - .applied(KafkaDrain.publish(settings)) + .pipe(KafkaDrain.publish(settings)) } ``` @@ -66,7 +66,7 @@ computed. For example: ```scala import ox.kafka.{ConsumerSettings, KafkaDrain, KafkaSource, ProducerSettings, SendPacket} import ox.kafka.ConsumerSettings.AutoOffsetReset -import ox.supervised +import ox.{pipe, supervised} import org.apache.kafka.clients.producer.ProducerRecord supervised { @@ -79,7 +79,7 @@ supervised { .subscribe(consumerSettings, sourceTopic) .map(in => (in.value.toLong * 2, in)) .map((value, original) => SendPacket(ProducerRecord[String, String](destTopic, value.toString), original)) - .applied(KafkaDrain.publishAndCommit(producerSettings)) + .pipe(KafkaDrain.publishAndCommit(producerSettings)) } ``` diff --git a/generated-doc/out/par.md b/generated-doc/out/par.md index fb2cea55..18991737 100644 --- a/generated-doc/out/par.md +++ b/generated-doc/out/par.md @@ -3,7 +3,8 @@ A number of computations can be ran in parallel using the `par` method, for example: ```scala -import ox.par +import ox.{par, sleep} +import scala.concurrent.duration.* def computation1: Int = sleep(2.seconds) @@ -24,7 +25,8 @@ It's also possible to run a sequence of computations given as a `Seq[() => T]` i parallelism using `parLimit`: ```scala -import ox.parLimit +import ox.{parLimit, sleep} +import scala.concurrent.duration.* def computation(n: Int): Int = sleep(1.second) @@ -45,7 +47,8 @@ It's possible to use an arbitrary [error mode](error-handling.md) by providing i Alternatively, a built-in version using `Either` is available as `parEither`: ```scala -import ox.parEither +import ox.{parEither, sleep} +import scala.concurrent.duration.* val result = parEither( { diff --git a/generated-doc/out/race.md b/generated-doc/out/race.md index 85c14c06..1561c68c 100644 --- a/generated-doc/out/race.md +++ b/generated-doc/out/race.md @@ -3,7 +3,8 @@ A number of computations can be raced against each other using the `race` method, for example: ```scala -import ox.race +import ox.{race, sleep} +import scala.concurrent.duration.* def computation1: Int = sleep(2.seconds) @@ -38,7 +39,8 @@ It's possible to use an arbitrary [error mode](error-handling.md) by providing i Alternatively, a built-in version using `Either` is available as `raceEither`: ```scala -import ox.raceEither +import ox.{raceEither, sleep} +import scala.concurrent.duration.* raceEither({ sleep(200.millis) diff --git a/generated-doc/out/resources.md b/generated-doc/out/resources.md index f3e41336..97731e40 100644 --- a/generated-doc/out/resources.md +++ b/generated-doc/out/resources.md @@ -1,6 +1,22 @@ # Resources -## Allocate & release +## Individual resource + +Ox provides convenience methods to allocate, use and (uninterruptibly) release resources with a try-finally block: `use` +and `useCloseable`. For example: + +```scala +import ox.useCloseable + +useCloseable(new java.io.PrintWriter("test.txt")) { writer => + writer.println("Hello, world!") +} +``` + +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. + +## Within a concurrency scope Resources can be allocated within a concurrency scope. They will be released in reverse acquisition order, after all forks started within the scope finish (but before the scope completes). E.g.: @@ -25,7 +41,7 @@ supervised { } ``` -## Release-only +### Release-only You can also register resources to be released (without acquisition logic), before the scope completes: diff --git a/generated-doc/out/retries.md b/generated-doc/out/retries.md index 020a6f19..ead43355 100644 --- a/generated-doc/out/retries.md +++ b/generated-doc/out/retries.md @@ -13,12 +13,12 @@ import ox.retry.retry retry(operation)(policy) ``` -or, using syntax sugar: +or, using [`pipe`](utility.md) sugar: ```scala -import ox.syntax.* +import ox.pipe -operation.retry(policy) +operation.pipe(retry(policy)) ``` ## Operation definition @@ -148,27 +148,27 @@ def eitherOperation: Either[String, Int] = ??? def unionOperation: String | Int = ??? // various operation definitions - same syntax -retry(directOperation)(RetryPolicy.immediate(3)) -retryEither(eitherOperation)(RetryPolicy.immediate(3)) +retry(RetryPolicy.immediate(3))(directOperation) +retryEither(RetryPolicy.immediate(3))(eitherOperation) // various policies with custom schedules and default ResultPolicy -retry(directOperation)(RetryPolicy.delay(3, 100.millis)) -retry(directOperation)(RetryPolicy.backoff(3, 100.millis)) // defaults: maxDelay = 1.minute, jitter = Jitter.None -retry(directOperation)(RetryPolicy.backoff(3, 100.millis, 5.minutes, Jitter.Equal)) +retry(RetryPolicy.delay(3, 100.millis))(directOperation) +retry(RetryPolicy.backoff(3, 100.millis))(directOperation) // defaults: maxDelay = 1.minute, jitter = Jitter.None +retry(RetryPolicy.backoff(3, 100.millis, 5.minutes, Jitter.Equal))(directOperation) // infinite retries with a default ResultPolicy -retry(directOperation)(RetryPolicy.delayForever(100.millis)) -retry(directOperation)(RetryPolicy.backoffForever(100.millis, 5.minutes, Jitter.Full)) +retry(RetryPolicy.delayForever(100.millis))(directOperation) +retry(RetryPolicy.backoffForever(100.millis, 5.minutes, Jitter.Full))(directOperation) // result policies // custom success -retry(directOperation)(RetryPolicy(Schedule.Immediate(3), ResultPolicy.successfulWhen(_ > 0))) +retry[Int](RetryPolicy(Schedule.Immediate(3), ResultPolicy.successfulWhen(_ > 0)))(directOperation) // fail fast on certain errors -retry(directOperation)(RetryPolicy(Schedule.Immediate(3), ResultPolicy.retryWhen(_.getMessage != "fatal error"))) -retryEither(eitherOperation)(RetryPolicy(Schedule.Immediate(3), ResultPolicy.retryWhen(_ != "fatal error"))) +retry(RetryPolicy(Schedule.Immediate(3), ResultPolicy.retryWhen(_.getMessage != "fatal error")))(directOperation) +retryEither(RetryPolicy(Schedule.Immediate(3), ResultPolicy.retryWhen(_ != "fatal error")))(eitherOperation) // custom error mode -retryWithErrorMode(UnionMode[String])(unionOperation)(RetryPolicy(Schedule.Immediate(3), ResultPolicy.retryWhen(_ != "fatal error"))) +retryWithErrorMode(UnionMode[String])(RetryPolicy(Schedule.Immediate(3), ResultPolicy.retryWhen(_ != "fatal error")))(unionOperation) ``` See the tests in `ox.retry.*` for more. diff --git a/generated-doc/out/utility.md b/generated-doc/out/utility.md new file mode 100644 index 00000000..7f33ccc9 --- /dev/null +++ b/generated-doc/out/utility.md @@ -0,0 +1,75 @@ +# Utilities + +In addition to concurrency, error handling and resiliency features, ox includes some utility methods, which make writing +direct-style Scala code more convenient. When possible, these are `inline` methods taking `inline` parameters, hence +incurring no runtime overhead. + +Top-level methods: + +* `uninterruptible { ... }` evaluates the given code block making sure it can't be interrupted +* `sleep(scala.concurrent.Duration)` blocks the current thread/fork for the given duration; same as `Thread.sleep`, but + using's Scala's `Duration` + +Extension functions on arbitrary expressions: + +* `.discard` extension method evaluates the given code block and discards its result, avoiding "discarded non-unit + value" warnings +* `.pipe(f)` applies `f` to the value of the expression and returns the result; useful for chaining operations +* `.tap(f)` applies `f` to the value of the expression and returns the original value; useful for side-effecting + operations +* `.tapException(Throwable => Unit)` and `.tapNonFatalException(Throwable => Unit)` allow running the provided + side-effecting callback when the expression throws an exception + +Extension functions on `scala.concurrent.Future[T]`: + +* `.get(): T` blocks the current thread/fork until the future completes; returns the successful value of the future, or + throws the exception, with which it failed + +## Boundary/break for `Either`s + +To streamline working with `Either` values, especially when used for [error handling](error-handling.md), ox provides +a specialised version of the [boundary/break](https://www.scala-lang.org/api/current/scala/util/boundary$.html) +mechanism. + +Within a code block passed to `either`, it allows "unwrapping" `Either`s using `.value`. The unwrapped value corresponds +to the right side of the `Either`, which by convention represents successful computations. In case a failure is +encountered (a left side of an `Either`), the computation is short-circuited, and the failure becomes the result. + +For example: + +```scala +import ox.either +import ox.either.value + +case class User() +case class Organization() +case class Assignment(user: User, org: Organization) + +def lookupUser(id1: Int): Either[String, User] = ??? +def lookupOrganization(id2: Int): Either[String, Organization] = ??? + +val result: Either[String, Assignment] = either: + val user = lookupUser(1).value + val org = lookupOrganization(2).value + Assignment(user, org) +``` + +You can also use union types to accumulate different types of errors, e.g.: + +```scala +val v1: Either[Int, String] = ??? +val v2: Either[Long, String] = ??? + +val result: Either[Int | Long, String] = either: + v1.value ++ v2.value +``` + +Finally, options can be unwrapped as well; the error type is then `Unit`: + +```scala +val v1: Option[String] = ??? +val v2: Option[Int] = ??? + +val result: Either[Unit, String] = either: + v1.value * v2.value +```