Akavache
Akavache copied to clipboard
[BUG] EXC_BAD_ACCESS when called when app is in background using BGAppRefreshTask
I implemented Akavache for storing many user & app related data locally. This works well overall.
But it seems there is a scenario where the app regularly crashes (but not systematically).
I implemented the iOS14 BGAppRefreshTask, which works ok. The task itself fetches some small data over the network and then saves some small amount of data using Akavache.
But sometimes, when the system executes the Task while the app is in background, the network call executes but then the app crashes, and it seems Akavache to be the culprit (because of the crashlog and also because I can see after relaunching the app that the data hasn't been saved as expected).
See the crash logs:
Exception Type: EXC_BAD_ACCESS (SIGBUS)
Exception Subtype: KERN_MEMORY_ERROR at 0x000000011e338000
VM Region Info: 0x11e338000 is in 0x11e338000-0x11e340000; bytes after start: 0 bytes before end: 32767
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
MALLOC_LARGE 11e330000-11e338000 [ 32K] rw-/rwx SM=PRV
---> mapped file 11e338000-11e340000 [ 32K] rw-/rw- SM=PRV ...t_id=2e2ff72f
MALLOC_LARGE 11e340000-11e358000 [ 96K] rw-/rwx SM=PRV
Triggered by Thread: 4
...
Thread 4 name: Thread Pool Worker
Thread 4 Crashed:
0 fipacapp.iOS 0x00000001046ddb88 walIndexTryHdr + 36
1 fipacapp.iOS 0x00000001046dd154 walIndexReadHdr + 164
2 fipacapp.iOS 0x00000001046dd154 walIndexReadHdr + 164
3 fipacapp.iOS 0x00000001046dca28 walTryBeginRead + 556
4 fipacapp.iOS 0x00000001046e3898 sqlite3PagerSharedLock + 188
5 fipacapp.iOS 0x00000001046bb068 sqlite3BtreeBeginTrans + 556
6 fipacapp.iOS 0x00000001046ef278 sqlite3VdbeExec + 4216
7 fipacapp.iOS 0x00000001046bef78 sqlite3_step + 296
8 fipacapp.iOS 0x000000010ae9eba8 wrapper_managed_to_native_SQLitePCL_SQLite3Provider_internal_NativeMethods_sqlite3_step_SQLitePCL_sqlite3_stmt + 109210536 (/<unknown>:1)
9 fipacapp.iOS 0x000000010ae84620 SQLitePCL_SQLite3Provider_internal_SQLitePCL_ISQLite3Provider_sqlite3_step_SQLitePCL_sqlite3_stmt + 109102624 (/<unknown>:1)
10 fipacapp.iOS 0x000000010ae47f18 SQLitePCL_raw_sqlite3_step_SQLitePCL_sqlite3_stmt + 108855064 (/<unknown>:1)
11 fipacapp.iOS 0x000000010ad93e1c Akavache_Sqlite3_BulkInsertSqliteOperation__c__DisplayClass7_0__PrepareToExecuteb__0 + 108117532 (/D:\a\Akavache\Akavache\src\Akavache.Sqlite3\Operations\BulkInsertSqliteOperation.cs:61)
12 fipacapp.iOS 0x000000010ad6441c Akavache_Sqlite3_SqliteOperationQueue_MarshalCompletion_object_System_Action_System_IObservable_1_System_Reactive_Unit + 107922460 (/D:\a\Akavache\Akavache\src\Akavache.Sqlite3\Queues\OperationQueue.cs:395)
13 fipacapp.iOS 0x000000010ad64e3c Akavache_Sqlite3_SqliteOperationQueue_ProcessItems_System_Collections_Generic_List_1_Akavache_Sqlite3_OperationQueueItem + 107925052 (/D:\a\Akavache\Akavache\src\Akavache.Sqlite3\Queues\OperationQueue.cs:436)
14 fipacapp.iOS 0x000000010ada69a4 Akavache_Sqlite3_SqliteOperationQueue__c__DisplayClass18_0___Startb__0d_MoveNext + 108194212 (/D:\a\Akavache\Akavache\src\Akavache.Sqlite3\Queues\OperationQueue.cs:157)
15 fipacapp.iOS 0x000000010adf2694 System_Runtime_CompilerServices_AsyncTaskMethodBuilder_Start_Akavache_Sqlite3_SqliteOperationQueue__c__DisplayClass18_0___Startb__0d_Akavache_Sqlite3_SqliteOperationQueue__c__DisplayClass18_0___Startb__0d_ + 108504724 (AsyncMethodBuilder.cs:317)
16 fipacapp.iOS 0x000000010ad9628c Akavache_Sqlite3_SqliteOperationQueue__c__DisplayClass18_0__Startb__0 + 284
17 fipacapp.iOS 0x0000000104aed528 System_Threading_Tasks_Task_1_TResult_REF_InnerInvoke + 88
18 fipacapp.iOS 0x0000000104af9564 System_Threading_Tasks_Task_Execute + 36
19 fipacapp.iOS 0x0000000104af97d8 System_Threading_Tasks_Task_ExecutionContextCallback_object + 88
20 fipacapp.iOS 0x0000000104ac205c System_Threading_ExecutionContext_RunInternal_System_Threading_ExecutionContext_System_Threading_ContextCallback_object_bool + 428
21 fipacapp.iOS 0x0000000104ac1e5c System_Threading_ExecutionContext_Run_System_Threading_ExecutionContext_System_Threading_ContextCallback_object_bool + 44
22 fipacapp.iOS 0x0000000104affab8 System_Threading_Tasks_Task_ExecuteWithThreadLocal_System_Threading_Tasks_Task_ + 296
23 fipacapp.iOS 0x0000000104af96f0 System_Threading_Tasks_Task_ExecuteEntry_bool + 272
24 fipacapp.iOS 0x0000000104af95c8 System_Threading_Tasks_Task_System_Threading_IThreadPoolWorkItem_ExecuteWorkItem + 24
25 fipacapp.iOS 0x0000000104acbdb8 System_Threading_ThreadPoolWorkQueue_Dispatch + 488
26 fipacapp.iOS 0x0000000106998940 ObjCRuntime_Runtime_ThreadPoolDispatcher_System_Func_1_bool + 36833600 (Runtime.cs:289)
27 fipacapp.iOS 0x0000000104acde78 System_Threading__ThreadPoolWaitCallback_PerformWaitCallback + 136
28 fipacapp.iOS 0x00000001051bcf10 wrapper_runtime_invoke_object_runtime_invoke_dynamic_intptr_intptr_intptr_intptr + 272
29 fipacapp.iOS 0x000000010b2c95e0 mono_jit_runtime_invoke + 113579488 (mini-runtime.c:3165)
30 fipacapp.iOS 0x000000010b3865c0 mono_runtime_try_invoke + 114353600 (object.c:3161)
31 fipacapp.iOS 0x000000010b3c9c0c worker_callback + 114629644 (threadpool.c:386)
32 fipacapp.iOS 0x000000010b3c74e8 worker_thread + 114619624 (threadpool-worker-default.c:476)
33 fipacapp.iOS 0x000000010b3d2c80 start_wrapper_internal + 114666624 (threads.c:1288)
34 fipacapp.iOS 0x000000010b3d2b04 start_wrapper + 114666244 (threads.c:1309)
35 libsystem_pthread.dylib 0x00000001f8df2b40 _pthread_start + 320
36 libsystem_pthread.dylib 0x00000001f8dfb768 thread_start + 8
Environment
- OS: iOS 14.2
- Device: iPhone XS Max
When I execute the exact same task while the app is active, it works 100% of the time flawlessly, with the exact same code.
Does this crashlog give any clue about something going wrong?
Thank you.
Could it be the same issue as https://github.com/reactiveui/Akavache/issues/567 ?
Do you have the Data Protection entitlement set to something strict (e.g. NSFileProtectionComplete or NSFileProtectionCompleteUnlessOpen)? If your sqlite (Akavache) files are covered by data protection and your background task calls into Akavache after the files have made inaccessible (typically, 10 seconds after the device is locked unless it is plugged into a 'trusted computer'), I'd expect to see an error like the above.
Ouch... good hint @rdavisau
I completely forgot about this entitlement, and yes it is indeed set to NSFileProtectionComplete.
Damn.
But then, why does it crash the app this way? Can't this condition be detected / caught somehow and a friendly error be returned to the calling api?
So changing this entitlement afterwards (once the app is already live) seems to be potentially problematic. cf https://pspdfkit.com/blog/2017/how-to-use-ios-data-protection/
As a matter of fact the introduction of Akavache hasn't been released yet (is about to be).
Would there be a way to instruct Akavache to use a specific NSFileProtection flag value as it seems that one can set this programmatically even if another value is set globally?
I've quickly search through Akavache API but haven't been able to find something suitable.
Not too sure what my best options are from there.
If you look at the stack trace it's not akavache but sqlite3 btw.
Yeah the error is in SQLite - and I wouldn't really consider it to be a bug for either SQLite or Akavache.
You can set the fileprotection flags for the SQLite files to be different from the rest of the app by using SetAttributes on NSFileManager - in your case you'd opt the SQLite files out. Just make sure you get all the files - besides the .db there are one or two others. I have done it in the past but unfortunately don't have code handy so you'll have to do a big of digging 📖
(Edit: obviously this means your cached data is not encrypted so think about whether that's OK for you. You can combine this with Akavache's own encryption support to maintain an encrypted cache without ios data protection)
Ok so after a bit of digging, I can see that the 3 files are (at least in my case):
userblobs.db
userblobs.db-shm
userblobs.db-wal
all stored at the following location: <AppRegistrationName>/BlobCache/
Looking through Akavache's source code, unless I'm missing something, I can see no way to instruct Akavache to create the db with specific NSFileProtection flags.
The SqlConnection is created as follows:
public SqlRawPersistentBlobCache(string databaseFile, IScheduler? scheduler = null)
{
Scheduler = scheduler ?? BlobCache.TaskpoolScheduler;
BlobCache.EnsureInitialized();
Connection = new SQLiteConnection(databaseFile, storeDateTimeAsTicks: true);
_initializer = Initialize();
}
and the SQLiteConnection constructor used is not the one where we could have passed such flags:
public SQLiteConnection(string databasePath, bool storeDateTimeAsTicks = false)
: this(databasePath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, storeDateTimeAsTicks)
{
}
The only ones passed are SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create.
So I guess I'm out of luck trying to set those flags at creation time / Akavache's initialisation time.
Which seems to leave me with at least 2 options:
- hardcode the path of the 3 files and set the appropriate NSFileProtection flags after db creation. Will that ever work as intended once the files have already been created?
- hardcode the root folder's path and set the appropriate NSFileProtection flags to the root folder (i.e.
<AppRegistrationName>/BlobCache/) before Akavache registration... as this link suggest it might be the right thing to do.
Any thoughts / advice?
Thanks
Yep those files match with what I remember.
I think either option is reasonable, although in the first case I would hardcode just the root folder and iterate the files underneath to apply the flags to. That's what I've done successfully in the past.
If either of you end up coding a solution if you want to submit a pr to the readme I can merge it in.