Skip to content

Commit

Permalink
Simplify resources allocation, add releaseAfterScope (#102)
Browse files Browse the repository at this point in the history
  • Loading branch information
adamw authored Mar 15, 2024
1 parent 7ed5554 commit 2b164c0
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 30 deletions.
21 changes: 15 additions & 6 deletions core/src/main/scala/ox/resource.scala
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
package ox

/** Use the given resource in the current scope. The resource is allocated using `acquire`, and released after the scope is done using
* `release`. Releasing is uninterruptible.
/** Use the given resource in the current concurrency scope. The resource is allocated using `acquire`, and released after the all forks in
* the scope complete (either successfully or with an error), using `release`. Releasing is uninterruptible.
*/
def useInScope[T](acquire: => T)(release: T => Unit)(using Ox): T =
val t = acquire
summon[Ox].addFinalizer(() => release(t))
t

/** Use the given resource, which implements [[AutoCloseable]], in the current concurrency scope. The resource is allocated using `acquire`,
* and released after the all forks in the scope complete (either successfully or with an error), using [[AutoCloseable.close()]].
* Releasing is uninterruptible.
*/
def useCloseableInScope[T <: AutoCloseable](c: => T)(using Ox): T = useInScope(c)(_.close())

def useScoped[T, U](acquire: => T)(release: T => Unit)(b: T => U): U = scoped(b(useInScope(acquire)(release)))
def useScoped[T <: AutoCloseable, U](acquire: => T)(b: T => U): U = scoped(b(useInScope(acquire)(_.close())))
/** Release the given resource, by running the `release` code block. Releasing is done after all the forks in the scope complete (either
* successfully or with an error), but before the current concurrency scope completes. Releasing is uninterruptible.
*/
def releaseAfterScope(release: => Unit)(using Ox): Unit = useInScope(())(_ => release)

def useSupervised[T, U](acquire: => T)(release: T => Unit)(b: T => U): U = supervised(b(useInScope(acquire)(release)))
def useSupervised[T <: AutoCloseable, U](acquire: => T)(b: T => U): U = supervised(b(useInScope(acquire)(_.close())))
/** Release the given resource, which implements [[AutoCloseable]], by running its `.close()` method. Releasing is done after all the forks
* in the scope complete (either successfully or with an error), but before the current concurrency scope completes. Releasing is
* uninterruptible.
*/
def releaseCloseableAfterScope(toRelease: AutoCloseable)(using Ox): Unit = useInScope(())(_ => toRelease.close())
3 changes: 0 additions & 3 deletions core/src/main/scala/ox/syntax.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package ox
import ox.retry.RetryPolicy

import scala.concurrent.duration.FiniteDuration
import scala.util.Try

object syntax:
extension [T](f: => T) def forever: Fork[Nothing] = ox.forever(f)
Expand All @@ -28,8 +27,6 @@ object syntax:

extension [T <: AutoCloseable](f: => T)(using Ox)
def useInScope: T = ox.useCloseableInScope(f)
def useScoped[U](p: T => U): U = ox.useScoped(f)(p)
def useSupervised[U](p: T => U): U = ox.useSupervised(f)(p)

extension [I, C[E] <: Iterable[E]](f: => C[I])
def mapPar[O](parallelism: Int)(transform: I => O): C[O] = ox.mapPar(parallelism)(f)(transform)
Expand Down
13 changes: 6 additions & 7 deletions core/src/test/scala/ox/ResourceTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,13 @@ class ResourceTest extends AnyFlatSpec with Matchers {
trail.get shouldBe Vector("allocate 1", "allocate 2", "release 2", "release 1", "exception")
}

"useScoped" should "release resources after allocation" in {
it should "release registered resources" in {
val trail = Trail()
useScoped {
trail.add("allocate"); 1
}(n => trail.add(s"release $n")) { r =>
r shouldBe 1
trail.get shouldBe Vector("allocate")

scoped {
releaseAfterScope(trail.add("release"))
trail.add("in scope")
}
trail.get shouldBe Vector("allocate", "release 1")
trail.get shouldBe Vector("in scope", "release")
}
}
24 changes: 10 additions & 14 deletions doc/resources.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Resources

## In-scope
## Allocate & release

Resources can be allocated within a scope. They will be released in reverse acquisition order, after the scope completes
(that is, after all forks started within finish). E.g.:
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.:

```scala mdoc:compile-only
import ox.{supervised, useInScope}
Expand All @@ -25,25 +25,21 @@ supervised {
}
```

## Supervised / scoped
## Release-only

Resources can also be used in a dedicated scope:
You can also register resources to be released (without acquisition logic), before the scope completes:

```scala mdoc:compile-only
import ox.useSupervised
import ox.{supervised, releaseAfterScope}

case class MyResource(c: Int)

def acquire(c: Int): MyResource =
println(s"acquiring $c ...")
MyResource(c)

def release(resource: MyResource): Unit =
println(s"releasing ${resource.c} ...")

useSupervised(acquire(10))(release) { resource =>
println(s"Using $resource ...")
supervised {
val resource1 = MyResource(10)
releaseAfterScope(release(resource1))
println(s"Using $resource1 ...")
}
```

If the resource extends `AutoCloseable`, the `release` method doesn't need to be provided.

0 comments on commit 2b164c0

Please sign in to comment.