NSPersistentContainer deallocation in iOS Today Widget Extension

Hi,

I'm using NSPersistentContainer inside my iOS App Today Widget Extension. So far so good everything seems to work fine excpet for the deallocation.

It looks like that since the deallocation of the NSPersistentContainer does also some background activity, and since the Today Widget Extension does not get the possibility to do those background tasks using the UIApplication API, I get this error in the console:


Can't endBackgroundTask: no background task exists with identifier 6, or it may have already been ended. Break in UIApplicationEndBackgroundTaskError() to debug.


I've already set the Breakpoint and traced that the error indeed happens while the extension deallocates the NSPersistentContainer instance. If I set to nil the NSPersistentContainer instance in the viewDidDisappear method of the extension's view controller, the error doesn't get triggerd. I am assuming that's because having the deallocation of the NSPersistentContainer instance happening in the viewDidDisappear method gives the deallocation process more time to get done and those background tasks get done and invalidated fine.

Should I use a different approach for deallocatiing that resource, perhaps using the NSProcessInfo API, or is it ok to do that deallocation in viewDidDisappear, having given that the Extesnion has only one (the main) viewController and that once that viewController diappears the system will most likely than not also deallocate it cause it'll kill the extension?

I've already set the Breakpoint …

Please post the backtrace see see what you hit this breakpoint.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi Eskimo,

sorry for the late reply (it looks like I didn't get the email notification for this thread) and thanks!

I now have a different problem. I had solved this issue in the immediate days after requesting help on this topic by deallocating the persistent container instance in a processinfo closure:


- (void)dealloc {
    NSProcessInfo *processInfo = [NSProcessInfo processInfo];
    [processInfo performExpiringActivityWithReason:@"dealloc-NSPersistentContainer" usingBlock:^(BOOL expired) {

        _persistentContainer = nil;
    }];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

Now, in XCode 8.x this was working fine and I didn't get anymore this issue.
Unfortunately now in XCode 9 (just upgraded) I get an EXC_BAD_ACCESS crash using this tecnique:


Foundation`__46-[_NSActivityAssertion _fireExpirationHandler]_block_invoke_2:
    0x104f9af69 <+0>:   pushq  %rbp
    0x104f9af6a <+1>:   movq   %rsp, %rbp
    0x104f9af6d <+4>:   pushq  %r14
    0x104f9af6f <+6>:   pushq  %rbx
    0x104f9af70 <+7>:   subq   $0x30, %rsp
    0x104f9af74 <+11>:  movq   %rdi, %rbx
    0x104f9af77 <+14>:  movq   0x20(%rbx), %rdi
->  0x104f9af7b <+18>:  callq  *0x10(%rdi)
    0x104f9af7e <+21>:  movq   0x20(%rbx), %rdi
    0x104f9af82 <+25>:  movq   0x177b1f(%rip), %rsi      ; "release"
    0x104f9af89 <+32>:  movq   0xca888(%rip), %r14       ; (void *)0x0000000105397940: objc_msgSend
    0x104f9af90 <+39>:  callq  *%r14
    0x104f9af93 <+42>:  movq   0x180bd6(%rip), %rdi      ; (void *)0x000000010512c518: _NSActivityAssertion
    0x104f9af9a <+49>:  movq   0x17f25f(%rip), %rsi      ; "_expiringAssertionManagementQueue"
    0x104f9afa1 <+56>:  callq  *%r14
    0x104f9afa4 <+59>:  movq   0xca36d(%rip), %rcx       ; (void *)0x000000010a5f5070: _NSConcreteStackBlock
    0x104f9afab <+66>:  leaq   -0x38(%rbp), %rsi
    0x104f9afaf <+70>:  movq   %rcx, (%rsi)
    0x104f9afb2 <+73>:  movl   $0xc2000000, 0x8(%rsi)    ; imm = 0xC2000000
    0x104f9afb9 <+80>:  movl   $0x0, 0xc(%rsi)
    0x104f9afc0 <+87>:  leaq   0x28(%rip), %rcx          ; __46-[_NSActivityAssertion _fireExpirationHandler]_block_invoke_3
    0x104f9afc7 <+94>:  movq   %rcx, 0x10(%rsi)
    0x104f9afcb <+98>:  leaq   0xdfa76(%rip), %rcx       ; __block_descriptor_tmp.100
    0x104f9afd2 <+105>: movq   %rcx, 0x18(%rsi)
    0x104f9afd6 <+109>: movq   0x28(%rbx), %rcx
    0x104f9afda <+113>: movq   %rcx, 0x20(%rsi)
    0x104f9afde <+117>: movq   %rax, %rdi
    0x104f9afe1 <+120>: callq  0x104fd1af0               ; symbol stub for: dispatch_async
    0x104f9afe6 <+125>: addq   $0x30, %rsp
    0x104f9afea <+129>: popq   %rbx
    0x104f9afeb <+130>: popq   %r14
    0x104f9afed <+132>: popq   %rbp
    0x104f9afee <+133>: retq


In the console I get this weird message prior the crash:

SimplePlusOneTodayWidget[3756:500441] writeASTCData:926: *** _astcPreTwiddle set for non-IIO_HAS_ASTCTWIDDLER codepath


When removing the processinfo clause in the dealloc method, hence setting to nil the _peristentContainer normally, I won't get the crash but still that message in the console everytime the widget is not shown anymore in the notification center (widget extensions get deallocated aggressively by the system to save resources as you may know).


EDIT:

Not using anymore the processinfo closure to dealloc the persistent container, I've got again —randomly— the UIApplicationEndBackgroundTaskError excpetion:


UIKit`UIApplicationEndBackgroundTaskError:
->  0x10be8c530 <+0>:  pushq  %rbp
    0x10be8c531 <+1>:  movq   %rsp, %rbp
    0x10be8c534 <+4>:  movq   %rdi, %rcx
    0x10be8c537 <+7>:  leaq   0x11778aa(%rip), %rdi     ; @"Can't endBackgroundTask: no background task exists with identifier %lx, or it may have already been ended. Break in UIApplicationEndBackgroundTaskError() to debug."
    0x10be8c53e <+14>: xorl   %eax, %eax
    0x10be8c540 <+16>: movq   %rcx, %rsi
    0x10be8c543 <+19>: callq  0x10ccbccd8               ; symbol stub for: NSLog
    0x10be8c548 <+24>: popq   %rbp
    0x10be8c549 <+25>: retq


This happens, as I stated orignally, when the widget gets deallocated and after the NSSQLiteConnection recives the disconnect message during the deallocation the NSPersistentStoreCooridinator, which itself happens during the deallocation of the NSPersistentContainer instance in the widget


In your first listing

_persistentContainer
is an ivar, right? If so, you’re having resurrection problems:
  1. You’re in your

    -dealloc
    method, so
    self
    is going away.
  2. You create an expiring activity and pass it a block that references in ivar. Thus, the block has to capture

    self
    , which means it attempts to resurrect an object that’s in the process of being deallocated.

Object resurrection is never a good idea and is very likely the underlying cause of the

-_fireExpirationHandler
crash you’re seeing.

I have some vague ideas on how you might proceed here but I’m reticent to suggestion anything specific without seeing the backtrace associated with the end background task.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi Eskimo,

I know it is not a good idea to resurrect self by using that NSProcessInfo closure in the dealloc method.

I can't either capture a weak reference to self in dealloc.

That's also not how a NSProcessInfo closure is inteneded to be used (not addressing both cases for having either performed or not the operation).

This was the only workaround I've found so far to let the NSPersistentContainer finish its deallocating job in the background.


As I've mentioned before, since the UIApplicationBackGroundTaskError happens sproadically and randomly, I've only taken a screen snapshot of thread backtrace,

but I'm having problems while posting it here as an image (it looks like it won't show up in the reply both being linked from Apple servers and Imagur servers).

Here's a link to it on Imagur: https://i.stack.imgur.com/cWbBS.png


Thanks again for any given help!

Hi, I'm having trouble posting a screen capture of the backtrace on the forum (that's also maybe why my replies got into moderation state).

Here is the text representation:


(lldb) thread backtrace
* thread #1, queue = 'SQLQueue 0x7f9654422920 for SimplePlusOne.sqlite', stop reason = breakpoint 1.1
  * frame #0: 0x000000011041f530 UIKit`UIApplicationEndBackgroundTaskError
    frame #1: 0x000000011041f5d7 UIKit`-[UIApplication _endBackgroundTask:] + 141
    frame #2: 0x000000010f7127f8 CoreData`-[NSSQLiteConnection disconnect] + 1096
    frame #3: 0x000000010f81745e CoreData`__37-[NSSQLiteConnection performAndWait:]_block_invoke + 30
    frame #4: 0x00000001142952b5 libdispatch.dylib`_dispatch_client_callout + 8
    frame #5: 0x000000011429bb11 libdispatch.dylib`_dispatch_queue_barrier_sync_invoke_and_complete + 92
    frame #6: 0x000000010f817394 CoreData`-[NSSQLiteConnection performAndWait:] + 164
    frame #7: 0x000000010f88ac6f CoreData`__57-[NSSQLDefaultConnectionManager disconnectAllConnections]_block_invoke + 1071
    frame #8: 0x00000001142952b5 libdispatch.dylib`_dispatch_client_callout + 8
    frame #9: 0x000000011429bb11 libdispatch.dylib`_dispatch_queue_barrier_sync_invoke_and_complete + 92
    frame #10: 0x000000010f88a832 CoreData`-[NSSQLDefaultConnectionManager disconnectAllConnections] + 82
    frame #11: 0x000000010f8916b9 CoreData`-[NSSQLCoreDispatchManager disconnectAllConnections] + 217
    frame #12: 0x000000010f7e5da4 CoreData`-[NSSQLCore _disconnectAllConnections] + 36
    frame #13: 0x000000010f71219c CoreData`-[NSSQLCore willRemoveFromPersistentStoreCoordinator:] + 92
    frame #14: 0x000000010f7c5948 CoreData`__39-[NSPersistentStoreCoordinator dealloc]_block_invoke + 1096
    frame #15: 0x000000010f7d7849 CoreData`gutsOfBlockToNSPersistentStoreCoordinatorPerform + 201
    frame #16: 0x00000001142952b5 libdispatch.dylib`_dispatch_client_callout + 8
    frame #17: 0x000000011429bb11 libdispatch.dylib`_dispatch_queue_barrier_sync_invoke_and_complete + 92
    frame #18: 0x000000010f7c32a5 CoreData`_perform + 213
    frame #19: 0x000000010f72b326 CoreData`-[NSPersistentStoreCoordinator dealloc] + 134
    frame #20: 0x000000010f11ba2e libobjc.A.dylib`objc_object::sidetable_release(bool) + 202
    frame #21: 0x000000010f7166db CoreData`-[NSManagedObjectContext(_NSInternalAdditions) _dispose:] + 1755
    frame #22: 0x000000010f715cce CoreData`-[NSManagedObjectContext _dealloc__] + 414
    frame #23: 0x000000010f715ac1 CoreData`-[NSManagedObjectContext dealloc] + 305
    frame #24: 0x000000010f70bd42 CoreData`developerSubmittedBlockToNSManagedObjectContextPerform + 498
    frame #25: 0x00000001142952b5 libdispatch.dylib`_dispatch_client_callout + 8
    frame #26: 0x000000011429f496 libdispatch.dylib`_dispatch_main_queue_callback_4CF + 1260
    frame #27: 0x000000010fcadef9 CoreFoundation`__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
    frame #28: 0x000000010fc72662 CoreFoundation`__CFRunLoopRun + 2402
    frame #29: 0x000000010fc71a89 CoreFoundation`CFRunLoopRunSpecific + 409
    frame #30: 0x00000001163d99c6 GraphicsServices`GSEventRunModal + 62
    frame #31: 0x00000001103fcd30 UIKit`UIApplicationMain + 159
    frame #32: 0x000000011465e0f8 libxpc.dylib`_xpc_objc_main + 491
    frame #33: 0x00000001146604cb libxpc.dylib`xpc_main + 143
    frame #34: 0x000000010eb56761 Foundation`-[NSXPCListener resume] + 165
    frame #35: 0x0000000118e9b2b3 PlugInKit`-[PKService run] + 709
    frame #36: 0x0000000118e9aeba PlugInKit`+[PKService main] + 55
    frame #37: 0x0000000118e9b2d7 PlugInKit`+[PKService _defaultRun:arguments:] + 17
    frame #38: 0x000000010ec06cfe Foundation`NSExtensionMain + 51
    frame #39: 0x0000000114309d81 libdyld.dylib`start + 1
    frame #40: 0x0000000114309d81 libdyld.dylib`start + 1
(lldb)


As you may see the problem arises during the process of deallocating the NSPersistentContainer, and specifically while the SQLite connection gets disconnected. I'd usually get this issue on thread 9.

I'm having trouble posting a screen capture of the backtrace on the forum …

DevForums doesn’t have direct support for posting images (r. 33172582). I generally recommend that you post a link to an image sharing site. That’ll require approval, but I can take care of that.

Normally that restriction is annoying but in the case of a backtrace the text is much more useful than a screen shot (-:

I'd usually get this issue on thread 9.

I’m confused by the above. Clearly the backtrace you posted is on the main thread (

UIApplicationMain
is there in frame 31). Are you saying that this backtrace isn’t indicative of how things normally work, and normally you’d break in
UIApplicationEndBackgroundTaskError
on some secondary thread?

Also, what version of iOS are you working with here?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi Eskimo thanks for the support. I didn't know there was this problem with posting images on the forum, I'm gald you'd clarified and I apologize for any inconvinece I might have caused trying to post one.


Anyway, let's try to sort things out.


Infos you are requesting:

1) Yes I'd usually get the exception while work is being done on thread 9 (anyway not on the main thread) and also it isn't work I've ever scheduled in the background by using beginBackgroundTaskWithExpirationHandler: (I cannot even use that part of UIApplication API in an extension…).

2) I'm targetting iOS 10.3 and now testing on iOS 11 simulator devices


If you look at the screenshot of the backtrace, the exception is happening while work is being done on a thread different from the main one, specifically on a serial queue (myAppName.sqlite).


In my understanding that's because some of the work NSPersistentContainer does in the process of deallocation gets scheduled in the background.


I am not using in any part of my Today Widget code a call to schedule a backgorund task with beginBackgroundTaskWithExpirationHandler:


I'm not even allowed and supposed to do it in an extension, but rather I can use (and I'm using) the NSProcessInfo API to extend the lifetime of the extension; which is what I'm doing by following what is clearly stated in the documentation:

"To extend the execution time of an app extension, use the

performExpiringActivityWithReason:usingBlock:
method of
NSProcessInfo
instead."


As I stated in my initial request, I was able to completely get rid of this problem by setting to nil the NSPersistentContainer instance in viewDidDisappear: it looks like in this way the extension lifetime will be long enough to let the deallocation process of the NSPersistentContainer instance end in the background.


Now, regarding that particular implementation for deallocating the NSPersistentContainer instance I was using before, I understand it was dirty trick, but it was the only way I've found so far to not get that exception, by "buying more lifetime for the extension" to let NSPersistentContainer do its deallocation work in the background.


Anyway, if there is no way to solve this issue, I'll just stick to deallocating the NSPersistentContainer in viewDidDisappear:, not take advantage of NCWidgetProviding's widgetPerformUpdateWithCompletionHandler: and re-instantiate every time the NSPersistentContainer instance by letting it fetch again in viewWillAppear: or I could not rely on NSPersistentContainer in my Today Widget and implement a simple Core Data stack.

I am not using in any part of my Today Widget code a call to schedule a backgorund task with

beginBackgroundTaskWithExpirationHandler:

Indeed. In fact, as you’ve already noted, you can’t do this because it’s not API that’s exposed to an app extension. This background task is clearly being started and ended within Core Data, and specifically the Core Data SQLite subsystem. The fact that it’s getting this log message (indicating that it’s either ending a non-existant background task or ending the same background task twice) makes me very suspicious that there’s a bug in its background task handling code.

I’d love to drive this to a conclusion in this context but, alas, I just don’t have the time. So, I have a couple of alternatives here:

  • You could open a DTS tech support incident to see if one of DTS’s Core Data experts can help you figure this out.

  • You could file a bug about your current problem and switch to the

    -viewDidDisappear:
    /
    -viewWillAppear:
    approach.

If you do the latter, please post your bug number, just for the record.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi Eskimo,

thanks a lot again for your support.

Since I don't think I can get the DTS tech support, not having yet paid my fee as developer (unfortunately I've just lost my job, and I'm avoiding in this period expeses untill I get —hopefully— this situation sorted out in upcming weeks), I'm going to fix the issue in my Today Widget Extension by resorting to that approach I've mentioned (and maybe find also an alternative for still taking advantage of NCWidgetProviding protocol for background updates… I'm already working on that). I'll also file the bug too ASAP.


Have a good one, and thanks again!

Update: I've filed the bug (#34693522). Also today while testing again for letting the Excpetion being triggered, it was always happening on the main thread rather than on a secondary thread as it was happening before.

I confirm that starting the deallocation of the NSPersistentContainer instance by setting it to nil in the viewDidDisappear: method of the Today Widget Extension ViewController gives enough time to the deallocation process of NSPersistentContainer to complete withouth triggering the UIApplicationEndBackgroundTaskError() exception.


I hope it might help also someone else who's gonna use NSPersistentContainer as Core Data stack in his/her project.


Thanks again Eskimo!

NSPersistentContainer deallocation in iOS Today Widget Extension
 
 
Q