noticed strange behavior, not sure if it using coroutineScope incorrectly - kotlin-coroutines

Having a list of the data processors. Each data processor has its own dpProcess() which returns true if the process successfully done.
all of the processor.dpProcess() run in parallel with others (via the launch builder).
but only after all data porcessors are completed then the final onAllDataProcessed() should be called.
This is achived by the coroutineScope {} block.
and also the coroutineScope {} block is controled by the withTimeoutOrNull(2000), so that if the processing of data is not completed in 2 sec then will abort it
suspend fun processData(): Boolean {
var allDone = dataProcesserList.size > 0
val result = withTimeoutOrNull(2000) { // should timeout at 2000
coroutineScope { // block the execution sequence until all sub coroutines are completes
for (processor in dataProcesserList) {
launch { // launch each individual process in parallel
allDone = allDone && processor.dpProcess() // update the global final allDone, if anyone returns false it should be return false
}
}
}
}
/**
* strange: one of the processor.dpProcess() has return false, but the allDone at here still shows true (and it is not timed out)
*/
if (result != null && allDone) { // not timeout and all process completed successfully
onAllDataProcessed()
} else {
allDone = false // timeout or some process failed
}
return allDone
}
Noticed a strange behavior and happens more on device of Android 7.1.1 device than others
Some time the final allDone is true even one of the processor.dpProcess() returns false and not timed out.
But same case sometime returns false as expected
Is it because that how it is coded which brings this behavior?
What is better way to put a few functions to run in parallel and wait until all of them are done then advanced to next step.
And the whole process should timeout and abort

the
launch { // launch each individual process in parallel
allDone = allDone && processor.dpProcess() // update the global final allDone, if anyone returns false it should be return false
}
is the problem that launch actually could considered to run the block in different thread, so there is concurrence accessing the global allDone, one may have read it before other update it.

Related

Kotlin coroutines - Kick off function in api call and return api call without waiting

I have an api call that I want to kick off a long running job and then return a 200 ok. Currently it will kick of the job and move on, but once the initial function finishes what it needs to do, it still seems to wait until the coroutine has finished. I'm sure this is related to my relatively new understanding of kotlin coroutines.
fun apiCall() {
log.info("started")
longJob()
log.info("finished")
return ResponseEntity.ok()
}
fun longJob() {
runBlocking{
launch {
do stufff...
}
}
}
So basically I'm expected to see the logs print and then kick off the longJob and then see 200 in postman. But I'm actually getting both logs printed out as well as the job kicked off, but I don't see my 200ok until the job finishes.
If I understand correctly, you want to launch the longJob in background, and return 200ok status without waiting for longJob to finish. If this is the case then you can't use runBlocking here, it blocks the current thread until all jobs, launched in it, finish. You can create a CoroutineScope and launch and forget a long running task. The sample code:
val scope = CoroutineScope(Dispatchers.IO) // or CoroutineScope(Dispatchers.IO + SupervisorJob())
fun apiCall() {
log.info("started")
scope.launch { longJob() }
log.info("finished")
return ResponseEntity.ok()
}
In this sample logs "started" and "finished" will be printed before longJob() starts executing.

How to wait for next #Composable function in jetpack compose test?

Suppose I have 3 #Composable functions: Start, Loading, Result.
In the test, I call the Start function, click the Begin button on it, and the Loading function is called.
The Loading function displays the loading procedure, takes some time, and then calls the Result function.
The Result function renders a field with the text OK.
How to wait in the test for the Result or few seconds function to be drawn and check that the text is rendered OK?
composeTestRule
.onNodeWithText("Begin")
.performClick()
// Here you have to wait ...
composeTestRule
.onNodeWithText("OK")
.assertIsDisplayed()
You can use the waitUntil function, as suggested in the comments:
composeTestRule.waitUntil {
composeTestRule
.onAllNodesWithText("OK")
.fetchSemanticsNodes().size == 1
}
There's a request to improve this API but in the meantime you can get the helpers from this blog post and use it like so:
composeTestRule.waitUntilExists(hasText("OK"))
So the options are:
It is possible to write to the global variable which function was last called. The disadvantage is that you will need to register in each function.
Subscribe to the state of the screen through the viewmodel and track when it comes. The disadvantage is that you will need to pull the viewmodel into the test and know the code. The plus is that the test is quickly executed and does not get stuck, as is the case with a timer.
I made this choice. I wrote a function for starting an asynchronous timer, that is, the application is running, the test waits, and after the timer ends, it continues checking in the test. The disadvantage is that you set a timer with a margin of time to complete the operation and the test takes a long time to idle. The advantage is that you don't have to dig into the source code.
Implemented the function like this.
fun asyncTimer (delay: Long = 1000) {
AsyncTimer.start (delay)
composeTestRule.waitUntil (
condition = {AsyncTimer.expired},
timeoutMillis = delay + 1000
)
}
object AsyncTimer {
var expired = false
fun start(delay: Long = 1000){
expired = false
Timer().schedule(delay) {
expired = true
}
}
}
Then I created a base class for the test and starting to write a new test, I inherit and I have the necessary ready-made functionality for tests.
Based on Pitry's answer I created this extension function:
fun ComposeContentTestRule.waitUntilTimeout(
timeoutMillis: Long
) {
AsyncTimer.start(timeoutMillis)
this.waitUntil(
condition = { AsyncTimer.expired },
timeoutMillis = timeoutMillis + 1000
)
}
object AsyncTimer {
var expired = false
fun start(delay: Long = 1000) {
expired = false
Timer().schedule(delay) {
expired = true
}
}
}
Usage in compose test
composeTestRule.waitUntilTimeout(2000L)

Is it safe to launch a coroutine in SmartLifecycle?

Is it safe to launch a coroutine in a SmartLifecycle?
I need to use CoroutineCrudRepository within an initializer on the very first startup like the following, but I am unsure about the implications as using GlobalScope is marked as a delicate API:
#Component
class Initializer(val configRepo: ConfigRepository) : SmartLifecycle {
private var running = false
override fun start() {
running = true
GlobalScope.launch {
val initialized = configRepo.findByKey(ConfigKey.INITIALIZED)
if (initialized == null) {
// very first run
// ... do some stuff ...
val c = Config(key = ConfigKey.INITIALIZED, value = "1")
configRepo.save(c)
}
running = false
}
}
override fun stop() {
}
override fun isRunning(): Boolean = running
}
From what I understood there is no possibility to stop the coroutine and I cannot implement stop(). But my guess was that this is ok-ish during startup, because startup either fails and the complete application is shutdown (hence the coroutine would stop consuming resources) or the application starts up fine and I at least can indicate the isRunning from within the coroutine.
I would assume the configRepo to work fine, but I do not fully understand what would happen if the coroutine would get stuck.

How to let withTimeoutOrNull return null but finish code in the block

I need my code to run a block and return value after 1 second in case timeout but let it finish the job.
I managed to implement something that works but IDE suggest replacing async with withContext(DefaultDispatcher) but it's not working the same.
So my question is how to make it work without IDE warnings. I am new to Kotlin Coroutines so I might be missing something here.
#Test
fun how_timeout_with_null_works() = runBlocking<Unit> {
val time = measureTimeMillis {
println("Start test")
val result = withTimeoutOrNull(1, TimeUnit.SECONDS) {
async { doSomeHardWork() }.await()
}
println("End test $result")
}
println("Time $time")
delay(3000)
}
private suspend fun doSomeHardWork(): String {
println("start hard work")
Thread.sleep(2000)
print("end hard work")
return "[hard work done]"
}
IDE gives a warning in this case because async(ctx) { ... }.await() is usually a mistake and withContext(ctx) { ... } usually better reflects the original intent of the author of the code.
In case of your code, your intent is different. Your intent is to await for 1 second, without constraining your doSomeHardWork code. However, the structure of your code does not reflect your intent. You've wrapped the whole block into withTimeout and put doSomeHardWork inside of it, while your intent was only to do a time-limited wait for it. So, if you rewrite your code in the way where the structure of your code matches your intent, it will work without any warnings:
val work = async { doSomeHardWork() } // start work
val result = withTimeoutOrNull(1, TimeUnit.SECONDS) { work.await() } // wait with timeout
If you happen to need this pattern of code more than once, then you can define yourself a handy extension:
suspend fun <T> Deferred<T>.awaitWithTimeout(time: Long, unit: TimeUnit): T? =
withTimeoutOrNull(time, unit) { await() }
And then write even nicer code that reflects your intent:
val result = async { doSomeHardWork() }.awaitWithTimeout(1, TimeUnit.SECONDS)
Be careful, though. These coroutines that you start with async will continue running after the wait had timed out. This can easily lead to resource leaks, unless you take steps to limit the number of those concurrently running background jobs.

Xcode Unit Testing, wait in `class setUp()`

I'm currently adding a lot of unit tests in my app, to check if WS are in a running state.
I know perfectly how to wait inside a testMethod, using expectations.
What I want is less common. I have a test case working on user favorites data. To be sure of the state of this user, I want to create a fully new user before the case ( and not before each test )
So I want to use
public class func setUp() {
//call here WS ( async call )
}
My issue is, as I am in a class func, I cannot use expectation and waitForExpectation because these are instance methods.
Does anybody as an idea on how to wait for my WS complete before executing the tests ?
Thanks!
You can use semaphore technique to accomplish what you want.
let fakeWSCallProcessingTimeInSeconds = 2.0
let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(fakeWSCallProcessingTimeInSeconds * Double(NSEC_PER_SEC)))
let semaphore = dispatch_semaphore_create(0)
dispatch_after(delayTime, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)) {
dispatch_semaphore_signal(semaphore)
}
let timeoutInNanoSeconds = 5 * 1000000000
let timeout = dispatch_time(DISPATCH_TIME_NOW, Int64(timeoutInNanoSeconds))
if dispatch_semaphore_wait(semaphore, timeout) != 0 {
XCTFail("WS operation timed out")
}
This code will fake a webservice call with 2 seconds delay (dispatch_after) which in your case should be replaced with the actual call. Once the call is done and your user object is set up, you use "dispatch_semaphore_signal(semaphore)" to free up the semaphore object. If it's not available within 5 seconds (see timeoutInNanoSeconds), then the test is treated as failed. Obviously, you can alter the values as you wish.
The rest of the tests will run either after semaphore is free or when timeout happened.
More info on semaphores can be found in Apple docs.
I found it myself. I should not use expectation because, the setup part is not part of the testing.
I should instead use dispatch_group:
override class func setUp() {
super.setUp()
let group = dispatch_group_create()
dispatch_group_enter(group)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let account = MyAccount()
WS.worker.createAccount(account, password: "test") { (success, error) in
dispatch_group_leave(group)
}
}
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, Int64(50 * Double(NSEC_PER_SEC))))
}

Resources