ios - How to correctly propagate delete from main thread's NSManagedObjectContext to child context on a background thread? -


i'm trying figure out how solve following situation

  1. there's main thread nsmanagedobjectcontext nsmainqueueconcurrencytype. spawns several background threads giving them nsmanagedobjectid of object work on.
  2. background threads perform work (e.g. send object data server, receive response , update object accordingly). threads use child contexts nsconfinenmentconcurrencytype
  3. meanwhile user deletes object main thread's context (via ui).
  4. background contexts should notified , handle situation prevent 'cannot fulfil fault' exception on background context save.

i thought main context (some custom object manages it) keep record of object ids deleted during background thread lifetime (or, more precisely, between creating background context , final save of background context). background context have perform deleteobject: on these objects before saves. , go smoothly.

in order sure main context not manage delete object when background thread finished deleting objects , call save: on context, , guarantee main context's delete not happen after child context created before child thread registers "notified" deleted objects, employed several mutex locks , came following proof-of-concept code:

@property (nonatomic, strong) id deletelock; @property (nonatomic, strong) nsmutabledictionary *deletedobjectidsperthreadlifetime;  - (void)coredatadeletesyncexample {      static int lastthreadno = 0;      self.deletelock = [[nsobject alloc] init];     self.deletedobjectidsperthreadlifetime = [[nsmutabledictionary alloc] init];      // main context created using nsmainqueueconcurrencytype     nsmanagedobjectcontext *maincontext = [self maincontext];      nsmanagedobjectid *myobjectid = nil;      // creating object     order *order = (order*)[nsentitydescription                             insertnewobjectforentityforname:@"order"                             inmanagedobjectcontext:maincontext];      payment *payment = (payment*)[nsentitydescription                                   insertnewobjectforentityforname:@"payment"                                   inmanagedobjectcontext:maincontext];       if (order) {         [payment setorder:order];         [payment setamount:[nsdecimalnumber decimalnumberwithstring:@"103"]];          nserror *error = nil;         if (![maincontext save:&error]) {             nslog(@"main context save failed");         }          myobjectid = [order objectid]; // have non-temporary objectid here can pass around     }      int threadno;      (threadno = lastthreadno ; threadno < 50+lastthreadno; threadno++) {         dispatch_async(dispatch_get_global_queue(dispatch_queue_priority_default, 0), ^{              nsnumber *threadnumber = [nsnumber numberwithint:threadno];             nsmanagedobjectcontext *bckcontext = nil;             nserror *error = nil;              @synchronized(self.deletelock) {                 bckcontext = [[nsmanagedobjectcontext alloc] initwithconcurrencytype:nsconfinementconcurrencytype];                 bckcontext.parentcontext = maincontext;                  [self.deletedobjectidsperthreadlifetime setobject:[nsmutableset set] forkey:threadnumber];                 nslog(@"bck #%d created delete list/dict", threadno);             }              order *order = (order*)[bckcontext existingobjectwithid:myobjectid error:&error];              (int = 0; < 30; i++) {                 order.status = [nsstring stringwithformat:@"some status set background thread, %d/%d", threadno, i];                 nslog(@"(dont clutter log):%d/%@", threadno, order.status);             }              // background context going save order, before deletes             // objects have been deleted main context in meantime              // make @synchronized call make sure maincontext has no chances delete             // additional objects after delete ones set             // , before save background             nslog(@"bck #%d saving context...", threadno);             @synchronized(self.deletelock) {                 nsset *objstodelete = [self.deletedobjectidsperthreadlifetime objectforkey:threadnumber];                 (nsmanagedobjectid *objectid in objstodelete) {                     nsmanagedobject *obj = [bckcontext objectwithid:objectid];                     nslog(@"bck #%d deleted obj %@ because on list", threadno,objectid);                     [bckcontext deleteobject:obj];                 }                  if (objstodelete == nil) {                     nslog(@"bck #%d not included in delete dictionary list.", threadno);                 } else {                     nslog(@"bck #%d has empty list of objs delete.", threadno);                 }                    nslog(@"bck #%d before save...", threadno);                 // saving bck outside lock wrong                 error = nil;                 if (![bckcontext save:&error]) {                     nslog(@"bck context #%d failed save: %@", threadno, error);                 } else {                     nslog(@"bck #%d saved context!", threadno);                 }             }              // saving main context outside lock             [maincontext performblockandwait:^{                 nserror *error = nil;                 nslog(@"main thread save context (requested bck #%d)", threadno);                 if (![maincontext save:&error]) {                     nslog(@"main context save failed");                 } else {                     nslog(@"main context saved (requested bck #%d)", threadno);                 }             }];         });     }      lastthreadno = threadno;      // let's delete object in meantime on main thread, , save main context after while     dispatch_after(dispatch_time(dispatch_time_now, (int64_t)(150 * nsec_per_msec)), dispatch_get_main_queue(), ^{          order *o = (order*)[maincontext objectwithid:myobjectid];         nslog(@"main - delete...");          //@synchronized(self.deletelock) {         objc_sync_enter(self.deletelock);             (nsnumber *threadnumber in self.deletedobjectidsperthreadlifetime) {                 nsmutableset *deletedids = [self.deletedobjectidsperthreadlifetime objectforkey:threadnumber];                  [deletedids addobject:myobjectid];             }             nslog(@"main -deleting- %@", myobjectid);             [maincontext deleteobject:o];             nslog(@"main -deleted- %@", myobjectid);          //}          dispatch_after(dispatch_time(dispatch_time_now, (int64_t)(150 * nsec_per_msec)), dispatch_get_main_queue(), ^{              dlog(@"and save main!");             nserror *error = nil;             if (![maincontext save:&error]) {                 nslog(@"main context save failed");             } else {                 nslog(@"main context saved (requested main context)");             }              objc_sync_exit(self.deletelock);         });      });   } 

it turns out code has several problems: 1. deadlocks. when background threads starts save "transactions" acquires lock, if mainthread manages encounter @synchronized block waits. background proceeds save: call. there seems coredata wants save child main context, tries use context. since can used on main thread, , main thread blocked lock acquired background thread, have deadlock. 2. still crashes 'cannot fulfil fault'. happens sometimes when main context's delete , save happens before background context created , fetches object. in situation object nil. sometimes not (why???) , got crash on background context save, in situation:

2014-11-27 14:00:13.179 concurrentcoredata[70490:1403] bck #0 created delete list/dict 2014-11-27 14:00:13.186 concurrentcoredata[70490:1403] (dont clutter log):0/some status set background thread, 0/0 2014-11-27 14:00:13.187 concurrentcoredata[70490:1403] (dont clutter log):0/some status set background thread, 0/1 2014-11-27 14:00:13.189 concurrentcoredata[70490:1403] (dont clutter log):0/some status set background thread, 0/2 2014-11-27 14:00:13.189 concurrentcoredata[70490:1403] (dont clutter log):0/some status set background thread, 0/3 2014-11-27 14:00:13.190 concurrentcoredata[70490:2c07] bck #1 created delete list/dict 2014-11-27 14:00:13.190 concurrentcoredata[70490:1403] (dont clutter log):0/some status set background thread, 0/4 2014-11-27 14:00:13.192 concurrentcoredata[70490:3907] bck #2 created delete list/dict 2014-11-27 14:00:13.191 concurrentcoredata[70490:1403] (dont clutter log):0/some status set background thread, 0/5 (...) 2014-11-27 14:00:13.309 concurrentcoredata[70490:4b03] (dont clutter log):7/some status set background thread, 7/10 2014-11-27 14:00:13.309 concurrentcoredata[70490:2c07] (dont clutter log):1/some status set background thread, 1/23 2014-11-27 14:00:13.311 concurrentcoredata[70490:1403] (dont clutter log):0/some status set background thread, 0/29 2014-11-27 14:00:13.329 concurrentcoredata[70490:90b] main - delete... 2014-11-27 14:00:13.333 concurrentcoredata[70490:4e03] bck #8 created delete list/dict 2014-11-27 14:00:13.316 concurrentcoredata[70490:4b03] (dont clutter log):7/some status set background thread, 7/11 2014-11-27 14:00:13.365 concurrentcoredata[70490:5003] bck #9 created delete list/dict 2014-11-27 14:00:13.367 concurrentcoredata[70490:5103] bck #10 created delete list/dict 2014-11-27 14:00:13.367 concurrentcoredata[70490:5203] bck #11 created delete list/dict 2014-11-27 14:00:13.366 concurrentcoredata[70490:4b03] (dont clutter log):7/some status set background thread, 7/12 2014-11-27 14:00:13.316 concurrentcoredata[70490:2c07] (dont clutter log):1/some status set background thread, 1/24 2014-11-27 14:00:13.311 concurrentcoredata[70490:3807] (dont clutter log):3/some status set background thread, 3/20 2014-11-27 14:00:13.312 concurrentcoredata[70490:3b03] (dont clutter log):4/some status set background thread, 4/19 2014-11-27 14:00:13.316 concurrentcoredata[70490:3c03] (dont clutter log):5/some status set background thread, 5/18 2014-11-27 14:00:13.314 concurrentcoredata[70490:3907] (dont clutter log):2/some status set background thread, 2/22 2014-11-27 14:00:13.312 concurrentcoredata[70490:4603] (dont clutter log):6/some status set background thread, 6/17 2014-11-27 14:00:13.365 concurrentcoredata[70490:1403] bck #0 saving context... 2014-11-27 14:00:13.369 concurrentcoredata[70490:90b] main -deleting- 0x8b24cd0 <x-coredata://06dfa035-e3df-497c-89b4-20e845a09712/order/p549> (...) 2014-11-27 14:00:13.372 concurrentcoredata[70490:90b] main -deleted- 0x8b24cd0 <x-coredata://06dfa035-e3df-497c-89b4-20e845a09712/order/p549> (...) 2014-11-27 14:00:13.420 concurrentcoredata[70490:2c07] bck #1 saving context... (...) 2014-11-27 14:00:13.453 concurrentcoredata[70490:3907] bck #2 saving context... (...) 2014-11-27 14:00:13.475 concurrentcoredata[70490:3807] bck #3 saving context... (...) 2014-11-27 14:00:13.488 concurrentcoredata[70490:3b03] bck #4 saving context... (...) 2014-11-27 14:00:13.496 concurrentcoredata[70490:3c03] bck #5 saving context... (...) 2014-11-27 14:00:13.558 concurrentcoredata[70490:90b] __43-[viewcontroller coredatadeletesyncexample]_block_invoke_2178 [line 260] , save main! 2014-11-27 14:00:13.559 concurrentcoredata[70490:4603] (dont clutter log):6/some status set background thread, 6/28 (...) 2014-11-27 14:00:13.564 concurrentcoredata[70490:4e03] (dont clutter log):8/some status set background thread, 8/13 2014-11-27 14:00:13.565 concurrentcoredata[70490:90b] main context saved (requested main context) 2014-11-27 14:00:13.565 concurrentcoredata[70490:4603] (dont clutter log):6/some status set background thread, 6/29 2014-11-27 14:00:13.566 concurrentcoredata[70490:5003] (dont clutter log):9/some status set background thread, 9/13 (...) 2014-11-27 14:00:13.663 concurrentcoredata[70490:2c07] bck #1 saved context! 2014-11-27 14:00:13.664 concurrentcoredata[70490:5003] (dont clutter log):9/some status set background thread, 9/24 2014-11-27 14:00:13.667 concurrentcoredata[70490:90b] main thread save context (requested bck #1) 2014-11-27 14:00:13.667 concurrentcoredata[70490:90b] main context saved (requested bck #1) 2014-11-27 14:00:13.667 concurrentcoredata[70490:5003] (dont clutter log):9/some status set background thread, 9/25 2014-11-27 14:00:13.668 concurrentcoredata[70490:3907] bck #2 deleted obj 0x8b24cd0 <x-coredata://06dfa035-e3df-497c-89b4-20e845a09712/order/p549> because on list 2014-11-27 14:00:13.668 concurrentcoredata[70490:5003] (dont clutter log):9/some status set background thread, 9/26 2014-11-27 14:00:13.668 concurrentcoredata[70490:3907] bck #2 has empty list of objs delete. 2014-11-27 14:00:13.668 concurrentcoredata[70490:5003] (dont clutter log):9/some status set background thread, 9/27 2014-11-27 14:00:13.669 concurrentcoredata[70490:5003] (dont clutter log):9/some status set background thread, 9/28 2014-11-27 14:00:13.669 concurrentcoredata[70490:5003] (dont clutter log):9/some status set background thread, 9/29 2014-11-27 14:00:13.670 concurrentcoredata[70490:5003] bck #9 saving context... 2014-11-27 14:00:13.668 concurrentcoredata[70490:3907] bck #2 before save... 2014-11-27 14:00:13.666 concurrentcoredata[70490:4e03] (dont clutter log):8/some status set background thread, 8/18 2014-11-27 14:00:13.671 concurrentcoredata[70490:3907] bck #2 saved context! 2014-11-27 14:00:13.671 concurrentcoredata[70490:5a03] bck #12 created delete list/dict 2014-11-27 14:00:13.672 concurrentcoredata[70490:5b03] bck #13 created delete list/dict 2014-11-27 14:00:13.672 concurrentcoredata[70490:5a03] (dont clutter log):12/some status set background thread, 12/0 2014-11-27 14:00:13.673 concurrentcoredata[70490:5a03] (dont clutter log):12/some status set background thread, 12/1 (!! , here queue #13 has object! deleted main context , main context saved before spawned child context!) 2014-11-27 14:00:13.673 concurrentcoredata[70490:5b03] (dont clutter log):13/some status set background thread, 13/0  2014-11-27 14:00:13.674 concurrentcoredata[70490:5a03] (dont clutter log):12/some status set background thread, 12/2 2014-11-27 14:00:13.674 concurrentcoredata[70490:5b03] (dont clutter log):13/some status set background thread, 13/1 2014-11-27 14:00:13.674 concurrentcoredata[70490:5a03] (dont clutter log):12/some status set background thread, 12/3 2014-11-27 14:00:13.674 concurrentcoredata[70490:5b03] (dont clutter log):13/some status set background thread, 13/2 2014-11-27 14:00:13.673 concurrentcoredata[70490:5c03] bck #14 created delete list/dict 2014-11-27 14:00:13.675 concurrentcoredata[70490:5a03] (dont clutter log):12/some status set background thread, 12/4 2014-11-27 14:00:13.675 concurrentcoredata[70490:5a03] (dont clutter log):12/some status set background thread, 12/5 2014-11-27 14:00:13.676 concurrentcoredata[70490:5a03] (dont clutter log):12/some status set background thread, 12/6 2014-11-27 14:00:13.676 concurrentcoredata[70490:5a03] (dont clutter log):12/some status set background thread, 12/7 2014-11-27 14:00:13.676 concurrentcoredata[70490:5a03] (dont clutter log):12/some status set background thread, 12/8 2014-11-27 14:00:13.675 concurrentcoredata[70490:5b03] (dont clutter log):13/some status set background thread, 13/3 2014-11-27 14:00:13.676 concurrentcoredata[70490:5a03] (dont clutter log):12/some status set background thread, 12/9 2014-11-27 14:00:13.677 concurrentcoredata[70490:5a03] (dont clutter log):12/some status set background thread, 12/10 (!!! queue #14 well???) 2014-11-27 14:00:13.675 concurrentcoredata[70490:5c03] (dont clutter log):14/some status set background thread, 14/0 (…) 2014-11-27 14:00:14.503 concurrentcoredata[70490:7a03] (dont clutter log):44/(null) 2014-11-27 14:00:14.504 concurrentcoredata[70490:7f03] (dont clutter log):49/(null) 2014-11-27 14:00:14.505 concurrentcoredata[70490:90b] main thread save context (requested bck #9) 2014-11-27 14:00:14.505 concurrentcoredata[70490:7e03] (dont clutter log):48/(null) 2014-11-27 14:00:14.505 concurrentcoredata[70490:7b03] (dont clutter log):45/(null) 2014-11-27 14:00:14.505 concurrentcoredata[70490:7d03] (dont clutter log):47/(null) 2014-11-27 14:00:14.505 concurrentcoredata[70490:5b03] bck #13 has empty list of objs delete. 2014-11-27 14:00:14.506 concurrentcoredata[70490:7903] (dont clutter log):43/(null) 2014-11-27 14:00:14.506 concurrentcoredata[70490:7c03] (dont clutter log):46/(null) 2014-11-27 14:00:14.509 concurrentcoredata[70490:90b] main context saved (requested bck #9) 2014-11-27 14:00:14.508 concurrentcoredata[70490:7a03] bck #44 saving context... 2014-11-27 14:00:14.510 concurrentcoredata[70490:7e03] (dont clutter log):48/(null) 2014-11-27 14:00:14.510 concurrentcoredata[70490:7b03] (dont clutter log):45/(null) (queue #13 tries save , crashes!) 2014-11-27 14:00:14.510 concurrentcoredata[70490:5b03] bck #13 before save... 2014-11-27 14:00:14.510 concurrentcoredata[70490:7d03] (dont clutter log):47/(null) 2014-11-27 14:00:14.508 concurrentcoredata[70490:7f03] (dont clutter log):49/(null) 2014-11-27 14:00:14.511 concurrentcoredata[70490:7903] bck #43 saving context... 2014-11-27 14:00:14.511 concurrentcoredata[70490:7c03] (dont clutter log):46/(null) 2014-11-27 14:00:14.514 concurrentcoredata[70490:7e03] (dont clutter log):48/(null) 2014-11-27 14:00:14.516 concurrentcoredata[70490:90b] *** terminating app due uncaught exception 'nsobjectinaccessibleexception', reason: 'coredata not fulfill fault '0x8b24cd0 <x-coredata://06dfa035-e3df-497c-89b4-20e845a09712/order/p549>'' *** first throw call stack: (     0   corefoundation                      0x018001e4 __exceptionpreprocess + 180     1   libobjc.a.dylib                     0x0157f8e5 objc_exception_throw + 44     2   coredata                            0x01a8cbeb _pffaulthandlerlookuprow + 2715     3   coredata                            0x01abee88 -[nsfaulthandler fulfillfault:withcontext:] + 40     4   coredata                            0x01b33169 -[nsmanagedobject(_nsinternalmethods) _updatefromrefreshsnapshot:includingtransients:] + 265     5   coredata                            0x01ac7902 -[nsmanagedobjectcontext(_nestedcontextsupport) _copychildobject:toparentobject:fromchildcontext:] + 994     6   coredata                            0x01ac71e8 -[nsmanagedobjectcontext(_nestedcontextsupport) _parentprocesssaverequest:incontext:error:] + 1480     7   coredata                            0x01b3fa14 __82-[nsmanagedobjectcontext(_nestedcontextsupport) executerequest:withcontext:error:]_block_invoke + 676     8   coredata                            0x01ac1b81 internalblocktonsmanagedobjectcontextperform + 17     9   libdispatch.dylib                   0x01f784d0 _dispatch_client_callout + 14     10  libdispatch.dylib                   0x01f67439 _dispatch_barrier_sync_f_slow_invoke + 80     11  libdispatch.dylib                   0x01f784d0 _dispatch_client_callout + 14     12  libdispatch.dylib                   0x01f66726 _dispatch_main_queue_callback_4cf + 340     13  corefoundation                      0x0186543e __cfrunloop_is_servicing_the_main_dispatch_queue__ + 14     14  corefoundation                      0x017a65cb __cfrunlooprun + 1963     15  corefoundation                      0x017a59d3 cfrunlooprunspecific + 467     16  corefoundation                      0x017a57eb cfrunloopruninmode + 123     17  graphicsservices                    0x03b0a5ee gseventrunmodal + 192     18  graphicsservices                    0x03b0a42b gseventrun + 104     19  uikit                               0x0023ff9b uiapplicationmain + 1225     20  concurrentcoredata                  0x00009d7d main + 141     21  libdyld.dylib                       0x021ab725 start + 0 ) libc++abi.dylib: terminating uncaught exception of type _nscoredataexception (lldb)  

i understand cause of first problem (deadlock). have no idea how solve it, guess custom locking not possible when using main context's child contexts.

but second 1 weird. why object not nil? after core data did delete , save object before child context created. why nil there object? caching issue? can not trust core data on returning nil in child context object deleted in main context (and saved!) before child context created?! solution fundamentally flawed?

what correct way handle situation background contexts having deal main context's deletes. i've got feeling whole main/child context feature nice , easy use, unless start delete objects in main contexts. whole thing becames useless , still have resort saving store , merging contexts.


Comments

Popular posts from this blog

javascript - Any ideas when Firefox is likely to implement lengthAdjust and textLength? -

matlab - "Contour not rendered for non-finite ZData" -

delphi - Indy UDP Read Contents of Adata -