Suspend `withTimeout` - kotlin

My app starts from executing coroutine:
model.viewModelScope.launch(Dispatchers.IO) {
val timeout = withTimeoutOrNull(TIMEOUT) {
//send some initialization server requests
true //timeout=true
}
if (timeout!=null){
// app started ok
} else {
// app freezed on start
}
}
The problem is one of the initialization step - obtain user consent to share his personal data(GDPR) Consent form is a modal popup that waits for user input.But the timer is ticking all this time. Therefore after gdpr user may see app starts too long error.
Is there way to suspend withTimeoutOrNull timer and resume it after some time?

It is not supported out of the box, but you can write your own version of withTimeoutOrNull that would support a pauseable timer. The key idea is to run your block in a separate coroutineScope { ... } and to launch a secondary helper coroutine that would cancel this scope after timeout. Now if you keep a reference to this helper cancellationJob, then you can cancel it when you need to pause your time and restart it when you need to resume timer.
That would give you essentially the same behavior that basic withTimeout has but with an added pauseability. A bit of extra code is needed to implement a withTimeoutOrNull variant. You'll need to catch your cancellation exception and replace it with null result.
A worked-out implementation can be found in this gist: https://gist.github.com/elizarov/c5b0fde43ca14efbb8bcab13ad43c6ca

Related

Properly cancelling kotlin coroutine job

I'm scratching my head around properly cancelling coroutine job.
Test case is simple I have a class with two methods:
class CancellationTest {
private var job: Job? = null
private var scope = MainScope()
fun run() {
job?.cancel()
job = scope.launch { doWork() }
}
fun doWork() {
// gets data from some source and send it to BE
}
}
Method doWork has an api call that is suspending and respects cancellation.
In the above example after counting objects that were successfully sent to backend I can see many duplicates, meaning that cancel did not really cancel previous invocation.
However if I use snippet found on the internet
internal class WorkingCancellation<T> {
private val activeTask = AtomicReference<Deferred<T>?>(null)
suspend fun cancelPreviousThenRun(block: suspend () -> T): T {
activeTask.get()?.cancelAndJoin()
return coroutineScope {
val newTask = async(start = CoroutineStart.LAZY) {
block()
}
newTask.invokeOnCompletion {
activeTask.compareAndSet(newTask, null)
}
val result: T
while (true) {
if (!activeTask.compareAndSet(null, newTask)) {
activeTask.get()?.cancelAndJoin()
yield()
} else {
result = newTask.await()
break
}
}
result
}
}
}
It works properly, objects are not duplicated and sent properly to BE.
One last thing is that I'm calling run method in a for loop - but anyways I'm not quire sure I understand why job?.cancel does not do its job properly and WorkingCancellation is actually working
Short answer: cancellation only works out-of-the box if you call suspending library functions. Non-suspending code needs manual checks to make it cancellable.
Cancellation in Kotlin coroutines is cooperative, and requires the job being cancelled to check for cancellation and terminate whatever work it's doing. If the job doesn't check for cancellation, it can quite happily carry on running forever and never find out it has been cancelled.
Coroutines automatically check for cancellation when you call built-in suspending functions. If you look at the docs for commonly-used suspending functions like await() and yield(), you'll see that they always say "This suspending function is cancellable".
Your doWork isn't a suspend function, so it can't call any other suspending functions and consequently will never hit one of those automatic checks for cancellation. If you do want to cancel it, you will need to have it periodically check whether the job is still active, or change its implementation to use suspending functions. You can manually check for cancellation by calling ensureActive on the Job.
In addition to Sam's answer, consider this example that mocks a continuous transaction, lets say location updates to a server.
var pingInterval = System.currentTimeMillis()
job = launch {
while (true) {
if (System.currentTimeMillis() > pingInterval) {
Log.e("LocationJob", "Executing location updates... ")
pingInterval += 1000L
}
}
}
Continuously it will "ping" the server with location udpates, or like any other common use-cases, say this will continuously fetch something from it.
Then I have a function here that's being called by a button that cancels this job operation.
fun cancel() {
job.cancel()
Log.e("LocationJob", "Location updates done.")
}
When this function is called, the job is cancelled, however the operation keeps on going because nothing ensures the coroutine scope to stop working, all actions above will print
Ping server my location...
Ping server my location...
Ping server my location...
Ping server my location...
Location updates done.
Ping server my location...
Ping server my location...
Now if we insert ensureActive() inside the infinite loop
while (true) {
ensureActive()
if (System.currentTimeMillis() > pingInterval) {
Log.e("LocationJob", "Ping server my location... ")
pingInterval += 1000L
}
}
Cancelling the job will guarantee that the operation will stop. I tested using delay though and it guaranteed total cancellation when the job it is being called in is cancelled. Emplacing ensureActive(), and cancelling after 2 seconds, prints
Ping server my location...
Ping server my location...
Location updates done.

Kotlin Coroutines with timeout

I'm currently writing a test-function which should run a block or (when a certain timeout is reached) throws an exception.
I was trying this with Coroutines in Kotlin but ended up with a mixture of Coroutines and CompletableFuture:
fun <T> runBlockWithTimeout(maxTimeout: Long, block: () -> T ): T {
val future = CompletableFuture<T>()
// runs the coroutine
launch { block() }
return future.get(maxTimeout, TimeUnit.MILLISECONDS)
}
This works, but I'm not sure if this is the intended way to solve that problem in kotlin.
I also tried other approaches:
runBlocking {
withTimeout(maxTimeout) {
block()
}
}
But this seems not to work as soon as the block calls e.g. Thread.sleep(...)
So is the CompletableFuture approach the way to go or is there a better one?
update 1
What I want to achieve:
Async Integration-Test code (like receiving data from RabbitMq) should be tested somehow like this:
var rabbitResults: List = ... // are filled async via RabbitListeners
...
waitMax(1000).toSucceed {
assertThat(rabbitResults).hasSize(1)
}
waitMax(1000).toSucceed {
assertThat(nextQueue).hasSize(3)
}
...
withTimeout { ... } is designed to cancel the ongoing operation on timeout, which is only possible if the operation in question is cancellable.
The reason it works with future.get(timeout, unit) is because it only waits with timeout. It does not actually cancel or abort in any way your background operation which still continues to execute after timeout had elapsed.
If you want to mimick similar behavior with coroutines, then you should wait with timeout, like this:
val d = async { block() } // run the block code in background
withTimeout(timeout, unit) { d.await() } // wait with timeout
It works properly because await is a cancellable function which you can verify by reading its API documentation.
However, if you want to actually cancel the ongoing operation on timeout, then then you should implement your code in asyncronous and cancellable way. Cancellation is cooperative, so, to start, the underlying library that you are using in your code has to provide asynchronous API that supports cancellation of ongoing operation.
You can read more about cancellation and timeouts in the corresponding section of the coroutines guide and watch the KotlinConf's Deep Dive into Coroutines on how to integrate coroutines with asynchronous libraries.

Use CMSensorRecorder to continuously collect Accelerometer data in Apple Watch

I want to use CMSensorRecorder to continuously collect the Accelerometer data, also if user didn't open my app on watch\phone.
What I want to do is - "whenever possible" (i.e. watch is awake and I can execute code), do the following:
call recordAccelerometerForDuration to tell watch to keep collecting data for as long as possible
call accelerometerDataFromDate to get data collected so far (or from last time I got the data)
My question is - how to implement the "whenever possible", i.e. how can I cause my watch application to wake up and execute these apis whenever the watch itself wakes up ?
CMSensorRecorder- to recored data continuously use ExtentionDelegate to triger CMSensorRecorder to invoke start recording and reading data.
func applicationDidBecomeActive() {
print("Active")
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
AccelorometerDataReader.sharedReader.sessionEndDate = Date()
AccelorometerDataReader.sharedReader.getRecordedData()
}
func applicationWillResignActive() {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, etc.
print("inactive")
AccelorometerDataReader.sharedReader.startReadingAccelorometerData()
AccelorometerDataReader.sharedReader.sessionStartDate = Date()
}
//AccelorometerReaderCode
func startReadingAccelorometerData()
{
if CMSensorRecorder.isAccelerometerRecordingAvailable()
{
if CMSensorRecorder.isAuthorizedForRecording()
{
print("Authorized.......")
DispatchQueue.global(qos: .background).async
{
self.recorder?.recordAccelerometer(forDuration: 3 * 60) // Record for 3 minutes
}
}
else
{
print("not authorized")
}
}
else
{
print("NOt available for recording")
}
}
func getRecordedData()
{
DispatchQueue.global(qos: .background).async
{
if self.sessionStartDate < self.sessionEndDate
{
if let list = self.recorder?.accelerometerData(from: self.sessionStartDate, to:self.sessionEndDate)
{
for record in list
{
let data = record as! CMRecordedAccelerometerData
print("x: \(data.acceleration.x) y: \(data.acceleration.y) z: \(data.acceleration.z) time :\(data.startDate.getFormattedDate())")
}
}
}
}
}
}
It doesn't seem you can. I have tried the following but none of these really work.
Register for backgroundApplicationRefresh (watchOS 3) and make sure the app is saved to the Dock. Dock apps can rely on getting one call an hour to update themselves.
On completion of the period query for the accelerometer data and archive the result to file and then transfer the file to the iOS companion app. The file transfer will occur in the background independent of the watch kit app assuming you get enough CPU time to write the accelerometer data to file.
Try using ProcessInfo API to keep the process running.
To date the only success I have had is in running an async thread to extract the data and keeping the watch kit app active by tapping on the screen each time the screen blanks.
Hope this helps and please post any better solution you find.

What happens when back button is pressed before asynchronous call is completed?

I have view models that are being stored in an array list on the application class. So their lifetime continues even if the activity gets destroyed because of rotation (however once isFinishing is true then the view model instance is removed from the list and ceases to exist).
I also have data service singletons that are used by the view models that also live indefinitely. So if I start an async call in my view model using the anko async block i.e.
async {
val data = DataService.instance.getData()
uiThread {
if (data != null) {
//do something
}
}
}
What happens if the user presses the back button before the call completes? IsFinishing will be true and my view model instance will no longer exist. Will the async call exception when it returns or will it die gracefully?
The code within uiThread call won't get executed if the underlying activity/fragment context has been garbage collected. A uiThread call within an async call will hold a WeakReference to the context, so the often encountered context memory leak won't occur. Details here.
I guess it will gracefully die. You could write a small test program in which you debug the behaviour. However, I would strongly suggest to keep a reference/pointer to the asynchronous task and kill it when you press the back button. No need to let it run in the background if you do not need the result / cannot handle the result anymore right

Having a winforms app wait for a few minutes before proceeding

I've seen a few references on Stack Overflow about using the Timer Class to do what I want but I'm not convinced it's the right solution to the problem.
Basically, I have a button (in .NET 4.0) that when clicked will go through a few different subroutines and do certain things:
Restart some services
Launch a command line application that finishes automatically
Launch a second command line application that finishes automatically
Launch a third command line application that finishes automatically
The problem I have right now is that the program just goes through each thing and fires it off as quickly as possible - not a problem except that the third command line application must only fire after the first three are completed.
I had a sleep call in the code, except that this froze the UI and I have a status bar on this application that I wanted to have update to let the user know things are occurring.
I was thinking about a Timer object but I'm not sure that would actually cause there to be a pause before doing the next thing.
I'm using a Process.Start method to fire off the command line applications, so it doesn't actually raise an event. Should I just have my subroutine raise an event and then have the third Process.Start method wait for that event to fire before it goes?
This small snippet might help you. Try to get the idea and implement your own code.
try
{
Process myProcess;
myProcess = Process.Start("Notepad.exe");
while (true)
{
if (!myProcess.HasExited)
{
// Discard cached information about the process.
myProcess.Refresh();
// Print working set to console.
Console.WriteLine("Physical Memory Usage: "
+ myProcess.WorkingSet.ToString());
// Wait 2 seconds.
Thread.Sleep(2000);
}
else {
break;
}
}
// Close process by sending a close message to its main window.
myProcess.CloseMainWindow();
// Free resources associated with process.
myProcess.Close();
}
catch(Exception e)
{
Console.WriteLine("The following exception was raised: ");
Console.WriteLine(e.Message);
}