mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 15:45:11 +00:00
Fix MediaLoader EXC_BAD_ACCESS ongoingRequests crashes
This commit is contained in:
@@ -12,10 +12,6 @@ import UIKit
|
|||||||
|
|
||||||
@preconcurrency import MatrixRustSDK
|
@preconcurrency import MatrixRustSDK
|
||||||
|
|
||||||
private final nonisolated class MediaRequest {
|
|
||||||
var continuations: [CheckedContinuation<Data, Error>] = []
|
|
||||||
}
|
|
||||||
|
|
||||||
actor MediaLoader: MediaLoaderProtocol {
|
actor MediaLoader: MediaLoaderProtocol {
|
||||||
// We noticed that the keyboard appears to hold onto a reference to the `Context` of the last
|
// 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
|
// 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
|
// 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.
|
// don't have an accompanying ClientMock to own it.
|
||||||
private weak var client: ClientProtocol?
|
private weak var client: ClientProtocol?
|
||||||
private var ongoingRequests = [MediaSourceProxy: MediaRequest]()
|
private var ongoingRequests = [MediaSourceProxy: Task<Data, Error>]()
|
||||||
|
|
||||||
init(client: ClientProtocol) {
|
init(client: ClientProtocol) {
|
||||||
self.client = client
|
self.client = client
|
||||||
@@ -61,30 +57,22 @@ actor MediaLoader: MediaLoaderProtocol {
|
|||||||
|
|
||||||
// MARK: - Private
|
// 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] {
|
if let ongoingRequest = ongoingRequests[source] {
|
||||||
return try await withCheckedThrowingContinuation { continuation in
|
return try await ongoingRequest.value
|
||||||
ongoingRequest.continuations.append(continuation)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
ongoingRequests[source] = ongoingRequest
|
||||||
|
|
||||||
defer {
|
defer {
|
||||||
ongoingRequests[source] = nil
|
ongoingRequests[source] = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
return try await ongoingRequest.value
|
||||||
let result = try await operation()
|
|
||||||
|
|
||||||
ongoingRequest.continuations.forEach { $0.resume(returning: result) }
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
} catch {
|
|
||||||
ongoingRequest.continuations.forEach { $0.resume(throwing: error) }
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user