-
Notifications
You must be signed in to change notification settings - Fork 378
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
Database is locked after hot restart and sometimes in production #835
Comments
Thanks for the report and for linking the original issue. It strongly sounds like this is due to multiple |
I am using get_it and the injector instance is empty after a hot-restart (everything works fine with hot-reload), Everything get's recreated. If there is a 2nd instance then I don't know where. I am aware of the multiple instances problem, however I don't think it is the case here. I am suspecting the production problem occurring due to the app being killed when another activity comes to foreground (camera), as this has so far only happened on low powered Android devices. |
I think I see the problem now. The Dart object gets destroyed of course, but we don't have a chance to ever call It sounds like we could use dart-lang/sdk#35770 to fix this, the docs say that
Is there a way for the Dart runtime to terminate without the entire app process terminating too? If the entire process goes away there shouldn't be an issue I think. |
That looks interesting. So you think the native instance ist still around while the Dart VM restarted. Is there a way to locate an existing native instance when the VM starts and possible reconnect/cleanup?
I have no idea, I will see check the next time it happens, need to see if there is some memory pressure before this occurs. |
Yes, exactly. We create a database connection by calling Leaking resources is bad enough, but when the underlying database is in a transaction, it blocks a second connection trying to write. I'm pretty sure that's what you're seeing here.
I'm not aware of any approach that would work in pure-Dart. Sqflite works around this by essentially storing these references in Java, so that they can survive Dart VM restarts. When that api is available, using finalizable handles to auto-dispose the native resources is probably the best approach here. I'll try to setup a small Flutter-specific package to implement the sqflite workaround for |
I found a way to do this in pure Dart. We can open a named in-memory database to keep track of open database connections! That database would have the same lifecycle as the problematic connections locking your actual app database. On the You could try out the void main() {
// Put this somewhere before you open your first VmDatabase
assert(() {
VmDatabase.closeExistingInstances();
return true;
}());
// remaining main
} It's very important to only call Alas, I hope we won't need this workaround for long. |
I'll try this tomorrow. |
This crashes on iOS 14 Simulator, I have not tested it on a real device, older iOS or Android yet.
|
…ackground of information here simolus3/drift#835 The solution from Simon Binder is simple and brilliant
@simolus3 This is brilliant! Simple et efficient. I copied your database_tracker to sqflite_common_ffi since it solves the same issue. I slightly adapt your solution: https://github.com/tekartik/sqflite/blob/master/sqflite_common_ffi/lib/src/database_tracker.dart
I have a manual test that was showing the issue (open, begin transaction, hot restart, open, begin transaction) which is now fixed. I will likely do something similar (close the database instead of trying to use a connection like i do in Android/iOS/MacOS) |
Great! And thanks for pointing me towards your initial workaround which I used for inspiration. Did you see the problem this caused on iOS? It might be related to sqlite re-using pointers where we might end up closing the same database twice. |
I only tested on Android and Linux. When a database is closed and another is opened, typically the pointer is the same (just saw this using print) |
@simolus3 Do you need more information on that iOS crash or do you already have a suspicion? |
I suspect that this would happen because we somehow close the same database more than once. So far I didn't have access to a Mac so I couldn't verify this though. If you want to help, adding a debug print here (something like |
It is always a different pointer and only gets called once. The app immediately crashes afterwards.
|
Same happens on a real iOS device. On Android this actually does not get executed or I can't get it to log.
|
Yeah I can reproduce this on Android too. The cached in-memory database used by the tracker seems to always be empty after a stateless restart, it doesn't even contain the table :/ So far I don't know why that happens, it doesn't seem like the VM would call Either way, that just makes it even weirder since I'd expect the real database to be closed too if in-memory databases vanish. Maybe the lock we're seeing here is file based and we can free it by deleting some files manually. |
I created a sqlite(sqlcipher actually) plugin using ffi, and I got the same issue, database get locked after hot-reload. What I don't understand is that, when I reload the app, there is no database operation existing, thus there should not be any transaction happening, why it's still get locked? |
any news on it? |
I tried VmDatabase.closeExistingInstances() on MainApp.initState(). It does not work properly on Android as written in method comment. Additional info about 'database is locked': #1042 (comment) |
When we kill our app. and restart. the app our database said
When we restart our device the database is readable again. For us it seems that the database file is not released when the app is killed. Anybody who has had the same issue? |
Hello, look like I hit this issue, for quite some time hot restart is broken and I never search for the reason ^^ but each time I try a hot restart on iOS Simulator, the app just close without any errors in the console. I search a bit and look like it crash when trying to open the DB, I'm using NativeDatabase on my side like this: LazyDatabase _openConnection(int shopId, PublishSubject<DatabaseConnectionEvent> status) {
// the LazyDatabase util lets us find the right location for the file async.
return LazyDatabase(() async {
// put the database file, called db.sqlite here, into the documents folder
// for your app.
final dbFolder = await getApplicationDocumentsDirectory();
// fix for https://github.com/simolus3/moor/issues/1061
await applyWorkaroundToOpenSqlite3OnOldAndroidVersions();
final file = File(path.join(dbFolder.path, 'db_${shopId}_${Config().env.flavor == EnvironmentType.prod ? 'prod' : 'staging'}.sqlite'));
if (!kIsProductionMode) {
kDebugLogger.info('database path is: ${file.path}');
}
return NativeDatabase(
file,
logStatements: !kIsProductionMode && kDatabaseDebug && Config().env.flavor != EnvironmentType.testing,
setup: (db) {
db.execute('PRAGMA foreign_keys = ON;');
status.add(DatabaseConnectionEvent.open);
},
);
});
} I've tried added same hack before launching the app: WidgetsFlutterBinding.ensureInitialized();
assert(
() {
NativeDatabase.closeExistingInstances();
return true;
}(),
'Close existing DBs',
); But app still crash after hot restart. I manage the get a crash report, here it is: iOS logs
PS: not crash on Android at all. |
I have not seen this problem occur in a long while in any of my projects. |
…ackground of information here simolus3/drift#835 The solution from Simon Binder is simple and brilliant
Yes I'm experiencing this in production |
@kfieldi5 A "file is not a database" error can also occur when using SQLCipher to open an encrypted database and providing the wrong key. Could you check whether it's possible that the key you used to create databases has changed between different versions of your app? You could also consider running a statement like setup: (rawDb) {
assert(_debugCheckHasCipher(rawDb));
rawDb.execute("PRAGMA key = '$dbPassphrase';");
// Make sure the database is readable after unlocking it:
rawDb.execute("SELECT * FROM sqlite_schema");
}, |
I am using a single instance of my database with
VmDatabase
as executor.When the app starts , data is loaded from remote and inserted into the database in several transactions.
If I hot restart the app during this time, there is a chance, that the database is locked after the restart.
This would be ok if it was only happening during development but I am seeing the same stacktraces in Sentry from production users, very rarely but it happens. I am not sure how that can happen.
I have a similar problem with Sembast using
sqflite_ffi
(tekartik/sembast_sqflite#5) which seems to have been solved by not using the ffi implementation.Since both use
sqlite3
, I wonder if it is related or if you have any idea what could prevent this.The text was updated successfully, but these errors were encountered: