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
- there's main thread
nsmanagedobjectcontext
nsmainqueueconcurrencytype
. spawns several background threads giving themnsmanagedobjectid
of object work on. - background threads perform work (e.g. send object data server, receive response , update object accordingly). threads use child contexts
nsconfinenmentconcurrencytype
- meanwhile user deletes object main thread's context (via ui).
- 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
Post a Comment