From 7c80957e5abbe262ed18060803b72418bae1c2fb Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Fri, 13 May 2022 12:01:10 +0200 Subject: [PATCH] Retroactively fix stuck timelines due to empty chunks Change-Id: I707d3c139e4731db5d5ced5fc113323646d305a3 --- .../room/timeline/LoadTimelineStrategy.kt | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt index c81dd103b1..c9da412bc7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt @@ -61,6 +61,10 @@ import java.util.concurrent.atomic.AtomicReference // TODO: once we feel comfortable that this is no longer necessary, // we probably want to disable this again for improving performance. const val ENABLE_TIMELINE_LOOP_SPLITTING = true +// Whether to search for stuck timelines due to empty self-linking chunks. +// TODO: once we feel comfortable that this is no longer necessary, +// we probably want to disable this again for improving performance. +const val ENABLE_TIMELINE_EMPTY_CHUNK_CLEANUP = true internal class LoadTimelineStrategy constructor( private val roomId: String, @@ -328,6 +332,10 @@ internal class LoadTimelineStrategy constructor( private fun RealmResults.createTimelineChunk(): TimelineChunk? { return firstOrNull()?.let { + if (ENABLE_TIMELINE_EMPTY_CHUNK_CLEANUP) { + // Before creating timeline chunks, make sure there are no empty chunks linking themselves, causing a stuck timeline + it.cleanupSelfLinkingChunks() + } if (ENABLE_TIMELINE_LOOP_SPLITTING) { // Before creating timeline chunks, make sure that the ChunkEntities do not form a loop it.fixChunkLoops() @@ -353,6 +361,29 @@ internal class LoadTimelineStrategy constructor( } } + private fun ChunkEntity.cleanupSelfLinkingChunks() { + cleanupSelfLinkingChunksInDirection( + "backward", + { it.prevChunk }, + { + if (it.prevChunk?.nextChunk == it) { + it.prevChunk?.nextChunk = null + } + it.prevChunk = null + } + ) + cleanupSelfLinkingChunksInDirection( + "forward", + { it.nextChunk }, + { + if (it.nextChunk?.prevChunk == it) { + it.nextChunk?.prevChunk = null + } + it.nextChunk = null + } + ) + } + private fun ChunkEntity.fixChunkLoops() { fixChunkLoopsInDirection("backward", { it.prevChunk }, @@ -382,6 +413,27 @@ internal class LoadTimelineStrategy constructor( } + private fun ChunkEntity.cleanupSelfLinkingChunksInDirection(directionName: String, + directionFun: (ChunkEntity) -> ChunkEntity?, + unlinkFun: (ChunkEntity) -> Unit) { + val visited = hashSetOf() + var chunk: ChunkEntity? = this + while (chunk != null) { + if (chunk.identifier() in visited) { + return + } + visited.add(chunk.identifier()) + val next = directionFun(chunk) + if (next != null && next.timelineEvents.isEmpty() && next.nextToken == next.prevToken) { + Timber.i("Stuck self-loop cleanup $directionName: remove empty ${next.identifier()}") + realm.executeTransaction { + unlinkFun(chunk!!) + } + } + chunk = next + } + } + private fun ChunkEntity.fixChunkLoopsInDirection(directionName: String, directionFun: (ChunkEntity) -> ChunkEntity?, lastEventFun: (RealmList) -> TimelineEventEntity?,