Repository navigation
perf: run independent hot-path calls concurrently - #1945
Merged
Merged
Conversation
updateTokens awaited the batched GetMintMetadata before it sent GetAccounts, though neither reads the other. Only the per-account build needs the warm cache. Split TokenController.fetchTokenAccounts into fetchPrimaryAccounts and buildTokenBalances so the coordinator can send both RPCs together and join the refresh before building. Every balance refresh (poller, reconnect, onBalanceUpdateRequested) saves one round trip. A failure in either call still ends the refresh as before.
buildTokenBalances looked up each account's metadata one at a time. Behind the coordinator's warm cache that is a memory read, but on a fresh login refreshAllMetadata has no known mints, so every token cost a Room read and a GetMintMetadata round trip in turn before the first balance showed. Run the lookups concurrently, at most 8 at once, keeping the account order. An unresolved mint is still dropped.
SharedStatePayment.pay resolved each payee's owner key with its own Resolve RPC, one after another, before building the intent. A group send to N people waited N round trips. The lookups are read-only and nothing reads one from another, so run them concurrently (at most 8 at once) and keep the payee order the multi-transfer zips against. Intent construction is unchanged and still starts only after every payee resolves. One difference: when several payees fail, the error surfaced is the first to fail rather than the first in the list.
…grants hydrateChat awaited a GetChat for every stored group to refresh the moderation grants, and openTranscript only started GetMessages once that returned. The grants write touches only the moderation columns and loadMessages never reads them, so opening a stored group paid one extra round trip before the transcript fetch began. The refresh moves out of hydrateChat into refreshViewerState, which the screen launches next to loadMessages. hydrateChat now returns Stored without a round trip, as its KDoc already said.
…ofile open Opening the profile of a group the viewer isn't in, with no cover stored, issued refreshRules and then refreshCover. Each is its own GetChat that writes one column and swallows its failure, so the second waited a full round trip for nothing. They now run side by side.
…ently loadChat fetched the OPEN first page and then the RESOLVED first page. They share no cursor and neither reads the other, so the second waited a round trip for nothing. Both now run under one coroutineScope after the stored copies are hydrated; either failing cancels the other and fails the load as before. When both fail, the error returned is whichever arrives first rather than always OPEN's.
loadCandidates waited for the featured-groups read, and the cover GetChat for each featured group without one, before it asked for the joined group feed. The feed reads nothing from that result; it is only merged in at the end. It now starts first and is awaited after the featured list is in. A failed featured read cancels it, and the screen shows the failure as before.
…a cancel After a cancelled transfer both transaction screens checked pending feed rows (a Room query plus GetNotificationsById) and then refreshed token balances. The two write separate data and each returns its failure as a Result, so the balance refresh no longer waits for the feed check.
checkBalances read the SOL balance, checked it, and only then read the USDC token account: two independent public-RPC round trips in a row on every Phantom buy or deposit. The USDC read now starts first and is awaited after the SOL check. SOL is still judged first, so a wallet short on both still reports InsufficientSol, and every early return cancels the pending USDC read. These are balance reads only; transaction building in this controller is unchanged.
Posting a push resolved each title substitution, then each body
substitution, one at a time. A name missing from the local cache is a
GetProfile round trip, so a push naming two unknown users waited on two
fetches in a row before it could post. The substitutions in a string, and
the title and body, now resolve concurrently; the {n} placeholders are
still replaced in index order. A failure still drops the post, as it did.
bmc08gt
added a commit
that referenced
this pull request
Nov 3, 2026
…ee-pane * origin/code/cash: (44 commits) refactor(chat-list): move the chat list into a KMP module and wire it into the desktop graph (#1950) build(deps): bump dev.zacsweers.metro from 1.4.5 to 1.5.1 (#1901) feat(flipcash): add return-payment contract scaffolding (#1949) refactor(chat-ui): make shared/chat-ui a KMP module (#1934) build(deps): bump com.google.firebase:firebase-bom from 34.19.0 to 35.1.0 (#1900) build(deps): bump io.ktor:ktor-network from 3.4.1 to 3.6.0 (#1909) build(deps): bump rinku from 1.6.0 to 1.9.0 (#1907) build(deps): bump androidx.webkit:webkit from 1.17.0 to 1.18.0 (#1906) build(deps): bump agp from 9.4.1 to 9.5.0 (#1905) build(deps): bump androidx-datastore from 1.2.1 to 1.3.0 (#1904) build(deps): bump compose-multiplatform from 1.12.1 to 1.12.2 (#1903) build(deps): bump com.google.android.gms:play-services-base (#1902) build(deps): bump okhttp from 5.5.0 to 5.5.1 (#1899) refactor(messenger): split ChatViewModel's reducer into per-area files (#1933) refactor(amount-entry): make shared/amount-entry a KMP module (#1932) fix(build): stop KMP host tests building release variants and debug manifests changing per commit (#1948) refactor(theme): collapse Avenir into one family with a real Medium face (#1947) perf: run independent hot-path calls concurrently (#1945) docs(claude): test gestures with device input and keep mode state at screen level (#1943) build: bump Flipcash to 2026.11.2 ... # Conflicts: # CLAUDE.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Several hot paths awaited network or disk calls back to back when neither read the other's result. This runs them concurrently. Each fix is its own commit.
TokenCoordinator.updateTokens)GetAccountscall; the build waits for bothTokenController.buildTokenBalances)SharedStatePayment.pay)Resolvecalls run concurrently, 8 at a time, in payee orderGetChatleaveshydrateChatfor a newrefreshViewerState, whichopenTranscriptruns next toloadMessagesrefreshRulesandrefreshCoverrun side by sidePaymentRequests.loadChatcheckBalances{n}is still replaced in orderFailure handling stays structured: one failed call cancels the rest and fails the operation as the sequential code did. Two differences:
payandloadChat, when more than one call fails, the error returned is whichever fails first rather than the first in the list.checkBalancesstill judges SOL first, so a wallet short on both reportsInsufficientSol. The Solana driver uses blocking OkHttpexecute(), so cancelling the USDC read doesn't stop a request already sent. The short-SOL and SOL-error results can wait until the USDC response arrives. Both reads start together, so that wait is only how much longer USDC takes than SOL.hydrateChatnow returnsStoredwithout a round trip, which its KDoc already said.New tests cover the token build (order, a dropped account, concurrency), concurrent payee resolves,
refreshViewerStateand the stored-group open, the featured-groups feed starting early, andInsufficientSolwinning when the USDC read fails.Left alone on purpose: transaction and intent building, account provisioning,
SharedStatePayment.price(amounts must share one verified state), E2EE key fetches, and feed catch-up, which loads one chat at a time by design.