0xdead10cc prevention

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!

Accepted Reply

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

Replies

I forgot the link to TN2151: https://developer.apple.com/library/archive/technotes/tn2151/_index.html

  • This link is dead. I'm quite shocked. Where can we find the text of TN2151 now? Where can we send users for reference?

    Please someone put this tech note somewhere online.

    Please someone in charge makes sure such important information is not erased from internet.

    Please someone stops considering documentation as radioactive waste.

    :-(

  • Just look how embarrassed I am when I try to help a user who is facing 0xdead10cc on watchOS: https://github.com/groue/GRDB.swift/issues/998, without any clear reference to show. Come on, seriously, when documentation is erased and links are broken, it has consequences on people. Even eskimo's post about background tasks has moved (just see the next post)! What a sick joke.

Add a Comment

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

No. Speaking personally, I think that’d be really helpful, and I encourage you to file an enhancement request outlining your requirements, but right now you have to track each resume-in-the-background source independently.

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?

I suspect I’m misunderstanding something here but here goes… If you start a

UIApplication
background task to cover your work, you won’t be suspended until that task either completes or expires. What else do you need?

Do you have any other advice regarding 0xdead10cc prevention?

Not necessarily, but I have a lot to say about

UIApplication
background tasks (-:

For the context, I'm the author of GRDB

Cool. Nice to see you in a different context (-:

Share and Enjoy

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

let myEmail = "eskimo" + "1" + "@apple.com"
  • Eskimo's post about background tasks in now available at https://developer.apple.com/forums/thread/85066

Add a Comment

Thanks for your anwer Eskimo :-) I'm also happy to meet you in your favorite forums!


>> Is there one way to be notified of this transition from suspended to background state?

> No [...] right now you have to track each resume-in-the-background source independently.


Great, that's one less question :-)


> I suspect I’m misunderstanding something here but here goes… If you start a

UIApplication
background task to cover your work, you won’t be suspended until that task either completes or expires. What else do you need?


After background task has expired, I need to know 1. when we're back from suspended state (answered in the previous question) and 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).


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. In this case, they do not call their expiration handler. There is no API which is able to reliably tell "you're in the danger zone". There are only APIs that reliably tell "you've transitioned from the safe zone to the danger zone". And that's quite different.


When there is no API, we call documentation, experience, and knowledgable people to the rescue ;-) 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? If the answer is yes, I'm happy. If it is no, then... I'll have to let some 0xdead10cc crashes pass, or spend an insane amount of time testing each one of the "resume-in-the-background source", on various iOS versions, until I can document to the GRDB users when they can "relax" database lock prevention or not (of course, this is a joke, because I can not physically perform those tests).

I'm really afraid it's difficult to understand why there is a problem.


I tried to explain again there:


> Welcome to the world of modern applications.

>

> After a background task has expired, the application is not suspended yet. Many things can happen until it gets suspended for good. Core Location can notify a location update. An application timer can fire. A network request can end. All of those events may trigger a database access.

>

> And if we would start a background task then, we would not be notified of its expiration, because it was started too late. Yes, that means 0xdead10cc.

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

Thank you very much Kevin! I have no question left, and I can proceed on using all this precious information :-) And kudos for Eskimo if he brought you here ;-)


I guess the Core Data team had the same questions for its SQLite store. And since Core Data-powered applications don't have to perform any specific handling in their background callbacks, I think there is still some information to gather. But right now I have one clear path that avoids 0xdead10cc, and that was my main concern.

> I guess the Core Data team had the same questions for its SQLite store. And since Core Data-powered applications don't have to perform any specific handling in their background callbacks, I think there is still some information to gather.


Well... Core Data is not immune against 0xdead10cc: https://blog.iconfactory.com/2019/08/the-curious-case-of-the-core-data-crash/


Thanks to all the information gathered in this thread, I think we'll be able to make GRDB better :-)

This link is dead.

TN2151 was replaced by a suite of articles. The redirect points you to the root article, Diagnosing Issues Using Crash Reports and Device Logs, because there’s no way to know which part of TN2151 you were interested in. That root article contains a link to Understanding the Exception Types in a Crash Report, which has the info for the 0xdead10cc exception.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I spent a day experimenting with 0xdead100c issues in my app, and managed to figure out a couple of things by trial and error:

  • It's triggered not only by holding a SQLite transaction during suspension, but also by holding an un-finalized prepared statement during suspension. So if you cache prepared statements, you need to flush and disable the cache when the app is suspended.
  • You can reproduce it in debug on a device. It shows up in the console log as "Message from debugger: Terminated due to signal 9"

I've filed FB12161496 for more clarity in documentation and tooling around this issue.