Skip to content

Instantly share code, notes, and snippets.

@densh
Last active October 18, 2020 17:02
Show Gist options
  • Star 20 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save densh/75d2d3063571d89ee34e161b4a61c74a to your computer and use it in GitHub Desktop.
Save densh/75d2d3063571d89ee34e161b4a61c74a to your computer and use it in GitHub Desktop.
Scoped Implicit Lifetimes

Scoped Implicit Lifetimes

All things considered, our experience in Scala Native has shown that resource management in Scala is way harder than it should be. This gist presents a simple design pattern that makes it resource management absolutely hassle-free: scoped implicit lifetimes.

The main idea behind it is to encode resource lifetimes through a concept of an implicit scope. Scopes are necessary to acquire resources. They are responsible for disposal of the resources once the evaluation exits the demarkated block in the source code.

Use cases

  • File handles
  • Network connections
  • Locks
  • Off-heap memory allocations
  • Thread pools
  • Actor systems
  • OpenGL Contexts
  • ...
import java.io.Closeable
import Resourceful._
object Resourceful {
type Resource = AutoCloseable
@annotation.implicitNotFound(msg = "Resource acquisition requires a scope.")
final class Scope extends Resource {
private[this] var resources: List[Resource] = Nil
final def acquire(res: Resource): Unit = {
resources ::= res
}
final def close(): Unit = resources match {
case Nil =>
()
case first :: rest =>
resources = rest
try first.close()
finally close()
}
}
object Scope {
def apply[T](f: Scope => T): T = {
val scope = new Scope
try f(scope)
finally scope.close()
}
}
def acquire[R <: Resource](res: R)(implicit in: Scope): R = {
in.acquire(res)
res
}
}
class SafeWriter(path: String)(implicit in: Scope) extends Resource {
acquire(this)
println(s"acquired $path")
private val writer = new java.io.PrintWriter(path)
def write(value: String) = writer.write(value)
def close(): Unit = { println(s"releasing $path"); writer.close() }
}
object MyApp extends App {
Scope { implicit in =>
val w1 = new SafeWriter("file1.txt")
w1.write("hello, world!")
val w2 = new SafeWriter("file2.txt")
w2.write("hello, world!")
}
}
@chaotic3quilibrium
Copy link

chaotic3quilibrium commented Sep 19, 2016

Your implementation above has a couple of different issues which result in resource leaks (per the exact specification of the java.lang.AutoCloseable documentation). Additionally, it doesn't reliably surface what and where a non-close exception blew up the scope.

For example, you call AutoCloseable.close even if the resource has not been successfully opened. This is clearly violating the explicit contract of AutoCloseable.close. There is a possibility of an AutoCloseable being "opened" and then an exception being thrown before the apply method of Scope successfully executes (imagine a System level exception akin to an OutOfMemory exception which must be assumed between EVERY single atomic execution of a code on the JVM) the resource's close command.

Additionally, it becomes confusing to use Scope if there is intermixed nesting where one resource must be closed before another one can be opened (imagine your w1 needed to be closed to reclaim resource space prior to engaging w2 or a like a FileWriter depending upon a BufferedWriter...and then imagine this with more than two resources which might have some sort of interaction more complex than simple linear nesting).

I believe your solution can be "fixed" to resolve the resource leak issue but will still remain cumbersome given any non-linear nesting pattern. However, I don't have time at the moment to dive in and fix it.

In an answer to a StackOverflow question, I came up with a much simpler FP immutable single method approach to handing this issue including arbitrary nesting. IOW, it doesn't need any sort of var to hold onto state for managing the future close operations. You can use the contents of this answer to refactor yours to eliminate the leaks. And I will try to revisit sometime in the future and actually "refactor" your solution to handle the leak.

@SethTisue
Copy link

anyone interested in this will likely also be interested in
https://github.com/scala/scala/blob/2.13.x/src/library/scala/util/Using.scala

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment