From 7ed7a466033f99a1d9066b14e56fbebef135695a Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Wed, 25 Sep 2024 18:33:13 +0900 Subject: [PATCH 1/5] Bump lib9c 1.18.0 --- ArenaService/ArenaService/ArenaService.csproj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ArenaService/ArenaService/ArenaService.csproj b/ArenaService/ArenaService/ArenaService.csproj index c91139d..8fc88e8 100644 --- a/ArenaService/ArenaService/ArenaService.csproj +++ b/ArenaService/ArenaService/ArenaService.csproj @@ -16,8 +16,7 @@ - - + From 36bc7f25125f8ffe290ddfb0de428e84983c9936 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Sun, 13 Oct 2024 08:38:04 +0900 Subject: [PATCH 2/5] Use keep alive --- ArenaService/ArenaService/RpcClient.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ArenaService/ArenaService/RpcClient.cs b/ArenaService/ArenaService/RpcClient.cs index c5bb345..71df132 100644 --- a/ArenaService/ArenaService/RpcClient.cs +++ b/ArenaService/ArenaService/RpcClient.cs @@ -80,6 +80,8 @@ private async Task Join(CancellationToken cancellationToken) HttpHandler = new SocketsHttpHandler { EnableMultipleHttp2Connections = true, + KeepAlivePingDelay = TimeSpan.FromSeconds(60), + KeepAlivePingTimeout = TimeSpan.FromSeconds(30), } } ); From b2b1151612da5d41db1c185c112ebe8064139b7d Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Tue, 5 Nov 2024 13:43:41 +0900 Subject: [PATCH 3/5] Bump lib9c 1.19.0 - add libplanet specify version avoid nuget dependency error --- ArenaService/ArenaService/ArenaService.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ArenaService/ArenaService/ArenaService.csproj b/ArenaService/ArenaService/ArenaService.csproj index 8fc88e8..a0b047c 100644 --- a/ArenaService/ArenaService/ArenaService.csproj +++ b/ArenaService/ArenaService/ArenaService.csproj @@ -16,7 +16,8 @@ - + + From dbaccfaa97368985f6a0d59a5a9ba59c6155896b Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Tue, 5 Nov 2024 13:56:32 +0900 Subject: [PATCH 4/5] Use redis healthcheck library --- ArenaService/ArenaService/ArenaService.csproj | 1 + ArenaService/ArenaService/Program.cs | 4 +-- .../RedisArenaParticipantsService.cs | 2 +- ArenaService/ArenaService/RedisHealthCheck.cs | 24 ---------------- .../ArenaService/RedisHealthCheckService.cs | 28 ------------------- 5 files changed, 3 insertions(+), 56 deletions(-) delete mode 100644 ArenaService/ArenaService/RedisHealthCheck.cs delete mode 100644 ArenaService/ArenaService/RedisHealthCheckService.cs diff --git a/ArenaService/ArenaService/ArenaService.csproj b/ArenaService/ArenaService/ArenaService.csproj index a0b047c..878b614 100644 --- a/ArenaService/ArenaService/ArenaService.csproj +++ b/ArenaService/ArenaService/ArenaService.csproj @@ -11,6 +11,7 @@ + diff --git a/ArenaService/ArenaService/Program.cs b/ArenaService/ArenaService/Program.cs index afc8b11..66e754f 100644 --- a/ArenaService/ArenaService/Program.cs +++ b/ArenaService/ArenaService/Program.cs @@ -27,9 +27,7 @@ builder.Services .AddSingleton(_ => redis) .AddScoped() - .AddSingleton() .AddSingleton() - .AddHostedService() .AddGraphQL(options => options.EnableMetrics = true) .AddSystemTextJson() .AddGraphTypes(typeof(AddressType)) @@ -47,7 +45,7 @@ var healthChecksBuilder = builder.Services .AddHealthChecks() - .AddCheck(nameof(RedisHealthCheck)); + .AddRedis(redis); if (enableWorker) { healthChecksBuilder.AddCheck(nameof(RpcNodeHealthCheck)); diff --git a/ArenaService/ArenaService/RedisArenaParticipantsService.cs b/ArenaService/ArenaService/RedisArenaParticipantsService.cs index 3e44148..44721b4 100644 --- a/ArenaService/ArenaService/RedisArenaParticipantsService.cs +++ b/ArenaService/ArenaService/RedisArenaParticipantsService.cs @@ -4,7 +4,7 @@ namespace ArenaService; -public class RedisArenaParticipantsService(IConnectionMultiplexer redis, RedisHealthCheck redisHealthCheck) +public class RedisArenaParticipantsService(IConnectionMultiplexer redis) : IRedisArenaParticipantsService { public const string SeasonKey = "season"; diff --git a/ArenaService/ArenaService/RedisHealthCheck.cs b/ArenaService/ArenaService/RedisHealthCheck.cs deleted file mode 100644 index 8ecd22e..0000000 --- a/ArenaService/ArenaService/RedisHealthCheck.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.Extensions.Diagnostics.HealthChecks; - -namespace ArenaService; - -public class RedisHealthCheck : IHealthCheck -{ - private volatile bool _ready; - - public bool ConnectCompleted - { - get => _ready; - set => _ready = value; - } - - public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = new CancellationToken()) - { - if (ConnectCompleted) - { - return Task.FromResult(HealthCheckResult.Healthy("redis caching completed")); - } - - return Task.FromResult(HealthCheckResult.Unhealthy("redis caching not completed")); - } -} diff --git a/ArenaService/ArenaService/RedisHealthCheckService.cs b/ArenaService/ArenaService/RedisHealthCheckService.cs deleted file mode 100644 index f76d6d3..0000000 --- a/ArenaService/ArenaService/RedisHealthCheckService.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace ArenaService; - -public class RedisHealthCheckService(IRedisArenaParticipantsService redisArenaParticipantsService, RedisHealthCheck redisHealthCheck) : IHostedService -{ - public async Task StartAsync(CancellationToken cancellationToken) - { - _ = HealthCheck(cancellationToken); - await Task.CompletedTask; - } - - public async Task StopAsync(CancellationToken cancellationToken) - { - await Task.CompletedTask; - } - - - private async Task HealthCheck(CancellationToken cancellationToken) - { - while (true) - { - if (cancellationToken.IsCancellationRequested) cancellationToken.ThrowIfCancellationRequested(); - - var seasonKey = await redisArenaParticipantsService.GetSeasonKeyAsync(); - redisHealthCheck.ConnectCompleted = !string.IsNullOrEmpty(seasonKey); - await Task.Delay(8000, cancellationToken); - } - } -} From 32ebb63f7768a27b4e2b4e6a56265d709874ef34 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Tue, 5 Nov 2024 14:00:05 +0900 Subject: [PATCH 5/5] Set timeout for worker task --- ArenaService/ArenaService/ArenaWorker.cs | 20 +-- ArenaService/ArenaService/RpcClient.cs | 187 +++++++++++++++++------ 2 files changed, 148 insertions(+), 59 deletions(-) diff --git a/ArenaService/ArenaService/ArenaWorker.cs b/ArenaService/ArenaService/ArenaWorker.cs index da6c2bf..e42f2e5 100644 --- a/ArenaService/ArenaService/ArenaWorker.cs +++ b/ArenaService/ArenaService/ArenaWorker.cs @@ -8,6 +8,7 @@ public class ArenaParticipantsWorker : BackgroundService private int _interval; private IRedisArenaParticipantsService _service; private ILogger _logger; + private static readonly CancellationTokenSource _cts = new CancellationTokenSource(); public ArenaParticipantsWorker(RpcClient rpcClient, IRedisArenaParticipantsService service, ILogger logger) { @@ -27,10 +28,9 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) await PrepareArenaParticipants(); } } - catch (OperationCanceledException) + finally { - //pass - _logger.LogInformation("[ArenaParticipantsWorker]Cancel ArenaParticipantsWorker"); + _cts.Dispose(); } } @@ -46,9 +46,11 @@ public async Task PrepareArenaParticipants() // Copy from NineChronicles RxProps.Arena // https://github.com/planetarium/NineChronicles/blob/80.0.1/nekoyume/Assets/_Scripts/State/RxProps.Arena.cs#L279 var retry = 0; + _cts.CancelAfter(TimeSpan.FromMinutes(5)); + var cancellationToken = _cts.Token; while (_rpcClient.Tip?.Index == _rpcClient.PreviousTip?.Index) { - await Task.Delay((5 - retry) * 1000); + await Task.Delay((5 - retry) * 1000, cancellationToken); retry++; if (retry >= 3) { @@ -56,9 +58,9 @@ public async Task PrepareArenaParticipants() } } - var tip = _rpcClient.Tip; - var currentRoundData = await _rpcClient.GetRoundData(tip); - var participants = await _rpcClient.GetArenaParticipantsState(tip, currentRoundData); + var tip = _rpcClient.Tip!; + var currentRoundData = await _rpcClient.GetRoundData(tip, cancellationToken); + var participants = await _rpcClient.GetArenaParticipantsState(tip, currentRoundData, cancellationToken); var cacheKey = $"{currentRoundData.ChampionshipId}_{currentRoundData.Round}"; var scoreCacheKey = $"{cacheKey}_scores"; var prevAddrAndScores = await _service.GetAvatarAddrAndScores(scoreCacheKey); @@ -73,13 +75,13 @@ public async Task PrepareArenaParticipants() var avatarAddrList = participants.AvatarAddresses; // 최신상태의 아바타 주소, 점수를 조회 - var avatarAddrAndScores = await _rpcClient.GetAvatarAddrAndScores(tip, avatarAddrList, currentRoundData); + var avatarAddrAndScores = await _rpcClient.GetAvatarAddrAndScores(tip, avatarAddrList, currentRoundData, cancellationToken); // 이전상태의 아바타 주소, 점수를 비교해서 추가되거나 점수가 변경된 대상만 찾음 var updatedAddressAndScores = avatarAddrAndScores.Except(prevAddrAndScores).ToList(); // 전체목록의 랭킹 순서 처리 var avatarAddrAndScoresWithRank = _rpcClient.AvatarAddrAndScoresWithRank(avatarAddrAndScores); // 전체목록의 ArenaParticipant 업데이트 - var result = await _rpcClient.GetArenaParticipants(tip, updatedAddressAndScores.Select(i => i.AvatarAddr).ToList(), avatarAddrAndScoresWithRank, prevArenaParticipants); + var result = await _rpcClient.GetArenaParticipants(tip, updatedAddressAndScores.Select(i => i.AvatarAddr).ToList(), avatarAddrAndScoresWithRank, prevArenaParticipants, cancellationToken); // 캐시 업데이트 await _service.SetArenaParticipantsAsync(cacheKey, result, expiry); await _service.SetSeasonAsync(cacheKey, expiry); diff --git a/ArenaService/ArenaService/RpcClient.cs b/ArenaService/ArenaService/RpcClient.cs index 71df132..0925bf2 100644 --- a/ArenaService/ArenaService/RpcClient.cs +++ b/ArenaService/ArenaService/RpcClient.cs @@ -158,14 +158,18 @@ public void OnPreloadEnd() /// Get by block index. /// /// target block + /// /// - public async Task GetSheet(Block block) where T : ISheet, new() + public async Task GetSheet(Block block, CancellationToken cancellationToken) where T : ISheet, new() { var address = Addresses.GetSheetAddress(); - var result = await _service.GetStateByStateRootHash( - block.StateRootHash.ToByteArray(), - ReservedAddresses.LegacyAccount.ToByteArray(), - address.ToByteArray()); + var result = await _service + .WithCancellationToken(cancellationToken) + .GetStateByStateRootHash( + block.StateRootHash.ToByteArray(), + ReservedAddresses.LegacyAccount.ToByteArray(), + address.ToByteArray() + ); if (_codec.Decode(result) is Text t) { var sheet = new T(); @@ -181,23 +185,34 @@ public void OnPreloadEnd() /// /// target block /// state address + /// /// or null if not found. - public async Task GetLegacyState(Block block, Address address) + public async Task GetLegacyState(Block block, Address address, CancellationToken cancellationToken) { + if (cancellationToken.IsCancellationRequested) + { + cancellationToken.ThrowIfCancellationRequested(); + } return await GetState( block, ReservedAddresses.LegacyAccount, - address); + address, + cancellationToken); } /// /// Gets the round data from specified block. /// /// target block + /// /// round data - public async Task GetRoundData(Block block) + public async Task GetRoundData(Block block, CancellationToken cancellationToken) { - var arenaSheet = await GetSheet(block); + if (cancellationToken.IsCancellationRequested) + { + cancellationToken.ThrowIfCancellationRequested(); + } + var arenaSheet = await GetSheet(block, cancellationToken); return arenaSheet.GetRoundByBlockIndex(block.Index); } @@ -206,13 +221,19 @@ public void OnPreloadEnd() /// /// target block /// The current round data. + /// /// The arena participants state, or null if not found. - public async Task GetArenaParticipantsState(Block block, ArenaSheet.RoundData currentRoundData) + public async Task GetArenaParticipantsState(Block block, ArenaSheet.RoundData currentRoundData, + CancellationToken cancellationToken) { + if (cancellationToken.IsCancellationRequested) + { + cancellationToken.ThrowIfCancellationRequested(); + } var participantsAddr = ArenaParticipants.DeriveAddress( currentRoundData.ChampionshipId, currentRoundData.Round); - var participants = await GetLegacyState(block, participantsAddr) is List participantsList + var participants = await GetLegacyState(block, participantsAddr, cancellationToken) is List participantsList ? new ArenaParticipants(participantsList) : null; return participants; @@ -292,8 +313,13 @@ public List AvatarAddrAndScoresWithRank(List> GetAvatarAddrAndScores(Block block, List
avatarAddrList, ArenaSheet.RoundData currentRoundData) + public async Task> GetAvatarAddrAndScores(Block block, List
avatarAddrList, + ArenaSheet.RoundData currentRoundData, CancellationToken cancellationToken) { + if (cancellationToken.IsCancellationRequested) + { + cancellationToken.ThrowIfCancellationRequested(); + } var avatarAndScoreAddrList = avatarAddrList .Select(avatarAddr => ( avatarAddr, @@ -305,7 +331,8 @@ public async Task> GetAvatarAddrAndScores(Block bloc var scores = await GetStates( block, ReservedAddresses.LegacyAccount, - avatarAndScoreAddrList.Select(tuple => tuple.Item2).ToList()); + avatarAndScoreAddrList.Select(tuple => tuple.Item2).ToList(), + cancellationToken); var avatarAddrAndScores = new List(); foreach (var tuple in avatarAndScoreAddrList) { @@ -324,41 +351,51 @@ public async Task> GetAvatarAddrAndScores(Block bloc /// The list of avatar addresses to filter the matching participants. /// The list of avatar addresses with their scores and ranks. /// The list of previous synced arena participants. if the score has not changed, is reused. + /// /// A list of arena participants. public async Task> GetArenaParticipants(Block block, List
avatarAddrList, - List avatarAddrAndScoresWithRank, List prevArenaParticipants) + List avatarAddrAndScoresWithRank, List prevArenaParticipants, + CancellationToken cancellationToken) { - var runeListSheet = await GetSheet(block); - var costumeSheet = await GetSheet(block); - var characterSheet = await GetSheet(block); - var runeOptionSheet = await GetSheet(block); - var runeLevelBonusSheet = await GetSheet(block); + if (cancellationToken.IsCancellationRequested) + { + cancellationToken.ThrowIfCancellationRequested(); + } + var runeListSheet = await GetSheet(block, cancellationToken); + var costumeSheet = await GetSheet(block, cancellationToken); + var characterSheet = await GetSheet(block, cancellationToken); + var runeOptionSheet = await GetSheet(block, cancellationToken); + var runeLevelBonusSheet = await GetSheet(block, cancellationToken); var row = characterSheet[GameConfig.DefaultAvatarCharacterId]; CollectionSheet collectionSheet = new CollectionSheet(); - var collectionStates = await GetCollectionStates(block, avatarAddrList); + var collectionStates = await GetCollectionStates(block, avatarAddrList, cancellationToken); bool collectionSheetExist = true; try { - collectionSheet = await GetSheet(block); + collectionSheet = await GetSheet(block, cancellationToken); } catch (Exception) { collectionSheetExist = false; } - var itemSlotStates = await GetItemSlotStates(block, avatarAddrList); - var runeSlotStates = await GetRuneSlotStates(block, avatarAddrList); - var avatarStates = await GetAvatarStates(block, avatarAddrList); - var allRuneStates = await GetAllRuneStates(block, avatarAddrList); + var itemSlotStates = await GetItemSlotStates(block, avatarAddrList, cancellationToken); + var runeSlotStates = await GetRuneSlotStates(block, avatarAddrList, cancellationToken); + var avatarStates = await GetAvatarStates(block, avatarAddrList, cancellationToken); + var allRuneStates = await GetAllRuneStates(block, avatarAddrList, cancellationToken); var tasks = avatarAddrAndScoresWithRank.Select(async tuple => { + if (cancellationToken.IsCancellationRequested) + { + cancellationToken.ThrowIfCancellationRequested(); + } var avatarAddr = tuple.AvatarAddr; // 점수가 변경된 경우, BattleArena를 실행한 아바타기때문에 전체 정보를 업데이트한다. if (avatarAddrList.Contains(avatarAddr)) { if (!allRuneStates.TryGetValue(avatarAddr, out var runeStates)) { - runeStates = await GetRuneState(block, avatarAddr, runeListSheet); + runeStates = await GetRuneState(block, avatarAddr, runeListSheet, cancellationToken); } var avatar = avatarStates[avatarAddr]; var itemSlotState = itemSlotStates[avatarAddr]; @@ -438,13 +475,18 @@ public async Task> GetArenaParticipants(Block block, List /// /// The world state used to retrieve the collection states. /// The list of addresses to retrieve the collection states for. + /// /// A dictionary of Address and CollectionState pairs representing the collection states /// for the given addresses, /// or an empty dictionary for addresses that do not have a collection state. - public async Task> GetCollectionStates(Block block, IReadOnlyList
addresses) + public async Task> GetCollectionStates(Block block, IReadOnlyList
addresses, CancellationToken cancellationToken) { + if (cancellationToken.IsCancellationRequested) + { + cancellationToken.ThrowIfCancellationRequested(); + } var result = new Dictionary(); - var values = await GetStates(block, Addresses.Collection, addresses); + var values = await GetStates(block, Addresses.Collection, addresses, cancellationToken); foreach (var address in addresses) { var serialized = values[address]; @@ -463,10 +505,16 @@ public async Task> GetCollectionStates(Bloc /// /// target account address. /// list of target state address. + /// /// chunking size. default value is 500 /// A dictionary of Address and IValue pairs for the given addresses. - public async Task> GetStates(Block block, Address accountAddress, IReadOnlyList
addresses, int chunkSize = 500) + public async Task> GetStates(Block block, Address accountAddress, + IReadOnlyList
addresses, CancellationToken cancellationToken, int chunkSize = 500) { + if (cancellationToken.IsCancellationRequested) + { + cancellationToken.ThrowIfCancellationRequested(); + } var result = new ConcurrentDictionary(); var chunks = addresses .Select((x, i) => new {Index = i, Value = x}) @@ -475,22 +523,27 @@ public async Task> GetStates(Block block, Address ac .ToList(); foreach (var chunk in chunks) { - var queryResult = await _service.GetBulkStateByStateRootHash(block.StateRootHash.ToByteArray(), accountAddress.ToByteArray(), chunk); + var queryResult = await _service.WithCancellationToken(cancellationToken).GetBulkStateByStateRootHash(block.StateRootHash.ToByteArray(), accountAddress.ToByteArray(), chunk); foreach (var kv in queryResult) result[new Address(kv.Key)] = _codec.Decode(kv.Value); } return result.ToDictionary(kv => kv.Key, kv => kv.Value); } - public async Task GetRuneState(Block block, Address avatarAddress, RuneListSheet runeListSheet) + public async Task GetRuneState(Block block, Address avatarAddress, RuneListSheet runeListSheet, + CancellationToken cancellationToken) { - var serialized = await GetState(block, Addresses.RuneState, avatarAddress); + if (cancellationToken.IsCancellationRequested) + { + cancellationToken.ThrowIfCancellationRequested(); + } + var serialized = await GetState(block, Addresses.RuneState, avatarAddress, cancellationToken); AllRuneState allRuneState; if (serialized is null) { // Get legacy rune states allRuneState = new AllRuneState(); var runeAddresses = runeListSheet.Values.Select(r => RuneState.DeriveAddress(avatarAddress, r.Id)).ToList(); - var runeStates = await GetRuneStates(block, runeAddresses); + var runeStates = await GetRuneStates(block, runeAddresses, cancellationToken); foreach (var runeState in runeStates) { allRuneState.AddRuneState(runeState); @@ -510,24 +563,33 @@ public async Task GetRuneState(Block block, Address avatarAddress, /// target block /// target account address /// state address + /// /// or null if not found. - public async Task GetState(Block block, Address accountAddress, Address address) + public async Task GetState(Block block, Address accountAddress, Address address, + CancellationToken cancellationToken) { + if (cancellationToken.IsCancellationRequested) + { + cancellationToken.ThrowIfCancellationRequested(); + } byte[] result = []; var retry = 0; while (retry < 3) { try { - result = await _service.GetStateByStateRootHash( - block.StateRootHash.ToByteArray(), - accountAddress.ToByteArray(), - address.ToByteArray()); + result = await _service + .WithCancellationToken(cancellationToken) + .GetStateByStateRootHash( + block.StateRootHash.ToByteArray(), + accountAddress.ToByteArray(), + address.ToByteArray() + ); break; } catch (RpcException) { - await Task.Delay((3 - retry) * 1000); + await Task.Delay((3 - retry) * 1000, cancellationToken); retry++; } } @@ -544,13 +606,19 @@ public async Task GetRuneState(Block block, Address avatarAddress, /// /// The world state used to retrieve the collection states. /// The list of addresses to retrieve the item slot states for. + /// /// A dictionary of Address and pairs representing the item slot states /// for the given addresses. - public async Task> GetItemSlotStates(Block block, IReadOnlyList
avatarAddresses) + public async Task> GetItemSlotStates(Block block, + IReadOnlyList
avatarAddresses, CancellationToken cancellationToken) { + if (cancellationToken.IsCancellationRequested) + { + cancellationToken.ThrowIfCancellationRequested(); + } var result = new Dictionary(); var slotAddresses = avatarAddresses.Select(a => ItemSlotState.DeriveAddress(a, BattleType.Arena)).ToList(); - var values = await GetStates(block, ReservedAddresses.LegacyAccount, slotAddresses); + var values = await GetStates(block, ReservedAddresses.LegacyAccount, slotAddresses, cancellationToken); foreach (var address in avatarAddresses) { var slotAddress = ItemSlotState.DeriveAddress(address, BattleType.Arena); @@ -568,13 +636,19 @@ public async Task> GetItemSlotStates(Block b /// /// The world state used to retrieve the collection states. /// The list of avatar addresses to retrieve the rune slot states for. + /// /// A dictionary of Address and pairs representing the rune slot states /// for the given addresses. - public async Task> GetRuneSlotStates(Block block, IReadOnlyList
avatarAddresses) + public async Task> GetRuneSlotStates(Block block, + IReadOnlyList
avatarAddresses, CancellationToken cancellationToken) { + if (cancellationToken.IsCancellationRequested) + { + cancellationToken.ThrowIfCancellationRequested(); + } var result = new Dictionary(); var slotAddresses = avatarAddresses.Select(a => RuneSlotState.DeriveAddress(a, BattleType.Arena)).ToList(); - var values = await GetStates(block, ReservedAddresses.LegacyAccount, slotAddresses); + var values = await GetStates(block, ReservedAddresses.LegacyAccount, slotAddresses, cancellationToken); foreach (var address in avatarAddresses) { var slotAddress = RuneSlotState.DeriveAddress(address, BattleType.Arena); @@ -588,10 +662,14 @@ public async Task> GetRuneSlotStates(Block b } public async Task> GetAvatarStates(Block block, - IReadOnlyList
avatarAddresses) + IReadOnlyList
avatarAddresses, CancellationToken cancellationToken) { - var avatarResults = await GetStates(block, Addresses.Avatar, avatarAddresses); - var inventoryResults = await GetStates(block, Addresses.Inventory, avatarAddresses); + if (cancellationToken.IsCancellationRequested) + { + cancellationToken.ThrowIfCancellationRequested(); + } + var avatarResults = await GetStates(block, Addresses.Avatar, avatarAddresses, cancellationToken); + var inventoryResults = await GetStates(block, Addresses.Inventory, avatarAddresses, cancellationToken); var result = new Dictionary(); foreach (var kv in avatarResults) { @@ -610,10 +688,15 @@ public async Task> GetAvatarStates(Block block return result; } - public async Task> GetRuneStates(Block block, IReadOnlyList
runeAddresses) + public async Task> GetRuneStates(Block block, IReadOnlyList
runeAddresses, + CancellationToken cancellationToken) { + if (cancellationToken.IsCancellationRequested) + { + cancellationToken.ThrowIfCancellationRequested(); + } var result = new List(); - var runeResults = await GetStates(block, ReservedAddresses.LegacyAccount, runeAddresses); + var runeResults = await GetStates(block, ReservedAddresses.LegacyAccount, runeAddresses, cancellationToken); foreach (var pair in runeResults) { if (pair.Value is List rawState) @@ -626,9 +709,13 @@ public async Task> GetRuneStates(Block block, IReadOnlyList> GetAllRuneStates(Block block, - IReadOnlyList
avatarAddresses) + IReadOnlyList
avatarAddresses, CancellationToken cancellationToken) { - var serializedResults = await GetStates(block, Addresses.RuneState, avatarAddresses); + if (cancellationToken.IsCancellationRequested) + { + cancellationToken.ThrowIfCancellationRequested(); + } + var serializedResults = await GetStates(block, Addresses.RuneState, avatarAddresses, cancellationToken); var result = new Dictionary(); foreach (var address in avatarAddresses) {