From 682f4c35d2956efe1e1827ee6834466494ca4b71 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Wed, 16 Mar 2022 13:39:19 +0100 Subject: [PATCH 1/3] Fix endless loading timeline due to conflicting chunks --- changelog.d/5554.bugfix | 1 + .../database/query/ChunkEntityQueries.kt | 11 ++++++++++ .../room/timeline/TokenChunkEventPersistor.kt | 22 ++++++++++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 changelog.d/5554.bugfix diff --git a/changelog.d/5554.bugfix b/changelog.d/5554.bugfix new file mode 100644 index 00000000000..ee69f0dbfec --- /dev/null +++ b/changelog.d/5554.bugfix @@ -0,0 +1 @@ +Fix sometimes endless loading timeline diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt index ece46555a7e..a33ba82f7aa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt @@ -40,6 +40,17 @@ internal fun ChunkEntity.Companion.find(realm: Realm, roomId: String, prevToken: return query.findFirst() } +internal fun ChunkEntity.Companion.findAll(realm: Realm, roomId: String, prevToken: String? = null, nextToken: String? = null): RealmResults? { + val query = where(realm, roomId) + if (prevToken != null) { + query.equalTo(ChunkEntityFields.PREV_TOKEN, prevToken) + } + if (nextToken != null) { + query.equalTo(ChunkEntityFields.NEXT_TOKEN, nextToken) + } + return query.findAll() +} + internal fun ChunkEntity.Companion.findLastForwardChunkOfRoom(realm: Realm, roomId: String): ChunkEntity? { return where(realm, roomId) .equalTo(ChunkEntityFields.IS_LAST_FORWARD, true) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt index 63383a99b3c..874915a6f0c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -38,6 +38,7 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.create import org.matrix.android.sdk.internal.database.query.find +import org.matrix.android.sdk.internal.database.query.findAll import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId @@ -80,7 +81,26 @@ internal class TokenChunkEventPersistor @Inject constructor( val existingChunk = ChunkEntity.find(realm, roomId, prevToken = prevToken, nextToken = nextToken) if (existingChunk != null) { - Timber.v("This chunk is already in the db, returns") + Timber.v("This chunk is already in the db, checking if this might be caused by broken links") + if (direction == PaginationDirection.FORWARDS) { + val prevChunks = ChunkEntity.findAll(realm, roomId, nextToken = prevToken) + Timber.v("Found ${prevChunks?.size} prevChunks") + prevChunks?.forEach { + if (it.nextChunk != existingChunk) { + Timber.i("Set nextChunk for ${it.identifier()} from ${it.nextChunk?.identifier()} to ${existingChunk.identifier()}") + it.nextChunk = existingChunk + } + } + } else { + val nextChunks = ChunkEntity.findAll(realm, roomId, prevToken = nextToken) + Timber.v("Found ${nextChunks?.size} nextChunks") + nextChunks?.forEach { + if (it.prevChunk != existingChunk) { + Timber.i("Set prevChunk for ${it.identifier()} from ${it.prevChunk?.identifier()} to ${existingChunk.identifier()}") + it.prevChunk = existingChunk + } + } + } return@awaitTransaction } val prevChunk = ChunkEntity.find(realm, roomId, nextToken = prevToken) From 6878a973ed48056b4982cea5232ab129f7a49810 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Fri, 18 Mar 2022 07:59:32 +0100 Subject: [PATCH 2/3] TokenChunkEventPersistor: always link all matching chunks The previous fix only works around the issue when it is detected. This may require re-entering the room once when it gets stuck. If we ensure proper linking from the beginning, hopefully we don't run into any issues at all. --- .../session/room/timeline/TokenChunkEventPersistor.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt index 874915a6f0c..7aceeb4a499 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -109,8 +109,14 @@ internal class TokenChunkEventPersistor @Inject constructor( this.nextChunk = nextChunk this.prevChunk = prevChunk } - nextChunk?.prevChunk = currentChunk - prevChunk?.nextChunk = currentChunk + val allNextChunks = ChunkEntity.findAll(realm, roomId, prevToken = nextToken) + val allPrevChunks = ChunkEntity.findAll(realm, roomId, nextToken = prevToken) + allNextChunks?.forEach { + it.prevChunk = currentChunk + } + allPrevChunks?.forEach { + it.nextChunk = currentChunk + } if (receivedChunk.events.isEmpty() && !receivedChunk.hasMore()) { handleReachEnd(roomId, direction, currentChunk) } else { From 902d2f73381422e1e5e8754d9166003c67c04533 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Mon, 11 Apr 2022 12:09:07 +0200 Subject: [PATCH 3/3] TokenChunkEventPersistor: move link fixing to its own method --- .../room/timeline/TokenChunkEventPersistor.kt | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt index 7aceeb4a499..1f76b36ef77 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -82,25 +82,7 @@ internal class TokenChunkEventPersistor @Inject constructor( val existingChunk = ChunkEntity.find(realm, roomId, prevToken = prevToken, nextToken = nextToken) if (existingChunk != null) { Timber.v("This chunk is already in the db, checking if this might be caused by broken links") - if (direction == PaginationDirection.FORWARDS) { - val prevChunks = ChunkEntity.findAll(realm, roomId, nextToken = prevToken) - Timber.v("Found ${prevChunks?.size} prevChunks") - prevChunks?.forEach { - if (it.nextChunk != existingChunk) { - Timber.i("Set nextChunk for ${it.identifier()} from ${it.nextChunk?.identifier()} to ${existingChunk.identifier()}") - it.nextChunk = existingChunk - } - } - } else { - val nextChunks = ChunkEntity.findAll(realm, roomId, prevToken = nextToken) - Timber.v("Found ${nextChunks?.size} nextChunks") - nextChunks?.forEach { - if (it.prevChunk != existingChunk) { - Timber.i("Set prevChunk for ${it.identifier()} from ${it.prevChunk?.identifier()} to ${existingChunk.identifier()}") - it.prevChunk = existingChunk - } - } - } + existingChunk.fixChunkLinks(realm, roomId, direction, prevToken, nextToken) return@awaitTransaction } val prevChunk = ChunkEntity.find(realm, roomId, nextToken = prevToken) @@ -135,6 +117,34 @@ internal class TokenChunkEventPersistor @Inject constructor( } } + private fun ChunkEntity.fixChunkLinks( + realm: Realm, + roomId: String, + direction: PaginationDirection, + prevToken: String?, + nextToken: String?, + ) { + if (direction == PaginationDirection.FORWARDS) { + val prevChunks = ChunkEntity.findAll(realm, roomId, nextToken = prevToken) + Timber.v("Found ${prevChunks?.size} prevChunks") + prevChunks?.forEach { + if (it.nextChunk != this) { + Timber.i("Set nextChunk for ${it.identifier()} from ${it.nextChunk?.identifier()} to ${identifier()}") + it.nextChunk = this + } + } + } else { + val nextChunks = ChunkEntity.findAll(realm, roomId, prevToken = nextToken) + Timber.v("Found ${nextChunks?.size} nextChunks") + nextChunks?.forEach { + if (it.prevChunk != this) { + Timber.i("Set prevChunk for ${it.identifier()} from ${it.prevChunk?.identifier()} to ${identifier()}") + it.prevChunk = this + } + } + } + } + private fun handleReachEnd(roomId: String, direction: PaginationDirection, currentChunk: ChunkEntity) { Timber.v("Reach end of $roomId") if (direction == PaginationDirection.FORWARDS) {