jeudi 10 mai 2018

kotlin - try with resources

This is a long time I did not post, no excuse, I decided to post my last attempt to close resources in Kotlin. In a earlier post, I wrote how much I found Kotlin to be refreshing.

I recently tried Scala and during my learning of Scala, I immediately wondered how to close properly the resource like the try-with-resources from Java. I was really hoping that it was built-in but it is not unfortunately.

Then I thought about Kotlin, which has now the .use {} pattern. However I know by experience that it can be cumbersome if we have multiple resources, ending with a lot of nested . use{ .use{ .use{} } }

Did you think how the kotlin .use{} or the java try-with-resources works if the close raises an exception? it just raises.

what if we don't want the close to raise?

In java, the best approach I have see is to wrap the resource as explained here https://stackoverflow.com/questions/6889697/close-resource-quietly-using-try-with-resources

basically to wrap the resource like
try(QuietResource qr = new QuietResource<>(new MyResource())){
    MyResource r = qr.get();
    r.read();
} catch (Exception e) {
    System.out.println("Exception: " + e.getMessage());
}

While we can use the same approach for kotlin and wrap the resource and apply the .use{} on this wrapper, I would like to avoid all the nested use{} when we have multiple resources.

Inspired by the "using" block proposed by one member of the JetBrains team
https://discuss.kotlinlang.org/t/kotlin-needs-try-with-resources/214

I started from there and improved it for the exception handling. I wanted to be able also to control if the close must raise or at least the exception must appear in the log.

IMO the standard behaviour must be kept to raise if the close raises an exception but we should be able to make it closing quietly without exception and log it to keep an eye on it. Of course, all resources must be closed even so some raised an exception.

My proposed code is

class UsingResourceHolder {

    data class Resource(val x: AutoCloseable, val canThrow: Boolean, val doLog: Boolean)

    internal val resources = LinkedList()
    var logger: Logger? = Logger.getLogger("global")

    fun <T : AutoCloseable> T.autoClose(canThrow:Boolean = true, log:Boolean = false): T {
        resources.addFirst(Resource(this, canThrow = canThrow, doLog = log))
        return this    }

    fun <T : AutoCloseable> T.autoCloseQuietly(log:Boolean = false): T {
        return this.autoClose(canThrow=false,log=log)
    }

}

fun <R> using(block: UsingResourceHolder.() -> R): R {
    val holder = UsingResourceHolder()

    var globalException: Throwable? = null    var exceptionOnClose: Throwable? = null
    try {
        return holder.block()
    }
    catch(t: Throwable) {
        globalException = t
        throw t
    }
    finally {
        holder.resources.forEach { r ->            try {
                r.x.close()
            }
            catch (e: Exception) {

                if( globalException == null ) {
                    if( r.canThrow ) {
                        if (exceptionOnClose == null) exceptionOnClose = e
                        else exceptionOnClose!!.addSuppressed(e)
                    }
                    else if(r.doLog) {
                        holder.logger?.warning(e.message)
                    }
                }
                else {
                    if( r.canThrow ) globalException!!.addSuppressed(e)
                }
            }
        }
        if (exceptionOnClose != null) throw exceptionOnClose!!
    }
}


and how to use it:



and the output is:
May 12, 2018 8:01:25 PM TryWithResourceKt using
WARNING: exception from ExceptionMaker.ex3
Exception in thread "main" java.lang.Exception: exception from ExceptionMaker.ex2
at ExceptionMaker.close(TryWithResource.kt:69)
at TryWithResourceKt.using(TryWithResource.kt:39)
at TryWithResourceKt.main(TryWithResource.kt:75)
Suppressed: java.lang.Exception: exception from ExceptionMaker.ex1
... 3 more

In case the using block is raising an exception, the exceptions from the close are then suppressed by this main exception:



Exception in thread "main" java.lang.Exception: Code failure
at TryWithResourceKt$main$1.invoke(TryWithResource.kt:81)
at TryWithResourceKt$main$1.invoke(TryWithResource.kt)
at TryWithResourceKt.using(TryWithResource.kt:30)
at TryWithResourceKt.main(TryWithResource.kt:75)
Suppressed: java.lang.Exception: exception from ExceptionMaker.ex2
at ExceptionMaker.close(TryWithResource.kt:69)
at TryWithResourceKt.using(TryWithResource.kt:39)
... 1 more
Suppressed: java.lang.Exception: exception from ExceptionMaker.ex1
at ExceptionMaker.close(TryWithResource.kt:69)
at TryWithResourceKt.using(TryWithResource.kt:39)
... 1 more

What do you think?


Aucun commentaire:

Enregistrer un commentaire