Fix MediaLoader EXC_BAD_ACCESS ongoingRequests crashes

This commit is contained in:
Stefan Ceriu
2026-06-29 14:24:00 +03:00
parent bfd3756671
commit ad8d70ebe0
@@ -12,10 +12,6 @@ import UIKit
@preconcurrency import MatrixRustSDK
private final nonisolated class MediaRequest {
var continuations: [CheckedContinuation<Data, Error>] = []
}
actor MediaLoader: MediaLoaderProtocol {
// We noticed that the keyboard appears to hold onto a reference to the `Context` of the last
// screen that had text input focus, resulting in its MediaProvider staying alive which in
@@ -28,7 +24,7 @@ actor MediaLoader: MediaLoaderProtocol {
// bunch of workarounds in our preview tests to keep the mock provider alive as some ViewModels
// don't have an accompanying ClientMock to own it.
private weak var client: ClientProtocol?
private var ongoingRequests = [MediaSourceProxy: MediaRequest]()
private var ongoingRequests = [MediaSourceProxy: Task<Data, Error>]()
init(client: ClientProtocol) {
self.client = client
@@ -61,30 +57,22 @@ actor MediaLoader: MediaLoaderProtocol {
// MARK: - Private
private func enqueueLoadMediaRequest(forSource source: MediaSourceProxy, operation: @escaping () async throws -> Data) async throws -> Data {
private func enqueueLoadMediaRequest(forSource source: MediaSourceProxy, operation: @escaping @Sendable () async throws -> Data) async throws -> Data {
if let ongoingRequest = ongoingRequests[source] {
return try await withCheckedThrowingContinuation { continuation in
ongoingRequest.continuations.append(continuation)
}
return try await ongoingRequest.value
}
let ongoingRequest = MediaRequest()
// Wrap the SDK call in a Task so `ongoingRequests` is only mutated when resuming from
// `task.value` (a runtime-managed await that hops back onto the actor) rather than when
// resuming directly from the SDK's continuation, which can fire on a Rust thread and
// mutate the dictionary off-actor, corrupting its storage.
let ongoingRequest = Task { try await operation() }
ongoingRequests[source] = ongoingRequest
defer {
ongoingRequests[source] = nil
}
do {
let result = try await operation()
ongoingRequest.continuations.forEach { $0.resume(returning: result) }
return result
} catch {
ongoingRequest.continuations.forEach { $0.resume(throwing: error) }
throw error
}
return try await ongoingRequest.value
}
}