Deadlock: makes sure timelineListener TaskHandle.cancel is called (and memory is released correctly)

This commit is contained in:
ganfra 2023-07-21 10:24:57 +02:00
parent ac108c4582
commit a87ae86398
3 changed files with 32 additions and 10 deletions

View file

@ -122,10 +122,12 @@ class RustMatrixRoom(
innerRoom.timelineDiffFlow { initialList -> innerRoom.timelineDiffFlow { initialList ->
_timeline.postItems(initialList) _timeline.postItems(initialList)
}.onEach { diff -> }.onEach { diff ->
if (diff.eventOrigin() == EventItemOrigin.SYNC) { diff.use {
_syncUpdateFlow.value = systemClock.epochMillis() if (diff.eventOrigin() == EventItemOrigin.SYNC) {
_syncUpdateFlow.value = systemClock.epochMillis()
}
_timeline.postDiff(diff)
} }
_timeline.postDiff(diff)
}.launchIn(this) }.launchIn(this)
innerRoom.backPaginationStatusFlow() innerRoom.backPaginationStatusFlow()

View file

@ -16,28 +16,45 @@
package io.element.android.libraries.matrix.impl.timeline package io.element.android.libraries.matrix.impl.timeline
import io.element.android.libraries.matrix.impl.util.destroyAll
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.callbackFlow
import org.matrix.rustcomponents.sdk.BackPaginationStatus import org.matrix.rustcomponents.sdk.BackPaginationStatus
import org.matrix.rustcomponents.sdk.BackPaginationStatusListener import org.matrix.rustcomponents.sdk.BackPaginationStatusListener
import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.RoomTimelineListenerResult
import org.matrix.rustcomponents.sdk.TimelineDiff import org.matrix.rustcomponents.sdk.TimelineDiff
import org.matrix.rustcomponents.sdk.TimelineItem import org.matrix.rustcomponents.sdk.TimelineItem
import org.matrix.rustcomponents.sdk.TimelineListener import org.matrix.rustcomponents.sdk.TimelineListener
import timber.log.Timber
internal fun Room.timelineDiffFlow(onInitialList: suspend (List<TimelineItem>) -> Unit): Flow<TimelineDiff> = internal fun Room.timelineDiffFlow(onInitialList: suspend (List<TimelineItem>) -> Unit): Flow<TimelineDiff> =
mxCallbackFlow { callbackFlow {
val roomId = id()
Timber.d("Open timelineDiffFlow for room $roomId")
val listener = object : TimelineListener { val listener = object : TimelineListener {
override fun onUpdate(diff: TimelineDiff) { override fun onUpdate(diff: TimelineDiff) {
trySendBlocking(diff) trySendBlocking(diff)
} }
} }
val result = addTimelineListener(listener) var result: RoomTimelineListenerResult? = null
onInitialList(result.items) try {
result.itemsStream result = addTimelineListener(listener)
onInitialList(result.items)
} catch (exception: Exception) {
Timber.d(exception, "Catch failure in timelineDiffFlow of room $roomId")
}
awaitClose {
Timber.d("Close timelineDiffFlow for room $roomId")
result?.itemsStream?.cancel()
result?.itemsStream?.destroy()
result?.items?.destroyAll()
}
}.buffer(Channel.UNLIMITED) }.buffer(Channel.UNLIMITED)
internal fun Room.backPaginationStatusFlow(): Flow<BackPaginationStatus> = internal fun Room.backPaginationStatusFlow(): Flow<BackPaginationStatus> =

View file

@ -26,12 +26,14 @@ import io.element.android.libraries.matrix.impl.timeline.item.event.EventMessage
import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper
import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper
import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTimelineItemMapper import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTimelineItemMapper
import kotlinx.coroutines.CompletableDeferred
import io.element.android.libraries.matrix.impl.timeline.postprocessor.TimelineEncryptedHistoryPostProcessor import io.element.android.libraries.matrix.impl.timeline.postprocessor.TimelineEncryptedHistoryPostProcessor
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -46,8 +48,8 @@ import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.TimelineDiff import org.matrix.rustcomponents.sdk.TimelineDiff
import org.matrix.rustcomponents.sdk.TimelineItem import org.matrix.rustcomponents.sdk.TimelineItem
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
import java.util.Date import java.util.Date
import java.util.concurrent.atomic.AtomicBoolean
private const val INITIAL_MAX_SIZE = 50 private const val INITIAL_MAX_SIZE = 50
@ -99,9 +101,10 @@ class RustMatrixTimeline(
encryptedHistoryPostProcessor.process(items) encryptedHistoryPostProcessor.process(items)
} }
internal suspend fun postItems(items: List<TimelineItem>) { internal suspend fun postItems(items: List<TimelineItem>) = coroutineScope {
// Split the initial items in multiple list as there is no pagination in the cached data, so we can post timelineItems asap. // Split the initial items in multiple list as there is no pagination in the cached data, so we can post timelineItems asap.
items.chunked(INITIAL_MAX_SIZE).reversed().forEach { items.chunked(INITIAL_MAX_SIZE).reversed().forEach {
ensureActive()
timelineDiffProcessor.postItems(it) timelineDiffProcessor.postItems(it)
} }
isInit.set(true) isInit.set(true)