The traditional approach in Java to execute an external process is to start a new Process, start two threads to consume its inputStream and errorStream and then call its blocking Process.waitFor() to wait till the external command has exited.
How can this be done in a (almost) non-blocking style with Kotlin coroutines?
I tried it this way. Do you have any suggestions to improve it?
(How to asynchronously read the streams, also call ProcessBuilder.start() in withContext(Dispatchers.IO), are there too many calls to Dispatchers.IO, ...?)
suspend fun executeCommand(commandArgs: List<String>): ExecuteCommandResult {
try {
val process = ProcessBuilder(commandArgs).start()
val outputStream = GlobalScope.async(Dispatchers.IO) { readStream(process.inputStream) }
val errorStream = GlobalScope.async(Dispatchers.IO) { readStream(process.errorStream) }
val exitCode = withContext(Dispatchers.IO) {
process.waitFor()
}
return ExecuteCommandResult(exitCode, outputStream.await(), errorStream.await())
} catch (e: Exception) {
return ExecuteCommandResult(-1, "", e.localizedMessage)
}
}
private suspend fun readStream(inputStream: InputStream): String {
val readLines = mutableListOf<String>()
withContext(Dispatchers.IO) {
try {
inputStream.bufferedReader().use { reader ->
var line: String?
do {
line = reader.readLine()
if (line != null) {
readLines.add(line)
}
} while (line != null)
}
} catch (e: Exception) {
// ..
}
}
return readLines.joinToString(System.lineSeparator())
}