https://kotlinlang.org logo
#ktor
Title
# ktor
a

Anders Sveen

04/24/2020, 6:02 PM
Hi. 🙂 We are using KTor server (Netty engine), but seeing quite slow response times. Some of them are definitely our own fault, but some are usually fast then suddenly end up taking 20-30 seconds suddenly. The weird thing is that the front proxy closes the connection on a time out, but I can see from the logs that the call completes after the time out. My theory: The slow requests ends up blocking processing in KTor. I see the default for runningLimit is 10, does that mean max 10 parallel taks? So if 10 ends up slow, any new ones will be queued? Any thoughts on increasing the runningLimit number? Thanks. 🙂
r

Robert Jaros

04/24/2020, 6:46 PM
You shouldn't block at all. Requests that can't respond in a short time should be run with a different dispatcher. And then it's up to you how much threads you give to that dispatcher.
m

mp

04/24/2020, 9:32 PM
yep, likely you need
<http://Dispatchers.IO|Dispatchers.IO>
around some blocking i/o or similar
a

Anders Sveen

04/26/2020, 4:50 PM
Hjm... This ansync stuff is pretty new for me... I realize it is probably because I added runBlocking around a respond to make it work inside a sync block... How would you guys do something like this?
Copy code
syncLib.syncMethod(parm1) { syncLibContext -> 
   call.respond(syncLibContext.value)
}
This of course gives the 'Suspend function 'respond' should be called only from a coroutine or another suspend function' . Any pointers is highly appreciated. 🙂 Is it as easy as adding a coroutine context with Dispatchers.IO around the synclib?
Hjm, so I am launching a CoroutineScope (IO dispatcher and some other stuff like MDC) within the syncMethod block, and that seems to be compiling. Am I on the right track? 🙂 Like this?
Copy code
syncLib.syncMethod(param1) { syncLibContext -> 
    CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO> + ...).launch {
        call.respond(syncLibContext.value)
    }
}
r

Robert Jaros

04/26/2020, 5:11 PM
I would say:
Copy code
CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO> + ...).launch {
    syncLib.syncMethod(param1) { syncLibContext -> 
        call.respond(syncLibContext.value)
    }
}
a

Anders Sveen

04/26/2020, 5:15 PM
That gives the same compile error though... 😕 'Suspend function should be called...'
r

Robert Jaros

04/26/2020, 5:17 PM
why is this
syncMethod
taking a callback parameter?
it looks like async for me
a

Anders Sveen

04/26/2020, 5:21 PM
It might be. I'm a bit confused. The (weird) thing I am trying to do here is stream a Jdbi result with Jackson streaming API. So they are basically Java libs. The real thing here (some stuff hidden in jsonHandler though) is:
Copy code
jdbi.inTransaction<Unit, RuntimeException> { handle ->
            val resultStream = generatorFunc(handle)
            call.respondOutputStream(ContentType.parse("application/json")) {
                jsonHandler.streamObjectsToJson(resultStream, this, receiptCallback, endCallback)
            }
    }
r

Robert Jaros

04/26/2020, 5:31 PM
I think
inTransaction
method should return a value.
So you should try something like this:
Copy code
val stream = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
	jdbi.inTransaction<Unit, RuntimeException> { handle ->
         val resultStream = generatorFunc(handle)
         jsonHandler.streamObjectsToJson(resultStream, this, receiptCallback, endCallback)
         resultStream
    }
}
call.respondOutputStream(ContentType.parse("application/json")) {
	stream
}
I don't know jdbi API, but maybe first run jdbi function with Dispatchers.IO, get the result and next sent it with Ktor call response
a

Anders Sveen

04/26/2020, 5:42 PM
The transaction needs to stay open (that's what the block is fore) while the stream is read (call.respondOutputStream). If not, the underlying (to the Stream) JDBC cursor will throw an Exception. A bit special, but an awesome way to make sure you can handle a lot of searches etc. without blowing up your memory consumption. 🙂
r

Robert Jaros

04/26/2020, 5:59 PM
I'm afraid I don't know how to use such api with ktor and coroutines 😞 Perhaps someone else will give you better advice ...
a

Anders Sveen

04/26/2020, 6:00 PM
Thanks. 🙂 I just realized I will have to wait for the entire result to complete before JDBI closes so you are right it should be around the sync... I'll have to dig more... 🙂
So this compiles... And no runBlocking... But I worry that it still blocks stuff in KTor (low comprehension here)... Anyone can tell? 🙂
Copy code
jdbi.open().use { handle ->
    val resultStream = generatorFunc(handle)
    call.respondOutputStream(ContentType.parse("application/json")) {
        jacksonFactory.createGenerator(this).use { jacksonGenerator ->
            jacksonGenerator.writeStartArray()
            resultStream.forEach {
                jacksonObjectMapper.writeValue(generator, it)
            }
            jacksonGenerator.writeEndArray()
        }
    }
}
r

Robert Jaros

04/26/2020, 8:09 PM
you should be able to put this inside
withContext(<http://Dispatchers.IO|Dispatchers.IO>) { ... }
to make sure it will not block Ktor
45 Views