Merge remote-tracking branch 'origin/main' into fix-ink-sparkle-remaining-tests
This commit is contained in:
+27
-10
@@ -7,6 +7,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sharedinbox/core/db_schema_version.dart';
|
||||
import 'package:sqlite3/sqlite3.dart' show Database;
|
||||
|
||||
part 'database.g.dart';
|
||||
|
||||
@@ -793,18 +794,34 @@ Future<String> resolveDatabasePathForTesting() => _resolveDatabasePath();
|
||||
void resetDatabasePathForTesting() => _dbPath = null;
|
||||
Future<String?> androidFallbackPathForTesting() => _androidFallbackPath();
|
||||
|
||||
/// Configures PRAGMAs on a newly opened SQLite connection.
|
||||
///
|
||||
/// busy_timeout must come first so subsequent statements retry on SQLITE_BUSY
|
||||
/// instead of immediately failing.
|
||||
///
|
||||
/// journal_mode = WAL is wrapped in a try/catch because a concurrent
|
||||
/// WorkManager background task may already have the DB open when the app
|
||||
/// starts. SQLITE_BUSY_SNAPSHOT (extended code 261, primary code 5) is
|
||||
/// returned in that situation; it only occurs when the DB is already in WAL
|
||||
/// mode, so the pragma would be a no-op anyway and it is safe to continue.
|
||||
void _setupPragmas(Database db) {
|
||||
db.execute('PRAGMA busy_timeout = 5000;');
|
||||
try {
|
||||
db.execute('PRAGMA journal_mode = WAL;');
|
||||
} on SqliteException catch (e) {
|
||||
// resultCode strips the extended bits: both SQLITE_BUSY (5) and
|
||||
// SQLITE_BUSY_SNAPSHOT (261) reduce to 5. Re-throw anything else.
|
||||
if (e.resultCode != 5) rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
LazyDatabase _openConnection() {
|
||||
return LazyDatabase(() async {
|
||||
final file = File(await _resolveDatabasePath());
|
||||
return NativeDatabase.createInBackground(
|
||||
file,
|
||||
setup: (db) {
|
||||
// WAL lets readers and writers proceed concurrently (different account
|
||||
// sync loops share the same DB). busy_timeout makes SQLite retry for
|
||||
// up to 5 s instead of immediately returning SQLITE_BUSY.
|
||||
db.execute('PRAGMA journal_mode = WAL;');
|
||||
db.execute('PRAGMA busy_timeout = 5000;');
|
||||
},
|
||||
);
|
||||
return NativeDatabase.createInBackground(file, setup: _setupPragmas);
|
||||
});
|
||||
}
|
||||
|
||||
// Exposed so tests can run the exact production setup logic on a raw
|
||||
// sqlite3 connection (same pattern as resolveDatabasePathForTesting).
|
||||
void setupPragmasForTesting(Database db) => _setupPragmas(db);
|
||||
|
||||
+1
-1
@@ -19,6 +19,7 @@ dependencies:
|
||||
|
||||
# Local persistence (offline-first)
|
||||
drift: ^2.20.3
|
||||
sqlite3: ^3.1.5 # used directly in lib/data/db/database.dart (_setupPragmas)
|
||||
sqlite3_flutter_libs: ^0.6.0+eol
|
||||
path_provider: ^2.1.5
|
||||
path: ^1.9.1
|
||||
@@ -78,7 +79,6 @@ dev_dependencies:
|
||||
mockito: ^5.4.4
|
||||
fake_async: ^1.3.1
|
||||
path_provider_platform_interface: ^2.1.2
|
||||
sqlite3: ^3.1.5 # used directly in test/unit/db_test_helper.dart; 3.x required for Database.close()
|
||||
url_launcher_platform_interface: ^2.3.2
|
||||
plugin_platform_interface: ^2.1.8
|
||||
flutter_launcher_icons: ^0.14.0
|
||||
|
||||
@@ -510,4 +510,40 @@ void main() {
|
||||
await db.close();
|
||||
});
|
||||
});
|
||||
|
||||
// Regression test for https://codeberg.org/guettli/sharedinbox/issues/508:
|
||||
// _openConnection's setup callback must not crash when PRAGMA journal_mode =
|
||||
// WAL fails with SQLITE_BUSY_SNAPSHOT (extended code 261, primary code 5)
|
||||
// because a WorkManager background task already has the DB open in WAL mode.
|
||||
group('WAL setup (#508)', () {
|
||||
test(
|
||||
'setupPragmasForTesting does not throw when WAL is already active and '
|
||||
'another connection holds an open read transaction',
|
||||
() {
|
||||
final dbFile = File('test_wal_busy_508.db');
|
||||
if (dbFile.existsSync()) dbFile.deleteSync();
|
||||
addTearDown(() {
|
||||
if (dbFile.existsSync()) dbFile.deleteSync();
|
||||
});
|
||||
|
||||
// conn1: enable WAL and keep a read transaction open — simulates a
|
||||
// WorkManager background task that opened the DB before the foreground
|
||||
// app starts.
|
||||
final conn1 = sqlite.sqlite3.open(dbFile.path);
|
||||
conn1.execute('PRAGMA journal_mode = WAL;');
|
||||
conn1.execute('BEGIN;');
|
||||
conn1.select('SELECT 1;');
|
||||
|
||||
// conn2: run the exact production setup through setupPragmasForTesting.
|
||||
// This must not throw even though conn1 holds an open transaction and
|
||||
// the DB is already in WAL mode.
|
||||
final conn2 = sqlite.sqlite3.open(dbFile.path);
|
||||
expect(() => setupPragmasForTesting(conn2), returnsNormally);
|
||||
|
||||
conn1.execute('ROLLBACK;');
|
||||
conn1.close();
|
||||
conn2.close();
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user