Hmm, I seem to be having a basic problem with ktor...
# ktor
r
Hmm, I seem to be having a basic problem with ktor cio client... If I execute a call once, it works, but the second time the coroutine never continues.
📝 1
Program:
Copy code
fun main(args: Array<String>) = runBlocking {
  val client = HttpClient(CIO.config { })

  suspend fun responseHandler(counter: Int, call: HttpClientCall) {
    println("-=-=-= response handling $counter")
    val readChannel = call.response.content
    println("-=-=-= readChannel $counter=$readChannel")
  }

  client.call("<http://localhost:8096/internal/LLQD6-89TNKjjgRaVtMc2Q>").use {
    responseHandler(1, it)
  }

  println("Second attempt!")

  client.call("<http://localhost:8096/internal/LLQD6-89TNKjjgRaVtMc2Q>").use {
    responseHandler(2, it)
  }
}
Output:
Copy code
-=-=-= response handling 1
-=-=-= readChannel 1=kotlinx.coroutines.experimental.io.ByteBufferChannel@1f7030a6
Second attempt!
The second call never unsuspends, and there is no thread in the system waiting for the response.
Also note that it works fine by changing to
val client = HttpClient(Apache)
o
@e5l ^^
d
@rocketraman have you tried to read or skip the contents of the response channel to see if that helps? Not sure if CIO reuses the connection (because you are connecting to the same endpoint) and it is waiting for you to read or skip the contents
e
What version of ktor do you use?
r
@Deactivated User No I haven't. I assumed
close
would release any resources.
@e5l 0.9.3
d
right, there is a use, didn’t notice 🙂
e
Could you add ‘Connection: close’ request header?(just for test)
r
@e5l Like this?
Copy code
client.call("<http://localhost:8096/internal/LLQD6-89TNKjjgRaVtMc2Q>") {
    this.header("Connection", "Close")
  }.use {
    responseHandler(1, it)
  }
If so, still didn't work.
Ah hang on, had the case of
Close
wrong. Yes, that worked.
@e5l -^
e
Yep. It’s not an issue :), but it’s obvious design problem here
You could also disable ‘pipelining’ in config
r
I don't mind leveraging the pipelining / existing connection for the second call. How would I do that?
e
You could disable it in CIO.config {} block
r
No, I mean, how would I use pipelining properly if I don't want to disable it?
e
If you want to use ‘HttpClientCall’ and channels - just discard content in channel.
You could also use receive() method, and ktor handle all cases for you
r
Ok, so basically what is happening is that I can't get the second response, because the first response remains in the pipeline and unconsumed. Should the
close
not automatically discard the first response in the pipeline though?
e
Maybe, we should consider more use cases here
r
It's kind of academic here as I actually want to consume this response, but generally speaking I would say the current behavior is surprising if the user wants to, say, throw away part of the response if they don't need it.
e
Also close could mean ‘graceful close after channel consumed’
r
It could, but that definitely needs some explicit docs then (@Deactivated User)
e
Sure. One more thing: ByteReadChannel is quite ‘lowlevel’ stuff, could you provide use cases when you have to use it instead of ‘receive’?
d
Yes. Sure ill check and try to provide some notes about this behaviour
r
I am reading a large file and don't want to read the entire thing into memory.
val readChannel= call.receive<ByteReadChannel>()
has the same behavior
d
you can discard the contents, without reading it in memory
I guess you could also abort the connection, but that’s usually not a good idea
also why is the server returning a big file that you don’t want to read? Could you do a HEAD request instead of a GET? Or request a byte range? (to prevent the other backend to do extra work and waste bandwidth)
r
I want to read the whole thing: I just want to read and process it in pieces, asynchronously.
@Deactivated User I do understand that this is not a problem for my current use case... it totally works discard or read the
ByteReadChannel
. My point is that should either by a) documented, or b) the
close
on the
call
should automatically discard any unread bytes.
d
oh, sorry then, it seems I misread 🙂
probably both, and I’m starting with the documentation part
👍 1
if it happens to be fixed/changed the behaviour in a future version I will update the docs
@e5l I would try to do the following: - For the same client and the same job, a new request discards the rest of the content, closes the readchannel, and throws an exception if someones tries to read from it. - Have a fork/clone of the client per job or at least store in the client+job if the client has started to read it. This should make more obvious the behaviour. And if it happens that someone tries to read it later, the channel would be closed and an exception thrown, so they know they have to read it before doing a next call in this coroutine job/thread. Maybe this approach have flaws or cannot be done like this, but right now, hanging without explanation is a bit counterintuitive. I remember I had similar situations when I started with ktor and some people won’t read the documentation and will think that ktor is not working properly.
👍 2