Resources
Single scoped resource
Ox provides convenience inline methods to allocate, use and (uninterruptibly) release resources with a try-finally
block: use and useCloseable. For example:
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 a
resource scope (described below), to avoid creating an additional syntactical scope.
Alternatively, you can use useInterruptible, where the releasing might be interrupted, and which is equivalent to a
try-finally block.
Warning
To properly release resources when the entire application is interrupted, make sure to use OxApp as the
application’s main entry point.
Resource scopes
Resources can be attached to a resource scope: they are then released when the scope completes (successfully or
with an exception), in reverse registration order. Releasing is uninterruptible. Resources are registered using
useInScope (with acquire & release logic) or useCloseableInScope (for AutoCloseables). A resource scope can be
obtained in two ways:
resourceScopestarts a dedicated, resource-only scope - without involving concurrency. This is ox’s analogue ofscala.util.Using.Manager:
import ox.{resourceScope, useCloseableInScope}
import java.io.{FileReader, FileWriter}
def process(): Unit = resourceScope {
val in = useCloseableInScope(new FileReader("in.txt"))
val out = useCloseableInScope(new FileWriter("out.txt"))
// both closed when the scope completes, out first
out.write(in.read())
}
every concurrency scope (e.g.
supervised) is also a resource scope; resources registered within one are released after all forks started within the scope finish (but before the scope completes):
import ox.{supervised, useInScope}
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} ...")
supervised {
val resource1 = useInScope(acquire(10))(release)
val resource2 = useInScope(acquire(20))(release)
println(s"Using $resource1 ...")
println(s"Using $resource2 ...")
}
Methods which only register resources can declare exactly the capability they need - using ResourceScope - without
claiming the ability to fork.
A resource scope can only be started where no concurrency scope is visible - this is verified at compile-time. The
reason: forks started within a lexically visible resource scope could outlive it, using resources after they have
been released (a concurrency scope doesn’t have this problem, as it waits for all forks before releasing). Within a
concurrency scope, register resources directly instead. To use a resource scope e.g. in the body of a fork, extract
it to a method which doesn’t take a using Ox parameter - a good practice
in itself:
import ox.{forkDiscard, resourceScope, supervised, useCloseableInScope}
import java.io.FileReader
def handleRequest(): Unit = resourceScope {
val in = useCloseableInScope(new FileReader("in.txt"))
println(s"Processing the request using: ${in.read()}")
}
supervised {
forkDiscard(handleRequest())
}
Release-only
You can also register resources to be released (without acquisition logic), before the scope completes:
import ox.{supervised, releaseAfterScope}
case class MyResource(c: Int)
def release(resource: MyResource): Unit =
println(s"releasing ${resource.c} ...")
supervised {
val resource1 = MyResource(10)
releaseAfterScope(release(resource1))
println(s"Using $resource1 ...")
}