Merge pull request #1688 from famedly/krille/zone-transaction-mixins

fix: Transactions on web by doing them in the same way as on io
This commit is contained in:
Krille-chan 2024-01-22 15:13:07 +01:00 committed by GitHub
commit 50d222cc19
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 94 additions and 127 deletions

View File

@ -28,6 +28,7 @@ import 'package:matrix/encryption/utils/outbound_group_session.dart';
import 'package:matrix/encryption/utils/ssss_cache.dart'; import 'package:matrix/encryption/utils/ssss_cache.dart';
import 'package:matrix/encryption/utils/stored_inbound_group_session.dart'; import 'package:matrix/encryption/utils/stored_inbound_group_session.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:matrix/src/database/zone_transaction_mixin.dart';
import 'package:matrix/src/utils/copy_map.dart'; import 'package:matrix/src/utils/copy_map.dart';
import 'package:matrix/src/utils/queued_to_device_event.dart'; import 'package:matrix/src/utils/queued_to_device_event.dart';
import 'package:matrix/src/utils/run_benchmarked.dart'; import 'package:matrix/src/utils/run_benchmarked.dart';
@ -39,7 +40,7 @@ import 'package:matrix/src/utils/run_benchmarked.dart';
/// This database does not support file caching! /// This database does not support file caching!
@Deprecated( @Deprecated(
'Use [HiveCollectionsDatabase] instead. Don\'t forget to properly migrate!') 'Use [HiveCollectionsDatabase] instead. Don\'t forget to properly migrate!')
class FamedlySdkHiveDatabase extends DatabaseApi { class FamedlySdkHiveDatabase extends DatabaseApi with ZoneTransactionMixin {
static const int version = 5; static const int version = 5;
final String name; final String name;
late Box _clientBox; late Box _clientBox;
@ -1305,57 +1306,9 @@ class FamedlySdkHiveDatabase extends DatabaseApi {
return; return;
} }
Completer<void>? _transactionLock;
final _transactionZones = <Zone>{};
@override @override
Future<void> transaction(Future<void> Function() action) async { Future<void> transaction(Future<void> Function() action) =>
// we want transactions to lock, however NOT if transactoins are run inside of each other. zoneTransaction(action);
// to be able to do this, we use dart zones (https://dart.dev/articles/archive/zones).
// _transactionZones holds a set of all zones which are currently running a transaction.
// _transactionLock holds the lock.
// first we try to determine if we are inside of a transaction currently
var isInTransaction = false;
Zone? zone = Zone.current;
// for that we keep on iterating to the parent zone until there is either no zone anymore
// or we have found a zone inside of _transactionZones.
while (zone != null) {
if (_transactionZones.contains(zone)) {
isInTransaction = true;
break;
}
zone = zone.parent;
}
// if we are inside a transaction....just run the action
if (isInTransaction) {
return await action();
}
// if we are *not* in a transaction, time to wait for the lock!
while (_transactionLock != null) {
await _transactionLock!.future;
}
// claim the lock
final lock = Completer<void>();
_transactionLock = lock;
try {
// run the action inside of a new zone
return await runZoned(() async {
try {
// don't forget to add the new zone to _transactionZones!
_transactionZones.add(Zone.current);
return await action();
} finally {
// aaaand remove the zone from _transactionZones again
_transactionZones.remove(Zone.current);
}
});
} finally {
// aaaand finally release the lock
_transactionLock = null;
lock.complete();
}
}
@override @override
Future<void> updateClient( Future<void> updateClient(

View File

@ -2,9 +2,11 @@ import 'dart:async';
import 'dart:html'; import 'dart:html';
import 'dart:indexed_db'; import 'dart:indexed_db';
import 'package:matrix/src/database/zone_transaction_mixin.dart';
/// Key-Value store abstraction over IndexedDB so that the sdk database can use /// Key-Value store abstraction over IndexedDB so that the sdk database can use
/// a single interface for all platforms. API is inspired by Hive. /// a single interface for all platforms. API is inspired by Hive.
class BoxCollection { class BoxCollection with ZoneTransactionMixin {
final Database _db; final Database _db;
final Set<String> boxNames; final Set<String> boxNames;
final String _name; final String _name;
@ -43,14 +45,17 @@ class BoxCollection {
Future<void> Function() action, { Future<void> Function() action, {
List<String>? boxNames, List<String>? boxNames,
bool readOnly = false, bool readOnly = false,
}) async { }) =>
zoneTransaction(() async {
boxNames ??= _db.objectStoreNames!.toList(); boxNames ??= _db.objectStoreNames!.toList();
_txnCache = []; final txnCache = _txnCache = [];
await action(); await action();
final cache = List<Future<void> Function(Transaction txn)>.from(_txnCache!); final cache =
List<Future<void> Function(Transaction txn)>.from(txnCache);
_txnCache = null; _txnCache = null;
if (cache.isEmpty) return; if (cache.isEmpty) return;
final txn = _db.transaction(boxNames, readOnly ? 'readonly' : 'readwrite'); final txn =
_db.transaction(boxNames, readOnly ? 'readonly' : 'readwrite');
for (final fun in cache) { for (final fun in cache) {
// The IDB methods return a Future in Dart but must not be awaited in // The IDB methods return a Future in Dart but must not be awaited in
// order to have an actual transaction. They must only be performed and // order to have an actual transaction. They must only be performed and
@ -61,7 +66,7 @@ class BoxCollection {
} }
await txn.completed; await txn.completed;
return; return;
} });
Future<void> clear() async { Future<void> clear() async {
final txn = _db.transaction(boxNames, 'readwrite'); final txn = _db.transaction(boxNames, 'readwrite');

View File

@ -3,9 +3,11 @@ import 'dart:convert';
import 'package:sqflite_common/sqflite.dart'; import 'package:sqflite_common/sqflite.dart';
import 'package:matrix/src/database/zone_transaction_mixin.dart';
/// Key-Value store abstraction over Sqflite so that the sdk database can use /// Key-Value store abstraction over Sqflite so that the sdk database can use
/// a single interface for all platforms. API is inspired by Hive. /// a single interface for all platforms. API is inspired by Hive.
class BoxCollection { class BoxCollection with ZoneTransactionMixin {
final Database _db; final Database _db;
final Set<String> boxNames; final Set<String> boxNames;
final DatabaseFactory? _factory; final DatabaseFactory? _factory;
@ -42,66 +44,18 @@ class BoxCollection {
Batch? _activeBatch; Batch? _activeBatch;
Completer<void>? _transactionLock;
final _transactionZones = <Zone>{};
Future<void> transaction( Future<void> transaction(
Future<void> Function() action, { Future<void> Function() action, {
List<String>? boxNames, List<String>? boxNames,
bool readOnly = false, bool readOnly = false,
}) async { }) =>
// we want transactions to lock, however NOT if transactoins are run inside of each other. zoneTransaction(() async {
// to be able to do this, we use dart zones (https://dart.dev/articles/archive/zones).
// _transactionZones holds a set of all zones which are currently running a transaction.
// _transactionLock holds the lock.
// first we try to determine if we are inside of a transaction currently
var isInTransaction = false;
Zone? zone = Zone.current;
// for that we keep on iterating to the parent zone until there is either no zone anymore
// or we have found a zone inside of _transactionZones.
while (zone != null) {
if (_transactionZones.contains(zone)) {
isInTransaction = true;
break;
}
zone = zone.parent;
}
// if we are inside a transaction....just run the action
if (isInTransaction) {
return await action();
}
// if we are *not* in a transaction, time to wait for the lock!
while (_transactionLock != null) {
await _transactionLock!.future;
}
// claim the lock
final lock = Completer<void>();
_transactionLock = lock;
try {
// run the action inside of a new zone
return await runZoned(() async {
try {
// don't forget to add the new zone to _transactionZones!
_transactionZones.add(Zone.current);
final batch = _db.batch(); final batch = _db.batch();
_activeBatch = batch; _activeBatch = batch;
await action(); await action();
_activeBatch = null; _activeBatch = null;
await batch.commit(noResult: true); await batch.commit(noResult: true);
return;
} finally {
// aaaand remove the zone from _transactionZones again
_transactionZones.remove(Zone.current);
}
}); });
} finally {
// aaaand finally release the lock
_transactionLock = null;
lock.complete();
}
}
Future<void> clear() => transaction( Future<void> clear() => transaction(
() async { () async {

View File

@ -0,0 +1,55 @@
import 'dart:async';
// we want transactions to lock, however NOT if transactoins are run inside of each other.
// to be able to do this, we use dart zones (https://dart.dev/articles/archive/zones).
// _transactionZones holds a set of all zones which are currently running a transaction.
// _transactionLock holds the lock.
mixin ZoneTransactionMixin {
Completer<void>? _transactionLock;
final _transactionZones = <Zone>{};
Future<void> zoneTransaction(Future<void> Function() action) async {
// first we try to determine if we are inside of a transaction currently
var isInTransaction = false;
Zone? zone = Zone.current;
// for that we keep on iterating to the parent zone until there is either no zone anymore
// or we have found a zone inside of _transactionZones.
while (zone != null) {
if (_transactionZones.contains(zone)) {
isInTransaction = true;
break;
}
zone = zone.parent;
}
// if we are inside a transaction....just run the action
if (isInTransaction) {
return await action();
}
// if we are *not* in a transaction, time to wait for the lock!
while (_transactionLock != null) {
await _transactionLock!.future;
}
// claim the lock
final lock = Completer<void>();
_transactionLock = lock;
try {
// run the action inside of a new zone
return await runZoned(() async {
try {
// don't forget to add the new zone to _transactionZones!
_transactionZones.add(Zone.current);
await action();
return;
} finally {
// aaaand remove the zone from _transactionZones again
_transactionZones.remove(Zone.current);
}
});
} finally {
// aaaand finally release the lock
_transactionLock = null;
lock.complete();
}
}
}