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
Saving new object from child context results in EXC_BAD_ACCESS (development branch) #1001
Comments
Also, the problem doesn't happen when PUT-ing an already persisted object. |
I am chasing down a bug somewhere in the Core Data relationship code right now that may be related… I am getting crashes for violations attempting to establish relationships between objects in different contexts on POST. Its only happening for me in the actual app code and so far is eluding me in the RK unit tests, the GateGuru unit tests, and our KIF integration tests… it may be related to your issue as I see applyRelationshipMappings on your stack... |
In this case that secondary thread (the one with applyRelationshipMappings on the stack) is explicitly dying at Do you use any KVO within RestKit proper? |
To add more information, it does happen in the simulator (at least, in iOS 6 -- I wasn't able to test it in the 5.1 right now due to a simulator bug.) My hardware iPad is actually running 5.1 (whatever the last release of that branch was), so it seems somewhat more unlikely that it's a CoreData bug. |
There is not any KVO in the mapping implementation. |
I think there might be something related to the fact that the POSTed object isn't yet in the root managed object context/persistent store. That is, it's trying to map back to an object that already has NSManagedObjectIDs, but those IDs aren't available in the context being used for mapping. I was able to work around it in my specific case by mapping the object to request parameters (just as RKObjectManager does--I lifted the code from there) and then using those parameters along with a nil object (rather than the managed object) when I call postObject. That way the returned object gets mapped as if it were merely a brand new object from the server that we didn't know about yet (which is quasi-true anyway since it only existed in a scratch context.) |
So to try recreate this in a test case I should:
Do I have all that? The workflow described should be fully supported and I can't imagine why it would crash… Is your managed object being mutated externally while being mapped or something? Would like to get this issue closed but need to figure out how to reproduce. |
I believe that that's the extent of it. The new object has an "id" property that RK would be using to unique in the persistent store--but the id would be nil at this point since the server doesn't yet know about it. I doubt that's it, but it seems potentially relevant. I would add that the object in question has a one-to-many relationship (and it seems to be failing within mapping of that.) I'm not doing any kind of mutation of the objects during mapping or anything out of the ordinary. It's all set up with the standard property/relationship mappings (nothing custom.) |
I seem to be occasionally getting the same crash, although it occurs when the context is saved after I POST a new object (technically, the code in question does a combination of PUT/POST/DELETE then saves, but I have not been able to reproduce it locally in order to verify the POST really is the problem). The context I'm using is the [[RKManagedObjectStore defaultStore] mainQueueManagedObjectContext], and I'm using performBlockAndWait: to both create the new object I'm posting, and to save the context. The POST is made with -[RKObjectManager postObject:usingBlock:]. A stack trace is below:
|
Interesting. I am still not seeing this crash in GateGuru. Would love to get this isolated and repeatable, please update if you have further insight. |
I don't have anything conclusive yet, but I figured it was worth describing things in a bit more detail than before. The API I'm working with dosen't return the newly created object when it is created with a post. It does provide a valid "Location" header in the response and the correct 201 response code. However, I've noticed that as a result, the original object sent in postObject:usingBlock: is not updated when the post succeeds. In retrospect, this is obvious as there would not be a general way to parse the location header and necessarily be able to use it to update the primary key of that object. So, the result is that I then have an "orphaned" object that was created for the push. The actual created object is not represented locally until I "pull" from the API. My current code cleans up such orphaned objects as part of that "pull" stage of my sync operation. I'm now thinking that it's these orphan objects that are responsible for these intermittent crashes, as they lack a primary key value and seem to cause RestKit to throw other warnings about issues with the RKInMemoryManagedObjectCache, as it is primary key based. I had not noticed the (possible) link between these two before. It may be worth trying to write specific code that does interpret the location header specifically for my case. As the crash occurred on save, I tried removing these orphan objects with deleteObject: before sending save: to the managagedobjectcontext. I now have a new crash that occurs with deleteObject:
|
I'm not a user of RestKit, but found this issue due to an identical stack trace in my own code. Perhaps my problem is related and thus my experience might be of some help. My case is an iOS app using UIManagedDocument. I create a parent object on the main queue, obtain a permanent object ID for it, and then use UIManagedDocument's updateChangeCount:UIDocumentChangeDone method to indicate that I'd like a save to be scheduled. I would then fetch XML data from a remote server, parse it, and hang the resulting managed objects from the parent object just created. This occurred on a background thread, and I used a thread-confined import context within an NSOperation to do so. In order to fault the parent object into the import context, I used objectWithID. This worked perfectly in iOS 5, but in iOS 6, I'd get stack traces identical to those here. Replacing use of objectWithID with existingObjectWithID:error to fault the parent object into the import context resolves the error in my case. I believe that what I was doing is a race condition; while I've asked for a save to occur, it may or may not have happened yet. Under iOS 5, it would appear that it happened before I attempted to fault the new parent object into the import context, but under iOS 6, that seems not to be the case. At any rate, use of existingObjectWithID:error instead of objectWithID to get the newly-created parent object into the import context resolves my problem. Hope this can be of some help. |
It seems I'm having the exact same problem as @jcoleman. Currently I'm upgrading a project to the latest RestKit version. I found out a lot of helper methods like Mapping logic:// Core data store
RKManagedObjectStore *objectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel: [NSManagedObjectModel mergedModelFromBundles:nil]];
[objectStore addSQLitePersistentStoreAtPath: [RKApplicationDataDirectory() stringByAppendingPathComponent:@"Store.sqlite"] fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:nil];
[objectStore createManagedObjectContexts];
// Manager
RKObjectManager* manager = [RKObjectManager managerWithBaseURL: [NSURL URLWithString: apiUrl]];
[manager setRequestSerializationMIMEType: @"application/json"];
[manager setManagedObjectStore: objectStore];
// Device
RKEntityMapping* responseDeviceMapping = [RKEntityMapping mappingForEntityForName: @"Device" inManagedObjectStore: objectStore];
[responseDeviceMapping addAttributeMappingsFromArray: @[@"uid", @"os", @"token"]];
[responseDeviceMapping setIdentificationAttributes: @[@"uid"]];
// Profile
RKEntityMapping* responseProfileMapping = [RKEntityMapping mappingForEntityForName: @"Profile" inManagedObjectStore: objectStore];
[responseProfileMapping addAttributeMappingsFromArray: @[ @"type", @"referenceId", @"token", @"expirationDate"]];
[responseProfileMapping setIdentificationAttributes: @[@"referenceId"] ];
// User
RKEntityMapping* responseUserMapping = [RKEntityMapping mappingForEntityForName: @"User" inManagedObjectStore: objectStore];
[responseUserMapping addAttributeMappingsFromArray: @[@"uid", @"firstname", @"lastname"]];
[responseUserMapping addPropertyMapping: [RKRelationshipMapping relationshipMappingFromKeyPath:@"devices"
toKeyPath:@"devices"
withMapping:responseDeviceMapping]];
[responseUserMapping addPropertyMapping: [RKRelationshipMapping relationshipMappingFromKeyPath:@"profiles"
toKeyPath:@"profiles"
withMapping:responseProfileMapping]];
[responseUserMapping setIdentificationAttributes: @[@"uid"] ];
RKResponseDescriptor * responseUserDescriptor = [RKResponseDescriptor responseDescriptorWithMapping: responseUserMapping pathPattern: nil keyPath: @"user" statusCodes:succesStatusCodes];
[manager addResponseDescriptor: responseUserDescriptor ];
RKObjectMapping* requestUserMapping = [responseUserMapping inverseMapping];
RKRequestDescriptor * requestUserDescriptor = [RKRequestDescriptor requestDescriptorWithMapping: requestUserMapping objectClass: [User class] rootKeyPath: @"user"];
[manager addRequestDescriptor: requestUserDescriptor]; Device.h@class User;
@interface Device : NSManagedObject
@property (nonatomic, strong) NSString *uid;
@property (nonatomic, strong) NSString *os;
@property (nonatomic, strong) NSString *token;
@property (nonatomic, weak) User *user;
@end Profile.h@class User;
@interface Profile : NSManagedObject
@property (nonatomic, strong) NSNumber *type; // kProfileType
@property (nonatomic, strong) NSString *referenceId;
@property (nonatomic, strong) NSString *token;
@property (nonatomic, strong) NSDate *expirationDate;
@property (nonatomic, strong) User *user;
@end User.h#import "Profile.h"
#import "Device.h"
@interface User : NSManagedObject
@property (nonatomic, strong) NSString *uid;
@property (nonatomic, strong) NSString *firstname;
@property (nonatomic, strong) NSString *lastname;
@property (nonatomic, strong) NSMutableSet *devices;
@property (nonatomic, strong) NSMutableSet *profiles;
- (void) addProfile: (Profile*)profile;
- (void) addDevice: (Device*)device;
@end NSManagedObject+ActiveRecord.hI use the following logic for creating new instances of Profile, Device or User + (id)object
{
id object = [[self alloc] initWithEntity: [self entity] insertIntoManagedObjectContext:[[RKObjectManager sharedManager] managedObjectStore].mainQueueManagedObjectContext];
return [object autorelease];
}
+ (NSEntityDescription *)entity
{
NSString *className = @(class_getName([self class]));
return [NSEntityDescription entityForName:className inManagedObjectContext:[[RKObjectManager sharedManager] managedObjectStore].mainQueueManagedObjectContext];
} When mapping the response I get an error on the same lines as @jcoleman writes above. It occurs while mapping a This is part of the trace log just before the crash..
|
@Geeza @blakewatters
I believe 2 is actually the reason that I no longer get the crash. I know that the mainQueueManagedObjectContext is a child of the primaryManagedObjectContext, and the primaryManagedObjectContext actually saves to the persistent store. I believe all mapping operations within RestKit actually occur on the mainQueueManagedObjectContext. The mainQueueManagedObjectContext saves quickly to the primaryManagedObjectContext, which then saves asyncronously to disk in the background. It seems as though after the POST, some objects were in an inconsistent state, but that things get rectified somehow after the GET, after which the context will save properly. That being said, I still get numerous messages in the console coming out of the RKEntityByAttributeCache about objects with nil primary keys, even though no such objects should be left when I save the mainQueueManagedObjectContext after by bookmark push/pull. It looks like that may have been fixed here #1039 which I can see is now closed. |
@pjk25 I'll write some reproduction code. |
Wow lots of substantive updates on here. I'll read through all of this and update shortly. |
Hello all, My post is about a JSON with 2 nested object with to-many relationship...ex. categories-products. Some additional information, workarounds:
Looks like to me that, during child mapping operation, RestKit cannot find out the father ID as it is not yet written into the persistent store and it goes in EXC_BAC_ACCESS. Any help on this? |
Drilling in here is next up on my priority list. |
Hi Blake, I still enforcing what I was saying in point 1. That crash seems happening when I POST an object with a nested relation and both objects (parent-children) are new and created just into the managedContextObject (no one in the persistent store, both with empty identificationAttributes). My server will answer back with the same nested JSON but with valued synchronization primary key (identificationAttributes generated by MySql). RESTKit seems to manage correctly the father: in fact it goes UPDATING that object (writing on it the synchronization primary key) while, when he starts managing children it crash. If the father was previously added into the persistentStore crash doesn't happens: father and children record are written correctly...but with duplicate children. So, perhaps, I could possibly smell than executing the instruction [destinationSet setSet:valueForRelationship]; maybe the exc_bad_access could be related to setting the relation of the B or C children. P.S. Impressive job, RESTKit is a very useful and welldone framework! Thanks and Happy New Year. |
I'm seeing the same issue here. Don't think I'm doing anything unusual. The use case is an attendee at a conference sending another attendee a message. In pseudo code:
I seem to have a reproducible test case within our app at least, so happy to try things out as needed. Not sure this helps, but it looks like the last reskit method called before the crash (but in a different thread then the crash) is in mapCoreDataToManyRelationshipValue, line 533.
|
So first doing: [destinationSet removeAllObjects]; Also results in an error. Note that directly calling [someobject.somerelationship removeAllObjects] works.... |
Okay I've just gotten up to speed on this thread. Trying to reproduce in a test case. Fix should be tractable once I get it reproduced in the unit tests. |
Okay I believe that I have figured this out. The issue is that we are only obtaining a permanent ID for the parent object, but not the children. I think we need to use a visitor to traverse the request descriptor mapping graph and collect all of the unsaved managed objects in the graph accessible from the parent. Working on that now. |
…via the object manager which have a temporary managed object ID to avoid crashes during mapping. refs #1001
Well, some progress but not solved. What happens now is the first message/receipient sent works ok. But the second time I get the same crash. Trying to figure out why...not really sure. As mentioned above, the error is happening on clearing out the destination's to many relationship - not actually in setting the new values. So not sure how/why the deeply traversing would have affected this. I'm tried to avoid the issue by saving my new objects first, and using the Replace assignment property on to many relations. But that results in the same error being generated by this call:
In the method:
Note that if I run our app on a iphone4 with ios 5.1, the crash happens even more frequently than on the simulator. And the other interesting thing is on a phone if I call save before PostObject I get duplicate rows in the UI (this seems like the same issue as here http://stackoverflow.com/questions/11336120/when-to-call-obtainpermanentidsforobjects). I don't see this on the simulator. So to summarize what I've seen so far:
|
So to be clear: The reason that obtaining the permanent ID's affects this is because when you are manipulating the relationship within the mapping, it's not actually a vanilla I will try blowing out some additional test cases tonight |
Actually let's cut out the middleman here: @cfis Can you give me temporary access to your project with instructions for how to recreate the issue in your UI? That way I can drive right at the crash and cut out the shadow-boxing trying to recreate it in tests from a description. |
Interesting - and thanks for the explanation. Yes, I can give you access. What's the most effective way to do that - email, skype chat, IR? |
If you project is on Github, just adding me is easiest. Otherwise a zip archive via e-mail / Dropbox download works |
Its on Github, I have added you in. I also sent you an email with instructions. Thanks for the help! |
…ts visitor. The implementation was under tested and only worked with a single relationship. refs #1001
There was an embarrassing bug in the implementation of the initial visitor. The fix was correct, but my implementation was under tested and only worked for entities with a single relationship as the recursion guard would prevent additional relationships from being considered. I have pushed a revision to development and tested it on @cfis's project and things look good. Going to declare victory unless someone else chimes in. |
Just tested it out. It works fine now. No crash anymore and the POST request end up correctly. Thanks! I keep having to clean original children record. After the response its create new records for every child received. I suppose because it cannot match the received one with the original children as they haven't the identificationAttributes set. But I suppose that could be considered another topic. |
Just a note saying my issue was resolved - thanks! |
I'm having this issue, using 0.20.0-pre6 via cocoapods, and I can't work out how to switch to the development branch so I can get this fix. Has anyone else been able to do that? |
@percsnoodle:
|
Thanks! |
…via the object manager which have a temporary managed object ID to avoid crashes during mapping. refs RestKit#1001
…ts visitor. The implementation was under tested and only worked with a single relationship. refs RestKit#1001
I have child managed object context created with RestKit's helpers, within that child context I've instantiated a new model instance. When I save that object (using RKObjectManager#postObject...) the application crashes. The thread causing the crashes always has a backtrace looking like this:
and the thread that seems likely to be generating the even that causes it looks like this:
It's possible that it's a bug in CoreData, but it'd be harder to prove/determine that. Also, in the debugging process I added code to ensure that I didn't have any KVO listeners or fetch request controllers outstanding on the original object. That would seem to suggest that it's not a bug in how I'm using it.
The text was updated successfully, but these errors were encountered: