Johannes
Johannes

Reputation: 2121

try-with-resources / use / multiple resources

I'm using a Java-API which heavily uses the Autoclosable-Interface and thus in Java try-with-resources. However in Java you can specify

try (res1, res2, res3...) {
  ...
}

Do we have a way to use more than one resource? It looks like the well known callback-hell:

val database = Databases.openDatabase(dbFile)

database.use {
  database.createResource(ResourceConfiguration.Builder(resPathName, config).build())

  val resMgr = database.getResourceManager(ResourceManagerConfiguration.Builder(resPathName).build())

  resMgr.use {
    val wtx = resMgr.beginNodeWriteTrx()

    wtx.use {
      wtx.insertSubtreeAsFirstChild(XMLShredder.createStringReader(resFileToStore))
    }
  }
}

Upvotes: 34

Views: 11512

Answers (6)

Hakanai
Hakanai

Reputation: 12670

Adding my own take here because none of the answers were acceptable to me:

import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

private fun closeChain(items: List<AutoCloseable>) {
    if (resources.isEmpty()) return
    try {
        resources.last().close()
    } finally {
        resources.dropLast(1).also(::closeChain)
    }
}

class ResourceInitializationScope {
    private val itemsToCloseLater = mutableListOf<AutoCloseable>()

    /**
     * Adds a resource to be closed later.
     *
     * @param resource the resource.
     */
    fun closeLater(item: AutoCloseable) {
        itemsToCloseLater.add(item)
    }

    fun closeNow() = closeChain(itemsToCloseLater)
}

/**
 * Begins a block to initialize multiple resources.
 *
 * This is done safely. If any failure occurs, all previously-initialized resources are closed.
 *
 * @param block the block of code to run.
 * @return an object which should be closed later to close all resources opened during the block.
 */
@OptIn(ExperimentalContracts::class)
inline fun initializingResources(block: ResourceInitializationScope.() -> Unit): AutoCloseable {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    val scope = ResourceInitializationScope()
    var success = false
    try {
        scope.apply(block)
        success = true
        return AutoCloseable { scope.closeNow() }
    } finally {
        if (!success) scope.closeNow()
    }
}

Example usage: (maybe don't trust the code, other than an example of how to use it, I don't trust it yet)

    // ... omitting surrounding code ...

    private val buffers: Array<AudioBuffer>
    private val device: AudioDevice
    private val context: AudioContext
    private val source: AudioSource
    private val resourcesToClose: AutoCloseable

    init {
        resourcesToClose = initializingResources {
            buffers = Array(BUFFER_COUNT) { AudioBuffer.generate().also(::closeLater) }
            device = AudioDevice.openDefaultDevice().also(::closeLater)
            context = AudioContext.create(device, intArrayOf(0)).also(::closeLater)

            context.makeCurrent()
            device.createCapabilities()
            source = AudioSource.generate().also(::closeLater)
            for (i in 0 until BUFFER_COUNT) {
                bufferSamples(ShortArray(0))
            }
            source.play()
            device.detectInternalExceptions()
        }
    }

    override fun close() {
        resourcesToClose.close()
    }

    // ... omitting surrounding code ...

Upvotes: 0

RomanMitasov
RomanMitasov

Reputation: 1021

Well there's plenty of answers already, but I noticed that this answer, especially Method 3 (with varargs), accepts already created resources, which is a bit different from Java's try-with-resources.

You see, according to this, Java not only closes all the resources after the try block, but it also catches all exceptions during resource initialisation. For example if resource2 wasn't created because of an exception, resource1 is still closed:

try (
  var resource1 = acquireResource();
  var resource2 = acquireResource() <- error here, but still is ok
) {
  // ...
}

I think that's why we have this nesting uses in Kotlin in the first place.

So, keeping the above detail in mind, I created this varargs variant:

inline fun <reified T : AutoCloseable?, R> withResources(vararg providers: () -> T, crossinline block: (Array<T?>) -> R): R {
  val array = arrayOfNulls<T>(providers.size)
  var useBlock = { block(array) }

  // reversed because we go from the deepest `use` to the top
  for (i in providers.indices.reversed()) {

    // we need a local variable here to pass it to the lambda
    // NOT by reference
    // otherwise lambda will capture the useBlock by ref
    // and create infinite recursion
    val goInside = useBlock
    useBlock = {
      array[i] = providers[i]()
      array[i].use { goInside.invoke() }
    }
  }
  return useBlock.invoke()
}

This function works exactly like Java's try-with-resources.

class TestAutoCloseable(val name: String, val closingMessage: String) : AutoCloseable {
  init {
    println("build $name")
  }
  override fun close() {
    println(closingMessage)
  }
  override fun toString(): String {
    return name
  }
}

fun main(args: Array<String>) {
  withResources(
    { TestAutoCloseable("A", "closing A") },
    { TestAutoCloseable("B", "closing B") },
    {
//    throw RuntimeException("boom")
      TestAutoCloseable("C", "closing C")
    }
  ) { (a, b, c) ->
    println("MY CODE, RESOURCES ARE: $a, $b, $c")
  }
}

Above example prints:

build A
build B
build C
MY CODE, RESOURCES ARE: A, B, C
closing C
closing B
closing A

But if I uncomment the RuntimeException in the C initialiser, I still get the "closing" parts for A and B:

build A
build B
closing B
closing A
Exception in thread "main" java.lang.RuntimeException: boom

The main downside of this function is the type inference, but there is nothing we can do about that.

I'd personally stick to the much simpler and more useful solution of just creating an inline function for 2 or 3 resources with simple use nesting inside. You'll probably never need more than 3 at the same time.

Upvotes: 0

Mir-Ismaili
Mir-Ismaili

Reputation: 16928

  • Method 1: For two resources and using native java resource manager:

    1. Define jUsing() in Kotlin:

      // crossinline version:
      inline fun <R, A : Closeable?, B : Closeable?>
              jUsing(a: A, b: B, crossinline block: (A, B) -> R): R = 
          J.jUsing(a, b) { c, d -> block(c, d) }
      
    2. And also Util.jUsing() in Util.java:

      Note: Below code is compatible with Java 9+. You can implement it with try-catch-finally to make it compatible with previous versions. See here for an example.

      public static <R, A extends AutoCloseable, B extends AutoCloseable> R 
      jUsing(A a, B b, Function2<A, B, R> block) throws Exception {
          try (a; b) {
              return block.invoke(a, b);
          }
      }
      

      (Function2 is kotlin.jvm.functions.Function2.)

    3. Then use like below:

      // Download url to destFile and close streams correctly:
      jUsing(URL(url).openStream(), FileOutputStream(destFile), InputStream::transferTo)
      

      Note: Above code used Java 9+ InputStream.transferTo() method. See here for a transferTo() Kotlin alternative that is compatible with previous versions.


    Note: You can write Kotlin jUsing() method more simple using noinline keyword instead of crossinline. But I think crossinline version has more performance:

    // noinline version:
    inline fun <R, A : Closeable?, B : Closeable?>
            jUsing(a: A, b: B, noinline block: (A, B) -> R): R =
            Util.jUsing(a, b, block)
    

  • Method 2: For two resources (and with similar usage to method 1):

    Thank @zsmb13's answer for the link

    /**
     * Based on https://github.com/FelixEngl/KotlinUsings/blob/master/Usings.kt
     * and with some changes
     */
    inline fun <R, A : Closeable, B : Closeable> using(a: A, b: B, block: (A, B) -> R): R {
        var exception: Throwable? = null
    
        try {
            return block(a, b)
        } catch (e: Throwable) {
            exception = e
            throw e
        } finally {
            if (exception == null) {
                a.close()
                b.close()
            } else {
                try {
                    a.close()
                } catch (closeException: Throwable) {
                    exception.addSuppressed(closeException)
                }
                try {
                    b.close()
                } catch (closeException: Throwable) {
                    exception.addSuppressed(closeException)
                }
            }
        }
    }
    

  • Method 3: For any number of resources (arrayOf(stream1, stream2, ...).use {...}):

    /**
     * Based on https://medium.com/@appmattus/effective-kotlin-item-9-prefer-try-with-resources-to-try-finally-aec8c202c30a
     * and with a few changes
     */
    inline fun <T : Closeable?, R> Array<T>.use(block: (Array<T>) -> R): R {
        var exception: Throwable? = null
    
        try {
            return block(this)
        } catch (e: Throwable) {
            exception = e
            throw e
        } finally {
            when (exception) {
                null -> forEach { it?.close() }
                else -> forEach {
                    try {
                        it?.close()
                    } catch (closeException: Throwable) {
                        exception.addSuppressed(closeException)
                    }
                }
            }
        }
    }
    

    See referenced link for more details.

Upvotes: 5

Michael Butscher
Michael Butscher

Reputation: 10959

Yet another approach for this:

val CloseableContext = ThreadLocal<MutableList<AutoCloseable>>()

inline fun scopeDef(inScope: () -> Unit) {
    val oldContext = CloseableContext.get()

    val currentContext = mutableListOf<AutoCloseable>()

    CloseableContext.set(currentContext)

    try {
        inScope()
    }
    finally {
        for(i in (currentContext.size - 1) downTo 0) {
            try {
                currentContext[i].close()
            }
            catch(e: Exception) {
                // TODO: Record as suppressed exception
            }
        }
        CloseableContext.set(oldContext)
    }
}

fun <T: AutoCloseable> autoClose(resource: T): T {
    CloseableContext.get()?.add(resource) ?: throw IllegalStateException(
            "Calling autoClose outside of scopeDef is forbidden")

    return resource
}

Usage:

class Close1(val name: String): AutoCloseable {
    override fun close() {
        println("close $name")
    }
}

fun main(args : Array<String>) {
    scopeDef {
        val c1 = autoClose(Close1("1"))

        scopeDef {
            val c3 = autoClose(Close1("3"))
        }

        val c2 = autoClose(Close1(c1.name + "+1"))

    }
}

Output:

close 3
close 1+1
close 1

Upvotes: 5

jrtapsell
jrtapsell

Reputation: 6991

For simplicity I will use A,B and C for the chained autocloseables.

import java.io.Closeable

open class MockCloseable: Closeable {
    override fun close() = TODO("Just for compilation")
}
class A: MockCloseable(){
    fun makeB(): B = TODO()
}
class B: MockCloseable(){
    fun makeC(): C = TODO()

}
class C: MockCloseable()

Using uses

This would look like this:

A().use {a ->
    a.makeB().use {b -> 
        b.makeC().use {c -> 
            println(c)
        }
    }
}

Making a chain use function with a wrapper

Definition

class ChainedCloseable<T: Closeable>(val payload: T, val parents: List<Closeable>) {
    fun <U> use(block: (T)->U): U {
        try {
            return block(payload)
        } finally {
            payload.close()
            parents.asReversed().forEach { it.close() }
        }
    }

    fun <U: Closeable> convert(block: (T)->U): ChainedCloseable<U> {
        val newPayload = block(payload)
        return ChainedCloseable(newPayload, parents + payload)
    }
}

fun <T: Closeable, U: Closeable> T.convert(block:(T)->U): ChainedCloseable<U> {
    val new = block(this)

}

Usage

A()
    .convert(A::makeB)
    .convert(B::makeC)
    .use { c ->
         println(c)
    }

This allows you to avoid having to nest deeply, at the cost of creating wrapper objects.

Upvotes: 6

zsmb13
zsmb13

Reputation: 89548

There is no standard solution for this. If you had all of the Closable instances ready at the start, you could use your own self-defined methods to handle them, like this blog post or this repository shows (and here is the discussion on the official forums that led to the latter).

In your case however, where subsequent objects rely on the previous ones, none of these apply like a regular try-with-resources would.

The only thing I can suggest is trying to define helper functions for yourself that hide the nested use calls, and immediately place you in the second/third/nth layer of these resourcs acquisitions, if that's at all possible.

Upvotes: 13

Related Questions