Unsafe Lazy Resource.scala
I need an impure way (no IO) to create a resource atomically only once and later be able to know if it was created or not, so I can close this resource safely. 🤔
Jules Ivanic (@guizmaii) ⬇️ August 2, 2021
import scala.util.control.NonFatal
/** Builds a "closeable" resource that's initialized on-demand.
*
* Works like a `lazy val`, except that the logic for closing
* the resource only happens in case the resource was initialized.
*
* NOTE: it's called "unsafe" because it is side-effecting.
* See homework.
*/
final class UnsafeLazyResource[A](
initRef: () => A,
closeRef: A => Unit,
) extends AutoCloseable {
/** Internal state that works like a FSM:
* - `null` is for pre-initialization
* - `Some(_)` is an active resource
* - `None` is the final state, a closed resource
*/
@volatile private[this] var ref: Option[A] = null
/**
* Returns the active resources. Initializes it if necessary.
*
* @return `Some(resource)` in case the resource is available,
* or `None` in case [[close]] was triggered.
*/
def get(): Option[A] =
ref match {
case null =>
// https://en.wikipedia.org/wiki/Double-checked_locking
this.synchronized {
if (ref == null) {
try {
ref = Some(initRef())
ref
} catch {
case NonFatal(e) =>
ref = None
throw e
}
} else {
ref
}
}
case other =>
other
}
override def close(): Unit =
if (ref ne None) {
val res = this.synchronized {
val old = ref
ref = None
old
}
res match {
case null | None => ()
case Some(a) => closeRef(a)
}
}
}
Example:
import java.io._
def openFile(path: File): UnsafeLazyResource[InputStream] =
new UnsafeLazyResource(
() => new FileInputStream(path),
in => in.close()
)
val lazyInput = openFile(new File("/tmp/file"))
// .. later
try {
val in = lazyInput.get().getOrElse(
throw new IllegalStateException("File already closed")
)
//...
} finally {
lazyInput.close()
}
Homework #
- Try using an AtomicReference instead of synchronizing a
var
— not as obvious as you’d think — initialization needs protection, you’ll need an indirection 😉 - Try designing a pure API with Cats Effect’s Resource (you might need Ref and Deferred for your internals too)
| Written by Alexandru Nedelcu