fix: Transactions on web by doing them in the same way as on io
This refactors the transaction workaround with "zone transactions" and abstracts them in a mixin. Then it just uses this mixin in the HiveDatabase and the sqflite_box and also applies them on indexedDB to fix transactions on web.
This commit is contained in:
parent
0d69e37389
commit
48ba7556b2
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue