diff --git a/app/src/main/java/androidx/room/InvalidationTracker.kt b/app/src/main/java/androidx/room/InvalidationTracker.kt index 38067b702f..76cf95815c 100644 --- a/app/src/main/java/androidx/room/InvalidationTracker.kt +++ b/app/src/main/java/androidx/room/InvalidationTracker.kt @@ -405,10 +405,14 @@ open class InvalidationTracker @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX private fun checkUpdatedTable(): Set { val invalidatedTableIds = buildSet { - database.query(SimpleSQLiteQuery(SELECT_UPDATED_TABLES_SQL)).useCursor { cursor -> - while (cursor.moveToNext()) { - add(cursor.getInt(0)) + try { + database.query(SimpleSQLiteQuery(SELECT_UPDATED_TABLES_SQL)).useCursor { cursor -> + while (cursor.moveToNext()) { + add(cursor.getInt(0)) + } } + } catch (ex: Throwable) { + eu.faircode.email.Log.w(ex) } } if (invalidatedTableIds.isNotEmpty()) { diff --git a/app/src/main/java/androidx/room/RoomTrackingLiveData.kt b/app/src/main/java/androidx/room/RoomTrackingLiveData.kt index 171b57d16e..85c14c1a93 100644 --- a/app/src/main/java/androidx/room/RoomTrackingLiveData.kt +++ b/app/src/main/java/androidx/room/RoomTrackingLiveData.kt @@ -54,62 +54,70 @@ internal class RoomTrackingLiveData ( val invalid = AtomicBoolean(true) val computing = AtomicBoolean(false) val registeredObserver = AtomicBoolean(false) + val queued = eu.faircode.email.ObjectHolder(0) + val lock = Object() val refreshRunnable = Runnable { - if (registeredObserver.compareAndSet(false, true)) { - database.invalidationTracker.addWeakObserver(observer) - } - var computed: Boolean - do { - computed = false - // compute can happen only in 1 thread but no reason to lock others. - if (computing.compareAndSet(false, true)) { - // as long as it is invalid, keep computing. - try { - var value: T? = null - while (invalid.compareAndSet(true, false)) { - computed = true - try { - value = computeFunction.call() - } catch (e: Exception) { - throw RuntimeException( - "Exception while computing database live data.", - e - ) - } - } - if (computed) { - postValue(value) - } - } finally { - // release compute lock - computing.set(false) - } - } - // check invalid after releasing compute lock to avoid the following scenario. - // Thread A runs compute() - // Thread A checks invalid, it is false - // Main thread sets invalid to true - // Thread B runs, fails to acquire compute lock and skips - // Thread A releases compute lock - // We've left invalid in set state. The check below recovers. - } while (computed && invalid.get()) - } + synchronized(lock) { + queued.value-- + if (queued.value < 0) { + eu.faircode.email.Log.e("$computeFunction queued=$queued.value") + queued.value = 0 + } + } + + if (registeredObserver.compareAndSet(false, true)) { + database.invalidationTracker.addWeakObserver(observer) + } + + var value: T? = null + var computed = false + synchronized(computeFunction) { + var retry = 0 + while (!computed) { + try { + value = computeFunction.call() + computed = true + } catch (e: Throwable) { + if (++retry > 5) { + eu.faircode.email.Log.e(e) + break + } + eu.faircode.email.Log.w(e) + try { + Thread.sleep(2000L) + } catch (ignored: InterruptedException) { + } + } + } + } + if (computed) { + postValue(value) + } + } val invalidationRunnable = Runnable { - val isActive = hasActiveObservers() - if (invalid.compareAndSet(false, true)) { - if (isActive) { - queryExecutor.execute(refreshRunnable) - } - } - } + val isActive = hasActiveObservers() + if (isActive) { + synchronized(lock) { + if (queued.value > 0) { + eu.faircode.email.Log.persist(eu.faircode.email.EntityLog.Type.Debug, "$computeFunction queued=$queued.value") + } else { + queued.value++ + queryExecutor.execute(refreshRunnable) + } + } + } + } @Suppress("UNCHECKED_CAST") override fun onActive() { - super.onActive() - container.onActive(this as LiveData) - queryExecutor.execute(refreshRunnable) - } + super.onActive() + container.onActive(this as LiveData) + synchronized(lock) { + queued.value++ + queryExecutor.execute(refreshRunnable) + } + } @Suppress("UNCHECKED_CAST") override fun onInactive() { diff --git a/app/src/main/java/androidx/room/paging/LimitOffsetDataSource.java b/app/src/main/java/androidx/room/paging/LimitOffsetDataSource.java index 2b5c391dbc..077ae233e8 100644 --- a/app/src/main/java/androidx/room/paging/LimitOffsetDataSource.java +++ b/app/src/main/java/androidx/room/paging/LimitOffsetDataSource.java @@ -153,7 +153,7 @@ public abstract class LimitOffsetDataSource extends androidx.paging.Positiona @NonNull LoadInitialCallback callback) { registerObserverIfNecessary(); List list = Collections.emptyList(); - int totalCount; + int totalCount = 0; int firstLoadPosition = 0; RoomSQLiteQuery sqLiteQuery = null; Cursor cursor = null; @@ -171,6 +171,8 @@ public abstract class LimitOffsetDataSource extends androidx.paging.Positiona mDb.setTransactionSuccessful(); list = rows; } + } catch (Throwable ex) { + eu.faircode.email.Log.w(ex); } finally { if (cursor != null) { cursor.close();