Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] EXC_BAD_ACCESS when called when app is in background using BGAppRefreshTask #631

Open
nbsoftware opened this issue Dec 5, 2020 · 8 comments
Labels

Comments

@nbsoftware
Copy link

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.

@nbsoftware nbsoftware added the Bug label Dec 5, 2020
@nbsoftware
Copy link
Author

Could it be the same issue as #567 ?

@rdavisau
Copy link
Member

rdavisau commented Dec 6, 2020

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.

@nbsoftware
Copy link
Author

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.

@glennawatson
Copy link
Contributor

If you look at the stack trace it's not akavache but sqlite3 btw.

@rdavisau
Copy link
Member

rdavisau commented Dec 6, 2020

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)

@nbsoftware
Copy link
Author

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

@rdavisau
Copy link
Member

rdavisau commented Dec 7, 2020

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.

@glennawatson
Copy link
Contributor

If either of you end up coding a solution if you want to submit a pr to the readme I can merge it in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants