Kotlin/kotlinx-cli

Suspendable Subcommand

rusnasonov opened this issue · 8 comments

Hi! Is the way to write async code in Subcommand, make fun execute suspendable?

Hello! Now fun execute isn't suspendable, only inside you can launch coroutines. But what do you exactly try to implement? I can't fins an example of situation when execute function itself should be suspendable. It could be useful if several subcommands could run in parallel. But only one subcommand of the same level can be used in command line (subcommands of subcommand is another level).

yes, you're right - it's useless

Can't agree that suspendable subcommands are useless. For instance I'm writing a cli linux utility which manages our Elasticsearch clusters. I have a linuxMain where runBlocking is called and a commonMain with all logic. So I cannot use sub-commands inside the commonMain as all the code is suspendable and it is not possible to call it from Subcommand.execute method.

The only thing I can think of is pass a channel to the sub-command and send a message with options (via offer method) into the channel. Thus a coroutine on the other side of the channel receive options and will be able to run suspendable code.

Also in my case I could move all the code into linuxMain but it's not the case for those who has real multiplatform project.

And in the end current API looks inconsistent as for sub-commands it behaves differently.

Do you want to call suspend functions inside execute? And you can'y launch a coroutine, because it's made in main in platform code? Is parse method also inside coroutine?

Yes, I want to call suspend functions inside execute.

It will be better if I make a concise example.

Platform code (linuxMain in my case):

fun main(args: Array<String>) = runBlocking<Unit> {
    try {
        run(Curl.create {}, args)
    } catch (ex: ElasticsearchException.TransportError) {
        println(ex)
        exitProcess(1)
    }
    exitProcess(0)
}

Common code:

suspend fun run(httpClientEngine: HttpClientEngine, args: Array<String>) {
    val parser = ArgParser("cluster-manager")
    parser.subcommands(ResumeAction(httpClientEngine), SwitchAction(httpClientEngine))
    parser.parse(args)
}

class ResumeAction(private val httpClientEngine: HttpClientEngine): Subcommand("resume") {
    override fn execute() {
        TODO("Cannot use async http client")
    }
}

What is the reason why you can't run coroutine inside inside exacute and just call parse without runBlocking?

@ilya-g Do you have any opinion connected with this question?

What is the reason why you can't run coroutine inside inside exacute and just call parse without runBlocking?

So I should pass a scope for a coroutine. It seems strange for me that parser needs a coroutine scope.