-
Notifications
You must be signed in to change notification settings - Fork 2
Portfolio legs degrade per-fetch instead of losing whole layers #58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
627871d
e6f64d0
7593fc6
d4b0ded
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -659,10 +659,28 @@ internal static async Task<List<R>> ProcessWithConcurrencyLimit<T, R>(IReadOnlyL | |
| return results.ToList(); | ||
| } | ||
|
|
||
| // Per-wallet cap on the balance fetch. The chain providers allow 10s per | ||
| // attempt, but the portfolioV2 chain leg has a 4.5s budget for the WHOLE | ||
| // layer — without a per-wallet cap, one cold/slow provider blows the leg | ||
| // timeout and the entire chain layer silently disappears from the response | ||
| // (observed intermittently in production). The capped wallet degrades to | ||
| // an error item instead of sinking its siblings. The concurrency width | ||
| // must keep ceil(n / width) * cap under the leg budget: at width 8, up to | ||
| // 16 wallets (2 batches) finish worst-case in 4s. | ||
| private const int ChainBalanceTimeoutMs = 2000; | ||
|
greptile-apps[bot] marked this conversation as resolved.
|
||
| private const int ChainFetchConcurrency = 8; | ||
| // Hard bound so the worst case is exactly two waves (4s < 4.5s leg) no | ||
| // matter how many addresses a profile lists. Wallets past the cap are not | ||
| // fetched but still appear as error items, so the response acknowledges | ||
| // every configured wallet instead of silently omitting the tail. | ||
| private const int MaxChainWallets = 16; | ||
|
|
||
| internal static async Task<List<JsonObject>> BuildChainLayer(JsonNode? accountData, JsonNode? marketData, string currency, bool? onlyEnabled) | ||
| { | ||
| var wallets = ExtractExternalWallets(accountData, onlyEnabled == true); | ||
| if (wallets.Count == 0) return new List<JsonObject>(); | ||
| var skipped = wallets.Skip(MaxChainWallets).ToList(); | ||
| if (skipped.Count > 0) wallets = wallets.Take(MaxChainWallets).ToList(); | ||
|
|
||
| var items = await ProcessWithConcurrencyLimit(wallets, async wallet => | ||
| { | ||
|
|
@@ -672,7 +690,10 @@ internal static async Task<List<JsonObject>> BuildChainLayer(JsonNode? accountDa | |
|
|
||
| try | ||
| { | ||
| var data = await PrivateApi.FetchChainBalance(chain, wallet.Address); | ||
| var fetchTask = PrivateApi.FetchChainBalance(chain, wallet.Address); | ||
| var completed = await Task.WhenAny(fetchTask, Task.Delay(ChainBalanceTimeoutMs)); | ||
|
greptile-apps[bot] marked this conversation as resolved.
|
||
| if (completed != fetchTask) throw new Exception("Chain balance request timed out"); | ||
|
greptile-apps[bot] marked this conversation as resolved.
Comment on lines
+693
to
+695
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a wallet fetch takes longer than 2s, this returns an error item but does not cancel or await the Useful? React with 👍 / 👎.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair observation on the semaphore accounting. Two bounds keep it tame: in-flight dedup in |
||
| var data = await fetchTask; | ||
| var balance = ConvertChainBalanceToAmount(data, decimals); | ||
| var price = GetTokenPrice(marketData, wallet.Symbol, currency); | ||
| var iconUrl = config.IconUrl ?? Constants.AssetIconUrls["CHAIN_PLACEHOLDER"]; | ||
|
|
@@ -695,9 +716,23 @@ internal static async Task<List<JsonObject>> BuildChainLayer(JsonNode? accountDa | |
| new ItemOptions { Address = wallet.Address, Error = errorMessage }, | ||
| config.IconUrl ?? Constants.AssetIconUrls["CHAIN_PLACEHOLDER"], ChainActions()); | ||
| } | ||
| }, 3); | ||
| }, ChainFetchConcurrency); | ||
|
|
||
| return items.Where(x => x != null).ToList(); | ||
| var result = items.Where(x => x != null).ToList(); | ||
| foreach (var wallet in skipped) | ||
| { | ||
| var chain = wallet.Chain.ToLowerInvariant(); | ||
| var config = ChainConfigs[chain]; | ||
| var decimals = (int)(wallet.Decimals ?? config.Decimals); | ||
| var price = GetTokenPrice(marketData, wallet.Symbol, currency); | ||
| result.Add(MakePortfolioItem( | ||
| !string.IsNullOrEmpty(wallet.Name) ? wallet.Name : config.Name, | ||
| !string.IsNullOrEmpty(wallet.Symbol) ? wallet.Symbol : config.Symbol, | ||
| "chain", 0, price, currency, decimals, | ||
| new ItemOptions { Address = wallet.Address, Error = "Wallet limit reached" }, | ||
| config.IconUrl ?? Constants.AssetIconUrls["CHAIN_PLACEHOLDER"], ChainActions())); | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| // ---- JS numeric string formatting ------------------------------------ | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.