Ktor: How to use call.respond in non-suspended function.

Created on 10 May 2017  路  4Comments  路  Source: ktorio/ktor

Hello there.

I am using ktor 0.3.2 with netty, kotlin1.1 and Java8.

And thank you to make those great framework.

I'm trying to make JDBC middle ware for communication .NET main server via RESTful way.

However i have a problem to call.respond in non-suspended function.

post("query") {
    Connection.connect {
         // call.respond occurs an error. (Message: Suspension functions can be called only within coroutine body)
         call.respond(JsonResponse(query("SELECT * FROM someTable").toList())      
    }
}

Connection.connect function is non-suspended function.

I think the functions as post(), get() is using lambda function that chanined by pipeline.

And that pipeline is also represented PipelineContext, And that function is suspended function.

However Connection.connect function can't be implemented pipeline patterns that designed ktor.

How do i solve this problem?

This is the source part of Connection.connect.

class Connection private constructor() {
    companion object {
        fun connect(init: Database.() -> Unit): Database {
            return connect("default", init);
        }

        fun connect(name: String, init: Database.() -> Unit): Database {
            var properties: Properties = Properties()
            val config: HashMap<String, Object>? = Config.get(name)

            if (config === null) {
                throw ConfigureNotFoundException("The configuration for connection to JDBC is not founded.")
            }

            DriverManager.registerDriver(com.amazonaws.athena.jdbc.AthenaDriver())
            val conn: Connection = DriverManager.getConnection(
                    config.get("host")?.toString(),
                    config.get("name")?.toString(),
                    config.get("password")?.toString()
            )

            var database = Database(conn)
            database.init()

            return database
        }
    }
}

Thank you.

Most helpful comment

@KennethanCeyer would be good let people know what the solution was...

wisdom_of_the_ancients

All 4 comments

I solved this issue myself.

Thank you.

@KennethanCeyer would be good let people know what the solution was...

wisdom_of_the_ancients

Hello all you guys :clap:

This is very old issue...

I almost forgotten what the problem was.. :cry:

However perhaps i had ignored contribution culture at Kotlin's ecosystem then.

So i would try to remember what the solution was now!

In this issue, The problem was the suspend function(like as call.response) should not be under the blocking body (like as Connection.connect).

This problem seems to be similar to the problem that the async function in other languages does not await under the sync function.

post("query") {
    Connection.connect {
         // call.respond occurs an error. (Message: Suspension functions can be called only within coroutine body)
         // query
         call.respond(JsonResponse(query("SELECT * FROM someTable").toList())      
    }
}

You could know that application.response or call.response is suspend inline function

Please check this link and, https://github.com/ktorio/ktor/blob/37cbb5c59e3c5ae26fd7d2185a26f6e23e076802/ktor-server/ktor-server-core/src/io/ktor/response/ApplicationResponseFunctions.kt#L11

However Connection.connect body is none-coroutine(blocking function) type :open_mouth:

class Connection private constructor() {
    companion object {
        ...

        // blocking function
        fun connect(name: String, init: Database.() -> Unit): Database {

When I looked at Commit in the past, I seem to have solved it like follows.

open class Connection private constructor() {
    companion object {
        suspend fun connect(init: suspend Database.() -> Unit): Database {
            ...
        }

        suspend fun connect(name: String, init: suspend Database.() -> Unit): Database {
            ...
        }   

        suspend fun connect(credential: Credential, init: suspend Database.() -> Unit): Database {
            ...
        } 
}

All function had been changed to suspend(coroutine) type

@location("/controller-prefix/query")
data class ControllerName(val credential: Credential, val query: String)

fun Route.controllerName() {
    location<ModelName> {
        method(HttpMethod.Post) {
            handle<ModelName> {
                Connection.connect(it.credential) {
                     call.respond(JsonResponse(query(it.query).toList()))
                }
            }
        }
    }
}

At the time, I was not familiar with Kotlin,

So i did not share because I didn't know what is the Best Practices

I know now that many people are suffering from this. 馃槆

Thanks @KennethanCeyer! This lead me down the correct path...

For reference - https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md#wrapping-callbacks is the correct way to wrap callbacks within a coroutine and avoid.

Suspension functions can be called only within coroutine body

So using your example above in an inline fashion...

post("query") {
    Connection.connect {
         // call.respond occurs an error. (Message: Suspension functions can be called only within coroutine body)
         // query
         call.respond(JsonResponse(query("SELECT * FROM someTable").toList())      
    }
}

Becomes

post("query") {
    val response = suspendCoroutine<List> { cont -> 
        Connection.connect {
            val result = query("SELECT * FROM someTable").toList()
            cont.resume(result)
        }
    }
    call.respond(JsonResponse(response))
}
Was this page helpful?
0 / 5 - 0 ratings