Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add scala.util.Using, for automatic resource management #6907

Merged
merged 7 commits into from
Aug 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
287 changes: 287 additions & 0 deletions src/library/scala/util/Using.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
package scala.util

import java.util.concurrent.atomic.AtomicBoolean

import scala.util.control.{ControlThrowable, NonFatal}

/** A utility for performing automatic resource management. It can be used to perform an
* operation using resources, after which it will release the resources, in reverse order
* of their creation. The resource opening, operation, and resource releasing are wrapped
* in a `Try`.
*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the doc should explain what a resource is, how the Resource type class is used, what the user needs to do for non-AutoClosables.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there are two ways of using Using, I think this comment (which is the entry-point) should mention both, say why they exist, what the differences are.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you say two ways, to you mean Using(foo){ _.bar } vs for (f <- Using(foo)) yield f.bar, or Using(foo){ _.bar } vs Using.resource(foo){ _.bar }?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant Using(foo) vs Using.resource(foo), but explaining the possibilities how to use the monadic version is also welcome.

* If more than one exception is thrown by the operation and releasing resources,
* the exception thrown ''first'' is returned within the `Try`, with the other exceptions
* [[java.lang.Throwable.addSuppressed(Throwable) added as suppressed exceptions]]
NthPortal marked this conversation as resolved.
Show resolved Hide resolved
* to the one thrown first. This is the case ''unless'' a later exception is
* [[scala.util.control.NonFatal fatal]], and the one preceding it is not. In that case,
* the first exception is added as a suppressed exception to the fatal one, and the fatal
* one is thrown. If an exception is a
* [[scala.util.control.ControlThrowable ControlThrowable]], no exception will be added to
* it as a suppressed exception.
*
* @example
* {{{
* val lines: Try[List[String]] = Using(resource1) { r1 =>
* r1.lines.toList
* }
* }}}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ichoran what are your thoughts on removing the apply method, so that there aren't two ways to use it - you have to use a for comprehension (or manual map/flatMap)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has the added benefit of allowing us to pass the implicit Resource on construction

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The for-comprehension-only version probably ought to be called Use or Used, not Using, if there's only that option. I generally prefer the apply version, but I don't have a strong preference.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't feel strongly about it either; I'm just concerned that some others might.

I'm going to leave it as is for now

*
* @example
* {{{
* val lines: Try[Seq[String]] = for {
* r1 <- Using(resource1)
* r2 <- Using(resource2)
* r3 <- Using(resource3)
* r4 <- Using(resource4)
* } yield {
* // use your resources here
* r1.lines ++ r2.lines ++ r3.lines ++ r4.lines
* }
* }}}
*/
final class Using[R] private(resource: => R) {
private[this] val used = new AtomicBoolean(false)

/** Performs an operation using a resource, and then releases the resource,
* even if the operation throws an exception.
*
* @param f the operation to perform
* @param r an implicit [[Using.Resource]]
* @tparam A the return type of the operation
* @throws java.lang.IllegalStateException if the resource has already been used
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am open to being convinced that the resource can be generated again using the by-name parameter, and that the Using instance can be used arbitrarily many times.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NthPortal Sorry. I forgot to review.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NthPortal It looks good to me as far as I can see

* @return a [[scala.util.Try `Try`]] containing the result of the operation, or
* an exception if one was thrown by the operation or by releasing the resource
*/
@throws[IllegalStateException]("if the resource has already been used")
@inline def apply[A](f: R => A)(implicit r: Using.Resource[R]): Try[A] = map(f)

/** Performs an operation using a resource, and then releases the resource,
* even if the operation throws an exception.
*
* @param f the operation to perform
* @param r an implicit [[Using.Resource]]
* @tparam A the return type of the operation
* @throws java.lang.IllegalStateException if the resource has already been used
* @return a [[scala.util.Try `Try`]] containing the result of the operation, or
* an exception if one was thrown by the operation or by releasing the resource
*/
@throws[IllegalStateException]("if the resource has already been used")
def map[A](f: R => A)(implicit r: Using.Resource[R]): Try[A] = Try { useWith(f) }

/** Performs an operation which returns a [[scala.util.Try `Try`]] using a resource,
* and then releases the resource, even if the operation throws an exception.
*
* @param f the `Try`-returning operation to perform
* @param r an implicit [[Using.Resource]]
* @tparam A the return type of the operation
* @throws java.lang.IllegalStateException if the resource has already been used
* @return the result of the inner operation, or a [[scala.util.Try `Try`]]
* containing an exception if one was thrown by the operation or by
* releasing the resource
*/
@throws[IllegalStateException]("if the resource has already been used")
def flatMap[A](f: R => Try[A])(implicit r: Using.Resource[R]): Try[A] =
map {
r => f(r).get // otherwise inner Failure will be lost on exceptional release
}

@inline private[this] def useWith[A](f: R => A)(implicit r: Using.Resource[R]): A =
if (used.getAndSet(true)) throw new IllegalStateException("resource has already been used")
else Using.resource(resource)(f)
}

/** @define recommendUsing It is highly recommended to use the `Using` construct,
* which safely wraps resource usage and management in a `Try`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's maybe not clear to everyone what the Using construct is

* @define multiResourceSuppressionBehavior If more than one exception is thrown by the operation and releasing resources,
* the exception thrown ''first'' is thrown, with the other exceptions
* [[java.lang.Throwable.addSuppressed(Throwable) added as suppressed exceptions]]
* to the one thrown first. This is the case ''unless'' a later exception is
* [[scala.util.control.NonFatal fatal]], and the one preceding it is not. In that case,
* the first exception is added as a suppressed exception to the fatal one, and the fatal
* one is thrown. If an exception is a
* [[scala.util.control.ControlThrowable ControlThrowable]], no exception will be added to
* it as a suppressed exception.
*/
object Using {
/** Creates a `Using` from the given resource.
*
* @note If the resource does not have an implicit [[Resource]] in
* scope, the returned `Using` will be useless.
NthPortal marked this conversation as resolved.
Show resolved Hide resolved
*/
def apply[R](resource: => R): Using[R] = new Using(resource)

/** Performs an operation using a resource, and then releases the resource,
* even if the operation throws an exception. This method behaves similarly
* to Java's try-with-resources.
*
* $recommendUsing
*
* If both the operation and releasing the resource throw exceptions, the one thrown
* when releasing the resource is
* [[java.lang.Throwable.addSuppressed(Throwable) added as a suppressed exception]]
* to the one thrown by the operation, ''unless'' the exception thrown when releasing
* the resource is [[scala.util.control.NonFatal fatal]], and the one thrown by the
* operation is not. In that case, the exception thrown by the operation is added
* as a suppressed exception to the one thrown when releasing the resource. If an
* exception is a [[scala.util.control.ControlThrowable ControlThrowable]], no
* exception will be added to it as a suppressed exception.
*
* @param resource the resource
* @param body the operation to perform with the resource
* @tparam R the type of the resource
* @tparam A the return type of the operation
* @return the result of the operation, if neither the operation nor
* releasing the resource throws
*/
def resource[R: Resource, A](resource: R)(body: R => A): A = {
if (resource == null) throw new NullPointerException("null resource")

@inline def safeAddSuppressed(t: Throwable, suppressed: Throwable): Unit = {
// don't `addSuppressed` to something which is a `ControlThrowable`
if (!t.isInstanceOf[ControlThrowable]) t.addSuppressed(suppressed)
}

var primary: Throwable = null
try {
body(resource)
} catch {
case t: Throwable =>
primary = t
null.asInstanceOf[A] // compiler doesn't know `finally` will throw
} finally {
if (primary eq null) implicitly[Resource[R]].release(resource)
else {
var toThrow = primary
try {
implicitly[Resource[R]].release(resource)
} catch {
case other: Throwable =>
if (NonFatal(primary) && !NonFatal(other)) {
// `other` is fatal, `primary` is not
toThrow = other
safeAddSuppressed(other, primary)
} else {
// `toThrow` is already `primary`
safeAddSuppressed(primary, other)
}
} finally {
throw toThrow
}
}
}
}

/** Performs an operation using two resources, and then releases the resources
* in reverse order, even if the operation throws an exception. This method
* behaves similarly to Java's try-with-resources.
*
* $recommendUsing
*
* $multiResourceSuppressionBehavior
*
* @param resource1 the first resource
* @param resource2 the second resource
* @param body the operation to perform using the resources
* @tparam R1 the type of the first resource
* @tparam R2 the type of the second resource
* @tparam A the return type of the operation
* @return the result of the operation, if neither the operation nor
* releasing the resources throws
*/
def resources[R1: Resource, R2: Resource, A](
resource1: R1,
resource2: => R2
)(body: (R1, R2) => A
): A =
resource(resource1) { r1 =>
resource(resource2) { r2 =>
body(r1, r2)
}
}

/** Performs an operation using three resources, and then releases the resources
* in reverse order, even if the operation throws an exception. This method
* behaves similarly to Java's try-with-resources.
*
* $recommendUsing
*
* $multiResourceSuppressionBehavior
*
* @param resource1 the first resource
* @param resource2 the second resource
* @param resource3 the third resource
* @param body the operation to perform using the resources
* @tparam R1 the type of the first resource
* @tparam R2 the type of the second resource
* @tparam R3 the type of the third resource
* @tparam A the return type of the operation
* @return the result of the operation, if neither the operation nor
* releasing the resources throws
*/
def resources[R1: Resource, R2: Resource, R3: Resource, A](
resource1: R1,
resource2: => R2,
resource3: => R3
)(body: (R1, R2, R3) => A
): A =
resource(resource1) { r1 =>
resource(resource2) { r2 =>
resource(resource3) { r3 =>
body(r1, r2, r3)
}
}
}

/** Performs an operation using four resources, and then releases the resources
* in reverse order, even if the operation throws an exception. This method
* behaves similarly to Java's try-with-resources.
*
* $recommendUsing
*
* $multiResourceSuppressionBehavior
*
* @param resource1 the first resource
* @param resource2 the second resource
* @param resource3 the third resource
* @param resource4 the fourth resource
* @param body the operation to perform using the resources
* @tparam R1 the type of the first resource
* @tparam R2 the type of the second resource
* @tparam R3 the type of the third resource
* @tparam R4 the type of the fourth resource
* @tparam A the return type of the operation
* @return the result of the operation, if neither the operation nor
* releasing the resources throws
*/
def resources[R1: Resource, R2: Resource, R3: Resource, R4: Resource, A](
resource1: R1,
resource2: => R2,
resource3: => R3,
resource4: => R4
)(body: (R1, R2, R3, R4) => A
): A =
resource(resource1) { r1 =>
resource(resource2) { r2 =>
resource(resource3) { r3 =>
resource(resource4) { r4 =>
body(r1, r2, r3, r4)
}
}
}
}

/** A typeclass describing a resource which can be released.
*
* @tparam R the type of the resource
*/
trait Resource[-R] {
/** Releases the specified resource. */
def release(resource: R): Unit
}

object Resource {
/** An implicit `Resource` for [[java.lang.AutoCloseable `AutoCloseable`s]]. */
implicit val autoCloseableResource: Resource[AutoCloseable] = (resource: AutoCloseable) => resource.close()
}

}
Loading