fix: Implement dummy transactions for hive
That way some concurrency bugs might be fixed, such as if two sync requests are processed at the same time. That can e.g. happen if you request history while a sync request is already being processed.
This commit is contained in:
parent
8b46fa3fc2
commit
efb5842959
|
|
@ -1049,8 +1049,57 @@ class FamedlySdkHiveDatabase extends DatabaseApi {
|
|||
return;
|
||||
}
|
||||
|
||||
Completer<void>? _transactionLock;
|
||||
final _transactionZones = <Zone>{};
|
||||
|
||||
@override
|
||||
Future<T> transaction<T>(Future<T> Function() action) => action();
|
||||
Future<T> transaction<T>(Future<T> Function() action) 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.
|
||||
|
||||
// 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
|
||||
Future<void> updateClient(
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
|
@ -49,6 +50,31 @@ void testDatabase(Future<DatabaseApi> futureDatabase, int clientId) {
|
|||
test('Open', () async {
|
||||
database = await futureDatabase;
|
||||
});
|
||||
test('transaction', () async {
|
||||
print('Starting test...');
|
||||
var counter = 0;
|
||||
await database.transaction(() async {
|
||||
expect(counter++, 0);
|
||||
await database.transaction(() async {
|
||||
expect(counter++, 1);
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(counter++, 2);
|
||||
});
|
||||
expect(counter++, 3);
|
||||
});
|
||||
|
||||
// we can't use Zone.root.run inside of tests so we abuse timers instead
|
||||
Timer(Duration(milliseconds: 50), () async {
|
||||
await database.transaction(() async {
|
||||
expect(counter++, 6);
|
||||
});
|
||||
});
|
||||
await database.transaction(() async {
|
||||
expect(counter++, 4);
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
expect(counter++, 5);
|
||||
});
|
||||
});
|
||||
test('insertIntoToDeviceQueue', () async {
|
||||
toDeviceQueueIndex = await database.insertIntoToDeviceQueue(
|
||||
clientId,
|
||||
|
|
|
|||
Loading…
Reference in New Issue