From 25a6a00fcfd6ddbe91507599589a962ad52d0ca5 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Wed, 1 May 2024 20:58:25 -0400 Subject: [PATCH] refactor, use external libraries, and add integration test for veilid_support --- .../account_repository.dart | 2 +- lib/chat/cubits/active_chat_cubit.dart | 3 +- .../active_conversations_bloc_map_cubit.dart | 2 +- ...ve_single_contact_chat_bloc_map_cubit.dart | 2 +- lib/chat_list/cubits/chat_list_cubit.dart | 2 +- .../cubits/contact_invitation_list_cubit.dart | 10 +- .../cubits/contact_request_inbox_cubit.dart | 2 +- .../cubits/invitation_generator_cubit.dart | 2 +- .../cubits/waiting_invitation_cubit.dart | 2 +- .../waiting_invitations_bloc_map_cubit.dart | 2 +- .../models/valid_contact_invitation.dart | 4 +- lib/contacts/cubits/conversation_cubit.dart | 8 +- .../home_account_ready_shell.dart | 2 +- lib/settings/preferences_cubit.dart | 2 +- lib/theme/views/widget_helpers.dart | 2 +- lib/tick.dart | 15 +- .../cubit/connection_state_cubit.dart | 2 +- packages/async_tools/.gitignore | 7 - packages/async_tools/analysis_options.yaml | 15 - packages/async_tools/build.bat | 2 - packages/async_tools/build.sh | 3 - .../example/async_tools_example.dart | 6 - packages/async_tools/lib/async_tools.dart | 11 - .../async_tools/lib/src/async_tag_lock.dart | 64 --- packages/async_tools/lib/src/async_value.dart | 209 -------- .../lib/src/async_value.freezed.dart | 480 ----------------- .../async_tools/lib/src/delayed_wait_set.dart | 18 - .../async_tools/lib/src/serial_future.dart | 57 -- .../async_tools/lib/src/single_future.dart | 45 -- .../lib/src/single_state_processor.dart | 67 --- .../lib/src/single_stateless_processor.dart | 48 -- packages/async_tools/lib/src/wait_set.dart | 18 - packages/async_tools/pubspec.yaml | 19 - .../async_tools/test/async_tools_test.dart | 16 - packages/bloc_tools/.gitignore | 7 - packages/bloc_tools/analysis_options.yaml | 15 - .../example/bloc_tools_example.dart | 6 - packages/bloc_tools/lib/bloc_tools.dart | 11 - .../lib/src/async_transformer_cubit.dart | 48 -- .../bloc_tools/lib/src/bloc_busy_wrapper.dart | 78 --- .../bloc_tools/lib/src/bloc_map_cubit.dart | 127 ----- .../lib/src/bloc_tools_extension.dart | 12 - packages/bloc_tools/lib/src/future_cubit.dart | 24 - .../lib/src/state_map_follower.dart | 125 ----- .../lib/src/stream_wrapper_cubit.dart | 25 - .../bloc_tools/lib/src/transformer_cubit.dart | 21 - packages/bloc_tools/pubspec.yaml | 24 - packages/bloc_tools/test/bloc_tools_test.dart | 16 - packages/mutex/.gitignore | 16 - packages/mutex/CHANGELOG.md | 50 -- packages/mutex/LICENSE | 24 - packages/mutex/README.md | 191 ------- packages/mutex/analysis_options.yaml | 15 - packages/mutex/example/example.dart | 114 ---- packages/mutex/lib/mutex.dart | 11 - packages/mutex/lib/src/mutex.dart | 89 ---- packages/mutex/lib/src/read_write_mutex.dart | 304 ----------- packages/mutex/pubspec.yaml | 12 - .../mutex/test/mutex_multiple_read_test.dart | 102 ---- packages/mutex/test/mutex_readwrite_test.dart | 486 ------------------ packages/mutex/test/mutex_test.dart | 341 ------------ .../example/analysis_options.yaml | 39 +- .../example/integration_test/app_test.dart | 50 +- .../example/integration_test/fixtures.dart | 152 ------ .../fixtures/dht_record_pool_fixture.dart | 36 ++ .../integration_test/fixtures/fixtures.dart | 1 + .../test_dht_record_pool.dart | 196 ++++++- .../test_dht_short_array.dart | 85 ++- .../ios/Runner.xcodeproj/project.pbxproj | 40 +- .../example/ios/Runner/Info.plist | 8 +- packages/veilid_support/example/pubspec.lock | 56 +- packages/veilid_support/example/pubspec.yaml | 73 +-- .../src/dht_record/dht_record.dart | 1 + .../src/dht_record/dht_record_pool.dart | 59 ++- .../src/dht_short_array/dht_short_array.dart | 105 ++-- .../dht_short_array_cubit.dart | 2 +- .../dht_short_array/dht_short_array_head.dart | 33 +- .../lib/src/async_table_db_backed_cubit.dart | 1 - packages/veilid_support/lib/src/identity.dart | 12 +- .../lib/src/persistent_queue.dart | 1 - packages/veilid_support/pubspec.lock | 27 +- packages/veilid_support/pubspec.yaml | 8 +- pubspec.lock | 19 +- pubspec.yaml | 14 +- 84 files changed, 626 insertions(+), 3835 deletions(-) delete mode 100644 packages/async_tools/.gitignore delete mode 100644 packages/async_tools/analysis_options.yaml delete mode 100644 packages/async_tools/build.bat delete mode 100755 packages/async_tools/build.sh delete mode 100644 packages/async_tools/example/async_tools_example.dart delete mode 100644 packages/async_tools/lib/async_tools.dart delete mode 100644 packages/async_tools/lib/src/async_tag_lock.dart delete mode 100644 packages/async_tools/lib/src/async_value.dart delete mode 100644 packages/async_tools/lib/src/async_value.freezed.dart delete mode 100644 packages/async_tools/lib/src/delayed_wait_set.dart delete mode 100644 packages/async_tools/lib/src/serial_future.dart delete mode 100644 packages/async_tools/lib/src/single_future.dart delete mode 100644 packages/async_tools/lib/src/single_state_processor.dart delete mode 100644 packages/async_tools/lib/src/single_stateless_processor.dart delete mode 100644 packages/async_tools/lib/src/wait_set.dart delete mode 100644 packages/async_tools/pubspec.yaml delete mode 100644 packages/async_tools/test/async_tools_test.dart delete mode 100644 packages/bloc_tools/.gitignore delete mode 100644 packages/bloc_tools/analysis_options.yaml delete mode 100644 packages/bloc_tools/example/bloc_tools_example.dart delete mode 100644 packages/bloc_tools/lib/bloc_tools.dart delete mode 100644 packages/bloc_tools/lib/src/async_transformer_cubit.dart delete mode 100644 packages/bloc_tools/lib/src/bloc_busy_wrapper.dart delete mode 100644 packages/bloc_tools/lib/src/bloc_map_cubit.dart delete mode 100644 packages/bloc_tools/lib/src/bloc_tools_extension.dart delete mode 100644 packages/bloc_tools/lib/src/future_cubit.dart delete mode 100644 packages/bloc_tools/lib/src/state_map_follower.dart delete mode 100644 packages/bloc_tools/lib/src/stream_wrapper_cubit.dart delete mode 100644 packages/bloc_tools/lib/src/transformer_cubit.dart delete mode 100644 packages/bloc_tools/pubspec.yaml delete mode 100644 packages/bloc_tools/test/bloc_tools_test.dart delete mode 100644 packages/mutex/.gitignore delete mode 100644 packages/mutex/CHANGELOG.md delete mode 100644 packages/mutex/LICENSE delete mode 100644 packages/mutex/README.md delete mode 100644 packages/mutex/analysis_options.yaml delete mode 100644 packages/mutex/example/example.dart delete mode 100644 packages/mutex/lib/mutex.dart delete mode 100644 packages/mutex/lib/src/mutex.dart delete mode 100644 packages/mutex/lib/src/read_write_mutex.dart delete mode 100644 packages/mutex/pubspec.yaml delete mode 100644 packages/mutex/test/mutex_multiple_read_test.dart delete mode 100644 packages/mutex/test/mutex_readwrite_test.dart delete mode 100644 packages/mutex/test/mutex_test.dart delete mode 100644 packages/veilid_support/example/integration_test/fixtures.dart create mode 100644 packages/veilid_support/example/integration_test/fixtures/dht_record_pool_fixture.dart create mode 100644 packages/veilid_support/example/integration_test/fixtures/fixtures.dart diff --git a/lib/account_manager/repository/account_repository/account_repository.dart b/lib/account_manager/repository/account_repository/account_repository.dart index 777c79e..4691d52 100644 --- a/lib/account_manager/repository/account_repository/account_repository.dart +++ b/lib/account_manager/repository/account_repository/account_repository.dart @@ -400,7 +400,7 @@ class AccountRepository { // Record not yet open, do it final pool = DHTRecordPool.instance; - final record = await pool.openOwned( + final record = await pool.openRecordOwned( userLogin.accountRecordInfo.accountRecord, debugName: 'AccountRepository::openAccountRecord::AccountRecord', parent: localAccount.identityMaster.identityRecordKey); diff --git a/lib/chat/cubits/active_chat_cubit.dart b/lib/chat/cubits/active_chat_cubit.dart index b57076e..e47caec 100644 --- a/lib/chat/cubits/active_chat_cubit.dart +++ b/lib/chat/cubits/active_chat_cubit.dart @@ -1,8 +1,7 @@ -import 'package:bloc_tools/bloc_tools.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:veilid_support/veilid_support.dart'; -class ActiveChatCubit extends Cubit with BlocTools { +class ActiveChatCubit extends Cubit { ActiveChatCubit(super.initialState); void setActiveChat(TypedKey? activeChatRemoteConversationRecordKey) { diff --git a/lib/chat_list/cubits/active_conversations_bloc_map_cubit.dart b/lib/chat_list/cubits/active_conversations_bloc_map_cubit.dart index ff903ca..b221208 100644 --- a/lib/chat_list/cubits/active_conversations_bloc_map_cubit.dart +++ b/lib/chat_list/cubits/active_conversations_bloc_map_cubit.dart @@ -1,5 +1,5 @@ import 'package:async_tools/async_tools.dart'; -import 'package:bloc_tools/bloc_tools.dart'; +import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; import 'package:equatable/equatable.dart'; import 'package:meta/meta.dart'; import 'package:veilid_support/veilid_support.dart'; diff --git a/lib/chat_list/cubits/active_single_contact_chat_bloc_map_cubit.dart b/lib/chat_list/cubits/active_single_contact_chat_bloc_map_cubit.dart index 19ad150..d9ecb67 100644 --- a/lib/chat_list/cubits/active_single_contact_chat_bloc_map_cubit.dart +++ b/lib/chat_list/cubits/active_single_contact_chat_bloc_map_cubit.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:async_tools/async_tools.dart'; -import 'package:bloc_tools/bloc_tools.dart'; +import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; import 'package:veilid_support/veilid_support.dart'; import '../../account_manager/account_manager.dart'; diff --git a/lib/chat_list/cubits/chat_list_cubit.dart b/lib/chat_list/cubits/chat_list_cubit.dart index 4a0818a..5023d51 100644 --- a/lib/chat_list/cubits/chat_list_cubit.dart +++ b/lib/chat_list/cubits/chat_list_cubit.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:bloc_tools/bloc_tools.dart'; +import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:veilid_support/veilid_support.dart'; diff --git a/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart b/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart index a24005a..8e133b1 100644 --- a/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart +++ b/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:bloc_tools/bloc_tools.dart'; +import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:fixnum/fixnum.dart'; import 'package:flutter/foundation.dart'; @@ -85,7 +85,7 @@ class ContactInvitationListCubit // to and it will be eventually encrypted with the DH of the contact's // identity key late final Uint8List signedContactInvitationBytes; - await (await pool.create( + await (await pool.createRecord( debugName: 'ContactInvitationListCubit::createInvitation::' 'LocalConversation', parent: _activeAccountInfo.accountRecordKey, @@ -114,7 +114,7 @@ class ContactInvitationListCubit // Create DHT unicast inbox for ContactRequest // Subkey 0 is the ContactRequest from the initiator // Subkey 1 will contain the invitation response accept/reject eventually - await (await pool.create( + await (await pool.createRecord( debugName: 'ContactInvitationListCubit::createInvitation::' 'ContactRequestInbox', parent: _activeAccountInfo.accountRecordKey, @@ -198,7 +198,7 @@ class ContactInvitationListCubit if (success && deletedItem != null) { // Delete the contact request inbox final contactRequestInbox = deletedItem.contactRequestInbox.toVeilid(); - await (await pool.openOwned(contactRequestInbox, + await (await pool.openRecordOwned(contactRequestInbox, debugName: 'ContactInvitationListCubit::deleteInvitation::' 'ContactRequestInbox', parent: accountRecordKey)) @@ -250,7 +250,7 @@ class ContactInvitationListCubit contactRequestInboxKey) != -1; - await (await pool.openRead(contactRequestInboxKey, + await (await pool.openRecordRead(contactRequestInboxKey, debugName: 'ContactInvitationListCubit::validateInvitation::' 'ContactRequestInbox', parent: _activeAccountInfo.accountRecordKey)) diff --git a/lib/contact_invitation/cubits/contact_request_inbox_cubit.dart b/lib/contact_invitation/cubits/contact_request_inbox_cubit.dart index a8de4f8..a376881 100644 --- a/lib/contact_invitation/cubits/contact_request_inbox_cubit.dart +++ b/lib/contact_invitation/cubits/contact_request_inbox_cubit.dart @@ -34,7 +34,7 @@ class ContactRequestInboxCubit contactInvitationRecord.contactRequestInbox.recordKey.toVeilid(); final writer = TypedKeyPair( kind: recordKey.kind, key: writerKey, secret: writerSecret); - return pool.openRead(recordKey, + return pool.openRecordRead(recordKey, debugName: 'ContactRequestInboxCubit::_open::' 'ContactRequestInbox', crypto: await DHTRecordCryptoPrivate.fromTypedKeyPair(writer), diff --git a/lib/contact_invitation/cubits/invitation_generator_cubit.dart b/lib/contact_invitation/cubits/invitation_generator_cubit.dart index c6f7258..cd785fa 100644 --- a/lib/contact_invitation/cubits/invitation_generator_cubit.dart +++ b/lib/contact_invitation/cubits/invitation_generator_cubit.dart @@ -1,6 +1,6 @@ import 'dart:typed_data'; -import 'package:bloc_tools/bloc_tools.dart'; +import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; class InvitationGeneratorCubit extends FutureCubit { InvitationGeneratorCubit(super.fut); diff --git a/lib/contact_invitation/cubits/waiting_invitation_cubit.dart b/lib/contact_invitation/cubits/waiting_invitation_cubit.dart index 07c2d63..f8c639f 100644 --- a/lib/contact_invitation/cubits/waiting_invitation_cubit.dart +++ b/lib/contact_invitation/cubits/waiting_invitation_cubit.dart @@ -1,7 +1,7 @@ import 'dart:typed_data'; import 'package:async_tools/async_tools.dart'; -import 'package:bloc_tools/bloc_tools.dart'; +import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; import 'package:equatable/equatable.dart'; import 'package:meta/meta.dart'; import 'package:veilid_support/veilid_support.dart'; diff --git a/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart b/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart index 7c06bf7..968e108 100644 --- a/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart +++ b/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart @@ -1,5 +1,5 @@ import 'package:async_tools/async_tools.dart'; -import 'package:bloc_tools/bloc_tools.dart'; +import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; import 'package:veilid_support/veilid_support.dart'; import '../../account_manager/account_manager.dart'; diff --git a/lib/contact_invitation/models/valid_contact_invitation.dart b/lib/contact_invitation/models/valid_contact_invitation.dart index 639af7f..12aacef 100644 --- a/lib/contact_invitation/models/valid_contact_invitation.dart +++ b/lib/contact_invitation/models/valid_contact_invitation.dart @@ -37,7 +37,7 @@ class ValidContactInvitation { _activeAccountInfo.localAccount.identityMaster.identityPublicKey; final accountRecordKey = _activeAccountInfo.accountRecordKey; - return (await pool.openWrite(_contactRequestInboxKey, _writer, + return (await pool.openRecordWrite(_contactRequestInboxKey, _writer, debugName: 'ValidContactInvitation::accept::' 'ContactRequestInbox', parent: accountRecordKey)) @@ -100,7 +100,7 @@ class ValidContactInvitation { final accountRecordKey = _activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey; - return (await pool.openWrite(_contactRequestInboxKey, _writer, + return (await pool.openRecordWrite(_contactRequestInboxKey, _writer, debugName: 'ValidContactInvitation::reject::' 'ContactRequestInbox', parent: accountRecordKey)) diff --git a/lib/contacts/cubits/conversation_cubit.dart b/lib/contacts/cubits/conversation_cubit.dart index 7e23a99..6edb533 100644 --- a/lib/contacts/cubits/conversation_cubit.dart +++ b/lib/contacts/cubits/conversation_cubit.dart @@ -48,7 +48,7 @@ class ConversationCubit extends Cubit> { final pool = DHTRecordPool.instance; final crypto = await _cachedConversationCrypto(); final writer = _activeAccountInfo.conversationWriter; - final record = await pool.openWrite( + final record = await pool.openRecordWrite( _localConversationRecordKey!, writer, debugName: 'ConversationCubit::LocalConversation', parent: accountRecordKey, @@ -67,7 +67,7 @@ class ConversationCubit extends Cubit> { // Open remote record key if it is specified final pool = DHTRecordPool.instance; final crypto = await _cachedConversationCrypto(); - final record = await pool.openRead(_remoteConversationRecordKey, + final record = await pool.openRecordRead(_remoteConversationRecordKey, debugName: 'ConversationCubit::RemoteConversation', parent: accountRecordKey, crypto: crypto); @@ -226,14 +226,14 @@ class ConversationCubit extends Cubit> { // Open with SMPL scheme for identity writer late final DHTRecord localConversationRecord; if (existingConversationRecordKey != null) { - localConversationRecord = await pool.openWrite( + localConversationRecord = await pool.openRecordWrite( existingConversationRecordKey, writer, debugName: 'ConversationCubit::initLocalConversation::LocalConversation', parent: accountRecordKey, crypto: crypto); } else { - localConversationRecord = await pool.create( + localConversationRecord = await pool.createRecord( debugName: 'ConversationCubit::initLocalConversation::LocalConversation', parent: accountRecordKey, diff --git a/lib/layout/home/home_account_ready/home_account_ready_shell.dart b/lib/layout/home/home_account_ready/home_account_ready_shell.dart index 4ff44d2..bf98bba 100644 --- a/lib/layout/home/home_account_ready/home_account_ready_shell.dart +++ b/lib/layout/home/home_account_ready/home_account_ready_shell.dart @@ -1,8 +1,8 @@ import 'package:async_tools/async_tools.dart'; +import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:provider/provider.dart'; import 'package:veilid_support/veilid_support.dart'; import '../../../account_manager/account_manager.dart'; diff --git a/lib/settings/preferences_cubit.dart b/lib/settings/preferences_cubit.dart index 6cfd249..f96d755 100644 --- a/lib/settings/preferences_cubit.dart +++ b/lib/settings/preferences_cubit.dart @@ -1,4 +1,4 @@ -import 'package:bloc_tools/bloc_tools.dart'; +import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; import 'settings.dart'; diff --git a/lib/theme/views/widget_helpers.dart b/lib/theme/views/widget_helpers.dart index 7cef561..6edca24 100644 --- a/lib/theme/views/widget_helpers.dart +++ b/lib/theme/views/widget_helpers.dart @@ -1,6 +1,6 @@ import 'package:async_tools/async_tools.dart'; import 'package:awesome_extensions/awesome_extensions.dart'; -import 'package:bloc_tools/bloc_tools.dart'; +import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; import 'package:blurry_modal_progress_hud/blurry_modal_progress_hud.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/lib/tick.dart b/lib/tick.dart index 197a1d6..8ec3db7 100644 --- a/lib/tick.dart +++ b/lib/tick.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:async_tools/async_tools.dart'; import 'package:flutter/material.dart'; import 'package:veilid_support/veilid_support.dart'; @@ -16,15 +17,12 @@ class BackgroundTicker extends StatefulWidget { class BackgroundTickerState extends State { Timer? _tickTimer; - bool _inTick = false; @override void initState() { super.initState(); _tickTimer = Timer.periodic(const Duration(seconds: 1), (timer) { - if (!_inTick) { - unawaited(_onTick()); - } + singleFuture(this, _onTick); }); } @@ -50,12 +48,7 @@ class BackgroundTickerState extends State { return; } - _inTick = true; - try { - // Tick DHT record pool - unawaited(DHTRecordPool.instance.tick()); - } finally { - _inTick = false; - } + // Tick DHT record pool + await DHTRecordPool.instance.tick(); } } diff --git a/lib/veilid_processor/cubit/connection_state_cubit.dart b/lib/veilid_processor/cubit/connection_state_cubit.dart index 8bf18f8..0bbfdc2 100644 --- a/lib/veilid_processor/cubit/connection_state_cubit.dart +++ b/lib/veilid_processor/cubit/connection_state_cubit.dart @@ -1,4 +1,4 @@ -import 'package:bloc_tools/bloc_tools.dart'; +import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; import '../models/models.dart'; import '../repository/processor_repository.dart'; diff --git a/packages/async_tools/.gitignore b/packages/async_tools/.gitignore deleted file mode 100644 index 3cceda5..0000000 --- a/packages/async_tools/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# https://dart.dev/guides/libraries/private-files -# Created by `dart pub` -.dart_tool/ - -# Avoid committing pubspec.lock for library packages; see -# https://dart.dev/guides/libraries/private-files#pubspeclock. -pubspec.lock diff --git a/packages/async_tools/analysis_options.yaml b/packages/async_tools/analysis_options.yaml deleted file mode 100644 index e1620f7..0000000 --- a/packages/async_tools/analysis_options.yaml +++ /dev/null @@ -1,15 +0,0 @@ -include: package:lint_hard/all.yaml -analyzer: - errors: - invalid_annotation_target: ignore - exclude: - - '**/*.g.dart' - - '**/*.freezed.dart' - - '**/*.pb.dart' - - '**/*.pbenum.dart' - - '**/*.pbjson.dart' - - '**/*.pbserver.dart' -linter: - rules: - unawaited_futures: true - avoid_positional_boolean_parameters: false \ No newline at end of file diff --git a/packages/async_tools/build.bat b/packages/async_tools/build.bat deleted file mode 100644 index 0e2e698..0000000 --- a/packages/async_tools/build.bat +++ /dev/null @@ -1,2 +0,0 @@ -@echo off -dart run build_runner build --delete-conflicting-outputs diff --git a/packages/async_tools/build.sh b/packages/async_tools/build.sh deleted file mode 100755 index 2a76503..0000000 --- a/packages/async_tools/build.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -set -e -dart run build_runner build --delete-conflicting-outputs diff --git a/packages/async_tools/example/async_tools_example.dart b/packages/async_tools/example/async_tools_example.dart deleted file mode 100644 index 33a41ab..0000000 --- a/packages/async_tools/example/async_tools_example.dart +++ /dev/null @@ -1,6 +0,0 @@ -// import 'package:async_tools/async_tools.dart'; - -// void main() { -// var awesome = Awesome(); -// print('awesome: ${awesome.isAwesome}'); -// } diff --git a/packages/async_tools/lib/async_tools.dart b/packages/async_tools/lib/async_tools.dart deleted file mode 100644 index 6438ff2..0000000 --- a/packages/async_tools/lib/async_tools.dart +++ /dev/null @@ -1,11 +0,0 @@ -/// Async Tools -library; - -export 'src/async_tag_lock.dart'; -export 'src/async_value.dart'; -export 'src/delayed_wait_set.dart'; -export 'src/serial_future.dart'; -export 'src/single_future.dart'; -export 'src/single_state_processor.dart'; -export 'src/single_stateless_processor.dart'; -export 'src/wait_set.dart'; diff --git a/packages/async_tools/lib/src/async_tag_lock.dart b/packages/async_tools/lib/src/async_tag_lock.dart deleted file mode 100644 index a0f6117..0000000 --- a/packages/async_tools/lib/src/async_tag_lock.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:mutex/mutex.dart'; - -class _AsyncTagLockEntry { - _AsyncTagLockEntry() - : mutex = Mutex.locked(), - waitingCount = 0; - // - Mutex mutex; - int waitingCount; -} - -class AsyncTagLock { - AsyncTagLock() - : _tableLock = Mutex(), - _locks = {}; - - Future lockTag(T tag) async { - await _tableLock.protect(() async { - final lockEntry = _locks[tag]; - if (lockEntry != null) { - lockEntry.waitingCount++; - await lockEntry.mutex.acquire(); - lockEntry.waitingCount--; - } else { - _locks[tag] = _AsyncTagLockEntry(); - } - }); - } - - bool isLocked(T tag) => _locks.containsKey(tag); - - bool tryLock(T tag) { - final lockEntry = _locks[tag]; - if (lockEntry != null) { - return false; - } - _locks[tag] = _AsyncTagLockEntry(); - return true; - } - - void unlockTag(T tag) { - final lockEntry = _locks[tag]!; - if (lockEntry.waitingCount == 0) { - // If nobody is waiting for the mutex we can just drop it - _locks.remove(tag); - } else { - // Someone's waiting for the tag lock so release the mutex for it - lockEntry.mutex.release(); - } - } - - Future protect(T tag, {required Future Function() closure}) async { - await lockTag(tag); - try { - return await closure(); - } finally { - unlockTag(tag); - } - } - - // - final Mutex _tableLock; - final Map _locks; -} diff --git a/packages/async_tools/lib/src/async_value.dart b/packages/async_tools/lib/src/async_value.dart deleted file mode 100644 index cc3bd9b..0000000 --- a/packages/async_tools/lib/src/async_value.dart +++ /dev/null @@ -1,209 +0,0 @@ -// ignore_for_file: avoid_catches_without_on_clauses - -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'async_value.freezed.dart'; - -/// An utility for safely manipulating asynchronous data. -/// -/// By using [AsyncValue], you are guaranteed that you cannot forget to -/// handle the loading/error state of an asynchronous operation. -/// -/// It also expose some utilities to nicely convert an [AsyncValue] to -/// a different object. -/// For example, a Flutter Widget may use [when] to convert an [AsyncValue] -/// into either a progress indicator, an error screen, or to show the data: -/// -/// ```dart -/// /// A provider that asynchronously expose the current user -/// final userProvider = StreamProvider((_) async* { -/// // fetch the user -/// }); -/// -/// class Example extends ConsumerWidget { -/// @override -/// Widget build(BuildContext context, ScopedReader watch) { -/// final AsyncValue user = watch(userProvider); -/// -/// return user.when( -/// loading: () => CircularProgressIndicator(), -/// error: (error, stack) => Text('Oops, something unexpected happened'), -/// data: (value) => Text('Hello ${user.name}'), -/// ); -/// } -/// } -/// ``` -/// -/// If a consumer of an [AsyncValue] does not care about the loading/error -/// state, consider using [asData] to read the state: -/// -/// ```dart -/// Widget build(BuildContext context, ScopedReader watch) { -/// // reads the data state directly – will be null during loading/error states -/// final User user = watch(userProvider).data?.value; -/// -/// return Text('Hello ${user?.name}'); -/// } -/// ``` -/// -/// See also: -/// -/// - [AsyncValue.guard], to simplify transforming a [Future] into an -/// [AsyncValue]. -/// - The package Freezed (https://github.com/rrousselgit/freezed), which have -/// generated this [AsyncValue] class and explains how [map]/[when] works. -@freezed -@sealed -abstract class AsyncValue with _$AsyncValue { - const AsyncValue._(); - - /// Creates an [AsyncValue] with a data. - /// - /// The data can be `null`. - const factory AsyncValue.data(T value) = AsyncData; - - /// Creates an [AsyncValue] in loading state. - /// - /// Prefer always using this constructor with the `const` keyword. - const factory AsyncValue.loading() = AsyncLoading; - - /// Creates an [AsyncValue] in error state. - /// - /// The parameter [error] cannot be `null`. - factory AsyncValue.error(Object error, [StackTrace? stackTrace]) = - AsyncError; - - /// Transforms a [Future] that may fail into something that is safe to read. - /// - /// This is useful to avoid having to do a tedious `try/catch`. Instead of: - /// - /// ```dart - /// class MyNotifier extends StateNotifier { - /// MyNotifier(): super(const AsyncValue.loading()) { - /// _fetchData(); - /// } - /// - /// Future _fetchData() async { - /// state = const AsyncValue.loading(); - /// try { - /// final response = await dio.get('my_api/data'); - /// final data = MyData.fromJson(response); - /// state = AsyncValue.data(data); - /// } catch (err, stack) { - /// state = AsyncValue.error(err, stack); - /// } - /// } - /// } - /// ``` - /// - /// which is redundant as the application grows and we need more and more of - /// this pattern – we can use [guard] to simplify it: - /// - /// - /// ```dart - /// class MyNotifier extends StateNotifier> { - /// MyNotifier(): super(const AsyncValue.loading()) { - /// _fetchData(); - /// } - /// - /// Future _fetchData() async { - /// state = const AsyncValue.loading(); - /// // does the try/catch for us like previously - /// state = await AsyncValue.guard(() async { - /// final response = await dio.get('my_api/data'); - /// return Data.fromJson(response); - /// }); - /// } - /// } - /// ``` - static Future> guard(Future Function() future) async { - try { - return AsyncValue.data(await future()); - } catch (err, stack) { - return AsyncValue.error(err, stack); - } - } - - /// The current data, or null if in loading/error. - /// - /// This is safe to use, as Dart (will) have non-nullable types. - /// As such reading [asData] still forces to handle the loading/error cases - /// by having to check `data != null`. - /// - /// ## Why does [AsyncValue.data] return [AsyncData] instead of [T]? - /// - /// The motivation behind this decision is to allow differentiating between: - /// - /// - There is a data, and it is `null`. - /// ```dart - /// // There is a data, and it is "null" - /// AsyncValue configs = AsyncValue.data(null); - /// - /// print(configs.data); // AsyncValue(value: null) - /// print(configs.data.value); // null - /// ``` - /// - /// - There is no data. [AsyncValue] is currently in loading/error state. - /// ```dart - /// // No data, currently loading - /// AsyncValue configs = AsyncValue.loading(); - /// - /// print(configs.data); // null, currently loading - /// print(configs.data.value); // throws null exception - /// ``` - AsyncData? get asData => map( - data: (data) => data, - loading: (_) => null, - error: (_) => null, - ); - - bool get isData => asData != null; - - /// Check if this is loading - AsyncLoading? get asLoading => map( - data: (_) => null, - loading: (loading) => loading, - error: (_) => null, - ); - - bool get isLoading => asLoading != null; - - /// Check if this is an error - AsyncError? get asError => map( - data: (_) => null, - loading: (_) => null, - error: (e) => e, - ); - - bool get isError => asError != null; - - /// Shorthand for [when] to handle only the `data` case. - AsyncValue whenData(R Function(T value) cb) => when( - data: (value) { - try { - return AsyncValue.data(cb(value)); - } catch (err, stack) { - return AsyncValue.error(err, stack); - } - }, - loading: () => const AsyncValue.loading(), - error: AsyncValue.error, - ); - - /// Check two AsyncData instances for equality - bool equalsData(AsyncValue other, - {required bool Function(T a, T b) equals}) => - other.when( - data: (nd) => when( - data: (d) => equals(d, nd), - loading: () => true, - error: (_e, _st) => true), - loading: () => when( - data: (_) => true, - loading: () => false, - error: (_e, _st) => true), - error: (ne, nst) => when( - data: (_) => true, - loading: () => true, - error: (e, st) => e != ne || st != nst)); -} diff --git a/packages/async_tools/lib/src/async_value.freezed.dart b/packages/async_tools/lib/src/async_value.freezed.dart deleted file mode 100644 index b6911e2..0000000 --- a/packages/async_tools/lib/src/async_value.freezed.dart +++ /dev/null @@ -1,480 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'async_value.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -/// @nodoc -mixin _$AsyncValue { - @optionalTypeArgs - TResult when({ - required TResult Function(T value) data, - required TResult Function() loading, - required TResult Function(Object error, StackTrace? stackTrace) error, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(T value)? data, - TResult? Function()? loading, - TResult? Function(Object error, StackTrace? stackTrace)? error, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(T value)? data, - TResult Function()? loading, - TResult Function(Object error, StackTrace? stackTrace)? error, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult map({ - required TResult Function(AsyncData value) data, - required TResult Function(AsyncLoading value) loading, - required TResult Function(AsyncError value) error, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(AsyncData value)? data, - TResult? Function(AsyncLoading value)? loading, - TResult? Function(AsyncError value)? error, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeMap({ - TResult Function(AsyncData value)? data, - TResult Function(AsyncLoading value)? loading, - TResult Function(AsyncError value)? error, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $AsyncValueCopyWith { - factory $AsyncValueCopyWith( - AsyncValue value, $Res Function(AsyncValue) then) = - _$AsyncValueCopyWithImpl>; -} - -/// @nodoc -class _$AsyncValueCopyWithImpl> - implements $AsyncValueCopyWith { - _$AsyncValueCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; -} - -/// @nodoc -abstract class _$$AsyncDataImplCopyWith { - factory _$$AsyncDataImplCopyWith( - _$AsyncDataImpl value, $Res Function(_$AsyncDataImpl) then) = - __$$AsyncDataImplCopyWithImpl; - @useResult - $Res call({T value}); -} - -/// @nodoc -class __$$AsyncDataImplCopyWithImpl - extends _$AsyncValueCopyWithImpl> - implements _$$AsyncDataImplCopyWith { - __$$AsyncDataImplCopyWithImpl( - _$AsyncDataImpl _value, $Res Function(_$AsyncDataImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? value = freezed, - }) { - return _then(_$AsyncDataImpl( - freezed == value - ? _value.value - : value // ignore: cast_nullable_to_non_nullable - as T, - )); - } -} - -/// @nodoc - -class _$AsyncDataImpl extends AsyncData { - const _$AsyncDataImpl(this.value) : super._(); - - @override - final T value; - - @override - String toString() { - return 'AsyncValue<$T>.data(value: $value)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$AsyncDataImpl && - const DeepCollectionEquality().equals(other.value, value)); - } - - @override - int get hashCode => - Object.hash(runtimeType, const DeepCollectionEquality().hash(value)); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$AsyncDataImplCopyWith> get copyWith => - __$$AsyncDataImplCopyWithImpl>(this, _$identity); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function(T value) data, - required TResult Function() loading, - required TResult Function(Object error, StackTrace? stackTrace) error, - }) { - return data(value); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(T value)? data, - TResult? Function()? loading, - TResult? Function(Object error, StackTrace? stackTrace)? error, - }) { - return data?.call(value); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(T value)? data, - TResult Function()? loading, - TResult Function(Object error, StackTrace? stackTrace)? error, - required TResult orElse(), - }) { - if (data != null) { - return data(value); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(AsyncData value) data, - required TResult Function(AsyncLoading value) loading, - required TResult Function(AsyncError value) error, - }) { - return data(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(AsyncData value)? data, - TResult? Function(AsyncLoading value)? loading, - TResult? Function(AsyncError value)? error, - }) { - return data?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(AsyncData value)? data, - TResult Function(AsyncLoading value)? loading, - TResult Function(AsyncError value)? error, - required TResult orElse(), - }) { - if (data != null) { - return data(this); - } - return orElse(); - } -} - -abstract class AsyncData extends AsyncValue { - const factory AsyncData(final T value) = _$AsyncDataImpl; - const AsyncData._() : super._(); - - T get value; - @JsonKey(ignore: true) - _$$AsyncDataImplCopyWith> get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class _$$AsyncLoadingImplCopyWith { - factory _$$AsyncLoadingImplCopyWith(_$AsyncLoadingImpl value, - $Res Function(_$AsyncLoadingImpl) then) = - __$$AsyncLoadingImplCopyWithImpl; -} - -/// @nodoc -class __$$AsyncLoadingImplCopyWithImpl - extends _$AsyncValueCopyWithImpl> - implements _$$AsyncLoadingImplCopyWith { - __$$AsyncLoadingImplCopyWithImpl( - _$AsyncLoadingImpl _value, $Res Function(_$AsyncLoadingImpl) _then) - : super(_value, _then); -} - -/// @nodoc - -class _$AsyncLoadingImpl extends AsyncLoading { - const _$AsyncLoadingImpl() : super._(); - - @override - String toString() { - return 'AsyncValue<$T>.loading()'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && other is _$AsyncLoadingImpl); - } - - @override - int get hashCode => runtimeType.hashCode; - - @override - @optionalTypeArgs - TResult when({ - required TResult Function(T value) data, - required TResult Function() loading, - required TResult Function(Object error, StackTrace? stackTrace) error, - }) { - return loading(); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(T value)? data, - TResult? Function()? loading, - TResult? Function(Object error, StackTrace? stackTrace)? error, - }) { - return loading?.call(); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(T value)? data, - TResult Function()? loading, - TResult Function(Object error, StackTrace? stackTrace)? error, - required TResult orElse(), - }) { - if (loading != null) { - return loading(); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(AsyncData value) data, - required TResult Function(AsyncLoading value) loading, - required TResult Function(AsyncError value) error, - }) { - return loading(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(AsyncData value)? data, - TResult? Function(AsyncLoading value)? loading, - TResult? Function(AsyncError value)? error, - }) { - return loading?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(AsyncData value)? data, - TResult Function(AsyncLoading value)? loading, - TResult Function(AsyncError value)? error, - required TResult orElse(), - }) { - if (loading != null) { - return loading(this); - } - return orElse(); - } -} - -abstract class AsyncLoading extends AsyncValue { - const factory AsyncLoading() = _$AsyncLoadingImpl; - const AsyncLoading._() : super._(); -} - -/// @nodoc -abstract class _$$AsyncErrorImplCopyWith { - factory _$$AsyncErrorImplCopyWith( - _$AsyncErrorImpl value, $Res Function(_$AsyncErrorImpl) then) = - __$$AsyncErrorImplCopyWithImpl; - @useResult - $Res call({Object error, StackTrace? stackTrace}); -} - -/// @nodoc -class __$$AsyncErrorImplCopyWithImpl - extends _$AsyncValueCopyWithImpl> - implements _$$AsyncErrorImplCopyWith { - __$$AsyncErrorImplCopyWithImpl( - _$AsyncErrorImpl _value, $Res Function(_$AsyncErrorImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? error = null, - Object? stackTrace = freezed, - }) { - return _then(_$AsyncErrorImpl( - null == error ? _value.error : error, - freezed == stackTrace - ? _value.stackTrace - : stackTrace // ignore: cast_nullable_to_non_nullable - as StackTrace?, - )); - } -} - -/// @nodoc - -class _$AsyncErrorImpl extends AsyncError { - _$AsyncErrorImpl(this.error, [this.stackTrace]) : super._(); - - @override - final Object error; - @override - final StackTrace? stackTrace; - - @override - String toString() { - return 'AsyncValue<$T>.error(error: $error, stackTrace: $stackTrace)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$AsyncErrorImpl && - const DeepCollectionEquality().equals(other.error, error) && - (identical(other.stackTrace, stackTrace) || - other.stackTrace == stackTrace)); - } - - @override - int get hashCode => Object.hash( - runtimeType, const DeepCollectionEquality().hash(error), stackTrace); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$AsyncErrorImplCopyWith> get copyWith => - __$$AsyncErrorImplCopyWithImpl>(this, _$identity); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function(T value) data, - required TResult Function() loading, - required TResult Function(Object error, StackTrace? stackTrace) error, - }) { - return error(this.error, stackTrace); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(T value)? data, - TResult? Function()? loading, - TResult? Function(Object error, StackTrace? stackTrace)? error, - }) { - return error?.call(this.error, stackTrace); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(T value)? data, - TResult Function()? loading, - TResult Function(Object error, StackTrace? stackTrace)? error, - required TResult orElse(), - }) { - if (error != null) { - return error(this.error, stackTrace); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(AsyncData value) data, - required TResult Function(AsyncLoading value) loading, - required TResult Function(AsyncError value) error, - }) { - return error(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(AsyncData value)? data, - TResult? Function(AsyncLoading value)? loading, - TResult? Function(AsyncError value)? error, - }) { - return error?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(AsyncData value)? data, - TResult Function(AsyncLoading value)? loading, - TResult Function(AsyncError value)? error, - required TResult orElse(), - }) { - if (error != null) { - return error(this); - } - return orElse(); - } -} - -abstract class AsyncError extends AsyncValue { - factory AsyncError(final Object error, [final StackTrace? stackTrace]) = - _$AsyncErrorImpl; - AsyncError._() : super._(); - - Object get error; - StackTrace? get stackTrace; - @JsonKey(ignore: true) - _$$AsyncErrorImplCopyWith> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/packages/async_tools/lib/src/delayed_wait_set.dart b/packages/async_tools/lib/src/delayed_wait_set.dart deleted file mode 100644 index 75223dc..0000000 --- a/packages/async_tools/lib/src/delayed_wait_set.dart +++ /dev/null @@ -1,18 +0,0 @@ -class DelayedWaitSet { - DelayedWaitSet(); - - void add(Future Function() closure) { - _closures.add(closure); - } - - Future call() async { - final futures = _closures.map((c) => c()).toList(); - _closures = []; - if (futures.isEmpty) { - return; - } - await futures.wait; - } - - List Function()> _closures = []; -} diff --git a/packages/async_tools/lib/src/serial_future.dart b/packages/async_tools/lib/src/serial_future.dart deleted file mode 100644 index 17225b7..0000000 --- a/packages/async_tools/lib/src/serial_future.dart +++ /dev/null @@ -1,57 +0,0 @@ -// Process a single future at a time per tag queued serially -// -// The closure function is called to produce the future that is to be executed. -// If a future with a particular tag is still executing, it is queued serially -// and executed when the previous tagged future completes. -// When a tagged serialFuture finishes executing, the onDone callback is called. -// If an unhandled exception happens in the closure future, the onError callback -// is called. - -import 'dart:async'; -import 'dart:collection'; - -import 'async_tag_lock.dart'; - -AsyncTagLock _keys = AsyncTagLock(); -typedef SerialFutureQueueItem = Future Function(); -Map> _queues = {}; - -SerialFutureQueueItem _makeSerialFutureQueueItem( - Future Function() closure, - void Function(T)? onDone, - void Function(Object e, StackTrace? st)? onError) => - () async { - try { - final out = await closure(); - if (onDone != null) { - onDone(out); - } - // ignore: avoid_catches_without_on_clauses - } catch (e, sp) { - if (onError != null) { - onError(e, sp); - } else { - rethrow; - } - } - }; - -void serialFuture(Object tag, Future Function() closure, - {void Function(T)? onDone, - void Function(Object e, StackTrace? st)? onError}) { - final queueItem = _makeSerialFutureQueueItem(closure, onDone, onError); - if (!_keys.tryLock(tag)) { - final queue = _queues[tag]; - queue!.add(queueItem); - return; - } - final queue = _queues[tag] = Queue.from([queueItem]); - unawaited(() async { - do { - final queueItem = queue.removeFirst(); - await queueItem(); - } while (queue.isNotEmpty); - _queues.remove(tag); - _keys.unlockTag(tag); - }()); -} diff --git a/packages/async_tools/lib/src/single_future.dart b/packages/async_tools/lib/src/single_future.dart deleted file mode 100644 index 11f6005..0000000 --- a/packages/async_tools/lib/src/single_future.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'dart:async'; - -import 'async_tag_lock.dart'; - -AsyncTagLock _keys = AsyncTagLock(); - -/// Process a single future at a time per tag -/// -/// The closure function is called to produce the future that is to be executed. -/// If a future with a particular tag is still executing, the onBusy callback -/// is called. -/// When a tagged singleFuture finishes executing, the onDone callback is called. -/// If an unhandled exception happens in the closure future, the onError callback -/// is called. -void singleFuture(Object tag, Future Function() closure, - {void Function()? onBusy, - void Function(T)? onDone, - void Function(Object e, StackTrace? st)? onError}) { - if (!_keys.tryLock(tag)) { - if (onBusy != null) { - onBusy(); - } - return; - } - unawaited(() async { - try { - final out = await closure(); - if (onDone != null) { - onDone(out); - } - // ignore: avoid_catches_without_on_clauses - } catch (e, sp) { - if (onError != null) { - onError(e, sp); - } else { - rethrow; - } - } finally { - _keys.unlockTag(tag); - } - }()); -} - -Future singleFuturePause(Object tag) async => _keys.lockTag(tag); -void singleFutureResume(Object tag) => _keys.unlockTag(tag); diff --git a/packages/async_tools/lib/src/single_state_processor.dart b/packages/async_tools/lib/src/single_state_processor.dart deleted file mode 100644 index 098238c..0000000 --- a/packages/async_tools/lib/src/single_state_processor.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'dart:async'; - -import '../async_tools.dart'; - -// Process a single state update at a time ensuring the most -// recent state gets processed asynchronously, possibly skipping -// states that happen while a previous state is still being processed. -// -// Eventually this will always process the most recent state passed to -// updateState. -// -// This is useful for processing state changes asynchronously without waiting -// from a synchronous execution context -class SingleStateProcessor { - SingleStateProcessor(); - - void updateState(State newInputState, Future Function(State) closure) { - // Use a singlefuture here to ensure we get dont lose any updates - // If the input stream gives us an update while we are - // still processing the last update, the most recent input state will - // be saved and processed eventually. - - singleFuture(this, () async { - var newState = newInputState; - var newClosure = closure; - var done = false; - while (!done) { - await newClosure(newState); - - // See if there's another state change to process - final nextState = _nextState; - final nextClosure = _nextClosure; - _nextState = null; - _nextClosure = null; - if (nextState != null) { - newState = nextState; - newClosure = nextClosure!; - } else { - done = true; - } - } - }, onBusy: () { - // Keep this state until we process again - _nextState = newInputState; - _nextClosure = closure; - }); - } - - Future pause() => singleFuturePause(this); - Future resume() async { - // Process any next state before resuming the singlefuture - try { - final nextState = _nextState; - final nextClosure = _nextClosure; - _nextState = null; - _nextClosure = null; - if (nextState != null) { - await nextClosure!(nextState); - } - } finally { - singleFutureResume(this); - } - } - - State? _nextState; - Future Function(State)? _nextClosure; -} diff --git a/packages/async_tools/lib/src/single_stateless_processor.dart b/packages/async_tools/lib/src/single_stateless_processor.dart deleted file mode 100644 index 7cb7ff0..0000000 --- a/packages/async_tools/lib/src/single_stateless_processor.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'dart:async'; - -import '../async_tools.dart'; - -// Process a single stateless update at a time ensuring each request -// gets processed asynchronously, and continuously while update is requested. -// -// This is useful for processing updates asynchronously without waiting -// from a synchronous execution context -class SingleStatelessProcessor { - SingleStatelessProcessor(); - - void update(Future Function() closure) { - singleFuture(this, () async { - do { - _more = false; - await closure(); - - // See if another update was requested - } while (_more); - }, onBusy: () { - // Keep this state until we process again - _more = true; - }); - } - - // Like update, but with a busy wrapper that - // clears once the updating is finished - void busyUpdate( - Future Function(Future Function(void Function(S))) busy, - Future Function(void Function(S)) closure) { - singleFuture( - this, - () async => busy((emit) async { - do { - _more = false; - await closure(emit); - - // See if another update was requested - } while (_more); - }), onBusy: () { - // Keep this state until we process again - _more = true; - }); - } - - bool _more = false; -} diff --git a/packages/async_tools/lib/src/wait_set.dart b/packages/async_tools/lib/src/wait_set.dart deleted file mode 100644 index ae79ba9..0000000 --- a/packages/async_tools/lib/src/wait_set.dart +++ /dev/null @@ -1,18 +0,0 @@ -class WaitSet { - WaitSet(); - - void add(Future Function() closure) { - _futures.add(Future.delayed(Duration.zero, closure)); - } - - Future call() async { - final futures = _futures; - _futures = []; - if (futures.isEmpty) { - return; - } - await futures.wait; - } - - List> _futures = []; -} diff --git a/packages/async_tools/pubspec.yaml b/packages/async_tools/pubspec.yaml deleted file mode 100644 index 42d7a71..0000000 --- a/packages/async_tools/pubspec.yaml +++ /dev/null @@ -1,19 +0,0 @@ -name: async_tools -description: Useful data structures and tools for async/Future code -version: 1.0.0 -publish_to: none - -environment: - sdk: '>=3.2.0 <4.0.0' - -# Add regular dependencies here. -dependencies: - freezed_annotation: ^2.4.1 - mutex: - path: ../mutex - -dev_dependencies: - build_runner: ^2.4.8 - freezed: ^2.4.7 - lint_hard: ^4.0.0 - test: ^1.25.2 diff --git a/packages/async_tools/test/async_tools_test.dart b/packages/async_tools/test/async_tools_test.dart deleted file mode 100644 index 0d54797..0000000 --- a/packages/async_tools/test/async_tools_test.dart +++ /dev/null @@ -1,16 +0,0 @@ -// import 'package:async_tools/async_tools.dart'; -// import 'package:test/test.dart'; - -// void main() { -// group('A group of tests', () { -// final awesome = Awesome(); - -// setUp(() { -// // Additional setup goes here. -// }); - -// test('First Test', () { -// expect(awesome.isAwesome, isTrue); -// }); -// }); -// } diff --git a/packages/bloc_tools/.gitignore b/packages/bloc_tools/.gitignore deleted file mode 100644 index 3cceda5..0000000 --- a/packages/bloc_tools/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# https://dart.dev/guides/libraries/private-files -# Created by `dart pub` -.dart_tool/ - -# Avoid committing pubspec.lock for library packages; see -# https://dart.dev/guides/libraries/private-files#pubspeclock. -pubspec.lock diff --git a/packages/bloc_tools/analysis_options.yaml b/packages/bloc_tools/analysis_options.yaml deleted file mode 100644 index e1620f7..0000000 --- a/packages/bloc_tools/analysis_options.yaml +++ /dev/null @@ -1,15 +0,0 @@ -include: package:lint_hard/all.yaml -analyzer: - errors: - invalid_annotation_target: ignore - exclude: - - '**/*.g.dart' - - '**/*.freezed.dart' - - '**/*.pb.dart' - - '**/*.pbenum.dart' - - '**/*.pbjson.dart' - - '**/*.pbserver.dart' -linter: - rules: - unawaited_futures: true - avoid_positional_boolean_parameters: false \ No newline at end of file diff --git a/packages/bloc_tools/example/bloc_tools_example.dart b/packages/bloc_tools/example/bloc_tools_example.dart deleted file mode 100644 index 25e6326..0000000 --- a/packages/bloc_tools/example/bloc_tools_example.dart +++ /dev/null @@ -1,6 +0,0 @@ -// import 'package:bloc_tools/bloc_tools.dart'; - -// void main() { -// var awesome = Awesome(); -// print('awesome: ${awesome.isAwesome}'); -// } diff --git a/packages/bloc_tools/lib/bloc_tools.dart b/packages/bloc_tools/lib/bloc_tools.dart deleted file mode 100644 index 507ae58..0000000 --- a/packages/bloc_tools/lib/bloc_tools.dart +++ /dev/null @@ -1,11 +0,0 @@ -/// BLoC Tools -library; - -export 'src/async_transformer_cubit.dart'; -export 'src/bloc_busy_wrapper.dart'; -export 'src/bloc_map_cubit.dart'; -export 'src/bloc_tools_extension.dart'; -export 'src/future_cubit.dart'; -export 'src/state_map_follower.dart'; -export 'src/stream_wrapper_cubit.dart'; -export 'src/transformer_cubit.dart'; diff --git a/packages/bloc_tools/lib/src/async_transformer_cubit.dart b/packages/bloc_tools/lib/src/async_transformer_cubit.dart deleted file mode 100644 index f31659d..0000000 --- a/packages/bloc_tools/lib/src/async_transformer_cubit.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'dart:async'; - -import 'package:async_tools/async_tools.dart'; -import 'package:bloc/bloc.dart'; - -// A cubit with state T that wraps another input cubit of state S and -// produces T fro S via an asynchronous transform closure -// The input cubit becomes 'owned' by the AsyncTransformerCubit and will -// be closed when the AsyncTransformerCubit closes. - -class AsyncTransformerCubit extends Cubit> { - AsyncTransformerCubit(this.input, {required this.transform}) - : super(const AsyncValue.loading()) { - _asyncTransform(input.state); - _subscription = input.stream.listen(_asyncTransform); - } - - void _asyncTransform(AsyncValue newInputState) { - _singleStateProcessor.updateState(newInputState, (newState) async { - // Emit the transformed state - try { - if (newState is AsyncLoading) { - emit(const AsyncValue.loading()); - } else if (newState is AsyncError) { - emit(AsyncValue.error(newState.error, newState.stackTrace)); - } else { - final transformedState = await transform(newState.asData!.value); - emit(transformedState); - } - } on Exception catch (e, st) { - emit(AsyncValue.error(e, st)); - } - }); - } - - @override - Future close() async { - await _subscription.cancel(); - await input.close(); - await super.close(); - } - - Cubit> input; - final SingleStateProcessor> _singleStateProcessor = - SingleStateProcessor(); - Future> Function(S) transform; - late final StreamSubscription> _subscription; -} diff --git a/packages/bloc_tools/lib/src/bloc_busy_wrapper.dart b/packages/bloc_tools/lib/src/bloc_busy_wrapper.dart deleted file mode 100644 index b6811f2..0000000 --- a/packages/bloc_tools/lib/src/bloc_busy_wrapper.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'dart:async'; - -import 'package:bloc/bloc.dart'; -import 'package:equatable/equatable.dart'; -import 'package:meta/meta.dart'; -import 'package:mutex/mutex.dart'; - -@immutable -class BlocBusyState extends Equatable { - const BlocBusyState(this.state) : busy = false; - const BlocBusyState._busy(this.state) : busy = true; - final bool busy; - final S state; - - @override - List get props => [busy, state]; -} - -mixin BlocBusyWrapper on BlocBase> { - Future busyValue(Future Function(void Function(S) emit) closure) => - _mutex.protect(() async { - void busyemit(S state) { - changedState = state; - } - - // Turn on busy state - emit(BlocBusyState._busy(state.state)); - - // Run the closure - final out = await closure(busyemit); - - // If the closure did one or more 'busy emits' then - // take the most recent one and emit it for real - final finalState = changedState; - if (finalState != null && finalState != state.state) { - emit(BlocBusyState._busy(finalState)); - } else { - emit(BlocBusyState._busy(state.state)); - } - - return out; - }); - - Future busy(Future Function(void Function(S) emit) closure) => - _mutex.protect(() async { - void busyemit(S state) { - changedState = state; - } - - // Turn on busy state - emit(BlocBusyState._busy(state.state)); - - // Run the closure - await closure(busyemit); - - // If the closure did one or more 'busy emits' then - // take the most recent one and emit it for real and - // turn off the busy state - final finalState = changedState; - if (finalState != null && finalState != state.state) { - emit(BlocBusyState(finalState)); - } else { - emit(BlocBusyState(state.state)); - } - }); - void changeState(S state) { - if (_mutex.isLocked) { - changedState = state; - } else { - emit(BlocBusyState(state)); - } - } - - bool get isBusy => _mutex.isLocked; - - final Mutex _mutex = Mutex(); - S? changedState; -} diff --git a/packages/bloc_tools/lib/src/bloc_map_cubit.dart b/packages/bloc_tools/lib/src/bloc_map_cubit.dart deleted file mode 100644 index b99ae2b..0000000 --- a/packages/bloc_tools/lib/src/bloc_map_cubit.dart +++ /dev/null @@ -1,127 +0,0 @@ -import 'dart:async'; - -import 'package:async_tools/async_tools.dart'; -import 'package:bloc/bloc.dart'; -import 'package:fast_immutable_collections/fast_immutable_collections.dart'; -import 'package:meta/meta.dart'; - -import 'state_map_follower.dart'; - -typedef BlocMapState = IMap; - -class _ItemEntry { - _ItemEntry({required this.bloc, required this.subscription}); - final B bloc; - final StreamSubscription subscription; -} - -// Streaming container cubit that is a map from some immutable key -// to a some other cubit's output state. Output state for this container -// cubit is an immutable map of the key to the output state of the contained -// cubits. -// -// K = Key type for the bloc map, used to look up some mapped cubit -// V = State type for the value, keys will look up values of this type -// B = Bloc/cubit type for the value, output states of type S -abstract class BlocMapCubit> - extends Cubit> - with StateMapFollowable, K, V> { - BlocMapCubit() - : _entries = {}, - _tagLock = AsyncTagLock(), - super(IMap()); - - @override - Future close() async { - await _entries.values.map((e) => e.subscription.cancel()).wait; - await _entries.values.map((e) => e.bloc.close()).wait; - await super.close(); - } - - @protected - @override - // ignore: unnecessary_overrides - void emit(BlocMapState state) { - super.emit(state); - } - - Future add(MapEntry Function() create) { - // Create new element - final newElement = create(); - final key = newElement.key; - final bloc = newElement.value; - - return _tagLock.protect(key, closure: () async { - // Remove entry with the same key if it exists - await _internalRemove(key); - - // Add entry with this key - _entries[key] = _ItemEntry( - bloc: bloc, - subscription: bloc.stream.listen((data) { - // Add sub-cubit's state to the map state - emit(state.add(key, data)); - })); - - emit(state.add(key, bloc.state)); - }); - } - - Future addState(K key, V value) => - _tagLock.protect(key, closure: () async { - // Remove entry with the same key if it exists - await _internalRemove(key); - - emit(state.add(key, value)); - }); - - Future _internalRemove(K key) async { - final sub = _entries.remove(key); - if (sub != null) { - await sub.subscription.cancel(); - await sub.bloc.close(); - } - } - - Future remove(K key) => _tagLock.protect(key, closure: () async { - await _internalRemove(key); - emit(state.remove(key)); - }); - - R operate(K key, {required R Function(B bloc) closure}) { - final bloc = _entries[key]!.bloc; - return closure(bloc); - } - - R? tryOperate(K key, {required R Function(B bloc) closure}) { - final entry = _entries[key]; - if (entry == null) { - return null; - } - return closure(entry.bloc); - } - - Future operateAsync(K key, - {required Future Function(B bloc) closure}) => - _tagLock.protect(key, closure: () async { - final bloc = _entries[key]!.bloc; - return closure(bloc); - }); - - Future tryOperateAsync(K key, - {required Future Function(B bloc) closure}) => - _tagLock.protect(key, closure: () async { - final entry = _entries[key]; - if (entry == null) { - return null; - } - return closure(entry.bloc); - }); - - /// StateMapFollowable ///////////////////////// - @override - IMap getStateMap(BlocMapState s) => s; - - final Map> _entries; - final AsyncTagLock _tagLock; -} diff --git a/packages/bloc_tools/lib/src/bloc_tools_extension.dart b/packages/bloc_tools/lib/src/bloc_tools_extension.dart deleted file mode 100644 index 44940da..0000000 --- a/packages/bloc_tools/lib/src/bloc_tools_extension.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:bloc/bloc.dart'; - -mixin BlocTools on BlocBase { - void withStateListen(void Function(State event)? onData, - {Function? onError, void Function()? onDone, bool? cancelOnError}) { - if (onData != null) { - onData(state); - } - stream.listen(onData, - onError: onError, onDone: onDone, cancelOnError: cancelOnError); - } -} diff --git a/packages/bloc_tools/lib/src/future_cubit.dart b/packages/bloc_tools/lib/src/future_cubit.dart deleted file mode 100644 index 39be126..0000000 --- a/packages/bloc_tools/lib/src/future_cubit.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'dart:async'; - -import 'package:async_tools/async_tools.dart'; -import 'package:bloc/bloc.dart'; - -abstract class FutureCubit extends Cubit> { - FutureCubit(Future fut) : super(const AsyncValue.loading()) { - _initWait.add(() async => fut.then((value) { - emit(AsyncValue.data(value)); - // ignore: avoid_types_on_closure_parameters - }, onError: (Object e, StackTrace stackTrace) { - emit(AsyncValue.error(e, stackTrace)); - })); - } - FutureCubit.value(State state) : super(AsyncValue.data(state)); - - @override - Future close() async { - await _initWait(); - await super.close(); - } - - final WaitSet _initWait = WaitSet(); -} diff --git a/packages/bloc_tools/lib/src/state_map_follower.dart b/packages/bloc_tools/lib/src/state_map_follower.dart deleted file mode 100644 index 4843f39..0000000 --- a/packages/bloc_tools/lib/src/state_map_follower.dart +++ /dev/null @@ -1,125 +0,0 @@ -import 'dart:async'; - -import 'package:async_tools/async_tools.dart'; -import 'package:bloc/bloc.dart'; -import 'package:fast_immutable_collections/fast_immutable_collections.dart'; -import 'package:meta/meta.dart'; - -/// Mixin that automatically keeps two blocs/cubits in sync with each other -/// Useful for having a BlocMapCubit 'follow' the state of another input cubit. -/// As the state of the input cubit changes, the BlocMapCubit can add/remove -/// mapped Cubits that automatically process the input state reactively. -/// -/// S = Input state type -/// K = Key derived from elements of input state -/// V = Value derived from elements of input state -mixin StateMapFollower on Closable { - void follow(StateMapFollowable followable) { - assert(_following == null, 'can only follow one followable at a time'); - _following = followable; - _lastInputStateMap = IMap(); - _subscription = followable.registerFollower(this); - } - - Future unfollow() async { - await _subscription?.cancel(); - _subscription = null; - _following?.unregisterFollower(this); - _following = null; - } - - @override - @mustCallSuper - Future close() async { - await unfollow(); - await super.close(); - } - - Future removeFromState(K key); - Future updateState(K key, V value); - - void _updateFollow(IMap newInputState) { - final following = _following; - if (following == null) { - return; - } - _singleStateProcessor.updateState(newInputState, (newStateMap) async { - for (final k in _lastInputStateMap.keys) { - if (!newStateMap.containsKey(k)) { - // deleted - await removeFromState(k); - } - } - for (final newEntry in newStateMap.entries) { - final v = _lastInputStateMap.get(newEntry.key); - if (v == null || v != newEntry.value) { - // added or changed - await updateState(newEntry.key, newEntry.value); - } - } - - // Keep this state map for the next time - _lastInputStateMap = newStateMap; - }); - } - - StateMapFollowable? _following; - late IMap _lastInputStateMap; - late StreamSubscription>? _subscription; - final SingleStateProcessor> _singleStateProcessor = - SingleStateProcessor(); -} - -/// Interface that allows a StateMapFollower to follow some other class's -/// state changes -abstract mixin class StateMapFollowable - implements StateStreamable { - IMap getStateMap(S state); - - StreamSubscription> registerFollower( - StateMapFollower follower) { - final stateMapTransformer = StreamTransformer>.fromHandlers( - handleData: (d, s) => s.add(getStateMap(d))); - - if (_followers.isEmpty) { - // start transforming stream - _transformedStream = stream.transform(stateMapTransformer); - } - _followers.add(follower); - follower._updateFollow(getStateMap(state)); - return _transformedStream!.listen((s) => follower._updateFollow(s)); - } - - void unregisterFollower(StateMapFollower follower) { - _followers.remove(follower); - if (_followers.isEmpty) { - // stop transforming stream - _transformedStream = null; - } - } - - Future syncFollowers(Future Function() closure) async { - // pause all followers - await _followers.map((f) => f._singleStateProcessor.pause()).wait; - - // run closure - final out = await closure(); - - // resume all followers and wait for current state map to be updated - final resumeState = getStateMap(state); - await _followers.map((f) async { - // Ensure the latest state has been updated - try { - f._updateFollow(resumeState); - } finally { - // Resume processing of the follower - await f._singleStateProcessor.resume(); - } - }).wait; - - return out; - } - - Stream>? _transformedStream; - final List> _followers = []; -} diff --git a/packages/bloc_tools/lib/src/stream_wrapper_cubit.dart b/packages/bloc_tools/lib/src/stream_wrapper_cubit.dart deleted file mode 100644 index 732695b..0000000 --- a/packages/bloc_tools/lib/src/stream_wrapper_cubit.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'dart:async'; - -import 'package:async_tools/async_tools.dart'; -import 'package:bloc/bloc.dart'; - -abstract class StreamWrapperCubit extends Cubit> { - StreamWrapperCubit(Stream stream, {State? defaultState}) - : super(defaultState != null - ? AsyncValue.data(defaultState) - : const AsyncValue.loading()) { - _subscription = stream.listen((event) => emit(AsyncValue.data(event)), - // ignore: avoid_types_on_closure_parameters - onError: (Object error, StackTrace stackTrace) { - emit(AsyncValue.error(error, stackTrace)); - }); - } - - @override - Future close() async { - await _subscription.cancel(); - await super.close(); - } - - late final StreamSubscription _subscription; -} diff --git a/packages/bloc_tools/lib/src/transformer_cubit.dart b/packages/bloc_tools/lib/src/transformer_cubit.dart deleted file mode 100644 index e9aa9b6..0000000 --- a/packages/bloc_tools/lib/src/transformer_cubit.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'dart:async'; - -import 'package:bloc/bloc.dart'; - -class TransformerCubit extends Cubit { - TransformerCubit(this.input, {required this.transform}) - : super(transform(input.state)) { - _subscription = input.stream.listen((event) => emit(transform(event))); - } - - @override - Future close() async { - await _subscription.cancel(); - await input.close(); - await super.close(); - } - - Cubit input; - T Function(S) transform; - late final StreamSubscription _subscription; -} diff --git a/packages/bloc_tools/pubspec.yaml b/packages/bloc_tools/pubspec.yaml deleted file mode 100644 index a51fa35..0000000 --- a/packages/bloc_tools/pubspec.yaml +++ /dev/null @@ -1,24 +0,0 @@ -name: bloc_tools -description: A starting point for Dart libraries or applications. -version: 1.0.0 -publish_to: none - -environment: - sdk: '>=3.2.0 <4.0.0' - -dependencies: - async_tools: - path: ../async_tools - bloc: ^8.1.3 - equatable: ^2.0.5 - fast_immutable_collections: ^10.1.1 - freezed_annotation: ^2.4.1 - meta: ^1.11.0 - mutex: - path: ../mutex - -dev_dependencies: - build_runner: ^2.4.8 - freezed: ^2.4.7 - lint_hard: ^4.0.0 - test: ^1.25.2 \ No newline at end of file diff --git a/packages/bloc_tools/test/bloc_tools_test.dart b/packages/bloc_tools/test/bloc_tools_test.dart deleted file mode 100644 index 5a81be4..0000000 --- a/packages/bloc_tools/test/bloc_tools_test.dart +++ /dev/null @@ -1,16 +0,0 @@ -// import 'package:bloc_tools/bloc_tools.dart'; -// import 'package:test/test.dart'; - -// void main() { -// group('A group of tests', () { -// final awesome = Awesome(); - -// setUp(() { -// // Additional setup goes here. -// }); - -// test('First Test', () { -// expect(awesome.isAwesome, isTrue); -// }); -// }); -// } diff --git a/packages/mutex/.gitignore b/packages/mutex/.gitignore deleted file mode 100644 index 2ca4cae..0000000 --- a/packages/mutex/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -# Files and directories created by pub -.packages -.pub/ -.dart_tool/ -build/ -packages -pubspec.lock - -# Directory created by dartdoc -doc/api/ - -# JetBrains IDEs -.idea/ -*.iml -*.ipr -*.iws diff --git a/packages/mutex/CHANGELOG.md b/packages/mutex/CHANGELOG.md deleted file mode 100644 index 9115310..0000000 --- a/packages/mutex/CHANGELOG.md +++ /dev/null @@ -1,50 +0,0 @@ -## 3.1.0 - -- Increased minimum Dart SDK to 2.15.0 for `unawaited` function. -- Added development dependencies lints ^2.1.1 and pana: ^0.21.37. -- Fixed code to remove lint warnings. - -## 3.0.1 - -- Fixed bug with new read mutexes preventing a write mutex from being acquired. - -## 3.0.0 - -- BREAKING CHANGE: critical section functions must return a Future. - - This is unlikely to affect real-world code, since only functions - containing asynchronous code would be critical. -- Protect method returns Future to the value from the critical section. - -## 2.0.0 - -- Null safety release. - -## 2.0.0-nullsafety.0 - -- Pre-release version: updated library to null safety (Non-nullable by default). -- Removed support for Dart 1.x. - -## 1.1.0 - -- Added protect, protectRead and protectWrite convenience methods. -- Improved tests to not depend on timing. - -## 1.0.3 - -- Added an example. - -## 1.0.2 - -- Code clean up to satisfy pana 0.13.2 health checks. - -## 1.0.1 - -- Fixed dartanalyzer warnings. - -## 1.0.0 - -- Updated the upper bound of the SDK constraint to <3.0.0. - -## 0.0.1 - -- Initial version diff --git a/packages/mutex/LICENSE b/packages/mutex/LICENSE deleted file mode 100644 index eb40cc8..0000000 --- a/packages/mutex/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -Copyright (c) 2016, Hoylen Sue. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/mutex/README.md b/packages/mutex/README.md deleted file mode 100644 index 1df6b41..0000000 --- a/packages/mutex/README.md +++ /dev/null @@ -1,191 +0,0 @@ -# mutex - -A library for creating locks to ensure mutual exclusion when -running critical sections of code. - -## Purpose - -Mutexes can be used to protect critical sections of code to prevent -race conditions. - -Although Dart uses a single thread of execution, race conditions -can still occur when asynchronous operations are used inside -critical sections. For example, - - x = 42; - synchronousOperations(); // this does not modify x - assert(x == 42); // x will NOT have changed - - y = 42; // a variable that other asynchronous code can modify - await asynchronousOperations(); // this does NOT modify y, but... - // There is NO GUARANTEE other async code didn't run and change it! - assert(y == 42 || y != 42); // WARNING: y might have changed - -An example is when Dart is used to implement a server-side Web server -that updates a database (assuming database transactions are not being -used). The update involves querying the database, performing -calculations on those retrieved values, and then updating the database -with the result. You don't want the database to be changed by -"something else" while performing the calculations, since the results -you would write will not incorporate those other changes. That -"something else" could be the same Web server handling another request -in parallel. - -This package provides a normal mutex and a read-write mutex. - -## Mutex - -A mutex guarantees at most only one lock can exist at any one time. - -If the lock has already been acquired, attempts to acquire another -lock will be blocked until the lock has been released. - -```dart -import 'package:mutex/mutex.dart'; - -... - -final m = Mutex(); -``` - -Acquiring the lock before running the critical section of code, -and then releasing the lock. - -```dart -await m.acquire(); -// No other lock can be acquired until the lock is released - -try { - // critical section with asynchronous code - await ... -} finally { - m.release(); -} -``` - -### protect - -The following code uses the _protect_ convenience method to do the -same thing as the above code. Use the convenence method whenever -possible, since it ensures the lock will always be released. - -```dart -await m.protect(() async { - // critical section -}); -``` - -If the critial section returns a Future to a value, the _protect_ -convenience method will return a Future to that value. - -```dart -final result = await m.protect(() async { - // critical section - return valueFromCriticalSection; -}); -// result contains the valueFromCriticalSection -``` - - -## Read-write mutex - -A read-write mutex allows multiple _reads locks_ to be exist -simultaneously, but at most only one _write lock_ can exist at any one -time. A _write lock_ and any _read locks_ cannot both exist together -at the same time. - -If there is one or more _read locks_, attempts to acquire a _write -lock_ will be blocked until all the _read locks_ have been -released. But attempts to acquire more _read locks_ will not be -blocked. If there is a _write lock_, attempts to acquire any lock -(read or write) will be blocked until that _write lock_ is released. - -A read-write mutex can also be described as a single-writer mutex, -multiple-reader mutex, or a reentrant lock. - -```dart -import 'package:mutex/mutex.dart'; - -... - -final m = ReadWriteMutex(); -``` - -Acquiring a write lock: - - await m.acquireWrite(); - // No other locks (read or write) can be acquired until released - - try { - // critical write section with asynchronous code - await ... - } finally { - m.release(); - } - -Acquiring a read lock: - - await m.acquireRead(); - // No write lock can be acquired until all read locks are released, - // but additional read locks can be acquired. - - try { - // critical read section with asynchronous code - await ... - } finally { - m.release(); - } - -### protectWrite and protectRead - -The following code uses the _protectWrite_ and _protectRead_ -convenience methods to do the same thing as the above code. Use the -convenence method whenever possible, since it ensures the lock will -always be released. - -```dart -await m.protectWrite(() async { - // critical write section -}); - -await m.protectRead(() async { - // critical read section -}); -``` - -If the critial section returns a Future to a value, these convenience -methods will return a Future to that value. - -```dart -final result1 await m.protectWrite(() async { - // critical write section - return valueFromCritialSection1; -}); -// result1 contains the valueFromCriticalSection1 - -final result2 = await m.protectRead(() async { - // critical read section - return valueFromCritialSection2; -}); -// result2 contains the valueFromCriticalSection2 -``` - -## When mutual exclusion is not needed - -The critical section should always contain some asynchronous code. If -the critical section only contains synchronous code, there is no need -to put it in a critical section. In Dart, synchronous code cannot be -interrupted, so there is no need to protect it using mutual exclusion. - -Also, if the critical section does not involve data or shared -resources that can be accessed by other asynchronous code, it also -does not need to be protected. For example, if it only uses local -variables that other asynchronous code won't have access to: while the -other asynchronous code could run, it won't be able to make unexpected -changes to the local variables it can't access. - -## Features and bugs - -Please file feature requests and bugs at the [issue tracker][tracker]. - -[tracker]: https://github.com/hoylen/dart-mutex/issues diff --git a/packages/mutex/analysis_options.yaml b/packages/mutex/analysis_options.yaml deleted file mode 100644 index e1620f7..0000000 --- a/packages/mutex/analysis_options.yaml +++ /dev/null @@ -1,15 +0,0 @@ -include: package:lint_hard/all.yaml -analyzer: - errors: - invalid_annotation_target: ignore - exclude: - - '**/*.g.dart' - - '**/*.freezed.dart' - - '**/*.pb.dart' - - '**/*.pbenum.dart' - - '**/*.pbjson.dart' - - '**/*.pbserver.dart' -linter: - rules: - unawaited_futures: true - avoid_positional_boolean_parameters: false \ No newline at end of file diff --git a/packages/mutex/example/example.dart b/packages/mutex/example/example.dart deleted file mode 100644 index c13b007..0000000 --- a/packages/mutex/example/example.dart +++ /dev/null @@ -1,114 +0,0 @@ -// Mutex example. -// -// This example demonstrates why a mutex is needed. - -import 'dart:async'; -import 'dart:math'; -import 'package:mutex/mutex.dart'; - -//---------------------------------------------------------------- -// Random asynchronous delays to try and simulate race conditions. - -const _maxDelay = 500; // milliseconds - -final _random = Random(); - -Future randomDelay() async { - await Future.delayed( - Duration(milliseconds: _random.nextInt(_maxDelay))); -} - -//---------------------------------------------------------------- -/// Account balance. -/// -/// The classical example of a race condition is when a bank account is updated -/// by different simultaneous operations. - -int balance = 0; - -//---------------------------------------------------------------- -/// Deposit without using mutex. - -Future unsafeUpdate(int id, int depositAmount) async { - // Random delay before updating starts - await randomDelay(); - - // Add the deposit to the balance. But this operation is not atomic if - // there are asynchronous operations in it (as simulated by the randomDelay). - - final oldBalance = balance; - await randomDelay(); - balance = oldBalance + depositAmount; - - print(' [$id] added $depositAmount to $oldBalance -> $balance'); -} - -//---------------------------------------------------------------- -/// Deposit using mutex. - -Mutex m = Mutex(); - -Future safeUpdate(int id, int depositAmount) async { - // Random delay before updating starts - await randomDelay(); - - // Acquire the mutex before running the critical section of code - - await m.protect(() async { - // critical section - - // This is the same as the unsafe update. But since it is performed only - // when the mutex is acquired, it is safe: no other safe update can happen - // until this mutex is released. - - final oldBalance = balance; - await randomDelay(); - balance = oldBalance + depositAmount; - - // end of critical section - - print(' [$id] added $depositAmount to $oldBalance -> $balance'); - }); -} - -//---------------------------------------------------------------- -/// Make a series of deposits and see if the final balance is correct. - -Future makeDeposits({bool safe = true}) async { - print(safe ? 'Using mutex:' : 'Not using mutex:'); - - const numberDeposits = 10; - const amount = 10; - - balance = 0; - - // Create a set of operations, each attempting to deposit the same amount - // into the account. - - final operations = >[]; - for (var x = 0; x < numberDeposits; x++) { - final f = (safe) ? safeUpdate(x, amount) : unsafeUpdate(x, amount); - operations.add(f); - } - - // Wait for all the deposit operations to finish - - await Future.wait(operations); - - // Check if all of the operations succeeded - - final expected = numberDeposits * amount; - if (balance != expected) { - print('Error: deposits were lost (final balance $balance != $expected)'); - } else { - print('Success: no deposits were lost'); - } -} - -//---------------------------------------------------------------- - -void main() async { - await makeDeposits(safe: false); - print(''); - await makeDeposits(safe: true); -} diff --git a/packages/mutex/lib/mutex.dart b/packages/mutex/lib/mutex.dart deleted file mode 100644 index ba224d1..0000000 --- a/packages/mutex/lib/mutex.dart +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) 2016, Hoylen Sue. All rights reserved. Use of this source code -// is governed by a BSD-style license that can be found in the LICENSE file. - -/// Mutual exclusion. -/// -library mutex; - -import 'dart:async'; - -part 'src/mutex.dart'; -part 'src/read_write_mutex.dart'; diff --git a/packages/mutex/lib/src/mutex.dart b/packages/mutex/lib/src/mutex.dart deleted file mode 100644 index 1c9e9ec..0000000 --- a/packages/mutex/lib/src/mutex.dart +++ /dev/null @@ -1,89 +0,0 @@ -part of mutex; - -/// Mutual exclusion. -/// -/// The [protect] method is a convenience method for acquiring a lock before -/// running critical code, and then releasing the lock afterwards. Using this -/// convenience method will ensure the lock is always released after use. -/// -/// Usage: -/// -/// m = Mutex(); -/// -/// await m.protect(() async { -/// // critical section -/// }); -/// -/// Alternatively, a lock can be explicitly acquired and managed. In this -/// situation, the program is responsible for releasing the lock after it -/// have been used. Failure to release the lock will prevent other code for -/// ever acquiring the lock. -/// -/// m = Mutex(); -/// -/// await m.acquire(); -/// try { -/// // critical section -/// } -/// finally { -/// m.release(); -/// } - -class Mutex { - //================================================================ - // Constructors - Mutex() : _rwMutex = ReadWriteMutex(); - Mutex.locked() : _rwMutex = ReadWriteMutex.writeLocked(); - - // Implemented as a ReadWriteMutex that is used only with write locks. - final ReadWriteMutex _rwMutex; - - /// Indicates if a lock has been acquired and not released. - bool get isLocked => _rwMutex.isLocked; - - /// Acquire a lock - /// - /// Returns a future that will be completed when the lock has been acquired. - /// - /// Consider using the convenience method [protect], otherwise the caller - /// is responsible for making sure the lock is released after it is no longer - /// needed. Failure to release the lock means no other code can acquire the - /// lock. - - Future acquire() => _rwMutex.acquireWrite(); - - /// Release a lock. - /// - /// Release a lock that has been acquired. - - void release() => _rwMutex.release(); - - /// Convenience method for protecting a function with a lock. - /// - /// This method guarantees a lock is always acquired before invoking the - /// [criticalSection] function. It also guarantees the lock is always - /// released. - /// - /// A critical section should always contain asynchronous code, since purely - /// synchronous code does not need to be protected inside a critical section. - /// Therefore, the critical section is a function that returns a _Future_. - /// If the critical section does not need to return a value, it should be - /// defined as returning `Future`. - /// - /// Returns a _Future_ whose value is the value of the _Future_ returned by - /// the critical section. - /// - /// An exception is thrown if the critical section throws an exception, - /// or an exception is thrown while waiting for the _Future_ returned by - /// the critical section to complete. The lock is released, when those - /// exceptions occur. - - Future protect(Future Function() criticalSection) async { - await acquire(); - try { - return await criticalSection(); - } finally { - release(); - } - } -} diff --git a/packages/mutex/lib/src/read_write_mutex.dart b/packages/mutex/lib/src/read_write_mutex.dart deleted file mode 100644 index 8a5c12e..0000000 --- a/packages/mutex/lib/src/read_write_mutex.dart +++ /dev/null @@ -1,304 +0,0 @@ -part of mutex; - -//################################################################ -/// Internal representation of a request for a lock. -/// -/// This is instantiated for each acquire and, if necessary, it is added -/// to the waiting queue. - -class _ReadWriteMutexRequest { - /// Internal constructor. - /// - /// The [isRead] indicates if this is a request for a read lock (true) or a - /// request for a write lock (false). - - _ReadWriteMutexRequest({required this.isRead}); - - /// Indicates if this is a read or write lock. - - final bool isRead; // true = read lock requested; false = write lock requested - - /// The job's completer. - /// - /// This [Completer] will complete when the job has acquired the lock. - - final Completer completer = Completer(); -} - -//################################################################ -/// Mutual exclusion that supports read and write locks. -/// -/// Multiple read locks can be simultaneously acquired, but at most only -/// one write lock can be acquired at any one time. -/// -/// **Protecting critical code** -/// -/// The [protectWrite] and [protectRead] are convenience methods for acquiring -/// locks and releasing them. Using them will ensure the locks are always -/// released after use. -/// -/// Create the mutex: -/// -/// m = ReadWriteMutex(); -/// -/// Code protected by a write lock: -/// -/// await m.protectWrite(() { -/// // critical write section -/// }); -/// -/// Other code can be protected by a read lock: -/// -/// await m.protectRead(() { -/// // critical read section -/// }); -/// -/// -/// **Explicitly managing locks** -/// -/// Alternatively, the locks can be explicitly acquired and managed. In this -/// situation, the program is responsible for releasing the locks after they -/// have been used. Failure to release the lock will prevent other code for -/// ever acquiring a lock. -/// -/// Create the mutex: -/// -/// m = ReadWriteMutex(); -/// -/// Some code can acquire a write lock: -/// -/// await m.acquireWrite(); -/// try { -/// // critical write section -/// assert(m.isWriteLocked); -/// } finally { -/// m.release(); -/// } -/// -/// Other code can acquire a read lock. -/// -/// await m.acquireRead(); -/// try { -/// // critical read section -/// assert(m.isReadLocked); -/// } finally { -/// m.release(); -/// } -/// -/// The current implementation lets locks be acquired in first-in-first-out -/// order. This ensures there will not be any lock starvation, which can -/// happen if some locks are prioritised over others. Submit a feature -/// request issue, if there is a need for another scheduling algorithm. - -class ReadWriteMutex { - //================================================================ - // Constructors - ReadWriteMutex(); - ReadWriteMutex.writeLocked() : _state = -1; - ReadWriteMutex.readLocked(int? count) : _state = count ?? 1 { - assert(_state > 0, "can't have a negative read lock count"); - } - - //================================================================ - // Members - - /// List of requests waiting for a lock on this mutex. - - final _waiting = <_ReadWriteMutexRequest>[]; - - /// State of the mutex - - int _state = 0; // -1 = write lock, +ve = number of read locks; 0 = no lock - - //================================================================ - // Methods - - /// Indicates if a lock (read or write) has been acquired and not released. - bool get isLocked => _state != 0; - - /// Indicates if a write lock has been acquired and not released. - bool get isWriteLocked => _state == -1; - - /// Indicates if one or more read locks has been acquired and not released. - bool get isReadLocked => 0 < _state; - - /// Indicates the number of waiters on this mutex - int get waiters => _waiting.length; - - /// Acquire a read lock - /// - /// Returns a future that will be completed when the lock has been acquired. - /// - /// A read lock can not be acquired when there is a write lock on the mutex. - /// But it can be acquired if there are other read locks. - /// - /// Consider using the convenience method [protectRead], otherwise the caller - /// is responsible for making sure the lock is released after it is no longer - /// needed. Failure to release the lock means no other code can acquire a - /// write lock. - - Future acquireRead() => _acquire(isRead: true); - - /// Acquire a write lock - /// - /// Returns a future that will be completed when the lock has been acquired. - /// - /// A write lock can only be acquired when there are no other locks (neither - /// read locks nor write locks) on the mutex. - /// - /// Consider using the convenience method [protectWrite], otherwise the caller - /// is responsible for making sure the lock is released after it is no longer - /// needed. Failure to release the lock means no other code can acquire the - /// lock (neither a read lock or a write lock). - - Future acquireWrite() => _acquire(isRead: false); - - /// Release a lock. - /// - /// Release the lock that was previously acquired. - /// - /// When the lock is released, locks waiting to be acquired can be acquired - /// depending on the type of lock waiting and if other locks have been - /// acquired. - /// - /// A [StateError] is thrown if the mutex does not currently have a lock on - /// it. - - void release() { - if (_state == -1) { - // Write lock released - _state = 0; - } else if (0 < _state) { - // Read lock released - _state--; - } else if (_state == 0) { - throw StateError('no lock to release'); - } else { - assert(false, 'invalid state'); - } - - // If there are jobs waiting and the next job can acquire the mutex, - // let it acquire it and remove it from the queue. - // - // This is a while loop, because there could be multiple jobs on the - // queue waiting for a read-only mutex. So they can all be allowed to run. - - while (_waiting.isNotEmpty) { - final nextJob = _waiting.first; - if (_jobAcquired(nextJob)) { - _waiting.removeAt(0); - } else { - // The next job cannot acquire the mutex. This only occurs when: the - // the currently running job has a write mutex (_state == -1); or the - // next job wants write mutex and there is a job currently running - // (regardless of what type of mutex it has acquired). - assert(_state < 0 || !nextJob.isRead, - 'unexpected: next job cannot be acquired'); - break; // no more can be removed from the queue - } - } - } - - /// Convenience method for protecting a function with a read lock. - /// - /// This method guarantees a read lock is always acquired before invoking the - /// [criticalSection] function. It also guarantees the lock is always - /// released. - /// - /// A critical section should always contain asynchronous code, since purely - /// synchronous code does not need to be protected inside a critical section. - /// Therefore, the critical section is a function that returns a _Future_. - /// If the critical section does not need to return a value, it should be - /// defined as returning `Future`. - /// - /// Returns a _Future_ whose value is the value of the _Future_ returned by - /// the critical section. - /// - /// An exception is thrown if the critical section throws an exception, - /// or an exception is thrown while waiting for the _Future_ returned by - /// the critical section to complete. The lock is released, when those - /// exceptions occur. - - Future protectRead(Future Function() criticalSection) async { - await acquireRead(); - try { - return await criticalSection(); - } finally { - release(); - } - } - - /// Convenience method for protecting a function with a write lock. - /// - /// This method guarantees a write lock is always acquired before invoking the - /// [criticalSection] function. It also guarantees the lock is always - /// released. - /// - /// A critical section should always contain asynchronous code, since purely - /// synchronous code does not need to be protected inside a critical section. - /// Therefore, the critical section is a function that returns a _Future_. - /// If the critical section does not need to return a value, it should be - /// defined as returning `Future`. - /// - /// Returns a _Future_ whose value is the value of the _Future_ returned by - /// the critical section. - /// - /// An exception is thrown if the critical section throws an exception, - /// or an exception is thrown while waiting for the _Future_ returned by - /// the critical section to complete. The lock is released, when those - /// exceptions occur. - - Future protectWrite(Future Function() criticalSection) async { - await acquireWrite(); - try { - return await criticalSection(); - } finally { - release(); - } - } - - /// Internal acquire method. - /// - /// Used to acquire a read lock (when [isRead] is true) or a write lock - /// (when [isRead] is false). - /// - /// Returns a Future that completes when the lock has been acquired. - - Future _acquire({required bool isRead}) { - final newJob = _ReadWriteMutexRequest(isRead: isRead); - - if (_waiting.isNotEmpty || !_jobAcquired(newJob)) { - // This new job cannot run yet. There are either other jobs already - // waiting, or there are no waiting jobs but this job cannot start - // because the mutex is currently acquired (namely, either this new job - // or the currently running job is read-write). - // - // Add the new job to the end of the queue. - - _waiting.add(newJob); - } - - return newJob.completer.future; - } - - /// Determine if the [job] can now acquire the lock. - /// - /// If it can acquire the lock, the job's completer is completed, the - /// state updated, and true is returned. If not, false is returned. - /// - /// A job for a read lock can only be acquired if there are no other locks - /// or there are read lock(s). A job for a write lock can only be acquired - /// if there are no other locks. - - bool _jobAcquired(_ReadWriteMutexRequest job) { - assert(-1 <= _state, 'must not be write locked'); - if (_state == 0 || (0 < _state && job.isRead)) { - // Can acquire - _state = (job.isRead) ? (_state + 1) : -1; - job.completer.complete(); - return true; - } else { - return false; - } - } -} diff --git a/packages/mutex/pubspec.yaml b/packages/mutex/pubspec.yaml deleted file mode 100644 index 753ae25..0000000 --- a/packages/mutex/pubspec.yaml +++ /dev/null @@ -1,12 +0,0 @@ -name: mutex -description: Mutual exclusion with implementation of normal and read-write mutex -version: 3.1.0 -publish_to: none - -environment: - sdk: '>=3.2.0 <4.0.0' - -dev_dependencies: - lint_hard: ^4.0.0 - pana: ^0.22.2 - test: ^1.25.2 diff --git a/packages/mutex/test/mutex_multiple_read_test.dart b/packages/mutex/test/mutex_multiple_read_test.dart deleted file mode 100644 index 5ed8345..0000000 --- a/packages/mutex/test/mutex_multiple_read_test.dart +++ /dev/null @@ -1,102 +0,0 @@ -// Test contributed by "Cat-sushi" -// - -import 'dart:async'; -// import 'dart:io'; - -import 'package:mutex/mutex.dart'; -import 'package:test/test.dart'; - -//================================================================ -// For debug output -// -// Uncomment the "stdout.write" line in the [debugWrite] method to enable -// debug output. - -int numReadAcquired = 0; -int numReadReleased = 0; - -enum State { waitingToAcquire, acquired, released } - -const stateSymbol = { - State.waitingToAcquire: '?', - State.acquired: '+', - State.released: '-' -}; - -var _outputCount = 0; // to manage line breaks - -void debugOutput(String id, State state) { - debugWrite('$id${stateSymbol[state]} '); - - _outputCount++; - if (_outputCount % 10 == 0) { - debugWrite('\n'); - } -} - -void debugWrite(String str) { - // Uncomment to show what is happening - // stdout.write(str); -} - -//================================================================ - -Future mySleep([int ms = 1000]) async { - await Future.delayed(Duration(milliseconds: ms)); -} - -Future sharedLoop1(ReadWriteMutex mutex, String symbol) async { - while (true) { - debugOutput(symbol, State.waitingToAcquire); - - await mutex.protectRead(() async { - numReadAcquired++; - debugOutput(symbol, State.acquired); - - await mySleep(100); - }); - numReadReleased++; - - debugOutput(symbol, State.released); - } -} - -void main() { - group('exclusive lock tests', () { - test('test1', () async { - const numReadLoops = 5; - - final mutex = ReadWriteMutex(); - - assert(numReadLoops < 26, 'too many read loops for lowercase letters'); - debugWrite('Number of read loops: $numReadLoops\n'); - - for (var x = 0; x < numReadLoops; x++) { - final symbol = String.fromCharCode('a'.codeUnitAt(0) + x); - unawaited(sharedLoop1(mutex, symbol)); - await mySleep(10); - } - - await mySleep(); - - debugWrite('\nAbout to acquireWrite' - ' (reads: acquired=$numReadAcquired released=$numReadReleased' - ' outstanding=${numReadAcquired - numReadReleased})\n'); - _outputCount = 0; // reset line break - - const writeSymbol = 'W'; - - debugOutput(writeSymbol, State.waitingToAcquire); - await mutex.acquireWrite(); - debugOutput(writeSymbol, State.acquired); - mutex.release(); - debugOutput(writeSymbol, State.released); - - debugWrite('\nWrite mutex released\n'); - _outputCount = 0; // reset line break - - expect('a', 'a'); - }); - }); -} diff --git a/packages/mutex/test/mutex_readwrite_test.dart b/packages/mutex/test/mutex_readwrite_test.dart deleted file mode 100644 index 310caa1..0000000 --- a/packages/mutex/test/mutex_readwrite_test.dart +++ /dev/null @@ -1,486 +0,0 @@ -import 'dart:async'; -import 'package:mutex/mutex.dart'; -import 'package:test/test.dart'; - -//################################################################ - -class RWTester { - int _operation = 0; - final _operationSequences = []; - - /// Execution sequence of the operations done. - /// - /// Each element corresponds to the position of the initial execution - /// order of the read/write operation future. - List get operationSequences => _operationSequences; - - ReadWriteMutex mutex = ReadWriteMutex(); - - /// Set to true to print out read/write to the balance during deposits - static const bool debugOutput = false; - - final DateTime _startTime = DateTime.now(); - - void _debugPrint(String message) { - if (debugOutput) { - final t = DateTime.now().difference(_startTime).inMilliseconds; - // ignore: avoid_print - print('$t: $message'); - } - } - - void reset() { - _operationSequences.clear(); - _debugPrint('reset'); - } - - /// Waits [startDelay] and then invokes critical section with mutex. - /// - /// Writes to [_operationSequences]. If the readwrite locks are respected - /// then the final state of the list will be in ascending order. - Future writing(int startDelay, int sequence, int endDelay) async { - await Future.delayed(Duration(milliseconds: startDelay)); - - await mutex.protectWrite(() async { - final op = ++_operation; - _debugPrint('[$op] write start: <- $_operationSequences'); - final tmp = _operationSequences; - expect(mutex.isWriteLocked, isTrue); - expect(_operationSequences, orderedEquals(tmp)); - // Add the position of operation to the list of operations. - _operationSequences.add(sequence); // add position to list - expect(mutex.isWriteLocked, isTrue); - await Future.delayed(Duration(milliseconds: endDelay)); - _debugPrint('[$op] write finish: -> $_operationSequences'); - }); - } - - /// Waits [startDelay] and then invokes critical section with mutex. - /// - /// - Future reading(int startDelay, int sequence, int endDelay) async { - await Future.delayed(Duration(milliseconds: startDelay)); - - await mutex.protectRead(() async { - final op = ++_operation; - _debugPrint('[$op] read start: <- $_operationSequences'); - expect(mutex.isReadLocked, isTrue); - _operationSequences.add(sequence); // add position to list - await Future.delayed(Duration(milliseconds: endDelay)); - _debugPrint('[$op] read finish: <- $_operationSequences'); - }); - } -} - -//################################################################ - -//---------------------------------------------------------------- - -void main() { - final account = RWTester(); - - setUp(account.reset); - - test('multiple read locks', () async { - await Future.wait([ - account.reading(0, 1, 1000), - account.reading(0, 2, 900), - account.reading(0, 3, 800), - account.reading(0, 4, 700), - account.reading(0, 5, 600), - account.reading(0, 6, 500), - account.reading(0, 7, 400), - account.reading(0, 8, 300), - account.reading(0, 9, 200), - account.reading(0, 10, 100), - ]); - // The first future acquires the lock first and waits the longest to give it - // up. This should however not block any of the other read operations - // as such the reads should finish in ascending orders. - expect( - account.operationSequences, - orderedEquals([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), - ); - }); - - test('multiple write locks', () async { - await Future.wait([ - account.writing(0, 1, 100), - account.writing(0, 2, 100), - account.writing(0, 3, 100), - ]); - // The first future writes first and holds the lock until 100 ms - // Even though the second future starts execution, the lock cannot be - // acquired until it is released by the first future. - // Therefore the sequence of operations will be in ascending order - // of the futures. - expect( - account.operationSequences, - orderedEquals([1, 2, 3]), - ); - }); - - test('acquireWrite() before acquireRead()', () async { - const lockTimeout = Duration(milliseconds: 100); - - final mutex = ReadWriteMutex(); - - await mutex.acquireWrite(); - expect(mutex.isReadLocked, equals(false)); - expect(mutex.isWriteLocked, equals(true)); - - // Since there is a write lock existing, a read lock cannot be acquired. - final readLock = mutex.acquireRead().timeout(lockTimeout); - expect( - () async => readLock, - throwsA(isA()), - ); - }); - - test('acquireRead() before acquireWrite()', () async { - const lockTimeout = Duration(milliseconds: 100); - - final mutex = ReadWriteMutex(); - - await mutex.acquireRead(); - expect(mutex.isReadLocked, equals(true)); - expect(mutex.isWriteLocked, equals(false)); - - // Since there is a read lock existing, a write lock cannot be acquired. - final writeLock = mutex.acquireWrite().timeout(lockTimeout); - expect( - () async => writeLock, - throwsA(isA()), - ); - }); - - test('mixture of read write locks execution order', () async { - await Future.wait([ - account.reading(0, 1, 100), - account.reading(10, 2, 100), - account.reading(20, 3, 100), - account.writing(30, 4, 100), - account.writing(40, 5, 100), - account.writing(50, 6, 100), - ]); - - expect( - account.operationSequences, - orderedEquals([1, 2, 3, 4, 5, 6]), - ); - }); - - group('protectRead', () { - test('lock obtained and released on success', () async { - final m = ReadWriteMutex(); - - await m.protectRead(() async { - // critical section - expect(m.isLocked, isTrue); - }); - expect(m.isLocked, isFalse); - }); - - test('value returned from critical section', () async { - // These are the normal scenario of the critical section running - // successfully. It tests different return types from the - // critical section. - - final m = ReadWriteMutex(); - - // returns Future - await m.protectRead(() async {}); - - // returns Future - final number = await m.protectRead(() async => 42); - expect(number, equals(42)); - - // returns Future completes with value - final optionalNumber = await m.protectRead(() async => 1024); - expect(optionalNumber, equals(1024)); - - // returns Future completes with null - final optionalNumberNull = await m.protectRead(() async => null); - expect(optionalNumberNull, isNull); - - // returns Future - final word = await m.protectRead(() async => 'foobar'); - expect(word, equals('foobar')); - - // returns Future completes with value - final optionalWord = await m.protectRead(() async => 'baz'); - expect(optionalWord, equals('baz')); - - // returns Future completes with null - final optionalWordNull = await m.protectRead(() async => null); - expect(optionalWordNull, isNull); - - expect(m.isLocked, isFalse); - }); - - test('exception in synchronous code', () async { - // Tests what happens when an exception is raised in the **synchronous** - // part of the critical section. - // - // Locks are correctly managed: the lock is obtained before executing - // the critical section, and is released when the exception is thrown - // by the _protect_ method. - // - // The exception is raised when waiting for the Future returned by - // _protect_ to complete. Even though the exception is synchronously - // raised by the critical section, it won't be thrown when _protect_ - // is invoked. The _protect_ method always successfully returns a - // _Future_. - - Future criticalSection() { - final c = Completer()..complete(42); - - // synchronous exception - throw const FormatException('synchronous exception'); - // ignore: dead_code - return c.future; - } - - // Check the criticalSection behaves as expected for the test - - try { - // ignore: unused_local_variable - final resultFuture = criticalSection(); - fail('critical section did not throw synchronous exception'); - } on FormatException { - // expected: invoking the criticalSection results in the exception - } - - final m = ReadWriteMutex(); - - try { - // Invoke protect to get the Future (this should succeed) - final resultFuture = m.protectRead(criticalSection); - expect(resultFuture, isA>()); - - // Wait for the Future (this should fail) - final result = await resultFuture; - expect(result, isNotNull); - fail('exception not thrown'); - } on FormatException catch (e) { - expect(m.isLocked, isFalse); - expect(e.message, equals('synchronous exception')); - } - - expect(m.isLocked, isFalse); - }); - - test('exception in asynchronous code', () async { - // Tests what happens when an exception is raised in the **asynchronous** - // part of the critical section. - // - // Locks are correctly managed: the lock is obtained before executing - // the critical section, and is released when the exception is thrown - // by the _protect_ method. - // - // The exception is raised when waiting for the Future returned by - // _protect_ to complete. - - Future criticalSection() async { - final c = Completer()..complete(42); - - await Future.delayed(const Duration(seconds: 1), () {}); - - // asynchronous exception (since it must wait for the above line) - throw const FormatException('asynchronous exception'); - // ignore: dead_code - return c.future; - } - - // Check the criticalSection behaves as expected for the test - - final resultFuture = criticalSection(); - expect(resultFuture, isA>()); - // invoking the criticalSection does not result in the exception - try { - await resultFuture; - fail('critical section did not throw asynchronous exception'); - } on FormatException { - // expected: exception happens on the await - } - - final m = ReadWriteMutex(); - - try { - // Invoke protect to get the Future (this should succeed) - final resultFuture = m.protectRead(criticalSection); - expect(resultFuture, isA>()); - - // Even though the criticalSection throws the exception in synchronous - // code, protect causes it to become an asynchronous exception. - - // Wait for the Future (this should fail) - final result = await resultFuture; - expect(result, isNotNull); - fail('exception not thrown'); - } on FormatException catch (e) { - expect(m.isLocked, isFalse); - expect(e.message, equals('asynchronous exception')); - } - - expect(m.isLocked, isFalse); - }); - }); - - group('protectWrite', () { - test('lock obtained and released on success', () async { - final m = ReadWriteMutex(); - - await m.protectWrite(() async { - // critical section - expect(m.isLocked, isTrue); - }); - expect(m.isLocked, isFalse); - }); - - test('value returned from critical section', () async { - // These are the normal scenario of the critical section running - // successfully. It tests different return types from the - // critical section. - - final m = ReadWriteMutex(); - - // returns Future - await m.protectWrite(() async {}); - - // returns Future - final number = await m.protectWrite(() async => 42); - expect(number, equals(42)); - - // returns Future completes with value - final optionalNumber = await m.protectWrite(() async => 1024); - expect(optionalNumber, equals(1024)); - - // returns Future completes with null - final optionalNumberNull = await m.protectWrite(() async => null); - expect(optionalNumberNull, isNull); - - // returns Future - final word = await m.protectWrite(() async => 'foobar'); - expect(word, equals('foobar')); - - // returns Future completes with value - final optionalWord = await m.protectWrite(() async => 'baz'); - expect(optionalWord, equals('baz')); - - // returns Future completes with null - final optionalWordNull = await m.protectWrite(() async => null); - expect(optionalWordNull, isNull); - - expect(m.isLocked, isFalse); - }); - - test('exception in synchronous code', () async { - // Tests what happens when an exception is raised in the **synchronous** - // part of the critical section. - // - // Locks are correctly managed: the lock is obtained before executing - // the critical section, and is released when the exception is thrown - // by the _protect_ method. - // - // The exception is raised when waiting for the Future returned by - // _protect_ to complete. Even though the exception is synchronously - // raised by the critical section, it won't be thrown when _protect_ - // is invoked. The _protect_ method always successfully returns a - // _Future_. - - Future criticalSection() { - final c = Completer()..complete(42); - - // synchronous exception - throw const FormatException('synchronous exception'); - // ignore: dead_code - return c.future; - } - - // Check the criticalSection behaves as expected for the test - - try { - // ignore: unused_local_variable - final resultFuture = criticalSection(); - fail('critical section did not throw synchronous exception'); - } on FormatException { - // expected: invoking the criticalSection results in the exception - } - - final m = ReadWriteMutex(); - - try { - // Invoke protect to get the Future (this should succeed) - final resultFuture = m.protectWrite(criticalSection); - expect(resultFuture, isA>()); - - // Wait for the Future (this should fail) - final result = await resultFuture; - expect(result, isNotNull); - fail('exception not thrown'); - } on FormatException catch (e) { - expect(m.isLocked, isFalse); - expect(e.message, equals('synchronous exception')); - } - - expect(m.isLocked, isFalse); - }); - - test('exception in asynchronous code', () async { - // Tests what happens when an exception is raised in the **asynchronous** - // part of the critical section. - // - // Locks are correctly managed: the lock is obtained before executing - // the critical section, and is released when the exception is thrown - // by the _protect_ method. - // - // The exception is raised when waiting for the Future returned by - // _protect_ to complete. - - Future criticalSection() async { - final c = Completer()..complete(42); - - await Future.delayed(const Duration(seconds: 1), () {}); - - // asynchronous exception (since it must wait for the above line) - throw const FormatException('asynchronous exception'); - // ignore: dead_code - return c.future; - } - - // Check the criticalSection behaves as expected for the test - - final resultFuture = criticalSection(); - expect(resultFuture, isA>()); - // invoking the criticalSection does not result in the exception - try { - await resultFuture; - fail('critical section did not throw asynchronous exception'); - } on FormatException { - // expected: exception happens on the await - } - - final m = ReadWriteMutex(); - - try { - // Invoke protect to get the Future (this should succeed) - final resultFuture = m.protectWrite(criticalSection); - expect(resultFuture, isA>()); - - // Even though the criticalSection throws the exception in synchronous - // code, protect causes it to become an asynchronous exception. - - // Wait for the Future (this should fail) - final result = await resultFuture; - expect(result, isNotNull); - fail('exception not thrown'); - } on FormatException catch (e) { - expect(m.isLocked, isFalse); - expect(e.message, equals('asynchronous exception')); - } - - expect(m.isLocked, isFalse); - }); - }); -} diff --git a/packages/mutex/test/mutex_test.dart b/packages/mutex/test/mutex_test.dart deleted file mode 100644 index 0db5f52..0000000 --- a/packages/mutex/test/mutex_test.dart +++ /dev/null @@ -1,341 +0,0 @@ -import 'dart:async'; -import 'package:mutex/mutex.dart'; -import 'package:test/test.dart'; - -//################################################################ -/// Account simulating the classic "simultaneous update" concurrency problem. -/// -/// The deposit operation reads the balance, waits for a short time (where -/// problems can occur if the balance is changed) and then writes out the -/// new balance. -/// -class Account { - int get balance => _balance; - int _balance = 0; - - int _operation = 0; - - Mutex mutex = Mutex(); - - /// Set to true to print out read/write to the balance during deposits - static const bool debugOutput = false; - - /// Time used for calculating time offsets in debug messages. - final DateTime _startTime = DateTime.now(); - - void _debugPrint(String message) { - if (debugOutput) { - final t = DateTime.now().difference(_startTime).inMilliseconds; - // ignore: avoid_print - print('$t: $message'); - } - } - - void reset([int startingBalance = 0]) { - _balance = startingBalance; - _debugPrint('reset: balance = $_balance'); - } - - /// Waits [startDelay] and then invokes critical section without mutex. - /// - Future depositUnsafe( - int amount, int startDelay, int dangerWindow) async { - await Future.delayed(Duration(milliseconds: startDelay)); - - await _depositCriticalSection(amount, dangerWindow); - } - - /// Waits [startDelay] and then invokes critical section with mutex. - /// - Future depositWithMutex( - int amount, int startDelay, int dangerWindow) async { - await Future.delayed(Duration(milliseconds: startDelay)); - - await mutex.acquire(); - try { - expect(mutex.isLocked, isTrue); - await _depositCriticalSection(amount, dangerWindow); - expect(mutex.isLocked, isTrue); - } finally { - mutex.release(); - } - } - - /// Critical section of adding [amount] to the balance. - /// - /// Reads the balance, then sleeps for [dangerWindow] milliseconds, before - /// saving the new balance. If not protected, another invocation of this - /// method while it is sleeping will read the balance before it is updated. - /// The one that saves its balance last will overwrite the earlier saved - /// balances (effectively those other deposits will be lost). - /// - Future _depositCriticalSection(int amount, int dangerWindow) async { - final op = ++_operation; - - _debugPrint('[$op] read balance: $_balance'); - - final tmp = _balance; - - await Future.delayed(Duration(milliseconds: dangerWindow)); - - _balance = tmp + amount; - - _debugPrint('[$op] write balance: $_balance (= $tmp + $amount)'); - } -} - -//################################################################ - -//---------------------------------------------------------------- - -void main() { - const correctBalance = 68; - - final account = Account(); - - test('without mutex', () async { - // First demonstrate that without mutex incorrect results are produced. - - // Without mutex produces incorrect result - // 000. a reads 0 - // 025. b reads 0 - // 050. a writes 42 - // 075. b writes 26 - account.reset(); - await Future.wait([ - account.depositUnsafe(42, 0, 50), - account.depositUnsafe(26, 25, 50) // result overwrites first deposit - ]); - expect(account.balance, equals(26)); // incorrect: first deposit lost - - // Without mutex produces incorrect result - // 000. b reads 0 - // 025. a reads 0 - // 050. b writes 26 - // 075. a writes 42 - account.reset(); - await Future.wait([ - account.depositUnsafe(42, 25, 50), // result overwrites second deposit - account.depositUnsafe(26, 0, 50) - ]); - expect(account.balance, equals(42)); // incorrect: second deposit lost - }); - - test('with mutex', () async { -// Test correct results are produced with mutex - - // With mutex produces correct result - // 000. a acquires lock - // 000. a reads 0 - // 025. b is blocked - // 050. a writes 42 - // 050. a releases lock - // 050. b acquires lock - // 050. b reads 42 - // 100. b writes 68 - account.reset(); - await Future.wait([ - account.depositWithMutex(42, 0, 50), - account.depositWithMutex(26, 25, 50) - ]); - expect(account.balance, equals(correctBalance)); - - // With mutex produces correct result - // 000. b acquires lock - // 000. b reads 0 - // 025. a is blocked - // 050. b writes 26 - // 050. b releases lock - // 050. a acquires lock - // 050. a reads 26 - // 100. a writes 68 - account.reset(); - await Future.wait([ - account.depositWithMutex(42, 25, 50), - account.depositWithMutex(26, 0, 50) - ]); - expect(account.balance, equals(correctBalance)); - }); - - test('multiple acquires are serialized', () async { - // Demonstrate that sections running in a mutex are effectively serialized - const delay = 200; // milliseconds - account.reset(); - await Future.wait([ - account.depositWithMutex(1, 0, delay), - account.depositWithMutex(1, 0, delay), - account.depositWithMutex(1, 0, delay), - account.depositWithMutex(1, 0, delay), - account.depositWithMutex(1, 0, delay), - account.depositWithMutex(1, 0, delay), - account.depositWithMutex(1, 0, delay), - account.depositWithMutex(1, 0, delay), - account.depositWithMutex(1, 0, delay), - account.depositWithMutex(1, 0, delay), - ]); - expect(account.balance, equals(10)); - }); - - group('protect', () { - test('lock obtained and released on success', () async { - // This is the normal scenario of the critical section running - // successfully. The lock is acquired before running the critical - // section, and it is released after it runs (and will remain - // unlocked after the _protect_ method returns). - - final m = Mutex(); - - await m.protect(() async { - // critical section: returns Future - expect(m.isLocked, isTrue); - }); - - expect(m.isLocked, isFalse); - }); - - test('value returned from critical section', () async { - // These are the normal scenario of the critical section running - // successfully. It tests different return types from the - // critical section. - - final m = Mutex(); - - // returns Future - await m.protect(() async {}); - - // returns Future - final number = await m.protect(() async => 42); - expect(number, equals(42)); - - // returns Future completes with value - final optionalNumber = await m.protect(() async => 1024); - expect(optionalNumber, equals(1024)); - - // returns Future completes with null - final optionalNumberNull = await m.protect(() async => null); - expect(optionalNumberNull, isNull); - - // returns Future - final word = await m.protect(() async => 'foobar'); - expect(word, equals('foobar')); - - // returns Future completes with value - final optionalWord = await m.protect(() async => 'baz'); - expect(optionalWord, equals('baz')); - - // returns Future completes with null - final optionalWordNull = await m.protect(() async => null); - expect(optionalWordNull, isNull); - - expect(m.isLocked, isFalse); - }); - - test('exception in synchronous code', () async { - // Tests what happens when an exception is raised in the **synchronous** - // part of the critical section. - // - // Locks are correctly managed: the lock is obtained before executing - // the critical section, and is released when the exception is thrown - // by the _protect_ method. - // - // The exception is raised when waiting for the Future returned by - // _protect_ to complete. Even though the exception is synchronously - // raised by the critical section, it won't be thrown when _protect_ - // is invoked. The _protect_ method always successfully returns a - // _Future_. - - Future criticalSection() { - final c = Completer()..complete(42); - - // synchronous exception - throw const FormatException('synchronous exception'); - // ignore: dead_code - return c.future; - } - - // Check the criticalSection behaves as expected for the test - - try { - // ignore: unused_local_variable - final resultFuture = criticalSection(); - fail('critical section did not throw synchronous exception'); - } on FormatException { - // expected: invoking the criticalSection results in the exception - } - - final m = Mutex(); - - try { - // Invoke protect to get the Future (this should succeed) - final resultFuture = m.protect(criticalSection); - expect(resultFuture, isA>()); - - // Wait for the Future (this should fail) - final result = await resultFuture; - expect(result, isNotNull); - fail('exception not thrown'); - } on FormatException catch (e) { - expect(m.isLocked, isFalse); - expect(e.message, equals('synchronous exception')); - } - - expect(m.isLocked, isFalse); - }); - - test('exception in asynchronous code', () async { - // Tests what happens when an exception is raised in the **asynchronous** - // part of the critical section. - // - // Locks are correctly managed: the lock is obtained before executing - // the critical section, and is released when the exception is thrown - // by the _protect_ method. - // - // The exception is raised when waiting for the Future returned by - // _protect_ to complete. - - Future criticalSection() async { - final c = Completer()..complete(42); - - await Future.delayed(const Duration(seconds: 1), () {}); - - // asynchronous exception (since it must wait for the above line) - throw const FormatException('asynchronous exception'); - // ignore: dead_code - return c.future; - } - - // Check the criticalSection behaves as expected for the test - - final resultFuture = criticalSection(); - expect(resultFuture, isA>()); - // invoking the criticalSection does not result in the exception - try { - await resultFuture; - fail('critical section did not throw asynchronous exception'); - } on FormatException { - // expected: exception happens on the await - } - - final m = Mutex(); - - try { - // Invoke protect to get the Future (this should succeed) - final resultFuture = m.protect(criticalSection); - expect(resultFuture, isA>()); - - // Even though the criticalSection throws the exception in synchronous - // code, protect causes it to become an asynchronous exception. - - // Wait for the Future (this should fail) - final result = await resultFuture; - expect(result, isNotNull); - fail('exception not thrown'); - } on FormatException catch (e) { - expect(m.isLocked, isFalse); - expect(e.message, equals('asynchronous exception')); - } - - expect(m.isLocked, isFalse); - }); - }); -} diff --git a/packages/veilid_support/example/analysis_options.yaml b/packages/veilid_support/example/analysis_options.yaml index 0d29021..04953d6 100644 --- a/packages/veilid_support/example/analysis_options.yaml +++ b/packages/veilid_support/example/analysis_options.yaml @@ -1,28 +1,15 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml - +include: package:lint_hard/all.yaml +analyzer: + errors: + invalid_annotation_target: ignore + exclude: + - '**/*.g.dart' + - '**/*.freezed.dart' + - '**/*.pb.dart' + - '**/*.pbenum.dart' + - '**/*.pbjson.dart' + - '**/*.pbserver.dart' linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at https://dart.dev/lints. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options + unawaited_futures: true + avoid_positional_boolean_parameters: false diff --git a/packages/veilid_support/example/integration_test/app_test.dart b/packages/veilid_support/example/integration_test/app_test.dart index 542ce54..0b411e0 100644 --- a/packages/veilid_support/example/integration_test/app_test.dart +++ b/packages/veilid_support/example/integration_test/app_test.dart @@ -1,19 +1,30 @@ -@Timeout(Duration(seconds: 60)) +@Timeout(Duration(seconds: 120)) + +library veilid_support_integration_test; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import 'package:veilid_test/veilid_test.dart'; -import 'fixtures.dart'; +import 'fixtures/fixtures.dart'; import 'test_dht_record_pool.dart'; import 'test_dht_short_array.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - final fixture = DefaultFixture(); + final veilidFixture = + DefaultVeilidFixture(programName: 'veilid_support integration test'); + final updateProcessorFixture = + UpdateProcessorFixture(veilidFixture: veilidFixture); + final tickerFixture = + TickerFixture(updateProcessorFixture: updateProcessorFixture); + final dhtRecordPoolFixture = DHTRecordPoolFixture( + tickerFixture: tickerFixture, + updateProcessorFixture: updateProcessorFixture); group('Started Tests', () { - setUpAll(fixture.setUp); - tearDownAll(fixture.tearDown); + setUpAll(veilidFixture.setUp); + tearDownAll(veilidFixture.tearDown); // group('Crypto Tests', () { // test('best cryptosystem', testBestCryptoSystem); @@ -23,15 +34,32 @@ void main() { // }); group('Attached Tests', () { - setUpAll(fixture.attach); - tearDownAll(fixture.detach); + setUpAll(veilidFixture.attach); + tearDownAll(veilidFixture.detach); group('DHT Support Tests', () { - group('DHTRecordPool Tests', () { - test('create pool', testDHTRecordPoolCreate); - }); + setUpAll(updateProcessorFixture.setUp); + setUpAll(tickerFixture.setUp); + tearDownAll(tickerFixture.tearDown); + tearDownAll(updateProcessorFixture.tearDown); + + test('create pool', testDHTRecordPoolCreate); + + // group('DHTRecordPool Tests', () { + // setUpAll(dhtRecordPoolFixture.setUp); + // tearDownAll(dhtRecordPoolFixture.tearDown); + + // test('create/delete record', testDHTRecordCreateDelete); + // test('record scopes', testDHTRecordScopes); + // test('create/delete deep record', testDHTRecordDeepCreateDelete); + // }); + group('DHTShortArray Tests', () { - test('create shortarray', testDHTShortArrayCreate); + setUpAll(dhtRecordPoolFixture.setUp); + tearDownAll(dhtRecordPoolFixture.tearDown); + + // test('create shortarray', testDHTShortArrayCreateDelete); + test('add shortarray', testDHTShortArrayAdd); }); }); }); diff --git a/packages/veilid_support/example/integration_test/fixtures.dart b/packages/veilid_support/example/integration_test/fixtures.dart deleted file mode 100644 index ef69e81..0000000 --- a/packages/veilid_support/example/integration_test/fixtures.dart +++ /dev/null @@ -1,152 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/foundation.dart'; -import 'package:mutex/mutex.dart'; -import 'package:veilid/veilid.dart'; - -class DefaultFixture { - DefaultFixture(); - - StreamSubscription? _veilidUpdateSubscription; - Stream? _veilidUpdateStream; - final StreamController _updateStreamController = - StreamController.broadcast(); - - static final _fixtureMutex = Mutex(); - - Future setUp() async { - await _fixtureMutex.acquire(); - - assert(_veilidUpdateStream == null, 'should not set up fixture twice'); - - final ignoreLogTargetsStr = - // ignore: do_not_use_environment - const String.fromEnvironment('IGNORE_LOG_TARGETS').trim(); - final ignoreLogTargets = ignoreLogTargetsStr.isEmpty - ? [] - : ignoreLogTargetsStr.split(',').map((e) => e.trim()).toList(); - - final Map platformConfigJson; - if (kIsWeb) { - final platformConfig = VeilidWASMConfig( - logging: VeilidWASMConfigLogging( - performance: VeilidWASMConfigLoggingPerformance( - enabled: true, - level: VeilidConfigLogLevel.debug, - logsInTimings: true, - logsInConsole: false, - ignoreLogTargets: ignoreLogTargets, - ), - api: VeilidWASMConfigLoggingApi( - enabled: true, - level: VeilidConfigLogLevel.info, - ignoreLogTargets: ignoreLogTargets, - ))); - platformConfigJson = platformConfig.toJson(); - } else { - final platformConfig = VeilidFFIConfig( - logging: VeilidFFIConfigLogging( - terminal: VeilidFFIConfigLoggingTerminal( - enabled: false, - level: VeilidConfigLogLevel.debug, - ignoreLogTargets: ignoreLogTargets, - ), - otlp: VeilidFFIConfigLoggingOtlp( - enabled: false, - level: VeilidConfigLogLevel.trace, - grpcEndpoint: 'localhost:4317', - serviceName: 'Veilid Tests', - ignoreLogTargets: ignoreLogTargets, - ), - api: VeilidFFIConfigLoggingApi( - enabled: true, - // level: VeilidConfigLogLevel.debug, - level: VeilidConfigLogLevel.info, - ignoreLogTargets: ignoreLogTargets, - ))); - platformConfigJson = platformConfig.toJson(); - } - Veilid.instance.initializeVeilidCore(platformConfigJson); - - var config = await getDefaultVeilidConfig( - isWeb: kIsWeb, - programName: 'Veilid Tests', - // ignore: avoid_redundant_argument_values, do_not_use_environment - bootstrap: const String.fromEnvironment('BOOTSTRAP'), - // ignore: avoid_redundant_argument_values, do_not_use_environment - networkKeyPassword: const String.fromEnvironment('NETWORK_KEY'), - ); - - config = - config.copyWith(tableStore: config.tableStore.copyWith(delete: true)); - config = config.copyWith( - protectedStore: config.protectedStore.copyWith(delete: true)); - config = - config.copyWith(blockStore: config.blockStore.copyWith(delete: true)); - - final us = - _veilidUpdateStream = await Veilid.instance.startupVeilidCore(config); - - _veilidUpdateSubscription = us.listen((update) { - if (update is VeilidLog) { - // print(update.message); - } else if (update is VeilidUpdateAttachment) { - } else if (update is VeilidUpdateConfig) { - } else if (update is VeilidUpdateNetwork) { - } else if (update is VeilidAppMessage) { - } else if (update is VeilidAppCall) { - } else if (update is VeilidUpdateValueChange) { - } else if (update is VeilidUpdateRouteChange) { - } else { - throw Exception('unexpected update: $update'); - } - _updateStreamController.sink.add(update); - }); - } - - Stream get updateStream => _updateStreamController.stream; - - Future attach() async { - await Veilid.instance.attach(); - - // Wait for attached state - while (true) { - final state = await Veilid.instance.getVeilidState(); - var done = false; - if (state.attachment.publicInternetReady) { - switch (state.attachment.state) { - case AttachmentState.detached: - break; - case AttachmentState.attaching: - break; - case AttachmentState.detaching: - break; - default: - done = true; - break; - } - } - if (done) { - break; - } - await Future.delayed(const Duration(seconds: 1)); - } - } - - Future detach() async { - await Veilid.instance.detach(); - } - - Future tearDown() async { - assert(_veilidUpdateStream != null, 'should not tearDown without setUp'); - - final cancelFut = _veilidUpdateSubscription?.cancel(); - await Veilid.instance.shutdownVeilidCore(); - await cancelFut; - - _veilidUpdateSubscription = null; - _veilidUpdateStream = null; - - _fixtureMutex.release(); - } -} diff --git a/packages/veilid_support/example/integration_test/fixtures/dht_record_pool_fixture.dart b/packages/veilid_support/example/integration_test/fixtures/dht_record_pool_fixture.dart new file mode 100644 index 0000000..216d00f --- /dev/null +++ b/packages/veilid_support/example/integration_test/fixtures/dht_record_pool_fixture.dart @@ -0,0 +1,36 @@ +import 'dart:async'; + +import 'package:async_tools/async_tools.dart'; +import 'package:veilid_support/veilid_support.dart'; +import 'package:veilid_test/veilid_test.dart'; + +class DHTRecordPoolFixture implements TickerFixtureTickable { + DHTRecordPoolFixture( + {required this.tickerFixture, required this.updateProcessorFixture}); + + static final _fixtureMutex = Mutex(); + UpdateProcessorFixture updateProcessorFixture; + TickerFixture tickerFixture; + + Future setUp() async { + await _fixtureMutex.acquire(); + await DHTRecordPool.init(); + tickerFixture.register(this); + } + + Future tearDown() async { + assert(_fixtureMutex.isLocked, 'should not tearDown without setUp'); + tickerFixture.unregister(this); + await DHTRecordPool.close(); + _fixtureMutex.release(); + } + + @override + Future onTick() async { + if (!updateProcessorFixture + .processorConnectionState.isPublicInternetReady) { + return; + } + await DHTRecordPool.instance.tick(); + } +} diff --git a/packages/veilid_support/example/integration_test/fixtures/fixtures.dart b/packages/veilid_support/example/integration_test/fixtures/fixtures.dart new file mode 100644 index 0000000..95e89a0 --- /dev/null +++ b/packages/veilid_support/example/integration_test/fixtures/fixtures.dart @@ -0,0 +1 @@ +export 'dht_record_pool_fixture.dart'; diff --git a/packages/veilid_support/example/integration_test/test_dht_record_pool.dart b/packages/veilid_support/example/integration_test/test_dht_record_pool.dart index 56d398e..2f05d0b 100644 --- a/packages/veilid_support/example/integration_test/test_dht_record_pool.dart +++ b/packages/veilid_support/example/integration_test/test_dht_record_pool.dart @@ -1,9 +1,201 @@ import 'dart:convert'; +import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:veilid_support/veilid_support.dart'; Future testDHTRecordPoolCreate() async { - // final cs = await Veilid.instance.bestCryptoSystem(); - // expect(await cs.defaultSaltLength(), equals(16)); + await DHTRecordPool.init(logger: debugPrintSynchronously); + final pool = DHTRecordPool.instance; + await pool.tick(); + await DHTRecordPool.close(); +} + +Future testDHTRecordCreateDelete() async { + final pool = DHTRecordPool.instance; + + // Close before delete + { + final rec = await pool.createRecord(debugName: 'test_create_delete 1'); + expect(rec.isOpen, isTrue); + await rec.close(); + expect(rec.isOpen, isFalse); + await pool.deleteRecord(rec.key); + // Set should fail + await expectLater(() async => rec.tryWriteBytes(utf8.encode('test')), + throwsA(isA())); + } + + // Close after delete + { + final rec2 = await pool.createRecord(debugName: 'test_create_delete 2'); + expect(rec2.isOpen, isTrue); + await pool.deleteRecord(rec2.key); + expect(rec2.isOpen, isTrue); + await rec2.close(); + expect(rec2.isOpen, isFalse); + // Set should fail + await expectLater(() async => rec2.tryWriteBytes(utf8.encode('test')), + throwsA(isA())); + } + + // Close after delete multiple + // Okay to request delete multiple times before close + { + final rec3 = await pool.createRecord(debugName: 'test_create_delete 3'); + await pool.deleteRecord(rec3.key); + await pool.deleteRecord(rec3.key); + // Set should succeed still + await rec3.tryWriteBytes(utf8.encode('test')); + await rec3.close(); + await rec3.close(); + // Set should fail + await expectLater(() async => rec3.tryWriteBytes(utf8.encode('test')), + throwsA(isA())); + // Delete already delete should fail + await expectLater(() async => pool.deleteRecord(rec3.key), + throwsA(isA())); + } +} + +Future testDHTRecordScopes() async { + final pool = DHTRecordPool.instance; + + // Delete scope with exception should propagate exception + { + final rec = await pool.createRecord(debugName: 'test_scope 1'); + await expectLater( + () async => rec.deleteScope((recd) async { + throw Exception(); + }), + throwsA(isA())); + // Set should fail + await expectLater(() async => rec.tryWriteBytes(utf8.encode('test')), + throwsA(isA())); + } + + // Delete scope without exception + { + final rec2 = await pool.createRecord(debugName: 'test_scope 2'); + try { + await rec2.deleteScope((rec2d) async { + // + }); + } on Exception { + assert(false, 'should not throw'); + } + await rec2.close(); + await pool.deleteRecord(rec2.key); + } + + // Close scope without exception + { + final rec3 = await pool.createRecord(debugName: 'test_scope 3'); + try { + await rec3.scope((rec3d) async { + // + }); + } on Exception { + assert(false, 'should not throw'); + } + // Set should fail because scope closed it + await expectLater(() async => rec3.tryWriteBytes(utf8.encode('test')), + throwsA(isA())); + await pool.deleteRecord(rec3.key); + } +} + +Future testDHTRecordGetSet() async { + final pool = DHTRecordPool.instance; + final valdata = utf8.encode('test'); + + // Test get without set + { + final rec = await pool.createRecord(debugName: 'test_get_set 1'); + final val = await rec.get(); + await pool.deleteRecord(rec.key); + expect(val, isNull); + } + + // Test set then get + { + final rec2 = await pool.createRecord(debugName: 'test_get_set 2'); + expect(await rec2.tryWriteBytes(valdata), isNull); + expect(await rec2.get(), equals(valdata)); + // Invalid subkey should throw + await expectLater( + () async => rec2.get(subkey: 1), throwsA(isA())); + await pool.deleteRecord(rec2.key); + } + + // Test set then delete then open then get + { + final rec3 = await pool.createRecord(debugName: 'test_get_set 3'); + expect(await rec3.tryWriteBytes(valdata), isNull); + expect(await rec3.get(), equals(valdata)); + await rec3.close(); + await pool.deleteRecord(rec3.key); + final rec4 = + await pool.openRecordRead(rec3.key, debugName: 'test_get_set 4'); + expect(await rec4.get(), equals(valdata)); + await rec4.close(); + await pool.deleteRecord(rec4.key); + } +} + +Future testDHTRecordDeepCreateDelete() async { + final pool = DHTRecordPool.instance; + const numChildren = 20; + const numIterations = 10; + + // Make root record + final recroot = await pool.createRecord(debugName: 'test_deep_create_delete'); + + for (var d = 0; d < numIterations; d++) { + // Make child set 1 + var parent = recroot; + final children = []; + for (var n = 0; n < numChildren; n++) { + final child = + await pool.createRecord(debugName: 'deep $n', parent: parent.key); + children.add(child); + parent = child; + } + + // Make child set 2 + final children2 = []; + parent = recroot; + for (var n = 0; n < numChildren; n++) { + final child = + await pool.createRecord(debugName: 'deep2 $n ', parent: parent.key); + children2.add(child); + parent = child; + } + // Should fail to delete root + await expectLater( + () async => pool.deleteRecord(recroot.key), throwsA(isA())); + + // Close child set 1 + await children.map((c) => c.close()).wait; + + // Delete child set 1 in reverse order + for (var n = numChildren - 1; n >= 0; n--) { + await pool.deleteRecord(children[n].key); + } + + // Should fail to delete root + await expectLater( + () async => pool.deleteRecord(recroot.key), throwsA(isA())); + + // Close child set 1 + await children2.map((c) => c.close()).wait; + + // Delete child set 2 in reverse order + for (var n = numChildren - 1; n >= 0; n--) { + await pool.deleteRecord(children2[n].key); + } + } + + // Should be able to delete root now + await pool.deleteRecord(recroot.key); } diff --git a/packages/veilid_support/example/integration_test/test_dht_short_array.dart b/packages/veilid_support/example/integration_test/test_dht_short_array.dart index 4a3c627..80497e3 100644 --- a/packages/veilid_support/example/integration_test/test_dht_short_array.dart +++ b/packages/veilid_support/example/integration_test/test_dht_short_array.dart @@ -3,7 +3,86 @@ import 'dart:convert'; import 'package:flutter_test/flutter_test.dart'; import 'package:veilid_support/veilid_support.dart'; -Future testDHTShortArrayCreate() async { - // final cs = await Veilid.instance.bestCryptoSystem(); - // expect(await cs.defaultSaltLength(), equals(16)); +Future testDHTShortArrayCreateDelete() async { + // Close before delete + { + final arr = await DHTShortArray.create(debugName: 'sa_create_delete 1'); + expect(await arr.operate((r) async => r.length), isZero); + expect(arr.isOpen, isTrue); + await arr.close(); + expect(arr.isOpen, isFalse); + await arr.delete(); + // Operate should fail + await expectLater(() async => arr.operate((r) async => r.length), + throwsA(isA())); + } + + // Close after delete + { + final arr = await DHTShortArray.create(debugName: 'sa_create_delete 2'); + await arr.delete(); + // Operate should still succeed because things aren't closed + expect(await arr.operate((r) async => r.length), isZero); + await arr.close(); + // Operate should fail + await expectLater(() async => arr.operate((r) async => r.length), + throwsA(isA())); + } + + // Close after delete multiple + // Okay to request delete multiple times before close + { + final arr = await DHTShortArray.create(debugName: 'sa_create_delete 3'); + await arr.delete(); + await arr.delete(); + // Operate should still succeed because things aren't closed + expect(await arr.operate((r) async => r.length), isZero); + await arr.close(); + await arr.close(); + // Operate should fail + await expectLater(() async => arr.operate((r) async => r.length), + throwsA(isA())); + } +} + +Future testDHTShortArrayAdd() async { + final arr = await DHTShortArray.create(debugName: 'sa_add 1'); + + final dataset = + Iterable.generate(256).map((n) => utf8.encode('elem $n')).toList(); + + print('adding'); + { + final (res, ok) = await arr.operateWrite((w) async { + for (var n = 0; n < dataset.length; n++) { + print('add $n'); + final success = await w.tryAddItem(dataset[n]); + expect(success, isTrue); + } + }); + expect(res, isNull); + expect(ok, isTrue); + } + + print('get all'); + { + final dataset2 = await arr.operate((r) async => r.getAllItems()); + expect(dataset2, equals(dataset)); + } + + print('clear'); + { + final (res, ok) = await arr.operateWrite((w) async => w.tryClear()); + expect(res, isTrue); + expect(ok, isTrue); + } + + print('get all'); + { + final dataset3 = await arr.operate((r) async => r.getAllItems()); + expect(dataset3, isEmpty); + } + + await arr.delete(); + await arr.close(); } diff --git a/packages/veilid_support/example/ios/Runner.xcodeproj/project.pbxproj b/packages/veilid_support/example/ios/Runner.xcodeproj/project.pbxproj index c3b1948..e9fe777 100644 --- a/packages/veilid_support/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/veilid_support/example/ios/Runner.xcodeproj/project.pbxproj @@ -45,6 +45,7 @@ 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 4380113E2BE01E850006987E /* libveilid_flutter.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libveilid_flutter.a; path = "../../../../../veilid/target/lipo-ios/libveilid_flutter.a"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -76,6 +77,14 @@ path = RunnerTests; sourceTree = ""; }; + 4380113D2BE01E850006987E /* Frameworks */ = { + isa = PBXGroup; + children = ( + 4380113E2BE01E850006987E /* libveilid_flutter.a */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -94,6 +103,7 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, + 4380113D2BE01E850006987E /* Frameworks */, ); sourceTree = ""; }; @@ -361,16 +371,22 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = W2TA5TB8Q5; + DEVELOPMENT_TEAM = XP5LBLT7M7; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Veilid Support Tests"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "org.veilid.packages.veilid-support.tests"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -541,16 +557,22 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = W2TA5TB8Q5; + DEVELOPMENT_TEAM = XP5LBLT7M7; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Veilid Support Tests"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "org.veilid.packages.veilid-support.tests"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -564,16 +586,22 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = W2TA5TB8Q5; + DEVELOPMENT_TEAM = XP5LBLT7M7; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Veilid Support Tests"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "org.veilid.packages.veilid-support.tests"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; diff --git a/packages/veilid_support/example/ios/Runner/Info.plist b/packages/veilid_support/example/ios/Runner/Info.plist index 5458fc4..f15383a 100644 --- a/packages/veilid_support/example/ios/Runner/Info.plist +++ b/packages/veilid_support/example/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -24,6 +26,8 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -41,9 +45,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - diff --git a/packages/veilid_support/example/pubspec.lock b/packages/veilid_support/example/pubspec.lock index 2903fb7..a31cdd2 100644 --- a/packages/veilid_support/example/pubspec.lock +++ b/packages/veilid_support/example/pubspec.lock @@ -10,12 +10,13 @@ packages: source: hosted version: "2.11.0" async_tools: - dependency: transitive + dependency: "direct dev" description: - path: "../../async_tools" - relative: true - source: path - version: "1.0.0" + name: async_tools + sha256: "972f68ab663724d86260a31e363c1355ff493308441b872bf4e7b8adc67c832c" + url: "https://pub.dev" + source: hosted + version: "0.1.0" bloc: dependency: transitive description: @@ -24,13 +25,14 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.4" - bloc_tools: + bloc_advanced_tools: dependency: transitive description: - path: "../../bloc_tools" - relative: true - source: path - version: "1.0.0" + name: bloc_advanced_tools + sha256: bc0e1d5c26ae7df011464ab6abc2134dcfb668952acc87359abc7457cab091dd + url: "https://pub.dev" + source: hosted + version: "0.1.0" boolean_selector: dependency: transitive description: @@ -145,14 +147,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" - url: "https://pub.dev" - source: hosted - version: "3.0.2" flutter_test: dependency: "direct dev" description: flutter @@ -221,14 +215,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" - lints: - dependency: transitive + lint_hard: + dependency: "direct dev" description: - name: lints - sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + name: lint_hard + sha256: "44d15ec309b1a8e1aff99069df9dcb1597f49d5f588f32811ca28fb7b38c32fe" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "4.0.0" loggy: dependency: transitive description: @@ -261,13 +255,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.11.0" - mutex: - dependency: "direct dev" - description: - path: "../../mutex" - relative: true - source: path - version: "3.1.0" path: dependency: transitive description: @@ -447,7 +434,7 @@ packages: path: "../../../../veilid/veilid-flutter" relative: true source: path - version: "0.3.1" + version: "0.3.2" veilid_support: dependency: "direct main" description: @@ -455,6 +442,13 @@ packages: relative: true source: path version: "1.0.2+0" + veilid_test: + dependency: "direct dev" + description: + path: "../../../../veilid/veilid-flutter/packages/veilid_test" + relative: true + source: path + version: "0.1.0" vm_service: dependency: transitive description: diff --git a/packages/veilid_support/example/pubspec.yaml b/packages/veilid_support/example/pubspec.yaml index 7575a7e..8860e2d 100644 --- a/packages/veilid_support/example/pubspec.yaml +++ b/packages/veilid_support/example/pubspec.yaml @@ -1,90 +1,27 @@ name: example description: "Veilid Support Example" -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -# In Windows, build-name is used as the major, minor, and patch parts -# of the product and file versions while build-number is used as the build suffix. version: 1.0.0+1 environment: sdk: '>=3.3.4 <4.0.0' -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. dependencies: + cupertino_icons: ^1.0.6 flutter: sdk: flutter - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.6 veilid_support: path: ../ - dev_dependencies: + async_tools: ^0.1.0 flutter_test: sdk: flutter integration_test: sdk: flutter - flutter_lints: ^3.0.1 - mutex: - path: ../../mutex + lint_hard: ^4.0.0 + veilid_test: + path: ../../../../veilid/veilid-flutter/packages/veilid_test -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart index 7d29292..ccb09f8 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart @@ -57,6 +57,7 @@ class DHTRecord { DHTRecordCrypto get crypto => _crypto; OwnedDHTRecordPointer get ownedDHTRecordPointer => OwnedDHTRecordPointer(recordKey: key, owner: ownerKeyPair!); + bool get isOpen => _open; Future close() async { if (!_open) { diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart index 608131c..a64f461 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart @@ -2,10 +2,10 @@ import 'dart:async'; import 'dart:math'; import 'dart:typed_data'; +import 'package:async_tools/async_tools.dart'; import 'package:equatable/equatable.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:mutex/mutex.dart'; import 'package:protobuf/protobuf.dart'; import '../../../../veilid_support.dart'; @@ -179,6 +179,13 @@ class DHTRecordPool with TableDBBackedJson { _singleton = globalPool; } + static Future close() async { + if (_singleton != null) { + _singleton!._routingContext.close(); + _singleton = null; + } + } + Veilid get veilid => _veilid; void log(String message) { @@ -191,8 +198,9 @@ class DHTRecordPool with TableDBBackedJson { required DHTSchema schema, KeyPair? writer, TypedKey? parent}) async { - assert(_mutex.isLocked, 'should be locked here'); - + if (!_mutex.isLocked) { + throw StateError('should be locked here'); + } // Create the record final recordDescriptor = await dhtctx.createDHTRecord(schema); @@ -225,8 +233,9 @@ class DHTRecordPool with TableDBBackedJson { required TypedKey recordKey, KeyPair? writer, TypedKey? parent}) async { - assert(_mutex.isLocked, 'should be locked here'); - + if (!_mutex.isLocked) { + throw StateError('should be locked here'); + } log('openDHTRecord: debugName=$debugName key=$recordKey'); // If we are opening a key that already exists @@ -303,8 +312,9 @@ class DHTRecordPool with TableDBBackedJson { // Collect all dependencies (including the record itself) // in reverse (bottom-up/delete order) List _collectChildrenInner(TypedKey recordKey) { - assert(_mutex.isLocked, 'should be locked here'); - + if (!_mutex.isLocked) { + throw StateError('should be locked here'); + } final allDeps = []; final currentDeps = [recordKey]; while (currentDeps.isNotEmpty) { @@ -318,16 +328,18 @@ class DHTRecordPool with TableDBBackedJson { return allDeps.reversedView; } - void _debugPrintChildren(TypedKey recordKey, {List? allDeps}) { + String _debugChildren(TypedKey recordKey, {List? allDeps}) { allDeps ??= _collectChildrenInner(recordKey); // ignore: avoid_print - print('Parent: $recordKey (${_state.debugNames[recordKey.toString()]})'); + var out = + 'Parent: $recordKey (${_state.debugNames[recordKey.toString()]})\n'; for (final dep in allDeps) { if (dep != recordKey) { // ignore: avoid_print - print(' Child: $dep (${_state.debugNames[dep.toString()]})'); + out += ' Child: $dep (${_state.debugNames[dep.toString()]})\n'; } } + return out; } Future _deleteRecordInner(TypedKey recordKey) async { @@ -343,8 +355,8 @@ class DHTRecordPool with TableDBBackedJson { final allDeps = _collectChildrenInner(recordKey); if (allDeps.singleOrNull != recordKey) { - _debugPrintChildren(recordKey, allDeps: allDeps); - assert(false, 'must delete children first'); + final dbgstr = _debugChildren(recordKey, allDeps: allDeps); + throw StateError('must delete children first: $dbgstr'); } final ori = _opened[recordKey]; @@ -359,7 +371,9 @@ class DHTRecordPool with TableDBBackedJson { } void _validateParentInner(TypedKey? parent, TypedKey child) { - assert(_mutex.isLocked, 'should be locked here'); + if (!_mutex.isLocked) { + throw StateError('should be locked here'); + } final childJson = child.toJson(); final existingParent = _state.parentByChild[childJson]; @@ -379,7 +393,9 @@ class DHTRecordPool with TableDBBackedJson { Future _addDependencyInner(TypedKey? parent, TypedKey child, {required String debugName}) async { - assert(_mutex.isLocked, 'should be locked here'); + if (!_mutex.isLocked) { + throw StateError('should be locked here'); + } if (parent == null) { if (_state.rootRecords.contains(child)) { // Dependency already added @@ -404,8 +420,9 @@ class DHTRecordPool with TableDBBackedJson { } Future _removeDependenciesInner(List childList) async { - assert(_mutex.isLocked, 'should be locked here'); - + if (!_mutex.isLocked) { + throw StateError('should be locked here'); + } var state = _state; for (final child in childList) { @@ -442,7 +459,7 @@ class DHTRecordPool with TableDBBackedJson { /////////////////////////////////////////////////////////////////////// /// Create a root DHTRecord that has no dependent records - Future create({ + Future createRecord({ required String debugName, VeilidRoutingContext? routingContext, TypedKey? parent, @@ -479,7 +496,7 @@ class DHTRecordPool with TableDBBackedJson { }); /// Open a DHTRecord readonly - Future openRead(TypedKey recordKey, + Future openRecordRead(TypedKey recordKey, {required String debugName, VeilidRoutingContext? routingContext, TypedKey? parent, @@ -508,7 +525,7 @@ class DHTRecordPool with TableDBBackedJson { }); /// Open a DHTRecord writable - Future openWrite( + Future openRecordWrite( TypedKey recordKey, KeyPair writer, { required String debugName, @@ -548,7 +565,7 @@ class DHTRecordPool with TableDBBackedJson { /// This is primarily used for backing up private content on to the DHT /// to synchronizing it between devices. Because it is 'owned', the correct /// parent must be specified. - Future openOwned( + Future openRecordOwned( OwnedDHTRecordPointer ownedDHTRecordPointer, { required String debugName, required TypedKey parent, @@ -556,7 +573,7 @@ class DHTRecordPool with TableDBBackedJson { int defaultSubkey = 0, DHTRecordCrypto? crypto, }) => - openWrite( + openRecordWrite( ownedDHTRecordPointer.recordKey, ownedDHTRecordPointer.owner, debugName: debugName, diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array.dart index de17b62..f15987a 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array.dart @@ -1,8 +1,8 @@ import 'dart:async'; import 'dart:typed_data'; +import 'package:async_tools/async_tools.dart'; import 'package:collection/collection.dart'; -import 'package:mutex/mutex.dart'; import 'package:protobuf/protobuf.dart'; import '../../../veilid_support.dart'; @@ -43,7 +43,7 @@ class DHTShortArray { final schema = DHTSchema.smpl( oCnt: 0, members: [DHTSchemaMember(mKey: smplWriter.key, mCnt: stride + 1)]); - dhtRecord = await pool.create( + dhtRecord = await pool.createRecord( debugName: debugName, parent: parent, routingContext: routingContext, @@ -52,7 +52,7 @@ class DHTShortArray { writer: smplWriter); } else { final schema = DHTSchema.dflt(oCnt: stride + 1); - dhtRecord = await pool.create( + dhtRecord = await pool.createRecord( debugName: debugName, parent: parent, routingContext: routingContext, @@ -80,7 +80,7 @@ class DHTShortArray { VeilidRoutingContext? routingContext, TypedKey? parent, DHTRecordCrypto? crypto}) async { - final dhtRecord = await DHTRecordPool.instance.openRead(headRecordKey, + final dhtRecord = await DHTRecordPool.instance.openRecordRead(headRecordKey, debugName: debugName, parent: parent, routingContext: routingContext, @@ -103,7 +103,7 @@ class DHTShortArray { TypedKey? parent, DHTRecordCrypto? crypto, }) async { - final dhtRecord = await DHTRecordPool.instance.openWrite( + final dhtRecord = await DHTRecordPool.instance.openRecordWrite( headRecordKey, writer, debugName: debugName, parent: parent, @@ -144,21 +144,31 @@ class DHTShortArray { /// Get the record pointer foir this shortarray OwnedDHTRecordPointer get recordPointer => _head.recordPointer; + /// Check if the shortarray is open + bool get isOpen => _head.isOpen; + /// Free all resources for the DHTShortArray Future close() async { + if (!isOpen) { + return; + } await _watchController?.close(); + _watchController = null; await _head.close(); } /// Free all resources for the DHTShortArray and delete it from the DHT + /// Will wait until the short array is closed to delete it Future delete() async { - await close(); - await DHTRecordPool.instance.deleteRecord(recordKey); + await _head.delete(); } /// Runs a closure that guarantees the DHTShortArray /// will be closed upon exit, even if an uncaught exception is thrown Future scope(Future Function(DHTShortArray) scopeFunction) async { + if (!isOpen) { + throw StateError('short array is not open"'); + } try { return await scopeFunction(this); } finally { @@ -171,6 +181,10 @@ class DHTShortArray { /// uncaught exception is thrown Future deleteScope( Future Function(DHTShortArray) scopeFunction) async { + if (!isOpen) { + throw StateError('short array is not open"'); + } + try { final out = await scopeFunction(this); await close(); @@ -182,11 +196,16 @@ class DHTShortArray { } /// Runs a closure allowing read-only access to the shortarray - Future operate(Future Function(DHTShortArrayRead) closure) async => - _head.operate((head) async { - final reader = _DHTShortArrayRead._(head); - return closure(reader); - }); + Future operate(Future Function(DHTShortArrayRead) closure) async { + if (!isOpen) { + throw StateError('short array is not open"'); + } + + return _head.operate((head) async { + final reader = _DHTShortArrayRead._(head); + return closure(reader); + }); + } /// Runs a closure allowing read-write access to the shortarray /// Makes only one attempt to consistently write the changes to the DHT @@ -206,38 +225,48 @@ class DHTShortArray { /// succeeded, returning false will trigger another eventual consistency /// attempt. Future operateWriteEventual( - Future Function(DHTShortArrayWrite) closure, - {Duration? timeout}) async => - _head.operateWriteEventual((head) async { - final writer = _DHTShortArrayWrite._(head); - return closure(writer); - }, timeout: timeout); + Future Function(DHTShortArrayWrite) closure, + {Duration? timeout}) async { + if (!isOpen) { + throw StateError('short array is not open"'); + } + + return _head.operateWriteEventual((head) async { + final writer = _DHTShortArrayWrite._(head); + return closure(writer); + }, timeout: timeout); + } /// Listen to and any all changes to the structure of this short array /// regardless of where the changes are coming from Future> listen( void Function() onChanged, - ) => - _listenMutex.protect(() async { - // If don't have a controller yet, set it up - if (_watchController == null) { - // Set up watch requirements - _watchController = StreamController.broadcast(onCancel: () { - // If there are no more listeners then we can get - // rid of the controller and drop our subscriptions - unawaited(_listenMutex.protect(() async { - // Cancel watches of head record - await _head.cancelWatch(); - _watchController = null; - })); - }); + ) { + if (!isOpen) { + throw StateError('short array is not open"'); + } - // Start watching head record - await _head.watch(); - } - // Return subscription - return _watchController!.stream.listen((_) => onChanged()); - }); + return _listenMutex.protect(() async { + // If don't have a controller yet, set it up + if (_watchController == null) { + // Set up watch requirements + _watchController = StreamController.broadcast(onCancel: () { + // If there are no more listeners then we can get + // rid of the controller and drop our subscriptions + unawaited(_listenMutex.protect(() async { + // Cancel watches of head record + await _head.cancelWatch(); + _watchController = null; + })); + }); + + // Start watching head record + await _head.watch(); + } + // Return subscription + return _watchController!.stream.listen((_) => onChanged()); + }); + } //////////////////////////////////////////////////////////////// // Fields diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_cubit.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_cubit.dart index cdce828..3375e9f 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_cubit.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_cubit.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:async_tools/async_tools.dart'; import 'package:bloc/bloc.dart'; -import 'package:bloc_tools/bloc_tools.dart'; +import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; import 'package:equatable/equatable.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:meta/meta.dart'; diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart index f6f3a5a..6da7791 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart @@ -50,13 +50,30 @@ class _DHTShortArrayHead { TypedKey get recordKey => _headRecord.key; OwnedDHTRecordPointer get recordPointer => _headRecord.ownedDHTRecordPointer; int get length => _index.length; + bool get isOpen => _headRecord.isOpen; Future close() async { - final futures = >[_headRecord.close()]; - for (final lr in _linkedRecords) { - futures.add(lr.close()); - } - await Future.wait(futures); + await _headMutex.protect(() async { + if (!isOpen) { + return; + } + final futures = >[_headRecord.close()]; + for (final lr in _linkedRecords) { + futures.add(lr.close()); + } + await Future.wait(futures); + }); + } + + Future delete() async { + await _headMutex.protect(() async { + final pool = DHTRecordPool.instance; + final futures = >[pool.deleteRecord(_headRecord.key)]; + for (final lr in _linkedRecords) { + futures.add(pool.deleteRecord(lr.key)); + } + await Future.wait(futures); + }); } Future operate(Future Function(_DHTShortArrayHead) closure) async => @@ -270,7 +287,7 @@ class _DHTShortArrayHead { final schema = DHTSchema.smpl( oCnt: 0, members: [DHTSchemaMember(mKey: smplWriter.key, mCnt: _stride)]); - final dhtRecord = await pool.create( + final dhtRecord = await pool.createRecord( debugName: '${_headRecord.debugName}_linked_$recordNumber', parent: parent, routingContext: routingContext, @@ -292,14 +309,14 @@ class _DHTShortArrayHead { TypedKey recordKey, int recordNumber) async { final writer = _headRecord.writer; return (writer != null) - ? await DHTRecordPool.instance.openWrite( + ? await DHTRecordPool.instance.openRecordWrite( recordKey, writer, debugName: '${_headRecord.debugName}_linked_$recordNumber', parent: _headRecord.key, routingContext: _headRecord.routingContext, ) - : await DHTRecordPool.instance.openRead( + : await DHTRecordPool.instance.openRecordRead( recordKey, debugName: '${_headRecord.debugName}_linked_$recordNumber', parent: _headRecord.key, diff --git a/packages/veilid_support/lib/src/async_table_db_backed_cubit.dart b/packages/veilid_support/lib/src/async_table_db_backed_cubit.dart index da278d4..5d0d84f 100644 --- a/packages/veilid_support/lib/src/async_table_db_backed_cubit.dart +++ b/packages/veilid_support/lib/src/async_table_db_backed_cubit.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:async_tools/async_tools.dart'; import 'package:bloc/bloc.dart'; import 'package:meta/meta.dart'; -import 'package:mutex/mutex.dart'; import 'table_db.dart'; diff --git a/packages/veilid_support/lib/src/identity.dart b/packages/veilid_support/lib/src/identity.dart index 9d26a7d..baae797 100644 --- a/packages/veilid_support/lib/src/identity.dart +++ b/packages/veilid_support/lib/src/identity.dart @@ -134,7 +134,7 @@ extension IdentityMasterExtension on IdentityMaster { identityRecordKey.kind, identitySecret); late final List accountRecordInfo; - await (await pool.openRead(identityRecordKey, + await (await pool.openRecordRead(identityRecordKey, debugName: 'IdentityMaster::readAccountsFromIdentity::IdentityRecord', parent: masterRecordKey, @@ -168,14 +168,14 @@ extension IdentityMasterExtension on IdentityMaster { // Open identity key for writing veilidLoggy.debug('Opening identity record'); - return (await pool.openWrite( + return (await pool.openRecordWrite( identityRecordKey, identityWriter(identitySecret), debugName: 'IdentityMaster::addAccountToIdentity::IdentityRecord', parent: masterRecordKey)) .scope((identityRec) async { // Create new account to insert into identity veilidLoggy.debug('Creating new account'); - return (await pool.create( + return (await pool.createRecord( debugName: 'IdentityMaster::addAccountToIdentity::AccountRecord', parent: identityRec.key)) .deleteScope((accountRec) async { @@ -231,14 +231,14 @@ class IdentityMasterWithSecrets { // IdentityMaster DHT record is public/unencrypted veilidLoggy.debug('Creating master identity record'); - return (await pool.create( + return (await pool.createRecord( debugName: 'IdentityMasterWithSecrets::create::IdentityMasterRecord', crypto: const DHTRecordCryptoPublic())) .deleteScope((masterRec) async { veilidLoggy.debug('Creating identity record'); // Identity record is private - return (await pool.create( + return (await pool.createRecord( debugName: 'IdentityMasterWithSecrets::create::IdentityRecord', parent: masterRec.key)) .scope((identityRec) async { @@ -296,7 +296,7 @@ Future openIdentityMaster( final pool = DHTRecordPool.instance; // IdentityMaster DHT record is public/unencrypted - return (await pool.openRead(identityMasterRecordKey, + return (await pool.openRecordRead(identityMasterRecordKey, debugName: 'IdentityMaster::openIdentityMaster::IdentityMasterRecord')) .deleteScope((masterRec) async { diff --git a/packages/veilid_support/lib/src/persistent_queue.dart b/packages/veilid_support/lib/src/persistent_queue.dart index fb76a10..63f0cfb 100644 --- a/packages/veilid_support/lib/src/persistent_queue.dart +++ b/packages/veilid_support/lib/src/persistent_queue.dart @@ -3,7 +3,6 @@ import 'dart:typed_data'; import 'package:async_tools/async_tools.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; -import 'package:mutex/mutex.dart'; import 'package:protobuf/protobuf.dart'; import 'table_db.dart'; diff --git a/packages/veilid_support/pubspec.lock b/packages/veilid_support/pubspec.lock index 9dcb93e..5cfd348 100644 --- a/packages/veilid_support/pubspec.lock +++ b/packages/veilid_support/pubspec.lock @@ -36,10 +36,11 @@ packages: async_tools: dependency: "direct main" description: - path: "../async_tools" - relative: true - source: path - version: "1.0.0" + name: async_tools + sha256: "972f68ab663724d86260a31e363c1355ff493308441b872bf4e7b8adc67c832c" + url: "https://pub.dev" + source: hosted + version: "0.1.0" bloc: dependency: "direct main" description: @@ -48,13 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.3" - bloc_tools: + bloc_advanced_tools: dependency: "direct main" description: - path: "../bloc_tools" - relative: true - source: path - version: "1.0.0" + name: bloc_advanced_tools + sha256: bc0e1d5c26ae7df011464ab6abc2134dcfb668952acc87359abc7457cab091dd + url: "https://pub.dev" + source: hosted + version: "0.1.0" boolean_selector: dependency: transitive description: @@ -409,13 +411,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" - mutex: - dependency: "direct main" - description: - path: "../mutex" - relative: true - source: path - version: "3.1.0" node_preamble: dependency: transitive description: diff --git a/packages/veilid_support/pubspec.yaml b/packages/veilid_support/pubspec.yaml index 0d2d439..99eb44e 100644 --- a/packages/veilid_support/pubspec.yaml +++ b/packages/veilid_support/pubspec.yaml @@ -7,11 +7,9 @@ environment: sdk: '>=3.2.0 <4.0.0' dependencies: - async_tools: - path: ../async_tools + async_tools: ^0.1.0 bloc: ^8.1.3 - bloc_tools: - path: ../bloc_tools + bloc_advanced_tools: ^0.1.0 collection: ^1.18.0 equatable: ^2.0.5 fast_immutable_collections: ^10.1.1 @@ -19,8 +17,6 @@ dependencies: json_annotation: ^4.8.1 loggy: ^2.0.3 meta: ^1.11.0 - mutex: - path: ../mutex protobuf: ^3.1.0 veilid: diff --git a/pubspec.lock b/pubspec.lock index 6d53fc0..09d4fb0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -60,10 +60,10 @@ packages: async_tools: dependency: "direct main" description: - path: "packages/async_tools" + path: "../dart_async_tools" relative: true source: path - version: "1.0.0" + version: "0.1.0" awesome_extensions: dependency: "direct main" description: @@ -96,13 +96,13 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.4" - bloc_tools: + bloc_advanced_tools: dependency: "direct main" description: - path: "packages/bloc_tools" + path: "../bloc_advanced_tools" relative: true source: path - version: "1.0.0" + version: "0.1.0" blurry_modal_progress_hud: dependency: "direct main" description: @@ -830,13 +830,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.9.1" - mutex: - dependency: "direct main" - description: - path: "packages/mutex" - relative: true - source: path - version: "3.1.0" nested: dependency: transitive description: @@ -1504,7 +1497,7 @@ packages: path: "../veilid/veilid-flutter" relative: true source: path - version: "0.3.1" + version: "0.3.2" veilid_support: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index f8ac76c..cd42f00 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,14 +11,12 @@ dependencies: animated_theme_switcher: ^2.0.10 ansicolor: ^2.0.2 archive: ^3.4.10 - async_tools: - path: packages/async_tools + async_tools: ^0.1.0 awesome_extensions: ^2.0.14 badges: ^3.1.2 basic_utils: ^5.7.0 bloc: ^8.1.4 - bloc_tools: - path: packages/bloc_tools + bloc_advanced_tools: ^0.1.0 blurry_modal_progress_hud: ^1.1.1 change_case: ^2.0.1 charcode: ^1.3.1 @@ -55,8 +53,6 @@ dependencies: meta: ^1.11.0 mobile_scanner: ^4.0.1 motion_toast: ^2.9.1 - mutex: - path: packages/mutex pasteboard: ^0.2.0 path: ^1.9.0 path_provider: ^2.1.3 @@ -87,6 +83,12 @@ dependencies: xterm: ^4.0.0 zxing2: ^0.2.3 +dependency_overrides: + async_tools: + path: ../dart_async_tools + bloc_advanced_tools: + path: ../bloc_advanced_tools + dev_dependencies: build_runner: ^2.4.9 freezed: ^2.5.2