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:
Sorunome 2021-09-01 18:32:08 +02:00
parent 8b46fa3fc2
commit efb5842959
No known key found for this signature in database
GPG Key ID: B19471D07FC9BE9C
2 changed files with 76 additions and 1 deletions

View File

@ -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(

View File

@ -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,