SQLite Databases in App Group Containers (Don’t)
Brent Simmons (2020):
The crash logs were not identical, but they had this same thing:
Namespace RUNNINGBOARD, Code 0xdead10ccThis meant that the system was killing the app because, after the background task was complete, the app still had references to a SQLite database (or sometimes another file).
0xdead10cc
(3735883980
) — pronounced “dead lock”The operating system terminated the app because it held on to a file lock or SQLite database lock during suspension. Request additional background execution time on the main thread with
beginBackgroundTask(withName:expirationHandler:)
. Make this request well before starting to write to the file in order to complete those operations and relinquish the lock before the app suspends. In an app extension, usebeginActivity(options:reason:)
to manage this work.
Don’t keep the SQLite database in the shared container. You’ll never get rid of all of those crashes. Instead, communicate with extensions via other means than having them read/write the DB directly, such as Darwin notifications or writing plist files in the shared container.
[…]
I tried wrapping every SQLite query in a background task once to avoid this. A standard Overcast session may issue hundreds or thousands of database queries. I later found that apparently each one generates a process power assertion, the OS wasn’t made for that level of usage, and after some time, Springboard would crash.
There’s also the NSProcessInfo background-task thing that allegedly works in extensions, except that it doesn’t.
GRDB:
This guide describes a recommended setup that applies as soon as several processes want to access the same SQLite database. It complements the Concurrency guide, that you should read first.
On iOS for example, you can share database files between multiple processes by storing them in an App Group Container.
[…]
Post
suspendNotification
when the application is about to be suspended.[…]
Once suspended, a database won’t acquire any new lock that could cause the
0xDEAD10CC
exception.In exchange, you will get
SQLITE_INTERRUPT
(code 9) orSQLITE_ABORT
(code 4) errors, with messages “Database is suspended”, “Transaction was aborted”, or “interrupted”.
One of the difficulties lies in the OS apis, in order to detect when the app is about to get suspended, and when the app becomes able to acquire database locks again. See this forum thread with @justkwin.
The other difficulty is that if the app is writing (holding a lock) at the moment it is notified it will get suspended, then all it can do is aborting the write.
It’s a common problem developers face when building iOS apps: you have a SQLite database, possibly backed by Core Data, and you want to use it to support features in your app extensions, such as widgets and intents.
The problem has a common and seemingly simple answer: just move the database to an App Group container! It’s relatively straightforward, with plenty of StackOverflow answers and YouTube tutorials on the matter.
[…]
These mitigations may sound reasonable at first, but implementing all of them correctly and consistently is surprisingly difficult—especially for database-heavy apps like Foodnoms. Trust me—I’ve tried.
Beyond the complexity, you also lose flexibility with background execution. For example, features like HealthKit background delivery become harder to support reliably.
[…]
Instead of moving your database to a group container, use these techniques instead and keep the database inside your app’s documents directory.
He suggests writing data to JSON files and using multiple copies of the same intent. These seem like caveman solutions. iPhones now have super fast processors and more RAM than Macs did when the iPhone was first released. How many more years before they support a less brittle process model?
Previously:
Update (2025-05-15): Paulo Andrade:
I believe requiring app extensions to be a separate process to have been a bad decision from the get go. And there’s no excuse for it now. The fact the new App Intents can be handled in the main app is kind of proves my point. App extensions could just be a new window on the main app’s process.