All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.rojoma.simplearm.v2.ResourceScope.scala Maven / Gradle / Ivy

The newest version!
package com.rojoma.simplearm.v2

import java.util.concurrent.atomic.AtomicReference

import scala.collection.mutable.ArrayBuilder
import scala.util.control.ControlThrowable
import java.util.IdentityHashMap

object ResourceScope {
  private val ctr = new java.util.concurrent.atomic.AtomicLong

  private class Node[A](val value: A, val res: Resource[A], val transitiveClose: List[Any], var prev: Node[_], var next: Node[_]) {
    def close() = res.close(value)
    def closeAbnormally(t: Throwable) = res.closeAbnormally(value, t)
  }

  implicit object ResourceScopeResource extends Resource[ResourceScope] {
    def close(rs: ResourceScope) = rs.close()
    override def closeAbnormally(rs: ResourceScope, t: Throwable) = rs.closeAbnormally(t)
  }

  class Transferator[A] private[ResourceScope] (self: ResourceScope, value: A) {
    def from(rs: ResourceScope) = rs.transfer(value).to(self)
    def to(rs: ResourceScope) {
      if(self.id < rs.id) {
        self.synchronized {
          rs.synchronized {
            rs.transferFrom(self, value)
          }
        }
      } else {
        rs.synchronized {
          self.synchronized {
            rs.transferFrom(self, value)
          }
        }
      }
    }
  }

  private val anonymousScopeName = "anonymous"

  private val notifyLeak = new AtomicReference[ResourceScope => Any](_.close())

  def onLeak[A](f: ResourceScope => A) =
    notifyLeak.set(f)
}

/** A (managable) object for managing resources with lifetimes that
  * do not nest nicely.
  *
  * {{{
  * using(new ResourceScope) { rs =>
  *   val in = rs.open(new FileInputStream("a"))
  *   val out = rs.open(new FileOutputStream("b"))
  *   rs.close(in) // explicit close of a resource
  *   throw new Exception // resource scope ensures "out" is closed
  * }
  * }}}
  *
  * Resource scopes may themselves be owned by other resource scopes.
  * It is possible to build an object which contains complex
  * sub-resources in this fashion.
  *
  * {{{
  * def allocateComplexThing() =
  *   using(new ResourceScope("temp")) { tmpScope =>
  *     new ComplexThing {
  *       // If ComplexThing's ctor throws, tmpScope will ensure cleanup
  *       val myScope = tmpScope.open(new ResourceScope("complex thing"))
  *       val sub1 = myScope.open(subResourceOne())
  *       val sub2 = myScope.open(subResourceTwo())
  *       // The very last thing the constructor does is unmanage
  *       // the scope and so take ownership.
  *       tmpScope.unmanage(myScope)
  *       def close() { myScope.close() }
  *     }
  *   }
  * }}}
  *
  * All methods on this class are thread-safe up to disposal of the
  * `ResourceScope` itself.
  */
final class ResourceScope(val name: String = ResourceScope.anonymousScopeName) {
  import ResourceScope._

  private val id = ctr.getAndIncrement()
  private[this] val managed = new IdentityHashMap[Any, Node[_]]
  private[this] var head: Node[_] = null
  private[this] var closed = false

  override def toString = s"ResourceScope($name)"

  // The main part of transferring a value from one scope to another;
  // at this point both locks are held.
  private def transferFrom(that: ResourceScope, value: Any) {
    val allNodes = that.findTransitiveCloseNodes(value)
    if(allNodes eq null) throw new IllegalArgumentException("transfer of resource not managed by " + that.name)
    if(that eq this) { return }

    for(node <- allNodes) {
      if(managed.put(node.value, node) ne null) throw new IllegalStateException("Value was managed by both " + this.name + " and " + that.name + "?")
      that.unmanageNode(node)

      if(head eq null) {
        node.next = null
        node.prev = null
        head = node
      } else {
        node.next = head
        head.prev = node
        node.prev = null
        head = node
      }
    }
  }

  // called during close, or during a transfer by the receiving scope;
  // returns nodes in order from deepest dependency to shallowest,
  // ending with the node for the value itself.
  private def findTransitiveCloseNodes(value: Any): Array[Node[_]] = {
    val node = managed.get(value)
    if(node eq null) null
    else if(node.transitiveClose.isEmpty) Array(node)
    else complexFindTransitiveCloseNodes(node)
  }

  private def complexFindTransitiveCloseNodes(node: Node[_]): Array[Node[_]] = {
    val seen = new java.util.HashSet[Node[_]]
    val result = new ArrayBuilder.ofRef[Node[_]]
    def loop(value: Any) {
      val node = managed.get(value)
      if((node ne null) && !seen.contains(node)) step(node)
    }
    def step(node: Node[_]) {
      seen.add(node)
      node.transitiveClose.foreach(loop)
      result += node
    }
    step(node)
    result.result()
  }

  // called during a transfer by the receiving scope after the value
  // has been successfully added to its own managed map
  private def unmanageNode(node: Node[_]) {
    val n = managed.remove(node.value)
    if(n eq null) throw new IllegalArgumentException("unmanage of resource not managed by " + name)
    if(n ne node) throw new IllegalStateException(name + " manages node.value but not via node?")
    if(n.next ne null) n.next.prev = n.prev
    if(n.prev ne null) n.prev.next = n.next
    else head = n.next
  }

  /** Manages a resource.  Once a resource is managed, to close it explicitly
    * use the `close(Any)` method on the `ResourceScope`, not any close on the
    * managed object.
    *
    * Optionally, this function can take a list of values to automatically close
    * when the current resource is closed.  Those values must be resources managed
    * by this scope.  If this value is transferred to another scope, the other
    * resources will be transferred along with it.
    *
    * @throws IllegalArgumentException if the value is already managed by this scope,
    *      or if any value listed in `transitiveClose` is not.
    * @throws IllegalStateException if this resource scope is closed.  It will check
    *      before evaluating the value to open, but if this scope is closed while
    *      that evaluation is happening, the exception will be thrown after the open
    *      actually occurs, and the value will be closed.
    */
  def open[T](value: => T, transitiveClose: Seq[Any] = Nil)(implicit res: Resource[T]): T = {
    val tc = transitiveClose.toList
    synchronized {
      if(closed) throw new IllegalStateException("resource scope " + name + " already closed")
      require(tc.forall(managed.containsKey), "dependency not managed by " + name)
    }
    val x: T = value
    res.openBeforeTry(x)
    try {
      res.openAfterTry(x)
      synchronized {
        if(closed) throw new IllegalStateException("resource scope " + name + " already closed")
        if(managed.containsKey(x)) throw new IllegalArgumentException("value already managed by " + name)
        val n = new Node(x, res, tc, null, head)

        managed.put(x, n)
        if(head ne null) head.prev = n
        head = n
      }
      x
    } catch {
      case control: ControlThrowable =>
        res.close(x)
        throw control
      case t: Throwable =>
        try {
          res.closeAbnormally(x, t)
        } catch {
          case control: ControlThrowable =>
            throw control
          case t2: Throwable =>
            if(t ne t2) t.addSuppressed(t2)
        }
        throw t
    }
  }

  /** Registers an unmanaged value with this scope.  This isn't very
    * useful in itself, but it can take a list of values to
    * transitively close, just like `open`.  This is useful for things
    * like iterator wrappers which are not themselves closeable but
    * which refer to closable things, and which may be transferred to
    * other scopes.
    *
    * @throws IllegalArgumentException if the value is already "managed" by this scope,
    *      or if any value listed in `transitiveClose` is not.
    */
  def openUnmanaged[T](value: => T, transitiveClose: Seq[Any] = Nil) =
    open(value, transitiveClose)(Resource.Noop.asInstanceOf[Resource[T]])

  /** Registers an value with this scope with an ad-hoc cleanup operation.
    *
    * @throws IllegalArgumentException if the value is already managed by this scope,
    *      or if any value listed in `transitiveClose` is not.
    */
  def withCleanup[T, U](value: => T, transitiveClose: Seq[Any] = Nil)(cleanup: T => U) =
    open(value, transitiveClose)(new Resource[T] { override def close(t: T) = cleanup(t) })

  /** Un-manages a resource.  If this succeeds, the value will no
    * longer be managed at all.
    *
    * @throws IllegalArgumentException if the value is not managed by this scope
    */
  def unmanage(value: Any) = synchronized {
    unmanageNode(managed.get(value))
  }

  /** Transfers a resource's ownership (and that of all its dependencies) between two scopes.
    * {{{
    * rs1.transfer(something).to(rs2)
    * rs2.transfer(something).from(rs1) // equivalent
    * }}}
    * If the resource has dependencies, the order in which they are transferred to the
    * other scope is defined only up to what can be inferred by the graph formed by
    * those dependencies.  That is:
    * {{{
    * val d1 = rs1.open(f())
    * val d2 = rs1.open(f())
    * val r = rs1.open(g(), transitiveClose = List(d1, d2))
    * // rs1.close() would be guaranteed to close [r, d2, d1]
    * rs1.transfer(r).to(rs2)
    * rs2.close() // may close either [r, d2, d1], or [r, d1, d2]
    * }}}
    *
    * @note This function is easy to use incorrectly; it depends on the
    *       declared dependency graph being correct.
    * @throws IllegalArgumentException if the value is not managed by the
    *       source scope
    */
  def transfer[A](value: A) = new Transferator(this, value)

  /** Closes a managed value and removes it from the scope.
    *
    * @throws IllegalArgumentException if the value is not managed by this scope
    */
  private[this] def closeImpl(value: Any, cause: Option[Throwable]) {
    val nodes = synchronized { findTransitiveCloseNodes(value) }
    if(nodes eq null) throw new IllegalArgumentException("close of resource not managed by " + name)

    def next(i: Int): Node[_] = {
      val v = nodes(i).value
      synchronized {
        val n = managed.remove(v)
        if(n eq null) return null
        if(n.next ne null) n.next.prev = n.prev
        if(n.prev ne null) n.prev.next = n.next
        else head = n.next
        n
      }
    }

    def continueClosingAbnormally(lastPlusOne: Int, cause: Throwable) {
      var i = lastPlusOne
      while(i != 0) {
        i -= 1
        val node = next(i)
        if(node ne null) {
          try {
            node.closeAbnormally(cause)
          } catch {
            case control: ControlThrowable =>
              continueClosingAbnormally(i, cause)
              throw control
            case t: Throwable =>
              if(cause ne t) cause.addSuppressed(t)
          }
        }
      }
    }

    def continueClosing(lastPlusOne: Int) {
      var i = lastPlusOne
      while(i != 0) {
        i -= 1
        val node = next(i)
        if(node ne null) {
          try {
            node.close()
          } catch {
            case control: ControlThrowable =>
              continueClosing(i)
              throw control
            case t: Throwable =>
              continueClosingAbnormally(i, t)
              throw t
          }
        }
      }
    }

    cause match {
      case None => continueClosing(nodes.length)
      case Some(t) => continueClosingAbnormally(nodes.length, t)
    }
  }

  def close(value: Any) {
    closeImpl(value, None)
  }

  private[this] def pop(): Node[_] = synchronized {
    if(head eq null) return null
    val h = head
    head = h.next
    if(head ne null) head.prev = null
    managed.remove(h.value)
  }

  /** Closes all resources contained in this scope, in the reverse order
    * they were added. */
  def close() {
    synchronized {
      if(closed) return
      else closed = true
    }
    doClose()
  }

  private def closeAbnormally(cause: Throwable) {
    synchronized {
      if(closed) return
      else closed = true
    }
    doCloseAbnormally(cause)
  }

  private[this] def doClose() {
    while(true) {
      val n = pop()
      if(n eq null) { return }
      try {
        n.close()
      } catch {
        case control: ControlThrowable =>
          doClose()
          throw control
        case e: Throwable =>
          doCloseAbnormally(e)
          throw e
      }
    }
  }

  private[this] def doCloseAbnormally(cause: Throwable) {
    while(true) {
      val n = pop()
      if(n eq null) { return }
      try {
        n.closeAbnormally(cause)
      } catch {
        case control: ControlThrowable =>
          doCloseAbnormally(cause) // finish closing
          throw control
        case t: Throwable =>
          if(cause ne t) cause.addSuppressed(t)
      }
    }
  }

  def isManaged(x: Any) = synchronized { managed.containsKey(x) }

  /** Utility for creating an owned-but-unmanaged object with an associated managed scope.
    * {{{
    * using(new ResourceScope("scope")) { rs =>
    *   val lines: Iterator[Line] = rs.unmanagedWithAssociatedScope("iterator scope") { itScope =>
    *     val stream = itScope.open(new FileInputStream("/tmp/foo"))
    *     linesOfFile(stream) // if this throws, stream will be closed before unmanagedWithAssociatedScope returns
    *   }
    *   rs.close(lines) // closes the associated FileInputStream
    * }
    * }}}
    */
  def unmanagedWithAssociatedScope[A](associatedScopeName: String = ResourceScope.anonymousScopeName)(f: ResourceScope => A): A = {
    val tmpScope = open(new ResourceScope(associatedScopeName))
    try {
      openUnmanaged(f(tmpScope), transitiveClose = List(tmpScope))
    } catch {
      case control: ControlThrowable =>
        close(tmpScope)
        throw control
      case cause: Throwable =>
        try {
          closeImpl(tmpScope, Some(cause))
        } catch {
          case t: Throwable =>
            if(cause ne t) cause.addSuppressed(t)
        }
        throw cause
    }
  }

  override protected def finalize() {
    if(!closed) {
      ResourceScope.notifyLeak.get.apply(this)
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy