-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
Changes from all commits
97f2a85
a19258d
c5a2295
db77195
1c8693a
c153bd1
40c2f3f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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`. | ||
* | ||
* 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 | ||
* } | ||
* }}} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Ichoran what are your thoughts on removing the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This has the added benefit of allowing us to pass the implicit There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The for-comprehension-only version probably ought to be called There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @NthPortal Sorry. I forgot to review. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's maybe not clear to everyone what the |
||
* @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() | ||
} | ||
|
||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.There was a problem hiding this comment.
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 }
vsfor (f <- Using(foo)) yield f.bar
, orUsing(foo){ _.bar }
vsUsing.resource(foo){ _.bar }
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant
Using(foo)
vsUsing.resource(foo)
, but explaining the possibilities how to use the monadic version is also welcome.