Workaround: Hold a weak reference to the SDK's Client in MediaLoader so it can be released when clearing the cache.

This commit is contained in:
Doug
2025-09-03 16:48:55 +01:00
committed by Doug
parent e33631affa
commit 927aea4bfe
7 changed files with 37 additions and 247 deletions
@@ -17,7 +17,7 @@ class ClientProxy: ClientProxyProtocol {
private let networkMonitor: NetworkMonitorProtocol
private let appSettings: AppSettings
private let mediaLoader: MediaLoaderProtocol
let mediaLoader: MediaLoaderProtocol
private let clientQueue: DispatchQueue
private var roomListService: RoomListService
@@ -1113,20 +1113,6 @@ class ClientProxy: ClientProxyProtocol {
}
}
extension ClientProxy: MediaLoaderProtocol {
func loadMediaContentForSource(_ source: MediaSourceProxy) async throws -> Data {
try await mediaLoader.loadMediaContentForSource(source)
}
func loadMediaThumbnailForSource(_ source: MediaSourceProxy, width: UInt, height: UInt) async throws -> Data {
try await mediaLoader.loadMediaThumbnailForSource(source, width: width, height: height)
}
func loadMediaFileForSource(_ source: MediaSourceProxy, filename: String?) async throws -> MediaFileHandleProxy {
try await mediaLoader.loadMediaFileForSource(source, filename: filename)
}
}
private class ClientDelegateWrapper: ClientDelegate {
private let authErrorCallback: (Bool) -> Void
@@ -73,7 +73,7 @@ enum TimelineMediaVisibility: Decodable {
}
// sourcery: AutoMockable
protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
protocol ClientProxyProtocol: AnyObject {
var actionsPublisher: AnyPublisher<ClientProxyAction, Never> { get }
var loadingStatePublisher: CurrentValuePublisher<ClientProxyLoadingState, Never> { get }
@@ -103,6 +103,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
var pusherNotificationClientIdentifier: String? { get }
var mediaLoader: MediaLoaderProtocol { get }
var roomSummaryProvider: RoomSummaryProviderProtocol { get }
/// Used for listing rooms that shouldn't be affected by the main `roomSummaryProvider` filtering
@@ -16,7 +16,17 @@ private final class MediaRequest {
}
actor MediaLoader: MediaLoaderProtocol {
private let client: ClientProtocol
// Something is holding onto our MediaProvider (and therefore this MediaLoader) which means
// when attempting to clear the caches, the SDK's Client hangs around and we end up with 2.
// I have spent a long time trying to understand what's going on – there's instances of both
// TimelineViewModel.Context and ComposerToolbarViewModel.Context still hanging around and
// a closure captures the media provider from both of those as far as I can tell, but I was
// unable to break the reference. Possibly related to the ElementTextView too.
//
// In lieu of the real fix, lets use a weak reference to the Client here so that it can be
// released and hopefully that will solve our logs files exploding in size when encountering
// a corrupt/missing database file.
private weak var client: ClientProtocol?
private var ongoingRequests = [MediaSourceProxy: MediaRequest]()
init(client: ClientProtocol) {
@@ -24,18 +34,21 @@ actor MediaLoader: MediaLoaderProtocol {
}
func loadMediaContentForSource(_ source: MediaSourceProxy) async throws -> Data {
try await enqueueLoadMediaRequest(forSource: source) {
try await self.client.getMediaContent(mediaSource: source.underlyingSource)
try await enqueueLoadMediaRequest(forSource: source) { [weak client] in
guard let client else { throw MediaLoaderError.missingClient }
return try await client.getMediaContent(mediaSource: source.underlyingSource)
}
}
func loadMediaThumbnailForSource(_ source: MediaSourceProxy, width: UInt, height: UInt) async throws -> Data {
try await enqueueLoadMediaRequest(forSource: source) {
try await self.client.getMediaThumbnail(mediaSource: source.underlyingSource, width: UInt64(width), height: UInt64(height))
try await enqueueLoadMediaRequest(forSource: source) { [weak client] in
guard let client else { throw MediaLoaderError.missingClient }
return try await client.getMediaThumbnail(mediaSource: source.underlyingSource, width: UInt64(width), height: UInt64(height))
}
}
func loadMediaFileForSource(_ source: MediaSourceProxy, filename: String?) async throws -> MediaFileHandleProxy {
guard let client else { throw MediaLoaderError.missingClient }
let result = try await client.getMediaFile(mediaSource: source.underlyingSource,
filename: filename,
mimeType: source.mimeType ?? "application/octet-stream",
@@ -7,6 +7,10 @@
import Foundation
enum MediaLoaderError: Error {
case missingClient
}
// sourcery: AutoMockable
protocol MediaLoaderProtocol {
func loadMediaContentForSource(_ source: MediaSourceProxy) async throws -> Data
@@ -94,7 +94,7 @@ class UserSessionStore: UserSessionStoreProtocol {
// MARK: - Private
private func buildUserSessionWithClient(_ clientProxy: ClientProxyProtocol) -> UserSessionProtocol {
let mediaProvider = MediaProvider(mediaLoader: clientProxy,
let mediaProvider = MediaProvider(mediaLoader: clientProxy.mediaLoader,
imageCache: .onlyInMemory,
networkMonitor: networkMonitor)