Skip to content

Saving new object from child context results in EXC_BAD_ACCESS (development branch) #1001

Closed
jcoleman opened this Issue Oct 18, 2012 · 37 comments

8 participants

@jcoleman

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:

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x0000000e
Crashed Thread:  0

Thread 0 name:  Dispatch queue: NSManagedObjectContext Queue
Thread 0 Crashed:
0   libobjc.A.dylib                 0x330bff72 objc_msgSend + 10
1   CoreData                        0x31dbad30 _PFObjectIDFastHash64 + 100
2   CoreFoundation                  0x37d599c8 __CFDictionaryHashKey + 12
3   CoreFoundation                  0x37cdc848 CFBasicHashFindBucket + 1780
4   CoreFoundation                  0x37cdc13e CFDictionaryGetValue + 110
5   CoreData                        0x31e3b4ae -[NSPersistentStoreCache incrementRefCountForObjectID:] + 18
6   CoreData                        0x31e5ece4 -[NSSQLCore managedObjectContextDidRegisterObjectsWithIDs:] + 124
7   CoreData                        0x31dc23a4 -[NSPersistentStoreCoordinator(_NSInternalMethods) _informAffectedStoresOfInterestByChildContextInObjectsWithObjectIDs:withSelector:] + 76
8   CoreData                        0x31e18350 __-[NSManagedObjectContext(_NestedContextSupport) managedObjectContextDidRegisterObjectsWithIDs:]_block_invoke_1 + 64
9   CoreData                        0x31e105b4 internalBlockToNSManagedObjectContextPerform + 4
10  libdispatch.dylib               0x346be7e0 _dispatch_barrier_sync_f_invoke + 20
11  libdispatch.dylib               0x346be640 dispatch_barrier_sync_f$VARIANT$mp + 56
12  libdispatch.dylib               0x346be260 dispatch_sync_f$VARIANT$mp + 12
13  CoreData                        0x31e1e55e _perform + 154
14  CoreData                        0x31e1e7cc -[NSManagedObjectContext(_NestedContextSupport) managedObjectContextDidRegisterObjectsWithIDs:] + 64
15  CoreData                        0x31e18350 __-[NSManagedObjectContext(_NestedContextSupport) managedObjectContextDidRegisterObjectsWithIDs:]_block_invoke_1 + 64
16  CoreData                        0x31e105b4 internalBlockToNSManagedObjectContextPerform + 4
17  libdispatch.dylib               0x346bfb80 _dispatch_barrier_sync_f_slow_invoke + 76
18  libdispatch.dylib               0x346beee0 _dispatch_main_queue_callback_4CF$VARIANT$mp + 188
19  CoreFoundation                  0x37d672a6 __CFRunLoopRun + 1262
20  CoreFoundation                  0x37cea49e CFRunLoopRunSpecific + 294
21  CoreFoundation                  0x37cea366 CFRunLoopRunInMode + 98
22  GraphicsServices                0x361cd432 GSEventRunModal + 130
23  UIKit                           0x30b9bcce UIApplicationMain + 1074
24  GlobalDriver2ClientBooking      0x000d8fae main (main.m:8)
25  GlobalDriver2ClientBooking      0x000d8f48 start + 32

and the thread that seems likely to be generating the even that causes it looks like this:

Thread 7 name:  Dispatch queue: NSManagedObjectContext Queue
Thread 7:
0   libsystem_kernel.dylib          0x309e5054 semaphore_wait_trap + 8
1   libdispatch.dylib               0x346c04ee _dispatch_thread_semaphore_wait$VARIANT$mp + 6
2   libdispatch.dylib               0x346be69e _dispatch_barrier_sync_f_slow + 86
3   libdispatch.dylib               0x346be618 dispatch_barrier_sync_f$VARIANT$mp + 16
4   libdispatch.dylib               0x346be260 dispatch_sync_f$VARIANT$mp + 12
5   CoreData                        0x31e1e55e _perform + 154
6   CoreData                        0x31e1e7cc -[NSManagedObjectContext(_NestedContextSupport) managedObjectContextDidRegisterObjectsWithIDs:] + 64
7   CoreData                        0x31dbf642 _PFFaultHandlerLookupRow + 1434
8   CoreData                        0x31dbed54 _PF_FulfillDeferredFault + 188
9   CoreData                        0x31e0dfc2 _PF_Handler_WillAccess_Property + 34
10  CoreData                        0x31e0dabe _PF_ManagedObject_WillChangeValueForKeyIndex + 58
11  CoreData                        0x31e0e4ec _sharedIMPL_setvfk_core + 104
12  CoreData                        0x31e1037e -[NSManagedObject(_PFDynamicAccessorsAndPropertySupport) _setGenericValue:forKey:withIndex:flags:] + 26
13  CoreData                        0x31e0862a _PF_Handler_Public_SetProperty + 46
14  CoreData                        0x31e087ce -[NSManagedObject(_NSInternalMethods) _maintainInverseRelationship:forProperty:oldDestination:newDestination:] + 242
15  CoreData                        0x31e07d7c -[NSManagedObject(_NSInternalMethods) _maintainInverseRelationship:forProperty:forChange:onSet:] + 820
16  CoreData                        0x31e0842a -[NSManagedObject(_NSInternalMethods) _didChangeValue:forRelationship:named:withInverse:] + 1582
17  CoreData                        0x31e1516a -[NSManagedObjectContext observeValueForKeyPath:ofObject:change:context:] + 322
18  Foundation                      0x36f41138 NSKeyValueNotifyObserver + 252
19  Foundation                      0x36f40d9e NSKeyValueDidChange + 318
20  Foundation                      0x36f653d4 -[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:withSetMutation:usingObjects:] + 88
21  CoreData                        0x31e04704 -[NSManagedObject didChangeValueForKey:withSetMutation:usingObjects:] + 56
22  CoreData                        0x31dd39e4 -[_NSNotifyingWrapperMutableSet setSet:] + 120
23  GlobalDriver2ClientBooking      0x00197b32 -[RKMappingOperation applyRelationshipMappings] (RKMappingOperation.m:541)
24  GlobalDriver2ClientBooking      0x001996e4 -[RKMappingOperation main] (RKMappingOperation.m:638)
25  Foundation                      0x36f4038c -[__NSOperationInternal start] + 856
26  GlobalDriver2ClientBooking      0x0018efc8 -[RKMapperOperation mapFromObject:toObject:atKeyPath:usingMapping:] (RKMapperOperation.m:219)
27  GlobalDriver2ClientBooking      0x0018e248 -[RKMapperOperation mapObject:atKeyPath:usingMapping:] (RKMapperOperation.m:147)
28  GlobalDriver2ClientBooking      0x0018f9a8 -[RKMapperOperation performMappingForObject:atKeyPath:usingMapping:] (RKMapperOperation.m:268)
29  GlobalDriver2ClientBooking      0x0018ff50 -[RKMapperOperation performKeyPathMappingUsingMappingDictionary:] (RKMapperOperation.m:308)
30  GlobalDriver2ClientBooking      0x001903e0 -[RKMapperOperation main] (RKMapperOperation.m:333)
31  Foundation                      0x36f4038c -[__NSOperationInternal start] + 856
32  GlobalDriver2ClientBooking      0x0016d0a6 __73-[RKManagedObjectResponseMapperOperation performMappingWithObject:error:]_block_invoke_0 (RKResponseMapperOperation.m:262)
33  CoreData                        0x31e1e3e8 developerSubmittedBlockToNSManagedObjectContextPerform + 68
34  libdispatch.dylib               0x346be7e0 _dispatch_barrier_sync_f_invoke + 20
35  libdispatch.dylib               0x346be640 dispatch_barrier_sync_f$VARIANT$mp + 56
36  libdispatch.dylib               0x346be260 dispatch_sync_f$VARIANT$mp + 12
37  CoreData                        0x31e11904 -[NSManagedObjectContext performBlockAndWait:] + 160
38  GlobalDriver2ClientBooking      0x0016c7fe -[RKManagedObjectResponseMapperOperation performMappingWithObject:error:] (RKResponseMapperOperation.m:231)
39  GlobalDriver2ClientBooking      0x0016bbe6 -[RKResponseMapperOperation main] (RKResponseMapperOperation.m:181)
40  Foundation                      0x36f4038c -[__NSOperationInternal start] + 856
41  GlobalDriver2ClientBooking      0x0015ed0e -[RKManagedObjectRequestOperation performMappingOnResponse:] (RKManagedObjectRequestOperation.m:98)
42  GlobalDriver2ClientBooking      0x00169330 -[RKObjectRequestOperation main] (RKObjectRequestOperation.m:196)
43  Foundation                      0x36f4038c -[__NSOperationInternal start] + 856
44  Foundation                      0x36fa978c __block_global_6 + 96
45  libdispatch.dylib               0x346bcc52 _dispatch_call_block_and_release + 6
46  libdispatch.dylib               0x346bf810 _dispatch_worker_thread2 + 252
47  libsystem_c.dylib               0x32f41df4 _pthread_wqthread + 288
48  libsystem_c.dylib               0x32f41cc8 start_wqthread + 0

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.

@jcoleman

Also, the problem doesn't happen when PUT-ing an already persisted object.

@blakewatters
The RestKit Project member

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...

@jcoleman

In this case that secondary thread (the one with applyRelationshipMappings on the stack) is explicitly dying at [destinationSet setSet:destinationObject]. Since then two up the stack from that is [NSManagedObject didChangeValueForKey:withSetMutation:usingObjects:], my first thought was that I was doing something illegal in one of my KVO bindings. But disconnecting all of them didn't help.

Do you use any KVO within RestKit proper?

@jcoleman

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.

@blakewatters
The RestKit Project member

There is not any KVO in the mapping implementation.

@jcoleman

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.)

@blakewatters
The RestKit Project member

So to try recreate this in a test case I should:

  1. Create a secondary context
  2. Assign context to managed object request operation
  3. Create new object in secondary context that doesn't exist in the persistent store
  4. POST the object

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.

@jcoleman

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.)

@pjk25
pjk25 commented Nov 13, 2012

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:

2 libsystem_c.dylib 0x34e5d7ec _sigtramp + 48
3 CoreData 0x37b55d36 _PFObjectIDFastHash64 + 106
4 CoreFoundation 0x30d339ce __CFDictionaryHashKey + 18
5 CoreFoundation 0x30cb684f CFBasicHashFindBucket + 1787
6 CoreFoundation 0x30cb6144 CFDictionaryGetValue + 116
7 CoreData 0x37bd64b4 -[NSPersistentStoreCache incrementRefCountForObjectID:] + 24
8 CoreData 0x37bf9cea -[NSSQLCore managedObjectContextDidRegisterObjectsWithIDs:] + 130
9 CoreData 0x37b5d4fc -[NSPersistentStoreCoordinator(_NSInternalMethods) _informAffectedStoresOfInterestByChildContextInObjectsWithObjectIDs:withSelector:] + 420
10 CoreData 0x37bb3356 __-[NSManagedObjectContext(_NestedContextSupport) managedObjectContextDidRegisterObjectsWithIDs:]_block_invoke_1 + 70
11 CoreData 0x37bab5ba internalBlockToNSManagedObjectContextPerform + 10
12 libdispatch.dylib 0x35591796 _dispatch_barrier_sync_f_invoke + 22
13 libdispatch.dylib 0x3559160a dispatch_barrier_sync_f$VARIANT$up + 62
14 libdispatch.dylib 0x3559123e dispatch_sync_f$VARIANT$up + 18
15 CoreData 0x37bb9564 _perform + 160
16 CoreData 0x37bb97d2 -[NSManagedObjectContext(_NestedContextSupport) managedObjectContextDidRegisterObjectsWithIDs:] + 70
17 CoreData 0x37bb162a -[NSManagedObjectContext(_NSInternalAdditions) _informParentStore:ofInterestInObjects:] + 222
18 CoreData 0x37baf2e8 -[NSManagedObjectContext save:] + 620
@blakewatters
The RestKit Project member

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.

@pjk25
pjk25 commented Nov 14, 2012

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:

2 libsystem_c.dylib 0x35a85538 _sigtramp + 48
3 CoreData 0x35afba7a _PFObjectIDFastHash64 + 106
4 CoreFoundation 0x3527b9fe __CFDictionaryHashKey + 18
5 CoreFoundation 0x351fe9df CFBasicHashFindBucket + 1787
6 CoreFoundation 0x351fe2d4 CFDictionaryGetValue + 116
7 CoreData 0x35b7bc58 -[NSPersistentStoreCache incrementRefCountForObjectID:] + 24
8 CoreData 0x35b9ef2e -[NSSQLCore managedObjectContextDidRegisterObjectsWithIDs:] + 130
9 CoreData 0x35b030f2 -[NSPersistentStoreCoordinator(_NSInternalMethods) _informAffectedStoresOfInterestByChildContextInObjectsWithObjectIDs:withSelector:] + 82
10 CoreData 0x35b58a1a __-[NSManagedObjectContext(_NestedContextSupport) managedObjectContextDidRegisterObjectsWithIDs:]_block_invoke_1 + 70
11 CoreData 0x35b510ea internalBlockToNSManagedObjectContextPerform + 10
12 libdispatch.dylib 0x32c667ea _dispatch_barrier_sync_f_invoke + 22
13 libdispatch.dylib 0x32c6665a dispatch_barrier_sync_f$VARIANT$up + 62
14 libdispatch.dylib 0x32c6628e dispatch_sync_f$VARIANT$up + 18
15 CoreData 0x35b5ee60 _perform + 160
16 CoreData 0x35b5f0ce -[NSManagedObjectContext(_NestedContextSupport) managedObjectContextDidRegisterObjectsWithIDs:] + 70
17 CoreData 0x35b00384 _PFFaultHandlerLookupRow + 1448
18 CoreData 0x35b3d110 -[NSFaultHandler fulfillFault:withContext:] + 24
19 CoreData 0x35b47998 -[NSManagedObject(_NSInternalMethods) _newPropertiesForRetainedTypes:andCopiedTypes:preserveFaults:] + 76
20 CoreData 0x35b468ee -[NSManagedObject(_NSInternalMethods) _newAllPropertiesWithRelationshipFaultsIntact__] + 78
21 CoreData 0x35b574da -[NSManagedObjectContext(_NSInternalChangeProcessing) _establishEventSnapshotsForObject:] + 46
22 CoreData 0x35b5594a -[NSManagedObjectContext deleteObject:] + 154
@bazineta

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.

@Geeza
Geeza commented Dec 6, 2012

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 NSManagedObject+ActiveRecord no longer exist so I replaced them with my own.

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.h

I 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 Profile instance.

This is part of the trace log just before the crash..

2012-12-06 19:42:37.969 SuperSale[60814:1b03] D restkit.object_mapping:RKMappingOperation.m:647 Starting mapping operation...
2012-12-06 19:42:37.974 SuperSale[60814:1b03] T restkit.object_mapping:RKMappingOperation.m:648 Performing mapping operation: <RKMappingOperation 0x3a6ddfd0> for 'Profile' object. Mapping values from object {
    expirationDate = "4001-01-01 00:00:00 +0000";
    referenceId = ..;
    token = ..;
    type = 1;
} to object <Profile: 0x3a495fc0> (entity: Profile; id: 0x31edaff0 <x-coredata://F5BA2F8C-65C4-4CE3-A6AB-C4772257D659/Profile/t4188CE37-E910-4300-B5B0-E93AE1A8A9D53> ; data: <fault>) with object mapping (null)
2012-12-06 19:42:37.976 SuperSale[60814:1b03] T restkit.object_mapping:RKMappingOperation.m:341 Mapping attribute value keyPath 'type' to 'type'
@pjk25
pjk25 commented Dec 7, 2012

@Geeza
It's a bit hard to tell what you exact issue is without the stack trace associated with the crash, and the portion of code that seems triggers it. Are you getting the crash when you attempt to load (GET) a user or create (POST) one?

@blakewatters
Also further update on my previous post, as I believe I may have resolved the issue in my own code with the following changes. I'm still working from my own fork of RestKit which includes OAuth patches on top of RestKit/RestKit commit 221e91e.

  1. As I mentioned previously, the api I connect to returns and empty response body on POST. I now added an onDidLoadResponse block to my POST in order to interpret the "Location" header of the response, and update the primary key of the posted object. This eliminates the "orphans" with nil primary keys.
[[RKObjectManager sharedManager] postObject:pushableBookmark usingBlock:^(RKObjectLoader *loader) {
    loader.onDidLoadResponse = ^(RKResponse *response) {
        if ([response isCreated]) {
            NSString *location = [[response allHeaderFields] objectForKey:@"Location"];
            NSError *error = nil;
            if (![pushableBookmark updateWithLocation:location error:&error]) {
                TFTrace(@"Response returned unexpected Location: %@", location);
                Trace(@"Error: %@", error);
            }
            TFTrace(@"Created %@", pushableBookmark);
        } else {
            TFTrace(@"Failed to create %@: %d", pushableBookmark, [response statusCode]);
        }
    };
}];
  1. Previously, I would call save: on the mainQueueManagedObject context after pushing (PUT/POST/DELETE) bookmarks, and then again after pulling (GET on the index). I now save only after push, pull, and cleanup are all complete.

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.

@Geeza
Geeza commented Dec 7, 2012

@pjk25 I'll write some reproduction code.

@blakewatters
The RestKit Project member

Wow lots of substantive updates on here. I'll read through all of this and update shortly.

@phaser82

Hello all,
I've got the exactly same issue than jcoleman.
My app crash after a postObject in the [RKMappingOperation mapCoreDataToManyRelationshipValue] method on the instruction [destinationSet setSet:valueForRelationship]; with a EXC_BAD_ACCESS. No help from Zombie.

My post is about a JSON with 2 nested object with to-many relationship...ex. categories-products.
Both object aren't in the persistent store yet but just into the managedContext and gets created with insertNewObjectForEntityForName.

Some additional information, workarounds:
1. I do exactly the same kind of post with same mapping etc...but for a different relation and it works fine.
The main difference is that in this case the highest object is already into the persistent store.
2. If I do a contextSave just after insertNewObjectForEntityForName for the main object it works fine except for the fact that the child are inserted twice in the persistentStore;
3. If I do [managedObjectContext obtainPermanentIDsForObjects:managedObjectContext.insertedObjects.allObjects error:&error]; same behavior than 2; It works fine but with duplicate childs;

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?
Thanks

@blakewatters
The RestKit Project member

Drilling in here is next up on my priority list.

@phaser82
phaser82 commented Jan 2, 2013

Hi Blake,
I would add the following informations to my previous post as, digging into it, I've clarified some points which could be related to this issue. Hope it helps...

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.
In fact I've noticed that RESTKit goes on updating the father but it isn't able to map children received from the server answer to the previously passed one with the post request (as the children's identificationAttributes is not evaluated).
So the result of this is:
A. Update Father: evaluating synchronization primary key;
B. Insert new records for the children (not updating the existing one);
C. Original children present into the managedObjectContext are unlinked (at core data level) from the parent;

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.

@cfis
cfis commented Jan 9, 2013

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:

Attendee *from = (Attendee*)[Attendee findById:[user.id intValue]];

ZMessage *message = (ZMessage*)[ZMessage insert];
message.fromAttendee = from;

Receipient *receipient = (Receipient*)[Receipient insert];
receipient.attendee = toAttendee;
[message addReceipientsObject:receipient] (this is 1 to many relationship)

[[self objectManager] postObject:message
                            path:path
                      parameters:params
                         success:nil
                         failure:nil];

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.

    [destinationSet setSet:valueForRelationship];
@cfis
cfis commented Jan 9, 2013

So first doing:

[destinationSet removeAllObjects];

Also results in an error. Note that directly calling [someobject.somerelationship removeAllObjects] works....

@blakewatters
The RestKit Project member

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.

@blakewatters
The RestKit Project member

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.

@blakewatters blakewatters added a commit that referenced this issue Jan 10, 2013
@blakewatters blakewatters Use a visitor to identify all `NSManagedObject` instances being sent …
…via the object manager which have a temporary managed object ID to avoid crashes during mapping. refs #1001
b61039c
@blakewatters
The RestKit Project member

@cfis @phaser82 @pjk25 @Geeza @jcoleman Please give the latest development bits a try. We are now deeply traversing the managed object graph to find all of the temporary ID's accessible from the object being POST'd.

@cfis
cfis commented Jan 10, 2013

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:

            [self.managedObjectContext deleteObject:managedObject];

In the method:

  • (BOOL)mappingOperation:(RKMappingOperation )mappingOperation deleteExistingValueOfRelationshipWithMapping:(RKRelationshipMapping *)relationshipMapping error:(NSError *)error

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:

  • Create a managed object that has a to many relationship - don't save it
  • Give it 1 or more related objects
  • Post the object
  • When the object is returned the parent object is created. But touching the relationship, by removing objects, results in the crash
@blakewatters
The RestKit Project member

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 NSSet that is being mutated. Its actually a proxy object for the Core Data relationship. So what was happening prior to my changes last night was you'd wind up with a permanent objectID for the parent object, which can be refetched across child contexts, but the child objects contained within the relationship still had temporary objectID's. Core Data could see that there were objects there, but attempting to touch them in the child context would explode. Getting a permanent ID for all of them fixed the crash in the unit tests as all of the objects are now visible to the child context.

I will try blowing out some additional test cases tonight

@blakewatters
The RestKit Project member

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.

@cfis
cfis commented Jan 10, 2013

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?

@blakewatters
The RestKit Project member

If you project is on Github, just adding me is easiest. Otherwise a zip archive via e-mail / Dropbox download works

@cfis
cfis commented Jan 10, 2013

Its on Github, I have added you in. I also sent you an email with instructions. Thanks for the help!

@blakewatters blakewatters added a commit that referenced this issue Jan 11, 2013
@blakewatters blakewatters Fixed problems with initial implementation of temporary managed objec…
…ts visitor. The implementation was under tested and only worked with a single relationship. refs #1001
3e42570
@blakewatters
The RestKit Project member

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.

@phaser82

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.

@blakewatters
The RestKit Project member

@phaser82 I have opened up a follow-up issue specific to the duplication case on #1149. Will try to take a look at that this weekend and see if I can't put this cluster of issues to bed once and for all.

@cfis
cfis commented Jan 20, 2013

Just a note saying my issue was resolved - thanks!

@percysnoodle
The RestKit Project member

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?

@blakewatters
The RestKit Project member

@percsnoodle:

  1. gem install cocoapods # Make sure you are on 0.16.1
  2. Update Podfile to pod 'RestKit', :git => 'https://github.com/RestKit/RestKit.git', :branch => 'development'
  3. rm Podfile.lock
  4. rm -rf Pods
  5. rm -rf ~/Library/Caches/CocoaPods/
  6. pod install
@percysnoodle
The RestKit Project member

Thanks!

@stojanovicigi stojanovicigi added a commit to CenterDevice/RestKit that referenced this issue Dec 12, 2013
@blakewatters blakewatters Use a visitor to identify all `NSManagedObject` instances being sent …
…via the object manager which have a temporary managed object ID to avoid crashes during mapping. refs #1001
1ea47cc
@stojanovicigi stojanovicigi added a commit to CenterDevice/RestKit that referenced this issue Dec 12, 2013
@blakewatters blakewatters Fixed problems with initial implementation of temporary managed objec…
…ts visitor. The implementation was under tested and only worked with a single relationship. refs #1001
ac29b92
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.