Skip to content

Commit

Permalink
fix: clear cache when clearing DB in MatrixSdkDatabase
Browse files Browse the repository at this point in the history
  • Loading branch information
coder-with-a-bushido committed Dec 30, 2024
1 parent 992c8e4 commit cb2285d
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 55 deletions.
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();
}
}

0 comments on commit cb2285d

Please sign in to comment.