Skip to content

Commit

Permalink
Fix a Core Data threading viloation in gutenbergMediaSources (#23089)
Browse files Browse the repository at this point in the history
  • Loading branch information
kean committed Apr 26, 2024
2 parents 7d360c3 + 9e9d112 commit ed5ed59
Show file tree
Hide file tree
Showing 2 changed files with 3 additions and 35 deletions.
33 changes: 0 additions & 33 deletions WordPress/Classes/Utility/ContextManager.swift
Expand Up @@ -337,36 +337,3 @@ private enum SaveContextOption {
case asynchronouslyWithCallback(completion: () -> Void, queue: DispatchQueue)
case alreadyInContextQueue
}

/// Use this temporary workaround to mitigate Core Data concurrency issues when accessing the given `object`.
///
/// When the app is launched from Xcode, some code may crash due to the effect of the "com.apple.CoreData.ConcurrencyDebug"
/// launch argument. The crash indicates the crash site violates [the following rule](https://developer.apple.com/documentation/coredata/using_core_data_in_the_background#overview)
///
/// > To use Core Data in a multithreaded environment, ensure that:
/// > - Managed objects retrieved from a context are bound to the same queue that the context is bound to.
///
/// This function can be used as a temporary workaround to mitigate aforementioned crashes during development.
///
/// - Warning: The workaround does not apply to release builds. In a release build, calling this function is exactly
/// the same as calling the given `closure` directly.
///
/// - Warning: This function is _not_ a solution for Core Data concurrency issues, and should only be used as a
/// temporary solution, to avoid the Core Data concurrency issue becoming a blocker to feature developlement.
@available(*, deprecated, message: "This workaround is meant as a temporary solution to mitigate Core Data concurrency issues when accessing the `object`. Please see this function's API doc for details.")
@inlinable
public func workaroundCoreDataConcurrencyIssue<Value>(accessing object: NSManagedObject, _ closure: () -> Value) -> Value {
#if DEBUG
guard let context = object.managedObjectContext else {
fatalError("The object must be bound to a context: \(object)")
}

var value: Value!
context.performAndWait {
value = closure()
}
return value
#else
return closure()
#endif /* DEBUG */
}
Expand Up @@ -582,6 +582,7 @@ extension GutenbergViewController {

// MARK: - GutenbergBridgeDelegate

/// - warning: the app can't make any assumption about the thread on which `GutenbergBridgeDelegate` gets invoked. In some scenarios, it gets called from the main thread, for example, if being invoked directly from [Gutenberg.swift](https://github.com/WordPress/gutenberg/blob/64f9d9d1ced7a5aa7f3874890306554c5b703ce6/packages/react-native-bridge/ios/Gutenberg.swift). And sometimes, it gets called on a dispatch queue created by the React Native runtime for a native module (see [React Native: Threading](https://reactnative.dev/docs/native-modules-ios#threading). It happens when the methods are invoked directly from JavaScript.
extension GutenbergViewController: GutenbergBridgeDelegate {
func gutenbergDidGetRequestFetch(path: String, completion: @escaping (Result<Any, NSError>) -> Void) {
guard let context = post.managedObjectContext else {
Expand Down Expand Up @@ -1139,14 +1140,14 @@ extension GutenbergViewController: GutenbergBridgeDataSource {
}

func gutenbergMediaSources() -> [Gutenberg.MediaSource] {
workaroundCoreDataConcurrencyIssue(accessing: post) {
post.managedObjectContext?.performAndWait {
[
post.blog.supports(.stockPhotos) ? .stockPhotos : nil,
post.blog.supports(.tenor) ? .tenor : nil,
.otherApps,
.allFiles,
].compactMap { $0 }
}
} ?? []
}

func gutenbergCapabilities() -> [Capabilities: Bool] {
Expand Down

0 comments on commit ed5ed59

Please sign in to comment.