Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8aeb2d50 authored by Ricki Hirner's avatar Ricki Hirner
Browse files

Fix onPerformSync crash on InterruptedException (bitfireAT/davx5#343)

- use Kotlin and CompletableDeferred instead of Java synchronization
- signal cancellation by completing CompletableDeferred instead of Thread.currentThread.interrupt()
parent f806122b
Loading
Loading
Loading
Loading
+37 −36
Original line number Diff line number Diff line
@@ -6,12 +6,7 @@ package at.bitfire.davdroid.syncadapter

import android.accounts.Account
import android.app.Service
import android.content.AbstractThreadedSyncAdapter
import android.content.ContentProviderClient
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.SyncResult
import android.content.*
import android.os.Bundle
import androidx.lifecycle.Observer
import androidx.work.WorkInfo
@@ -19,8 +14,7 @@ import androidx.work.WorkManager
import at.bitfire.davdroid.InvalidAccountException
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.settings.AccountSettings
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.*
import java.util.logging.Level

abstract class SyncAdapterService: Service() {
@@ -46,6 +40,17 @@ abstract class SyncAdapterService: Service() {
                           // However, if it is -1 by accident, set it to 1 to avoid endless sync loops.
    ) {

        /**
         * Completable [Boolean], which will be set to
         *
         * - `true` when the related sync worker has finished
         * - `false` when the sync framework has requested cancellation.
         *
         * In any case, the sync framework shouldn't be blocked anymore as soon as a
         * value is available.
         */
        val finished = CompletableDeferred<Boolean>()

        override fun onPerformSync(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) {
            // We seem to have to pass this old SyncFramework extra for an Android 7 workaround
            val upload = extras.containsKey(ContentResolver.SYNC_EXTRAS_UPLOAD)
@@ -76,36 +81,33 @@ abstract class SyncAdapterService: Service() {
            val workManager = WorkManager.getInstance(context)
            val status = workManager.getWorkInfosForUniqueWorkLiveData(workerName)

            var finished = false
            val lock = Object()

            val observer = Observer<List<WorkInfo>> { workInfoList ->
                for (workInfo in workInfoList) {
                    if (workInfo.state.isFinished) {
                        synchronized(lock) {
                            finished = true
                            lock.notify()
                        }
                    }
                    if (workInfo.state.isFinished)
                        finished.complete(true)
                }
            }

            try {
                runBlocking(Dispatchers.Main) {     // observeForever not allowed in background thread
                    status.observeForever(observer)
                }

            synchronized(lock) {
                runBlocking {
                    try {
                    if (!finished)
                        lock.wait(10*60*1000)  // wait max 10 minutes
                } catch (e: InterruptedException) {
                    Logger.log.info("Interrupted while blocking sync framework. Sync may still be running")
                        withTimeout(10 * 60 * 1000) {   // block max. 10 minutes
                            finished.await()
                        }
                    } catch (e: TimeoutCancellationException) {
                        Logger.log.info("Sync job timed out, won't block sync framework anymore")
                    }

                }
            } finally {
                // remove observer in any case
                runBlocking(Dispatchers.Main) {
                    status.removeObserver(observer)
                }
            }

            Logger.log.info("Returning to sync framework")
        }
@@ -115,15 +117,14 @@ abstract class SyncAdapterService: Service() {
        }

        override fun onSyncCanceled() {
            Logger.log.info("Ignoring sync adapter cancellation")
            super.onSyncCanceled()
        }
            Logger.log.info("Sync adapter requested cancellation – won't cancel sync, but also won't block sync framework anymore")

        override fun onSyncCanceled(thread: Thread) {
            Logger.log.info("Ignoring sync adapter cancellation")
            super.onSyncCanceled(thread)
            // unblock sync framework
            finished.complete(false)
        }

        override fun onSyncCanceled(thread: Thread) = onSyncCanceled()

    }

}