-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix crash when perform is called on a context with deprecated concurrency type
#19765
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Generated by 🚫 dangerJS |
| } | ||
|
|
||
| /// Ensure that the `context`'s concurrency type is not `confinementConcurrencyType`, since it will crash if `perform` or `performAndWait` is called. | ||
| guard context.concurrencyType == .mainQueueConcurrencyType || context.concurrencyType == .privateQueueConcurrencyType else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can do if context.concurrencyType == .confinementConcurrencyType {, but Xcode will throw out a warning if we type a deprecated enum value. 🙃
You can test the changes in WordPress from this Pull Request by:
|
You can test the changes in Jetpack from this Pull Request by:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This appears to work fine but I feel like we should be changing the ReaderPost's didSave instead. It's a little strange to me to be double saving an object.
After playing around with it a bit, it seems like we can delete the didSave and then update this section to:
Updated code
[self.managedObjectContext performBlockAndWait:^{
// Get a the post in our own context
NSError *error;
ReaderPost *readerPost = (ReaderPost *)[self.managedObjectContext existingObjectWithID:post.objectID error:&error];
if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (failure) {
failure(error);
}
});
return;
}
readerPost.isSavedForLater = !readerPost.isSavedForLater;
if ([readerPost respondsToSelector:@selector(card)] && readerPost.card.count > 0) {
readerPost.card.allObjects[0].topics = NULL;
}
[[ContextManager sharedInstance] saveContext:self.managedObjectContext];
success();
}];The main change being performBlock → performBlockAndWait. There was a race condition with the UI. It'd toggle the flag and then immediately try updating the UI without waiting for the object to save. I also re-added the code from the didSave here but I'm not really sure if that's doing much. The original PR (#17117) that added the didSave appears to be trying to fix the UI state.
|
Thanks for the review, @wargcm ! 👍🏼
Good point. The crash no longer happens after the
Good observation, and your suggestion makes sense 👍🏼 . I've thought of doing this instead of modifying the logic in That said, the root cause for the crash was not because we're performing a double save1; Rather, we're calling a method unsupported by the context's concurrency type. Assume we have another scenario that unknowingly leads to an If my assumption is correct, this change would act as an additional safeguard instead of introducing side effects, and I'd prefer going ahead with this for 21.4. However, I also think that the double save part is a technical debt that needs to be addressed, so I'm thinking to open a separate issue and PR for it. What do you think? Footnotes
|
wargcm
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If my assumption is correct, this change would act as an additional safeguard instead of introducing side effects, and I'd prefer going ahead with this for 21.4. However, I also think that the double save part is a technical debt that needs to be addressed, so I'm thinking to open a separate issue and PR for it. What do you think?
Yep, that works! It's a good idea to safeguard this in case there are other areas that aren't as obvious as this is. I'm also not sure about the side effects of moving the topics being set to NULL so it'd be a good idea to look at it a bit more.
crazytonyli
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL about confinementConcurrencyType. Thanks for making the change!
I agree with @wargcm , this saving changes code in a didSave function looks a bit strange.
One small question: do you think it's be possible to add an unit test to cover this crash?
|
I've added tests for |
Refs pcdRpT-1i3-p2#comment-2354
This fixes a crash that happens during the database export process after previously saving a Reader post. Here are some findings during my investigation:
Saving a post updates a property on the
ReaderPostmanaged object, and triggers thedidSavemethod that updates another model,ReaderCard. The export process makes use ofNSPersistentStoreCoordinator's migratePersistentStore(_:to:options:withType:) method1.While reading through the stack trace, it seems that the
migratePersistentStoremethod creates its ownNSManagedObjectContextand callssave. This call somehow triggersReaderPost'sdidSavemethod on the saved post. Note that I've tried to modify the save post method such that it's persisted in the store before the export process is initiated (i.e., the post'sisUpdatedisfalse), but thedidSavestill gets called duringmigratePersistentStore.📜 Click to see the a snippet of the stack trace
Within the
ReaderPost'sdidSave, there's anothersavecall to the managed object context that ends up inContainerContextFactory.WordPress-iOS/WordPress/Classes/Models/ReaderPost.m
Lines 513 to 522 in be75d66
Finally: in the
ContainerContextFactory'ssavemethod, the completion block is wrapped in aperformBlock:WordPress-iOS/WordPress/Classes/Utility/ContainerContextFactory.swift
Lines 30 to 34 in be75d66
And this is where the issue is. The
NSManagedObjectContextthat's internally created by themigratePersistentStoremethod is initialized with.confinementConcurrencyType— which, apparently, does NOT have a queue (as seen on the stack trace above). So, the solution is to wrap the block withperformONLY when the context has the correct concurrency types.To test
Verify that the crash issue is fixed
contentMigrationflag is enabled.Verify that everywhere else is business as usual
Do a light smoke test to ensure that everything works as before and is not impacted by this change:
Regression Notes
Potential unintended areas of impact
There may be some unexpected thread-related issues? But this should be very very unlikely since there's nothing from our side that creates an
NSManagedObjectContextwith the now deprecated.confinedConcurrencyType.What I did to test those areas of impact (or what existing automated tests I relied on)
Manually tested the changes.
What automated tests I added (or what prevented me from doing so)
N/A.
PR submission checklist:
RELEASE-NOTES.txtif necessary.Footnotes
The method is deprecated, but the recommended method is only available on iOS 15.0+. We are still supporting iOS 14. ↩
Famous last words. 🙃 ↩