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

fix: clear cache when clearing DB in MatrixSdkDatabase #1961

Merged
merged 1 commit into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 33 additions & 34 deletions lib/src/database/indexeddb_box.dart
Original file line number Diff line number Diff line change
Expand Up @@ -100,27 +100,25 @@ class BoxCollection with ZoneTransactionMixin {
class Box<V> {
final String name;
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
/// 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
/// delete* so that the cache does not become outdated.
Set<String>? _cachedKeys;

bool get _keysCached => _cachedKeys != null;
Set<String>? _quickAccessCachedKeys;

Box(this.name, this.boxCollection);

Future<List<String>> getAllKeys([Transaction? txn]) async {
if (_keysCached) return _cachedKeys!.toList();
if (_quickAccessCachedKeys != null) return _quickAccessCachedKeys!.toList();
txn ??= boxCollection._db.transaction(name, 'readonly');
final store = txn.objectStore(name);
final request = store.getAllKeys(null);
await request.onSuccess.first;
final keys = request.result.cast<String>();
_cachedKeys = keys.toSet();
_quickAccessCachedKeys = keys.toSet();
return keys;
}

Expand All @@ -136,49 +134,49 @@ class Box<V> {
}

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');
final store = txn.objectStore(name);
_cache[key] = await store.getObject(key).then(_fromValue);
return _cache[key];
_quickAccessCache[key] = await store.getObject(key).then(_fromValue);
return _quickAccessCache[key];
}

Future<List<V?>> getAll(List<String> keys, [Transaction? txn]) async {
if (keys.every((key) => _cache.containsKey(key))) {
return keys.map((key) => _cache[key]).toList();
if (keys.every((key) => _quickAccessCache.containsKey(key))) {
return keys.map((key) => _quickAccessCache[key]).toList();
}
txn ??= boxCollection._db.transaction(name, 'readonly');
final store = txn.objectStore(name);
final list = await Future.wait(
keys.map((key) => store.getObject(key).then(_fromValue)),
);
for (var i = 0; i < keys.length; i++) {
_cache[keys[i]] = list[i];
_quickAccessCache[keys[i]] = list[i];
}
return list;
}

Future<void> put(String key, V val, [Transaction? txn]) async {
if (boxCollection._txnCache != null) {
boxCollection._txnCache!.add((txn) => put(key, val, txn));
_cache[key] = val;
_cachedKeys?.add(key);
_quickAccessCache[key] = val;
_quickAccessCachedKeys?.add(key);
return;
}

txn ??= boxCollection._db.transaction(name, 'readwrite');
final store = txn.objectStore(name);
await store.put(val as Object, key);
_cache[key] = val;
_cachedKeys?.add(key);
_quickAccessCache[key] = val;
_quickAccessCachedKeys?.add(key);
return;
}

Future<void> delete(String key, [Transaction? txn]) async {
if (boxCollection._txnCache != null) {
boxCollection._txnCache!.add((txn) => delete(key, txn));
_cache[key] = null;
_cachedKeys?.remove(key);
_quickAccessCache[key] = null;
_quickAccessCachedKeys?.remove(key);
return;
}

Expand All @@ -188,45 +186,46 @@ class Box<V> {

// Set to null instead remove() so that inside of transactions null is
// returned.
_cache[key] = null;
_cachedKeys?.remove(key);
_quickAccessCache[key] = null;
_quickAccessCachedKeys?.remove(key);
return;
}

Future<void> deleteAll(List<String> keys, [Transaction? txn]) async {
if (boxCollection._txnCache != null) {
boxCollection._txnCache!.add((txn) => deleteAll(keys, txn));
for (final key in keys) {
_cache[key] = null;
_quickAccessCache[key] = null;
}
_cachedKeys?.removeAll(keys);
_quickAccessCachedKeys?.removeAll(keys);
return;
}

txn ??= boxCollection._db.transaction(name, 'readwrite');
final store = txn.objectStore(name);
for (final key in keys) {
await store.delete(key);
_cache[key] = null;
_cachedKeys?.remove(key);
_quickAccessCache[key] = null;
_quickAccessCachedKeys?.remove(key);
}
return;
}

void clearQuickAccessCache() {
_quickAccessCache.clear();
_quickAccessCachedKeys = null;
}

Future<void> clear([Transaction? txn]) async {
if (boxCollection._txnCache != null) {
boxCollection._txnCache!.add((txn) => clear(txn));
_cache.clear();
_cachedKeys = null;
return;
} else {
txn ??= boxCollection._db.transaction(name, 'readwrite');
final store = txn.objectStore(name);
await store.clear();
}

txn ??= boxCollection._db.transaction(name, 'readwrite');
final store = txn.objectStore(name);
await store.clear();
_cache.clear();
_cachedKeys = null;
return;
clearQuickAccessCache();
}

V? _fromValue(Object? value) {
Expand Down
27 changes: 26 additions & 1 deletion lib/src/database/matrix_sdk_database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,32 @@ class MatrixSdkDatabase extends DatabaseApi with DatabaseFileStorage {
}

@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
Future<void> clearCache() => transaction(() async {
Expand Down
42 changes: 22 additions & 20 deletions lib/src/database/sqflite_box.dart
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,14 @@ class BoxCollection with ZoneTransactionMixin {
class Box<V> {
final String name;
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
/// 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
/// delete* so that the cache does not become outdated.
Set<String>? _cachedKeys;
bool get _keysCached => _cachedKeys != null;
Set<String>? _quickAccessCachedKeys;

static const Set<Type> allowedValueTypes = {
List<dynamic>,
Expand Down Expand Up @@ -148,14 +147,14 @@ class Box<V> {
}

Future<List<String>> getAllKeys([Transaction? txn]) async {
if (_keysCached) return _cachedKeys!.toList();
if (_quickAccessCachedKeys != null) return _quickAccessCachedKeys!.toList();

final executor = txn ?? boxCollection._db;

final result = await executor.query(name, columns: ['k']);
final keys = result.map((row) => row['k'] as String).toList();

_cachedKeys = keys.toSet();
_quickAccessCachedKeys = keys.toSet();
return keys;
}

Expand All @@ -174,7 +173,7 @@ class Box<V> {
}

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;

Expand All @@ -186,13 +185,13 @@ class Box<V> {
);

final value = result.isEmpty ? null : _fromString(result.single['v']);
_cache[key] = value;
_quickAccessCache[key] = value;
return value;
}

Future<List<V?>> getAll(List<String> keys, [Transaction? txn]) async {
if (!keys.any((key) => !_cache.containsKey(key))) {
return keys.map((key) => _cache[key]).toList();
if (!keys.any((key) => !_quickAccessCache.containsKey(key))) {
return keys.map((key) => _quickAccessCache[key]).toList();
}

// The SQL operation might fail with more than 1000 keys. We define some
Expand Down Expand Up @@ -224,7 +223,7 @@ class Box<V> {
// `resultMap.values`.
list.addAll(keys.map((key) => resultMap[key]));

_cache.addAll(resultMap);
_quickAccessCache.addAll(resultMap);

return list;
}
Expand All @@ -250,8 +249,8 @@ class Box<V> {
);
}

_cache[key] = val;
_cachedKeys?.add(key);
_quickAccessCache[key] = val;
_quickAccessCachedKeys?.add(key);
return;
}

Expand All @@ -266,8 +265,8 @@ class Box<V> {

// Set to null instead remove() so that inside of transactions null is
// returned.
_cache[key] = null;
_cachedKeys?.remove(key);
_quickAccessCache[key] = null;
_quickAccessCachedKeys?.remove(key);
return;
}

Expand All @@ -290,12 +289,17 @@ class Box<V> {
}

for (final key in keys) {
_cache[key] = null;
_cachedKeys?.removeAll(keys);
_quickAccessCache[key] = null;
_quickAccessCachedKeys?.removeAll(keys);
}
return;
}

void clearQuickAccessCache() {
_quickAccessCache.clear();
_quickAccessCachedKeys = null;
}

Future<void> clear([Batch? txn]) async {
txn ??= boxCollection._activeBatch;

Expand All @@ -305,8 +309,6 @@ class Box<V> {
txn.delete(name);
}

_cache.clear();
_cachedKeys = null;
return;
clearQuickAccessCache();
}
}
Loading