From da572df89ad31c7866ceefe98cf036784ebb8bca Mon Sep 17 00:00:00 2001 From: Jonas Lochmann Date: Mon, 17 May 2021 02:00:00 +0200 Subject: [PATCH] Handle race condition during sync --- .../android/data/dao/PendingSyncActionDao.kt | 5 ++- .../android/sync/ApplyServerDataStatus.kt | 13 +++++++- .../io/timelimit/android/sync/SyncUtil.kt | 33 +++++++++++++++++-- 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/io/timelimit/android/data/dao/PendingSyncActionDao.kt b/app/src/main/java/io/timelimit/android/data/dao/PendingSyncActionDao.kt index bbd3723..a49e35c 100644 --- a/app/src/main/java/io/timelimit/android/data/dao/PendingSyncActionDao.kt +++ b/app/src/main/java/io/timelimit/android/data/dao/PendingSyncActionDao.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 Jonas Lochmann + * TimeLimit Copyright 2019 - 2021 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -51,6 +51,9 @@ interface PendingSyncActionDao { @Query("SELECT COUNT(*) FROM pending_sync_action") fun countAllActionsLive(): LiveData + @Query("SELECT COUNT(*) FROM pending_sync_action") + fun countAllActions(): Long + @Query("SELECT * FROM pending_sync_action WHERE scheduled_for_upload = 0 ORDER BY sequence_number DESC") fun getLatestUnscheduledActionSync(): PendingSyncAction? diff --git a/app/src/main/java/io/timelimit/android/sync/ApplyServerDataStatus.kt b/app/src/main/java/io/timelimit/android/sync/ApplyServerDataStatus.kt index 2f9b96e..e1fd438 100644 --- a/app/src/main/java/io/timelimit/android/sync/ApplyServerDataStatus.kt +++ b/app/src/main/java/io/timelimit/android/sync/ApplyServerDataStatus.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2020 Jonas Lochmann + * TimeLimit Copyright 2019 - 2021 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,6 +36,15 @@ object ApplyServerDataStatus { fun applyServerDataStatusSync(status: ServerDataStatus, database: Database, platformIntegration: PlatformIntegration) { database.runInTransaction { + // this would override some local data which was not sent yet + // so it's better to cancel in this case (or, more complicated, + // apply the delta which was not sent locally) + // + // locking the database during a sync is no option as the network + // connection could be really bad + if (database.pendingSyncAction().countAllActions() > 0) + throw PendingSyncActionException() + run { // apply ful version until and message @@ -539,4 +548,6 @@ object ApplyServerDataStatus { } } } + + class PendingSyncActionException: RuntimeException() } diff --git a/app/src/main/java/io/timelimit/android/sync/SyncUtil.kt b/app/src/main/java/io/timelimit/android/sync/SyncUtil.kt index 38474a0..b9feea9 100644 --- a/app/src/main/java/io/timelimit/android/sync/SyncUtil.kt +++ b/app/src/main/java/io/timelimit/android/sync/SyncUtil.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2020 Jonas Lochmann + * TimeLimit Copyright 2019 - 2021 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,6 +33,7 @@ import io.timelimit.android.sync.network.api.UnauthorizedHttpError import io.timelimit.android.ui.IsAppInForeground import io.timelimit.android.work.SyncInBackgroundWorker import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext @@ -190,8 +191,7 @@ class SyncUtil (private val logic: AppLogic) { } try { - pushActions(server = server) - pullStatus(server = server) + doSyncRoundRetryOnNewActions(server) logic.syncNotificationLogic.sync(forceUiSync = false) } catch (ex: UnauthorizedHttpError) { @@ -214,6 +214,33 @@ class SyncUtil (private val logic: AppLogic) { } } + private suspend fun doSyncRoundRetryOnNewActions(server: ServerLogic.ServerConfig) { + // retry two times + for (i in 1..2) { + try { + doSyncRound(server = server) + + return + } catch (ex: ApplyServerDataStatus.PendingSyncActionException) { + if (BuildConfig.DEBUG) { + Log.d(LOG_TAG, "got new actions while receiving the status => retry") + } + + delay(250) + + continue + } + } + + // and do not catch at the last time + doSyncRound(server = server) + } + + private suspend fun doSyncRound(server: ServerLogic.ServerConfig) { + pushActions(server = server) + pullStatus(server = server) + } + private suspend fun pushActions(server: ServerLogic.ServerConfig) { upload.uploadActions(server) }