Hello,
Technical Note TN2151 describes the 0xdead10cc exception:
> The exception code
0xdead10cc
indicates that an application has been terminated by the OS because it held on to a file lock or sqlite database lock during suspension. If your application is performing operations on a locked file or sqlite database at suspension time, it must request additional background execution time to complete those operations and relinquish the lock before suspending.This is all pretty clear. After the additional background execution time has expired, all SQLite accesses that create a lock create a risk for sudden termination. An application which wants to avoid 0xdead10cc has thus to actively prevent such accesses... until the application wakes up from the suspended state. My questions are all about the end of this critical lapse of time in the app's life cycle.
Eventually, the application may become active, and this clearly marks the end of the 0xdead10cc danger. This moment is notified by
UIApplicationWillEnterForegroundNotification
. After this notification is posted, it becomes possible again to access an SQLite database without risking any 0xdead10cc exception, until the next UIApplicationDidEnterBackgroundNotification
, and the next notification of additional background execution time expiration.But what about cases when the application leaves the suspended state and reenters the background state? This may happen due to the various background modes supported by iOS.
First question: Is there one way to be notified of this transition from suspended to background state, regardless of the reason for this background wake-up (core location, VoIP, background fetch, etc.) ?
Second question: Is it then possible to start another request for additional background execution time and be reliably notified when accessing the SQLite database creates a 0xdead10cc threat again?
Third question: do you have any other advice regarding 0xdead10cc prevention?
For the context, I'm the author of GRDB, and I'm looking for a way to package 0xdead10cc prevention in a way that reduces to a minimum the amount of code users have to write in order to protect their app from this exception. Ideally, it would be reduced to a simple boolean flag targetted at users who store their database in an App Group container.
Thank you very much!
2. if it is possible, at that moment, to start another background task which will reliably notify its expiration = the beginning of new threat of 0xdead10cc = the beginning of a new lapse of database lock prevention (instead of database lock regrets).
Yes, it is possible. More specifically, an app can basically start a background task anytime it's being kept active by some other background tasks. Strictly speaking you could have a problem if an app was unsuspended by the system and that assertion was IMMEDIATELY ended, but in practice the actual cycle is long enough that this doesn't really happen*. However, it does lead to this problem:
The "problem" with both beginBackgroundTask(expirationHandler:) and performExpiringActivity(withReason:using:) is that none of them tell when they are called improperly, when it is no longer time to call them because app has entered its last seconds before being suspended again.
Not exactly. The real problem here is basically that the ONLY time it's truly safe to start a background task is in EXACTLY the same runloop cycle as your app was woken by the system. In any other situation (for example, in a different thread or at some later point after you were woken), there isn't any way to guarantee that your app hasn't already started to suspend.
My last question: from each of the "resume-in-the-background source", are we sure it is possible to begin a new background task and be notified of its expiration?
Yes** but again, ONLY in the callback your app receives once it's woken by the relevant system component. Once you've returned from that callback, the exact behavior depends on exactly what background subsystem you're working with. More to the point, even if it did work in a particular case there isn't any guarantee that it would continue to work in the future, since the behavior really depends on the exact dynamics of the underlying API.
*What's actually going on here is that the underlying SPIs that manage this are all async, so even if the subsystem ends it's assertion as soon as you return from it's callback, your app would still be awake for a short window as that "end" was processed.
**The point above is actuallly why this always works. In the simplest case, you can think of our background APIs doing this:
1) beginBackgroundTask
2) Call app callback
3) endBackgroundTask
If your app calls "beingBackgroundTask" inside #2, then that request gets cued ahead of #3, so it will always succeeds.
-Kevin Elliott
DTS Core OS/Hardware