fix deadlock

clean up async handling
improve styled alerts
This commit is contained in:
Christien Rioux 2024-08-04 18:49:49 -05:00
parent 22390f31ff
commit 8edccb8a0f
21 changed files with 125 additions and 108 deletions

View File

@ -4,6 +4,7 @@
},
"menu": {
"accounts_menu_tooltip": "Accounts Menu",
"settings_tooltip": "Settings",
"contacts_tooltip": "Contacts List",
"new_chat_tooltip": "Start New Chat",
"add_account_tooltip": "Add Account",

View File

@ -8,7 +8,7 @@ import '../../proto/proto.dart' as proto;
import '../account_manager.dart';
typedef AccountRecordState = proto.Account;
typedef _sspUpdateState = (
typedef _SspUpdateState = (
AccountSpec accountSpec,
Future<void> Function() onSuccess
);
@ -96,5 +96,5 @@ class AccountRecordCubit extends DefaultDHTRecordCubit<AccountRecordState> {
}
}
final _sspUpdate = SingleStateProcessor<_sspUpdateState>();
final _sspUpdate = SingleStateProcessor<_SspUpdateState>();
}

View File

@ -24,13 +24,13 @@ class PerAccountCollectionBlocMapCubit extends BlocMapCubit<TypedKey,
// Add account record cubit
Future<void> _addPerAccountCollectionCubit(
{required TypedKey superIdentityRecordKey}) async =>
add(() => MapEntry(
add(
superIdentityRecordKey,
PerAccountCollectionCubit(
() async => PerAccountCollectionCubit(
locator: _locator,
accountInfoCubit: AccountInfoCubit(
accountRepository: _accountRepository,
superIdentityRecordKey: superIdentityRecordKey))));
superIdentityRecordKey: superIdentityRecordKey)));
/// StateFollower /////////////////////////

View File

@ -78,13 +78,13 @@ class PerAccountCollectionCubit extends Cubit<PerAccountCollectionState> {
await _accountRecordSubscription?.cancel();
_accountRecordSubscription = null;
// Update state to 'loading'
nextState = _updateAccountRecordState(nextState, null);
emit(nextState);
// Close AccountRecordCubit
await accountRecordCubit?.close();
accountRecordCubit = null;
// Update state to 'loading'
nextState = _updateAccountRecordState(nextState, null);
emit(nextState);
} else {
///////////////// Logged in ///////////////////

View File

@ -120,23 +120,23 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
try {
final success = await AccountRepository.instance.deleteLocalAccount(
widget.superIdentityRecordKey, widget.accountRecord);
if (success && mounted) {
if (mounted) {
if (success) {
context
.read<NotificationsCubit>()
.info(text: translate('edit_account_page.account_removed'));
GoRouterHelper(context).pop();
} else if (mounted) {
} else {
context
.read<NotificationsCubit>()
.error(text: translate('edit_account_page.failed_to_remove'));
}
}
} finally {
if (mounted) {
setState(() {
_isInAsyncCall = false;
});
}
}
} on Exception catch (e, st) {
if (mounted) {
await showErrorStacktraceModal(
@ -188,23 +188,22 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
try {
final success = await AccountRepository.instance.destroyAccount(
widget.superIdentityRecordKey, widget.accountRecord);
if (success && mounted) {
if (mounted) {
if (success) {
context
.read<NotificationsCubit>()
.info(text: translate('edit_account_page.account_destroyed'));
GoRouterHelper(context).pop();
} else if (mounted) {
context
.read<NotificationsCubit>()
.error(text: translate('edit_account_page.failed_to_destroy'));
} else {
context.read<NotificationsCubit>().error(
text: translate('edit_account_page.failed_to_destroy'));
}
}
} finally {
if (mounted) {
setState(() {
_isInAsyncCall = false;
});
}
}
} on Exception catch (e, st) {
if (mounted) {
await showErrorStacktraceModal(

View File

@ -252,7 +252,7 @@ class ContactInvitationListCubit
.openRecordRead(contactRequestInboxKey,
debugName: 'ContactInvitationListCubit::validateInvitation::'
'ContactRequestInbox',
parent: pool.getParentRecordKey(contactRequestInboxKey) ??
parent: await pool.getParentRecordKey(contactRequestInboxKey) ??
_accountInfo.accountRecordKey)
.withCancel(cancelRequest))
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {

View File

@ -48,15 +48,15 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
Future<void> _addWaitingInvitation(
{required proto.ContactInvitationRecord
contactInvitationRecord}) async =>
add(() => MapEntry(
add(
contactInvitationRecord.contactRequestInbox.recordKey.toVeilid(),
WaitingInvitationCubit(
() async => WaitingInvitationCubit(
ContactRequestInboxCubit(
accountInfo: _accountInfo,
contactInvitationRecord: contactInvitationRecord),
accountInfo: _accountInfo,
accountRecordCubit: _accountRecordCubit,
contactInvitationRecord: contactInvitationRecord)));
contactInvitationRecord: contactInvitationRecord));
// Process all accepted or rejected invitations
Future<void> _invitationStatusListener(

View File

@ -37,7 +37,7 @@ class ValidContactInvitation {
return (await pool.openRecordWrite(_contactRequestInboxKey, _writer,
debugName: 'ValidContactInvitation::accept::'
'ContactRequestInbox',
parent: pool.getParentRecordKey(_contactRequestInboxKey) ??
parent: await pool.getParentRecordKey(_contactRequestInboxKey) ??
_accountInfo.accountRecordKey))
// ignore: prefer_expression_function_bodies
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {

View File

@ -78,7 +78,7 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
{required TypedKey remoteIdentityPublicKey,
required TypedKey localConversationRecordKey,
required TypedKey remoteConversationRecordKey}) async =>
add(() {
add(localConversationRecordKey, () async {
// Conversation cubit the tracks the state between the local
// and remote halves of a contact's relationship with this account
final conversationCubit = ConversationCubit(
@ -123,7 +123,7 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
loading: AsyncValue.loading,
error: AsyncValue.error));
return MapEntry(localConversationRecordKey, transformedCubit);
return transformedCubit;
});
/// StateFollower /////////////////////////

View File

@ -55,24 +55,17 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
Future<void> _addConversationMessages(_SingleContactChatState state) async {
// xxx could use atomic update() function
final cubit = await tryOperateAsync<SingleContactMessagesCubit>(
state.localConversationRecordKey, closure: (cubit) async {
await cubit.updateRemoteMessagesRecordKey(state.remoteMessagesRecordKey);
return cubit;
});
if (cubit == null) {
await add(() => MapEntry(
state.localConversationRecordKey,
SingleContactMessagesCubit(
await update(state.localConversationRecordKey,
onUpdate: (cubit) async =>
cubit.updateRemoteMessagesRecordKey(state.remoteMessagesRecordKey),
onCreate: () async => SingleContactMessagesCubit(
accountInfo: _accountInfo,
remoteIdentityPublicKey: state.remoteIdentityPublicKey,
localConversationRecordKey: state.localConversationRecordKey,
remoteConversationRecordKey: state.remoteConversationRecordKey,
localMessagesRecordKey: state.localMessagesRecordKey,
remoteMessagesRecordKey: state.remoteMessagesRecordKey,
)));
}
));
}
_SingleContactChatState? _mapStateValue(

View File

@ -73,7 +73,8 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
final record = await pool.openRecordRead(_remoteConversationRecordKey,
debugName: 'ConversationCubit::RemoteConversation',
parent: pool.getParentRecordKey(_remoteConversationRecordKey) ??
parent:
await pool.getParentRecordKey(_remoteConversationRecordKey) ??
accountInfo.accountRecordKey,
crypto: crypto);

View File

@ -95,8 +95,8 @@ Future<void> showErrorModal(
required String title,
required String text}) async {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
final scaleConfig = theme.extension<ScaleConfig>()!;
// final scale = theme.extension<ScaleScheme>()!;
// final scaleConfig = theme.extension<ScaleConfig>()!;
await Alert(
context: context,
@ -145,8 +145,8 @@ Future<void> showWarningModal(
required String title,
required String text}) async {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
final scaleConfig = theme.extension<ScaleConfig>()!;
// final scale = theme.extension<ScaleScheme>()!;
// final scaleConfig = theme.extension<ScaleConfig>()!;
await Alert(
context: context,
@ -184,8 +184,8 @@ Future<void> showWarningWidgetModal(
required String title,
required Widget child}) async {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
final scaleConfig = theme.extension<ScaleConfig>()!;
// final scale = theme.extension<ScaleScheme>()!;
// final scaleConfig = theme.extension<ScaleConfig>()!;
await Alert(
context: context,
@ -223,8 +223,8 @@ Future<bool> showConfirmModal(
required String title,
required String text}) async {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
final scaleConfig = theme.extension<ScaleConfig>()!;
// final scale = theme.extension<ScaleScheme>()!;
// final scaleConfig = theme.extension<ScaleConfig>()!;
var confirm = false;

View File

@ -28,11 +28,7 @@ class BackgroundTickerState extends State<BackgroundTicker> {
@override
void dispose() {
final tickTimer = _tickTimer;
if (tickTimer != null) {
tickTimer.cancel();
}
_tickTimer?.cancel();
super.dispose();
}

View File

@ -37,10 +37,10 @@ packages:
dependency: "direct dev"
description:
name: async_tools
sha256: "9166e8fe65fc65eb79202a6d540f4de768553d78141b885f5bd3f8d7d30eef5e"
sha256: "93df8b92d54d92e3323c630277e902b4ad4f05f798b55cfbc451e98c3e2fb7ba"
url: "https://pub.dev"
source: hosted
version: "0.1.5"
version: "0.1.6"
bloc:
dependency: transitive
description:
@ -53,10 +53,10 @@ packages:
dependency: transitive
description:
name: bloc_advanced_tools
sha256: "2b2dd492a350e7192a933d09f15ea04d5d00e7bd3fe2a906fe629cd461ddbf94"
sha256: f0b2dbe028792c97d1eb30480ed4e8035b5c70ea3bcc95a9c5255142592857f7
url: "https://pub.dev"
source: hosted
version: "0.1.5"
version: "0.1.7"
boolean_selector:
dependency: transitive
description:
@ -650,7 +650,7 @@ packages:
path: "../../../../veilid/veilid-flutter"
relative: true
source: path
version: "0.3.3"
version: "0.3.4"
veilid_support:
dependency: "direct main"
description:

View File

@ -14,7 +14,7 @@ dependencies:
path: ../
dev_dependencies:
async_tools: ^0.1.5
async_tools: ^0.1.6
integration_test:
sdk: flutter
lint_hard: ^4.0.0

View File

@ -79,7 +79,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord> {
return false;
}
await serialFuturePause((this, _sfListen));
await serialFutureClose((this, _sfListen));
await _watchController?.close();
_watchController = null;
await DHTRecordPool.instance._recordClosed(this);

View File

@ -65,7 +65,7 @@ class OwnedDHTRecordPointer with _$OwnedDHTRecordPointer {
class DHTRecordPool with TableDBBackedJson<DHTRecordPoolAllocations> {
DHTRecordPool._(Veilid veilid, VeilidRoutingContext routingContext)
: _state = const DHTRecordPoolAllocations(),
_mutex = Mutex(),
_mutex = Mutex(debugLockTimeout: 30),
_recordTagLock = AsyncTagLock(),
_opened = <TypedKey, _OpenedRecordInfo>{},
_markedForDelete = <TypedKey>{},
@ -207,10 +207,8 @@ class DHTRecordPool with TableDBBackedJson<DHTRecordPoolAllocations> {
);
/// Get the parent of a DHTRecord key if it exists
TypedKey? getParentRecordKey(TypedKey child) {
final childJson = child.toJson();
return _state.parentByChild[childJson];
}
Future<TypedKey?> getParentRecordKey(TypedKey child) =>
_mutex.protect(() async => _getParentRecordKeyInner(child));
/// Check if record is allocated
Future<bool> isValidRecordKey(TypedKey key) =>
@ -505,12 +503,16 @@ class DHTRecordPool with TableDBBackedJson<DHTRecordPoolAllocations> {
// Check to see if this key can finally be deleted
// If any parents are marked for deletion, try them first
Future<void> _checkForLateDeletesInner(TypedKey key) async {
if (!_mutex.isLocked) {
throw StateError('should be locked here');
}
// Get parent list in bottom up order including our own key
final parents = <TypedKey>[];
TypedKey? nextParent = key;
while (nextParent != null) {
parents.add(nextParent);
nextParent = getParentRecordKey(nextParent);
nextParent = _getParentRecordKeyInner(nextParent);
}
// If any parent is ready to delete all its children do it
@ -547,6 +549,10 @@ class DHTRecordPool with TableDBBackedJson<DHTRecordPoolAllocations> {
// Actual delete function
Future<void> _finalizeDeleteRecordInner(TypedKey recordKey) async {
if (!_mutex.isLocked) {
throw StateError('should be locked here');
}
log('_finalizeDeleteRecordInner: key=$recordKey');
// Remove this child from parents
@ -557,6 +563,10 @@ class DHTRecordPool with TableDBBackedJson<DHTRecordPoolAllocations> {
// Deep delete mechanism inside mutex
Future<bool> _deleteRecordInner(TypedKey recordKey) async {
if (!_mutex.isLocked) {
throw StateError('should be locked here');
}
final toDelete = _readyForDeleteInner(recordKey);
if (toDelete.isNotEmpty) {
// delete now
@ -656,7 +666,20 @@ class DHTRecordPool with TableDBBackedJson<DHTRecordPoolAllocations> {
}
}
TypedKey? _getParentRecordKeyInner(TypedKey child) {
if (!_mutex.isLocked) {
throw StateError('should be locked here');
}
final childJson = child.toJson();
return _state.parentByChild[childJson];
}
bool _isValidRecordKeyInner(TypedKey key) {
if (!_mutex.isLocked) {
throw StateError('should be locked here');
}
if (_state.rootRecords.contains(key)) {
return true;
}
@ -667,6 +690,10 @@ class DHTRecordPool with TableDBBackedJson<DHTRecordPoolAllocations> {
}
bool _isDeletedRecordKeyInner(TypedKey key) {
if (!_mutex.isLocked) {
throw StateError('should be locked here');
}
// Is this key gone?
if (!_isValidRecordKeyInner(key)) {
return true;
@ -679,7 +706,7 @@ class DHTRecordPool with TableDBBackedJson<DHTRecordPoolAllocations> {
if (_markedForDelete.contains(nextParent)) {
return true;
}
nextParent = getParentRecordKey(nextParent);
nextParent = _getParentRecordKeyInner(nextParent);
}
return false;

View File

@ -37,10 +37,10 @@ packages:
dependency: "direct main"
description:
name: async_tools
sha256: "9166e8fe65fc65eb79202a6d540f4de768553d78141b885f5bd3f8d7d30eef5e"
sha256: "93df8b92d54d92e3323c630277e902b4ad4f05f798b55cfbc451e98c3e2fb7ba"
url: "https://pub.dev"
source: hosted
version: "0.1.5"
version: "0.1.6"
bloc:
dependency: "direct main"
description:
@ -53,10 +53,10 @@ packages:
dependency: "direct main"
description:
name: bloc_advanced_tools
sha256: "2ad82be752ab5e983ad9097ed9f334e47a4472c04d5c6b61c99a1bb14a039053"
sha256: f0b2dbe028792c97d1eb30480ed4e8035b5c70ea3bcc95a9c5255142592857f7
url: "https://pub.dev"
source: hosted
version: "0.1.6"
version: "0.1.7"
boolean_selector:
dependency: transitive
description:

View File

@ -7,9 +7,9 @@ environment:
sdk: '>=3.2.0 <4.0.0'
dependencies:
async_tools: ^0.1.5
async_tools: ^0.1.6
bloc: ^8.1.4
bloc_advanced_tools: ^0.1.6
bloc_advanced_tools: ^0.1.7
charcode: ^1.3.1
collection: ^1.18.0
equatable: ^2.0.5

View File

@ -85,10 +85,10 @@ packages:
dependency: "direct main"
description:
name: async_tools
sha256: "9166e8fe65fc65eb79202a6d540f4de768553d78141b885f5bd3f8d7d30eef5e"
sha256: "93df8b92d54d92e3323c630277e902b4ad4f05f798b55cfbc451e98c3e2fb7ba"
url: "https://pub.dev"
source: hosted
version: "0.1.5"
version: "0.1.6"
awesome_extensions:
dependency: "direct main"
description:
@ -141,10 +141,10 @@ packages:
dependency: "direct main"
description:
name: bloc_advanced_tools
sha256: "2ad82be752ab5e983ad9097ed9f334e47a4472c04d5c6b61c99a1bb14a039053"
sha256: f0b2dbe028792c97d1eb30480ed4e8035b5c70ea3bcc95a9c5255142592857f7
url: "https://pub.dev"
source: hosted
version: "0.1.6"
version: "0.1.7"
blurry_modal_progress_hud:
dependency: "direct main"
description:

View File

@ -14,12 +14,12 @@ dependencies:
animated_theme_switcher: ^2.0.10
ansicolor: ^2.0.2
archive: ^3.6.1
async_tools: ^0.1.5
async_tools: ^0.1.6
awesome_extensions: ^2.0.16
badges: ^3.1.2
basic_utils: ^5.7.0
bloc: ^8.1.4
bloc_advanced_tools: ^0.1.6
bloc_advanced_tools: ^0.1.7
blurry_modal_progress_hud: ^1.1.1
change_case: ^2.1.0
charcode: ^1.3.1