Merge pull request #1961 from famedly/karthi/cache-clear-matrix-sdk-db

fix: clear cache when clearing DB in MatrixSdkDatabase
This commit is contained in:
Karthikeyan S 2025-01-07 16:39:48 +05:30 committed by GitHub
commit f2e2ee8daf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 81 additions and 55 deletions

View File

@ -100,27 +100,25 @@ class BoxCollection with ZoneTransactionMixin {
class Box<V> { class Box<V> {
final String name; final String name;
final BoxCollection boxCollection; final BoxCollection boxCollection;
final Map<String, V?> _cache = {}; final Map<String, V?> _quickAccessCache = {};
/// _cachedKeys is only used to make sure that if you fetch all keys from a /// _quickAccessCachedKeys is only used to make sure that if you fetch all keys from a
/// box, you do not need to have an expensive read operation twice. There is /// box, you do not need to have an expensive read operation twice. There is
/// no other usage for this at the moment. So the cache is never partial. /// no other usage for this at the moment. So the cache is never partial.
/// Once the keys are cached, they need to be updated when changed in put and /// Once the keys are cached, they need to be updated when changed in put and
/// delete* so that the cache does not become outdated. /// delete* so that the cache does not become outdated.
Set<String>? _cachedKeys; Set<String>? _quickAccessCachedKeys;
bool get _keysCached => _cachedKeys != null;
Box(this.name, this.boxCollection); Box(this.name, this.boxCollection);
Future<List<String>> getAllKeys([Transaction? txn]) async { Future<List<String>> getAllKeys([Transaction? txn]) async {
if (_keysCached) return _cachedKeys!.toList(); if (_quickAccessCachedKeys != null) return _quickAccessCachedKeys!.toList();
txn ??= boxCollection._db.transaction(name, 'readonly'); txn ??= boxCollection._db.transaction(name, 'readonly');
final store = txn.objectStore(name); final store = txn.objectStore(name);
final request = store.getAllKeys(null); final request = store.getAllKeys(null);
await request.onSuccess.first; await request.onSuccess.first;
final keys = request.result.cast<String>(); final keys = request.result.cast<String>();
_cachedKeys = keys.toSet(); _quickAccessCachedKeys = keys.toSet();
return keys; return keys;
} }
@ -136,16 +134,16 @@ class Box<V> {
} }
Future<V?> get(String key, [Transaction? txn]) async { Future<V?> get(String key, [Transaction? txn]) async {
if (_cache.containsKey(key)) return _cache[key]; if (_quickAccessCache.containsKey(key)) return _quickAccessCache[key];
txn ??= boxCollection._db.transaction(name, 'readonly'); txn ??= boxCollection._db.transaction(name, 'readonly');
final store = txn.objectStore(name); final store = txn.objectStore(name);
_cache[key] = await store.getObject(key).then(_fromValue); _quickAccessCache[key] = await store.getObject(key).then(_fromValue);
return _cache[key]; return _quickAccessCache[key];
} }
Future<List<V?>> getAll(List<String> keys, [Transaction? txn]) async { Future<List<V?>> getAll(List<String> keys, [Transaction? txn]) async {
if (keys.every((key) => _cache.containsKey(key))) { if (keys.every((key) => _quickAccessCache.containsKey(key))) {
return keys.map((key) => _cache[key]).toList(); return keys.map((key) => _quickAccessCache[key]).toList();
} }
txn ??= boxCollection._db.transaction(name, 'readonly'); txn ??= boxCollection._db.transaction(name, 'readonly');
final store = txn.objectStore(name); final store = txn.objectStore(name);
@ -153,7 +151,7 @@ class Box<V> {
keys.map((key) => store.getObject(key).then(_fromValue)), keys.map((key) => store.getObject(key).then(_fromValue)),
); );
for (var i = 0; i < keys.length; i++) { for (var i = 0; i < keys.length; i++) {
_cache[keys[i]] = list[i]; _quickAccessCache[keys[i]] = list[i];
} }
return list; return list;
} }
@ -161,24 +159,24 @@ class Box<V> {
Future<void> put(String key, V val, [Transaction? txn]) async { Future<void> put(String key, V val, [Transaction? txn]) async {
if (boxCollection._txnCache != null) { if (boxCollection._txnCache != null) {
boxCollection._txnCache!.add((txn) => put(key, val, txn)); boxCollection._txnCache!.add((txn) => put(key, val, txn));
_cache[key] = val; _quickAccessCache[key] = val;
_cachedKeys?.add(key); _quickAccessCachedKeys?.add(key);
return; return;
} }
txn ??= boxCollection._db.transaction(name, 'readwrite'); txn ??= boxCollection._db.transaction(name, 'readwrite');
final store = txn.objectStore(name); final store = txn.objectStore(name);
await store.put(val as Object, key); await store.put(val as Object, key);
_cache[key] = val; _quickAccessCache[key] = val;
_cachedKeys?.add(key); _quickAccessCachedKeys?.add(key);
return; return;
} }
Future<void> delete(String key, [Transaction? txn]) async { Future<void> delete(String key, [Transaction? txn]) async {
if (boxCollection._txnCache != null) { if (boxCollection._txnCache != null) {
boxCollection._txnCache!.add((txn) => delete(key, txn)); boxCollection._txnCache!.add((txn) => delete(key, txn));
_cache[key] = null; _quickAccessCache[key] = null;
_cachedKeys?.remove(key); _quickAccessCachedKeys?.remove(key);
return; return;
} }
@ -188,8 +186,8 @@ class Box<V> {
// Set to null instead remove() so that inside of transactions null is // Set to null instead remove() so that inside of transactions null is
// returned. // returned.
_cache[key] = null; _quickAccessCache[key] = null;
_cachedKeys?.remove(key); _quickAccessCachedKeys?.remove(key);
return; return;
} }
@ -197,9 +195,9 @@ class Box<V> {
if (boxCollection._txnCache != null) { if (boxCollection._txnCache != null) {
boxCollection._txnCache!.add((txn) => deleteAll(keys, txn)); boxCollection._txnCache!.add((txn) => deleteAll(keys, txn));
for (final key in keys) { for (final key in keys) {
_cache[key] = null; _quickAccessCache[key] = null;
} }
_cachedKeys?.removeAll(keys); _quickAccessCachedKeys?.removeAll(keys);
return; return;
} }
@ -207,26 +205,27 @@ class Box<V> {
final store = txn.objectStore(name); final store = txn.objectStore(name);
for (final key in keys) { for (final key in keys) {
await store.delete(key); await store.delete(key);
_cache[key] = null; _quickAccessCache[key] = null;
_cachedKeys?.remove(key); _quickAccessCachedKeys?.remove(key);
} }
return; return;
} }
void clearQuickAccessCache() {
_quickAccessCache.clear();
_quickAccessCachedKeys = null;
}
Future<void> clear([Transaction? txn]) async { Future<void> clear([Transaction? txn]) async {
if (boxCollection._txnCache != null) { if (boxCollection._txnCache != null) {
boxCollection._txnCache!.add((txn) => clear(txn)); boxCollection._txnCache!.add((txn) => clear(txn));
_cache.clear(); } else {
_cachedKeys = null; txn ??= boxCollection._db.transaction(name, 'readwrite');
return; final store = txn.objectStore(name);
await store.clear();
} }
txn ??= boxCollection._db.transaction(name, 'readwrite'); clearQuickAccessCache();
final store = txn.objectStore(name);
await store.clear();
_cache.clear();
_cachedKeys = null;
return;
} }
V? _fromValue(Object? value) { V? _fromValue(Object? value) {

View File

@ -338,7 +338,32 @@ class MatrixSdkDatabase extends DatabaseApi with DatabaseFileStorage {
} }
@override @override
Future<void> clear() => _collection.clear(); Future<void> clear() async {
_clientBox.clearQuickAccessCache();
_accountDataBox.clearQuickAccessCache();
_roomsBox.clearQuickAccessCache();
_preloadRoomStateBox.clearQuickAccessCache();
_nonPreloadRoomStateBox.clearQuickAccessCache();
_roomMembersBox.clearQuickAccessCache();
_toDeviceQueueBox.clearQuickAccessCache();
_roomAccountDataBox.clearQuickAccessCache();
_inboundGroupSessionsBox.clearQuickAccessCache();
_inboundGroupSessionsUploadQueueBox.clearQuickAccessCache();
_outboundGroupSessionsBox.clearQuickAccessCache();
_olmSessionsBox.clearQuickAccessCache();
_userDeviceKeysBox.clearQuickAccessCache();
_userDeviceKeysOutdatedBox.clearQuickAccessCache();
_userCrossSigningKeysBox.clearQuickAccessCache();
_ssssCacheBox.clearQuickAccessCache();
_presencesBox.clearQuickAccessCache();
_timelineFragmentsBox.clearQuickAccessCache();
_eventsBox.clearQuickAccessCache();
_seenDeviceIdsBox.clearQuickAccessCache();
_seenDeviceKeysBox.clearQuickAccessCache();
_userProfilesBox.clearQuickAccessCache();
await _collection.clear();
}
@override @override
Future<void> clearCache() => transaction(() async { Future<void> clearCache() => transaction(() async {

View File

@ -81,15 +81,14 @@ class BoxCollection with ZoneTransactionMixin {
class Box<V> { class Box<V> {
final String name; final String name;
final BoxCollection boxCollection; final BoxCollection boxCollection;
final Map<String, V?> _cache = {}; final Map<String, V?> _quickAccessCache = {};
/// _cachedKeys is only used to make sure that if you fetch all keys from a /// _quickAccessCachedKeys is only used to make sure that if you fetch all keys from a
/// box, you do not need to have an expensive read operation twice. There is /// box, you do not need to have an expensive read operation twice. There is
/// no other usage for this at the moment. So the cache is never partial. /// no other usage for this at the moment. So the cache is never partial.
/// Once the keys are cached, they need to be updated when changed in put and /// Once the keys are cached, they need to be updated when changed in put and
/// delete* so that the cache does not become outdated. /// delete* so that the cache does not become outdated.
Set<String>? _cachedKeys; Set<String>? _quickAccessCachedKeys;
bool get _keysCached => _cachedKeys != null;
static const Set<Type> allowedValueTypes = { static const Set<Type> allowedValueTypes = {
List<dynamic>, List<dynamic>,
@ -148,14 +147,14 @@ class Box<V> {
} }
Future<List<String>> getAllKeys([Transaction? txn]) async { Future<List<String>> getAllKeys([Transaction? txn]) async {
if (_keysCached) return _cachedKeys!.toList(); if (_quickAccessCachedKeys != null) return _quickAccessCachedKeys!.toList();
final executor = txn ?? boxCollection._db; final executor = txn ?? boxCollection._db;
final result = await executor.query(name, columns: ['k']); final result = await executor.query(name, columns: ['k']);
final keys = result.map((row) => row['k'] as String).toList(); final keys = result.map((row) => row['k'] as String).toList();
_cachedKeys = keys.toSet(); _quickAccessCachedKeys = keys.toSet();
return keys; return keys;
} }
@ -174,7 +173,7 @@ class Box<V> {
} }
Future<V?> get(String key, [Transaction? txn]) async { Future<V?> get(String key, [Transaction? txn]) async {
if (_cache.containsKey(key)) return _cache[key]; if (_quickAccessCache.containsKey(key)) return _quickAccessCache[key];
final executor = txn ?? boxCollection._db; final executor = txn ?? boxCollection._db;
@ -186,13 +185,13 @@ class Box<V> {
); );
final value = result.isEmpty ? null : _fromString(result.single['v']); final value = result.isEmpty ? null : _fromString(result.single['v']);
_cache[key] = value; _quickAccessCache[key] = value;
return value; return value;
} }
Future<List<V?>> getAll(List<String> keys, [Transaction? txn]) async { Future<List<V?>> getAll(List<String> keys, [Transaction? txn]) async {
if (!keys.any((key) => !_cache.containsKey(key))) { if (!keys.any((key) => !_quickAccessCache.containsKey(key))) {
return keys.map((key) => _cache[key]).toList(); return keys.map((key) => _quickAccessCache[key]).toList();
} }
// The SQL operation might fail with more than 1000 keys. We define some // The SQL operation might fail with more than 1000 keys. We define some
@ -224,7 +223,7 @@ class Box<V> {
// `resultMap.values`. // `resultMap.values`.
list.addAll(keys.map((key) => resultMap[key])); list.addAll(keys.map((key) => resultMap[key]));
_cache.addAll(resultMap); _quickAccessCache.addAll(resultMap);
return list; return list;
} }
@ -250,8 +249,8 @@ class Box<V> {
); );
} }
_cache[key] = val; _quickAccessCache[key] = val;
_cachedKeys?.add(key); _quickAccessCachedKeys?.add(key);
return; return;
} }
@ -266,8 +265,8 @@ class Box<V> {
// Set to null instead remove() so that inside of transactions null is // Set to null instead remove() so that inside of transactions null is
// returned. // returned.
_cache[key] = null; _quickAccessCache[key] = null;
_cachedKeys?.remove(key); _quickAccessCachedKeys?.remove(key);
return; return;
} }
@ -290,12 +289,17 @@ class Box<V> {
} }
for (final key in keys) { for (final key in keys) {
_cache[key] = null; _quickAccessCache[key] = null;
_cachedKeys?.removeAll(keys); _quickAccessCachedKeys?.removeAll(keys);
} }
return; return;
} }
void clearQuickAccessCache() {
_quickAccessCache.clear();
_quickAccessCachedKeys = null;
}
Future<void> clear([Batch? txn]) async { Future<void> clear([Batch? txn]) async {
txn ??= boxCollection._activeBatch; txn ??= boxCollection._activeBatch;
@ -305,8 +309,6 @@ class Box<V> {
txn.delete(name); txn.delete(name);
} }
_cache.clear(); clearQuickAccessCache();
_cachedKeys = null;
return;
} }
} }