How do you implement a responding shell script in a Kotlin web service coroutines request? - kotlin

I created a web service with spring boot 2 and Kotlin to access same unix scripts and other programs of a certain server via a process builder. The response messages shall contain the stdout of the shell script I use. But I have problems with the coroutines in Kotlin. When I use process.waitFor() this is a blocking function. How can you use a dedicated thread(-pool) to track the external processes and suspend the coroutines for that time?
In the following code snippet you see how I get the return code of a shell script and the stdout information of it:
val process = ProcessBuilder(cmd)
.redirectErrorStream(true)
.start()
val exitCode = process.waitFor()
return CmdResult(
exitCode,
process.inputStream
)
I failed with the following trial, because I did not get the stdout information and did not know how to get the result out of the scope to use it as a return type:
val dispatcher = newFixedThreadPoolContext(4, "myPool")
launch(dispatcher) {
val process = ProcessBuilder(listOf(cmdScript))
.redirectErrorStream(true)
.start()
val exitCode = process.waitFor()
CmdResult(
exitCode,
process.inputStream
)
}.join()

You can use top level function withContext to return from a coroutine:
val dispatcher = newFixedThreadPoolContext(4, "myPool")
val result = withContext(dispatcher) {
val process = ProcessBuilder(listOf(cmdScript))
.redirectErrorStream(true)
.start()
val exitCode = process.waitFor()
CmdResult(
exitCode,
process.inputStream
)
}

Related

What's the proper way of returning a result out of a IO coroutine job?

The problem is very simple, but I can't really seem to wrap my head around it. I'm launching a non-blocking thread in the IO scope in order to read from a file. However, I can't get the result in time before I return from the method - it always returns the initial empty value "". What am I missing here?
private fun getFileContents(): String {
var result = ""
val fileName = getFilename()
val job = CoroutineScope(Dispatchers.IO).launch {
kotlin.runCatching {
val file = getFile(fileName)
file.openFileInput().use { inputStream ->
result = String(inputStream.readBytes(), Charsets.UTF_8)
}
}
}
return result
}
Coroutines are launched asynchronously. Your non-suspending function cannot wait for the result without blocking. For more information about why asynchronous code results in your function returning with the default result, read the answers here.
getFileContents() has to be a suspend function to be able to return something without blocking, in which case you don't need to launch a coroutine either. But then whatever calls this function must be in a suspend function or coroutine.
private suspend fun getFileContents(): String = withContext(Dispatchers.IO) {
val fileName = getFilename()
kotlin.runCatching {
val file = getFile(fileName)
file.openFileInput().use { inputStream ->
result = String(inputStream.readBytes(), Charsets.UTF_8)
}
}.getOrDefault("")
}
There are two "worlds" of code: either you are in a suspending/coroutine context or you are not. When you are in a function that is not a suspend function, you can only return results that can be computed immediately, or you can block until the result is ready.
Generally, if you're using coroutines, you launch a coroutine at some high level in your code, and then you are free to use suspend functions everywhere because almost all of your code is initially triggered by a coroutine. By "high level", I mean you launch the coroutine when a UI screen appears or a UI button is pressed, for example.
Basically, your coroutine launches are usually in UI listeners and UI event functions, not in lower-level code like the function in your question. The coroutine calls a suspend function, which can call other suspend functions, so you don't need to launch more coroutines to perform your various sequential tasks.
The alternate solution is to return a Deferred with the result, like this:
private fun getFileContents(): Deferred<String> {
val fileName = getFilename()
return CoroutineScope(Dispatchers.IO).async {
kotlin.runCatching {
val file = getFile(fileName)
file.openFileInput().use { inputStream ->
result = String(inputStream.readBytes(), Charsets.UTF_8)
}
}.getOrDefault("")
}
}
But to unpack the result, you will need to call await() on the Deferred instance inside a coroutine somewhere.

Testing Kotlin Flows with shareIn()

I'm trying to test a Flow that uses shareIn with Turbine, but I'm a bit lost why my tests are failing and how I can fix it.
class MyTest {
private val scope = CoroutineScope(Dispatchers.Default)
private val mutableSharedFlow = MutableSharedFlow<Int>()
#Test
fun succeeds() = runBlocking {
val sharedFlow = mutableSharedFlow
sharedFlow.test {
expectNoEvents()
mutableSharedFlow.emit(3)
expect(expectItem()).toBe(3)
}
}
#Test
fun fails() = runBlocking {
val sharedFlow = mutableSharedFlow
.shareIn(scope, started = SharingStarted.WhileSubscribed())
sharedFlow.test {
expectNoEvents()
mutableSharedFlow.emit(3)
expect(expectItem()).toBe(3)
}
}
}
In these tests, the first succeeds() test runs fine, but as soon as I include shareIn in the fails() test, the test fails with a timeout:
Timed out waiting for 1000 ms
kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1000 ms
(Coroutine boundary)
at app.cash.turbine.ChannelBasedFlowTurbine$expectEvent$2.invokeSuspend(FlowTurbine.kt:238)
at app.cash.turbine.ChannelBasedFlowTurbine$withTimeout$2.invokeSuspend(FlowTurbine.kt:206)
at app.cash.turbine.ChannelBasedFlowTurbine.expectItem(FlowTurbine.kt:243)
What should I do to test flows that use shareIn?
I do not know why you've decided to use a scope with Dispatchers.Default as here:
...
private val scope = CoroutineScope(Dispatchers.Default)
...
For tests, just use Dispatchers.Unconfined instead because it executes coroutines immediately on the current thread and that's exactly what you need there.
...
private val scope = CoroutineScope(Dispatchers.Unconfined)
...
So, after applying the change above, both of your tests passed successfully.
You can find my sample project for this question here.

Coroutine in Vertx never execute?

In Vert.x, suppose I have functions like this:
fun caller() {
runBlocking {
val job = GlobalScope.launch(vertx.dispatcher()) {
val r = suspendPart()
println(r) // never execute
}
println(1) // printed
job.join()
println(2) // never execute
}
}
suspend fun asyncPart(): Future<Int> {
val promise: Promise<Int> = Promise.promise()
delay(500)
promise.complete(0)
return promise.future()
}
suspend fun suspendPart(): Int {
return asyncPart().await()
}
r(which is 0) and 2 will never be printed, only 1 is printed. How should I fix it?
My intention is to wait for asyncPart completes (I have a AsyncResult inside actually).
Presumably your caller() method is called by vert.x and this means you're breaking one of the pivotal rules of vert.x:
Don’t block me!
Vert.x is mostly based on very fast single-threaded work, what this means is that when you block the thread in caller, it is unable to execute the coroutine scheduled with launch leading to a deadlock.
The proper way to solve this is to remove your blocking code through the integration vert.x provides for kotlin coroutines.
Alternatively using a different dispatcher for launch would also work since the other thread would unblock the vert.x dispatcher. But this would not solve the primary issue of blocking calls in the vert.x dispatcher.

Why Kotlin coroutines run in the same thread sequentially?

I thought that calling a "suspend" function from coroutine context using launch makes the call asynchronous. But in the example below I see that 2 invocations of placeOrder method are not running in the same thread one after another.
What is my mistake?
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.File
fun main() = runBlocking {
t("1")
launch {
t("2")
placeOrder("C:\\Users")
t("3")
}
launch {
t("12")
placeOrder("C:\\Program Files")
t("13")
}
t("4")
}
fun t(s: String) {
val currentThread = Thread.currentThread()
println(s + ": " + currentThread.name + " " + currentThread.id)
}
suspend fun placeOrder(d:String): String {
t("placeOrder $d")
val user = createUser(d) // asynchronous call to user service
val order = createOrder(user) // asynchronous call to order service
t("placeOrder $d finished")
return order
}
suspend fun createUser(d:String): String {
t("createUser $d")
val toString = File(d).walk().map {
it.length()
}.sum().toString()
t("createUser $d finished")
return toString
}
suspend fun createOrder(user: String): String {
t("createOrder $user")
val toString = File("C:\\User").walk().map {
it.length()
}.sum().toString()
t("createOrder $user finished")
return toString
}
Output:
1: main 1
4: main 1
2: main 1
placeOrder C:\Users: main 1
createUser C:\Users: main 1
createUser C:\Users finished: main 1
createOrder 1094020270277: main 1
createOrder 1094020270277 finished: main 1
placeOrder C:\Users finished: main 1
3: main 1
12: main 1
placeOrder C:\Program Files: main 1
createUser C:\Program Files: main 1
createUser C:\Program Files finished: main 1
createOrder 5651227104: main 1
createOrder 5651227104 finished: main 1
placeOrder C:\Program Files finished: main 1
13: main 1
Instead of writing suspendable IO, you wrote blocking IO:
File(d).walk().map {
it.length()
}
Your functions never actually suspend and instead they block the single thread associated with their runBlocking dispatcher.
You gave your coroutines no opportunity to execute concurrently.
If you applied withContext(IO) { ... } around the above code, you'd get concurrency, but of the plain-old Java type, several threads being blocked in IO operations together.
The reason for this behavior is twofold:
All your coroutines are executed in the runBlocking scope, which is a single-threaded event loop. So this means only a single thread is ever used unless a different context is specified. (launch(Dispatchers.IO) as an example)
Even then it would be possible for the coroutines to interleave, except your coroutines do call suspending functions which actually have to suspend. This means it is effectively a normal sequential function call. If your functions included a yield() or delay(..) call you would see the coroutines interleave in execution.
launch function signature:
fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job (source)
As per the official Kotlin documentation link:
When launch { ... } is used without parameters, it inherits the context (and
thus dispatcher) from the CoroutineScope it is being launched from.
In your case, it inherits the context of the main runBlocking coroutine which runs in the main thread.
As coroutine context includes a coroutine dispatcher that determines what thread or threads the corresponding coroutine uses for its execution, so you can provide a different CoroutineContext with your launch coroutine builder. For Example:
fun main() = runBlocking {
t("1")
launch(Dispatchers.Default) {
t("2")
placeOrder("C:\\Users")
t("3")
}
launch(Dispatchers.Default) {
t("12")
placeOrder("C:\\Program Files")
t("13")
}
t("4")
}
Regarding suspend functions, a suspending function is just a regular Kotlin function with an additional suspend modifier which indicates that the function can suspend the execution of a coroutine. It doesn't make the call asynchronous by default.
You can execute your function code with custom dispatcher(e.g. IO dispatcher) using Kotlin's withContext() function as below:
suspend fun get(url: String) = withContext(Dispatchers.IO){/* Code for N/W logic */}
This will execute the body of the function in separate Thread than the calling coroutine context.
Here is 3-part series blog explaining usage of coroutines in Android apps :
https://medium.com/androiddevelopers/coroutines-on-android-part-i-getting-the-background-3e0e54d20bb
replace launch with async
read this
Essentially the code above is running synchronously even without runBlocking!
Since all the coroutines are launched into the main running on a single thread, ultimately you can use IO dispatcher which uses multiple threads.
Also, note that multiple coroutines can run on a single thread but they are never executed in parallel, they may appear as running in parallel because of thread switching from one coroutine to another when a new coroutine is launched or suspended.

Concurrent S3 File Upload via Kotlin Coroutines

I need to upload many files to S3, it would take hours to complete that job sequentially. That's exactly what Kotlin's new coroutines excels in, so I wanted to give them a first try instead of fiddling around again with some Thread-based execution service.
Here is my (simplified) code:
fun upload(superTiles: Map<Int, Map<Int, SuperTile>>) = runBlocking {
val s3 = AmazonS3ClientBuilder.standard().withRegion("eu-west-1").build()
for ((x, ys) in superTiles) {
val jobs = mutableListOf<Deferred<Any>>()
for ((y, superTile) in ys) {
val job = async(CommonPool) {
uploadTile(s3, x, y, superTile)
}
jobs.add(job)
}
jobs.map { it.await() }
}
}
suspend fun uploadTile(s3: AmazonS3, x: Int, y: Int, superTile: SuperTile) {
val json: String = "{}"
val key = "$s3Prefix/x4/$z/$x/$y.json"
s3.putObject(PutObjectRequest("my_bucket", ByteArrayInputStream(json.toByteArray()), metadata))
}
The problem: the code is still very slow and logging reveals that requests are still executed sequentially: a job is finished before the next one is created. Only in very few cases (1 out of 10) I see jobs running concurrently.
Why does the code not run much faster / concurrently? What can I do about it?
Kotlin coroutines excel when you work with asynchronous API, while AmazonS3.putObject API that you are using is an old-school blocking, synchronous API, so you get only as many concurrent uploads as the number of threads in the CommonPool that you are using. There is no value in marking your uploadTile function with suspend modified, because it does not use any suspending functions in its body.
The first step in getting more throughput in your upload task is to start using asynchronous API for that. I'd suggest to look at Amazon S3 TransferManager for that purse. See if that gets your problem solved first.
Kotlin coroutines are designed to help you to combine your async APIs into a easy-to-use logical workflows. For example, it is straightforward to adapt asynchronous API of TransferManager for use with coroutines by writing the following extension function:
suspend fun Upload.await(): UploadResult = suspendCancellableCoroutine { cont ->
addProgressListener {
if (isDone) {
// we know it should not actually wait when done
try { cont.resume(waitForUploadResult()) }
catch (e: Throwable) { cont.resumeWithException(e) }
}
}
cont.invokeOnCompletion { abort() }
}
This extension enables you to write very fluent code that works with TransferManager and you can rewrite your uploadTile function to work with TransferManager instead of working with blocking AmazonS3 interface:
suspend fun uploadTile(tm: TransferManager, x: Int, y: Int, superTile: SuperTile) {
val json: String = "{}"
val key = "$s3Prefix/x4/$z/$x/$y.json"
tm.upload(PutObjectRequest("my_bucket", ByteArrayInputStream(json.toByteArray()), metadata))
.await()
}
Notice, how this new version of uploadTile uses a suspending function await that was defined above.