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

closing FMDB database #848

Open
KodaKoder opened this issue Sep 6, 2023 · 11 comments
Open

closing FMDB database #848

KodaKoder opened this issue Sep 6, 2023 · 11 comments

Comments

@KodaKoder
Copy link

KodaKoder commented Sep 6, 2023

do I have to close the database when the app is sent to background or I can keep it open?
Thanks

@ccgus
Copy link
Owner

ccgus commented Sep 6, 2023

I'm not sure what the policies of iOS would be concerning this. Maybe close it just to be safe, so in case the disk encryption kicks in or something you won't have a handle later on that's not in good working order?

@KodaKoder
Copy link
Author

KodaKoder commented Sep 6, 2023

I have this crash 0xdead10cc.
I close the fmdb on func applicationDidEnterBackground(_ application: UIApplication)

https://developer.apple.com/forums/thread/126438

I was wondering if is necessary or I have to 'sql interrupt' what the transaction is doing before iOS system kills the app

@ccgus
Copy link
Owner

ccgus commented Sep 6, 2023

This is beyond my expertise (I'm not an iOS coder). So I'd just say do whatever the Apple person is recommending in that post.

@robertmryan
Copy link
Collaborator

robertmryan commented Sep 6, 2023

FWIW, 0xdead10cc means:

0xdead10cc (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, use beginActivity(options:reason:) to manage this work.

So, there are one of two possibilities. Either:

  1. You have an update to your database that is simply still underway as the user suspends the app. In that case, use the beginBackgroundTask(withName:expirationHandler:) pattern.

    This is likely to be the case if you find that this 0xdead10cc is intermittent (i.e., it doesn't crash if you let the app reach quiescence, but does crash if you start some lengthy database update and quickly leave the app while it is still underway … note, do not do this test while attached to the Xcode debugger, as that artificially changes the app lifecycle, but rather test while running the app outside the Xcode environment).

    Personally, I wrap all of my slow database updates (and network POST/PUT requests) in these background tasks for this reason. (Obviously, I also look for opportunities to reduce this time, for example using transactions to speed up the process where practical … updating lots of records goes much more quickly with transactions.) But we always want to give the app a chance to finish up what it started. This background task technique results in the OS giving your app 30 seconds to finish up whatever it was doing. If you need more than that, then other patterns might be needed (e.g. BGProcessingTask, etc.).

  2. You have a previously undetected, actual deadlock somewhere in your code (e.g., you dispatch to the FMDatabaseQueue from within the same queue, or whatever).

    In general, if the app always generates this 0xdead10cc even when you think the app has had a chance to finish what database updates it started, then there might be a real deadlock hiding in the code and the background task approach (discussed above) will not be able to solve that.

    If that is the case, then it probably will entail adding some instrumentation (e.g., “points of interest” intervals or other debugging statements) to identify the source of the problem. Or you can run the app, let it get to a point of quiescence, and then pause execution in the debugger and look at the stack traces for the various threads. Either way, the problem should jump out at you.

Bottom line, there are multiple sources of this problem and each has its own solution.

@KodaKoder
Copy link
Author

KodaKoder commented Sep 6, 2023

the function is the main refresh function of the app, on a background fetch event I trigger it so I'm not that sure there is an hang or the users would experience it also in other cases, and I have no hang reports in Xcode

I have some crash reports instead on:

- (void)close {
    FMDBRetain(self);
    dispatch_sync(_queue, ^() {
        [self->_db close]; <---------------- //HERE
        FMDBRelease(_db);
        self->_db = 0x00;
    });
    FMDBRelease(self);
}
Screenshot 2023-09-06 at 19 51 11

that point of the stack is out of my control, what could hang in LeaveMutexandCloseZombie? another sql queue on the same file?

should I try to use sqlite3_close_v2?

Thank for the detailed response
Giovanni

@robertmryan
Copy link
Collaborator

No, sqlite3_close_v2 is definitely not a good idea. (You’re not doing sqlite3_xxx calls anywhere in your code, are you? The whole point is that FMDB is your wrapper and you should avoid ever directly interfacing with SQLite.)

IMHO, it is exceedingly unlikely that the problem rests in FMDB. The problem is undoubtedly how you are using it. Like I said, the problem is likely that the FMDatabaseQueue is blocked/busy (or if you're not using FMDatabaseQueue, that you have some FMDatabase queries in-flight).

I have no hang reports in Xcode

I’m not entirely surprised by that. When SQLite is busy or a GCD queue is blocked, that won't generally result in Xcode warnings. Instruments is a little better in this regard, but not even that will always show it. Besides, as I said, you cannot test this lifecycle stuff while attached to the Xcode debugger anyway.

I'd suggest you post a MRE. Note, we don’t want to see your whole project, but create a small, standalone example that manifests the problem in question. There is simply not enough here for us to diagnose the problem.

But do I gather that you are reporting an intermittent crash? That suggests option 1, above, with beginBackgroundTask(withName:expirationHandler:) approach. So, have you done that already?

@KodaKoder
Copy link
Author

KodaKoder commented Sep 8, 2023

I wrapped the close procedure function in a background task to see if it solves the problem.

Checking my code I found that I open 2 FMDB queue on the same file 1 dedicated to read the other to write
could be this the problem? When I close both the dbqueue, sometimes, the second queue hangs

@ccgus
Copy link
Owner

ccgus commented Sep 8, 2023

You should only have one queue open. Two queues open will possibly lead to a deadlock.

@KodaKoder
Copy link
Author

KodaKoder commented Sep 8, 2023

the idea was to have 2 FMDBQueue instances (1reader/1writer) on the same file.
So the first one can read even the second is writing without blocking the gui.
I'm trying to use the concurrency features of the sqlite WAL

2.2. Concurrency
When a read operation begins on a WAL-mode database, it first remembers the location of the last valid commit record in the WAL. Call this point the "end mark". Because the WAL can be growing and adding new commit records while various readers connect to the database, each reader can potentially have its own end mark. But for any particular reader, the end mark is unchanged for the duration of the transaction, thus ensuring that a single read transaction only sees the database content as it existed at a single point in time.

....

Writers merely append new content to the end of the WAL file. Because writers do nothing that would interfere with the actions of readers, writers and readers can run at the same time. However, since there is only one WAL file, there can only be one writer at a time.

[WAL](https://www.sqlite.org/wal.html)

If I use the FMDBQueue methods I don't see how they can hang they are processed in separate dispatch_queue and sqlite seems built to manage the internal concurrency

@robertmryan
Copy link
Collaborator

The crash report is simply telling you that the SQLite database (or some other file) is open when the app suspends. End of story.

So, it strikes me that you first have to determine whether it is really deadlocking, or whether it’s just slow and is still in progress when the app suspends. (Given that the problem is intermittent, I bet it is not really deadlocking, but is just slow.) Some logging statements would help here. (And if you want to watch these logging statements while running the app while not connected to the Xcode debugger, rather than using print, you might use Logger or os_log and watch the logging messages from the Mac Console app while you run the app. See https://stackoverflow.com/a/25951564/1271826 for discussion of the various logging options.)

And if it’s just slow, the next question is whether it is taking more time than beginBackgroundTask affords you (i.e., roughly 30 seconds, but check backgroundTimeRemaining). And if it is slower than even that beginBackgroundTask provides, then figure out whether you can speed it up. (If doing lots of updates/inserts, transactions can have a remarkable impact on the performance. It also ensures that the database won’t be corrupted if terminated in the middle of some update.) Note, speeding it up only reduces the chance that the problem will manifest itself; you will still want to use beginBackgroundTask.

But there simply is not enough here for us to help you further. We need more diagnostic information (or a MRE).


By the way, you say that you’re closing the database in applicationDidEnterBackground. That probably is not the right place to beginBackgroundTask/endBackgroundTask. As the documentation says:

If you need additional time to perform any final tasks, request additional execution time from the system by calling beginBackgroundTask(expirationHandler:). Call beginBackgroundTask(expirationHandler:) as early as possible. Because the system needs time to process your request, there’s a chance that the system might suspend your app before that task assertion is granted. For example, don’t call beginBackgroundTask(expirationHandler:) at the very end of your applicationDidEnterBackground(_:) method and expect your app to continue running.

Personally, I never start and end background tasks in applicationDidEnterBackground. I do that where I start the time-consuming work (e.g., start the background task it when I begin the update transaction, and end the background task when that transaction is done). If the app is not being backgrounded, it has no impact, but it protects you in case the app happens to be suspended while the update is in progress.

@KodaKoder
Copy link
Author

KodaKoder commented Sep 8, 2023

Thanks :)
I guess is "just slow and is still in progress when the app suspends"

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

No branches or pull requests

3 participants