diff options
author | Florian Dold <florian@dold.me> | 2022-10-07 14:23:23 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2022-10-07 14:23:23 +0200 |
commit | a93a0cae131da4c3dd3f30ac5300f16f759758ff (patch) | |
tree | 6cc17143bc2e30965a459419fbf5c23b321a6303 | |
parent | 1256c8704b11faceb21629cd50813ef2b7b9015b (diff) |
wallet-core: fix busy wait when long-polling for manual withdrawal
-rw-r--r-- | packages/taler-wallet-core/src/operations/pending.ts | 100 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/pending-types.ts | 11 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/wallet.ts | 10 |
3 files changed, 82 insertions, 39 deletions
diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts index 156feadbb..e4c270d85 100644 --- a/packages/taler-wallet-core/src/operations/pending.ts +++ b/packages/taler-wallet-core/src/operations/pending.ts @@ -41,7 +41,28 @@ import { RetryTags } from "../util/retries.js"; import { Wallet } from "../wallet.js"; import { GlobalIDB } from "@gnu-taler/idb-bridge"; +function getPendingCommon( + ws: InternalWalletState, + opTag: string, + timestampDue: AbsoluteTime, +): { + id: string; + isDue: boolean; + timestampDue: AbsoluteTime; + isLongpolling: boolean; +} { + const isDue = + AbsoluteTime.isExpired(timestampDue) && !ws.activeLongpoll[opTag]; + return { + id: opTag, + isDue, + timestampDue, + isLongpolling: !!ws.activeLongpoll[opTag], + }; +} + async function gatherExchangePending( + ws: InternalWalletState, tx: GetReadOnlyAccess<{ exchanges: typeof WalletStoresV1.exchanges; exchangeDetails: typeof WalletStoresV1.exchangeDetails; @@ -54,12 +75,12 @@ async function gatherExchangePending( await tx.exchanges.iter().forEachAsync(async (exch) => { const opTag = RetryTags.forExchangeUpdate(exch); let opr = await tx.operationRetries.get(opTag); + const timestampDue = + opr?.retryInfo.nextRetry ?? AbsoluteTime.fromTimestamp(exch.nextUpdate); resp.pendingOperations.push({ type: PendingTaskType.ExchangeUpdate, - id: opTag, + ...getPendingCommon(ws, opTag, timestampDue), givesLifeness: false, - timestampDue: - opr?.retryInfo.nextRetry ?? AbsoluteTime.fromTimestamp(exch.nextUpdate), exchangeBaseUrl: exch.baseUrl, lastError: opr?.lastError, }); @@ -69,7 +90,7 @@ async function gatherExchangePending( if (!opr?.lastError) { resp.pendingOperations.push({ type: PendingTaskType.ExchangeCheckRefresh, - id: RetryTags.forExchangeCheckRefresh(exch), + ...getPendingCommon(ws, opTag, timestampDue), timestampDue: AbsoluteTime.fromTimestamp(exch.nextRefreshCheck), givesLifeness: false, exchangeBaseUrl: exch.baseUrl, @@ -79,6 +100,7 @@ async function gatherExchangePending( } async function gatherRefreshPending( + ws: InternalWalletState, tx: GetReadOnlyAccess<{ refreshGroups: typeof WalletStoresV1.refreshGroups; operationRetries: typeof WalletStoresV1.operationRetries; @@ -99,11 +121,12 @@ async function gatherRefreshPending( const opId = RetryTags.forRefresh(r); const retryRecord = await tx.operationRetries.get(opId); + const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(); + resp.pendingOperations.push({ type: PendingTaskType.Refresh, - id: opId, + ...getPendingCommon(ws, opId, timestampDue), givesLifeness: true, - timestampDue: retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(), refreshGroupId: r.refreshGroupId, finishedPerCoin: r.statusPerCoin.map( (x) => x === RefreshCoinStatus.Finished, @@ -114,6 +137,7 @@ async function gatherRefreshPending( } async function gatherWithdrawalPending( + ws: InternalWalletState, tx: GetReadOnlyAccess<{ withdrawalGroups: typeof WalletStoresV1.withdrawalGroups; planchets: typeof WalletStoresV1.planchets; @@ -147,9 +171,12 @@ async function gatherWithdrawalPending( } resp.pendingOperations.push({ type: PendingTaskType.Withdraw, - id: opTag, + ...getPendingCommon( + ws, + opTag, + opr.retryInfo?.nextRetry ?? AbsoluteTime.now(), + ), givesLifeness: true, - timestampDue: opr.retryInfo?.nextRetry ?? AbsoluteTime.now(), withdrawalGroupId: wsr.withdrawalGroupId, lastError: opr.lastError, retryInfo: opr.retryInfo, @@ -158,6 +185,7 @@ async function gatherWithdrawalPending( } async function gatherProposalPending( + ws: InternalWalletState, tx: GetReadOnlyAccess<{ proposals: typeof WalletStoresV1.proposals; operationRetries: typeof WalletStoresV1.operationRetries; @@ -175,9 +203,8 @@ async function gatherProposalPending( retryRecord?.retryInfo?.nextRetry ?? AbsoluteTime.now(); resp.pendingOperations.push({ type: PendingTaskType.ProposalDownload, - id: opId, + ...getPendingCommon(ws, opId, timestampDue), givesLifeness: true, - timestampDue, merchantBaseUrl: proposal.merchantBaseUrl, orderId: proposal.orderId, proposalId: proposal.proposalId, @@ -190,6 +217,7 @@ async function gatherProposalPending( } async function gatherDepositPending( + ws: InternalWalletState, tx: GetReadOnlyAccess<{ depositGroups: typeof WalletStoresV1.depositGroups; operationRetries: typeof WalletStoresV1.operationRetries; @@ -209,9 +237,8 @@ async function gatherDepositPending( const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(); resp.pendingOperations.push({ type: PendingTaskType.Deposit, - id: opId, + ...getPendingCommon(ws, opId, timestampDue), givesLifeness: true, - timestampDue, depositGroupId: dg.depositGroupId, lastError: retryRecord?.lastError, retryInfo: retryRecord?.retryInfo, @@ -220,6 +247,7 @@ async function gatherDepositPending( } async function gatherTipPending( + ws: InternalWalletState, tx: GetReadOnlyAccess<{ tips: typeof WalletStoresV1.tips; operationRetries: typeof WalletStoresV1.operationRetries; @@ -234,10 +262,11 @@ async function gatherTipPending( } const opId = RetryTags.forTipPickup(tip); const retryRecord = await tx.operationRetries.get(opId); + const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(); if (tip.acceptedTimestamp) { resp.pendingOperations.push({ type: PendingTaskType.TipPickup, - id: opId, + ...getPendingCommon(ws, opId, timestampDue), givesLifeness: true, timestampDue: retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(), merchantBaseUrl: tip.merchantBaseUrl, @@ -249,6 +278,7 @@ async function gatherTipPending( } async function gatherPurchasePending( + ws: InternalWalletState, tx: GetReadOnlyAccess<{ purchases: typeof WalletStoresV1.purchases; operationRetries: typeof WalletStoresV1.operationRetries; @@ -270,9 +300,8 @@ async function gatherPurchasePending( payRetryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(); resp.pendingOperations.push({ type: PendingTaskType.Pay, - id: payOpId, + ...getPendingCommon(ws, payOpId, timestampDue), givesLifeness: true, - timestampDue, isReplay: false, proposalId: pr.proposalId, retryInfo: payRetryRecord?.retryInfo, @@ -284,12 +313,12 @@ async function gatherPurchasePending( const refundQueryRetryRecord = await tx.operationRetries.get( refundQueryOpId, ); + const timestampDue = + refundQueryRetryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(); resp.pendingOperations.push({ type: PendingTaskType.RefundQuery, - id: refundQueryOpId, + ...getPendingCommon(ws, refundQueryOpId, timestampDue), givesLifeness: true, - timestampDue: - refundQueryRetryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(), proposalId: pr.proposalId, retryInfo: refundQueryRetryRecord?.retryInfo, lastError: refundQueryRetryRecord?.lastError, @@ -299,6 +328,7 @@ async function gatherPurchasePending( } async function gatherRecoupPending( + ws: InternalWalletState, tx: GetReadOnlyAccess<{ recoupGroups: typeof WalletStoresV1.recoupGroups; operationRetries: typeof WalletStoresV1.operationRetries; @@ -312,11 +342,11 @@ async function gatherRecoupPending( } const opId = RetryTags.forRecoup(rg); const retryRecord = await tx.operationRetries.get(opId); + const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(); resp.pendingOperations.push({ type: PendingTaskType.Recoup, - id: opId, + ...getPendingCommon(ws, opId, timestampDue), givesLifeness: true, - timestampDue: retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(), recoupGroupId: rg.recoupGroupId, retryInfo: retryRecord?.retryInfo, lastError: retryRecord?.lastError, @@ -325,6 +355,7 @@ async function gatherRecoupPending( } async function gatherBackupPending( + ws: InternalWalletState, tx: GetReadOnlyAccess<{ backupProviders: typeof WalletStoresV1.backupProviders; operationRetries: typeof WalletStoresV1.operationRetries; @@ -336,20 +367,23 @@ async function gatherBackupPending( const opId = RetryTags.forBackup(bp); const retryRecord = await tx.operationRetries.get(opId); if (bp.state.tag === BackupProviderStateTag.Ready) { + const timestampDue = AbsoluteTime.fromTimestamp( + bp.state.nextBackupTimestamp, + ); resp.pendingOperations.push({ type: PendingTaskType.Backup, - id: opId, + ...getPendingCommon(ws, opId, timestampDue), givesLifeness: false, - timestampDue: AbsoluteTime.fromTimestamp(bp.state.nextBackupTimestamp), backupProviderBaseUrl: bp.baseUrl, lastError: undefined, }); } else if (bp.state.tag === BackupProviderStateTag.Retrying) { + const timestampDue = + retryRecord?.retryInfo?.nextRetry ?? AbsoluteTime.now(); resp.pendingOperations.push({ type: PendingTaskType.Backup, - id: opId, + ...getPendingCommon(ws, opId, timestampDue), givesLifeness: false, - timestampDue: retryRecord?.retryInfo?.nextRetry ?? AbsoluteTime.now(), backupProviderBaseUrl: bp.baseUrl, retryInfo: retryRecord?.retryInfo, lastError: retryRecord?.lastError, @@ -382,15 +416,15 @@ export async function getPendingOperations( const resp: PendingOperationsResponse = { pendingOperations: [], }; - await gatherExchangePending(tx, now, resp); - await gatherRefreshPending(tx, now, resp); - await gatherWithdrawalPending(tx, now, resp); - await gatherProposalPending(tx, now, resp); - await gatherDepositPending(tx, now, resp); - await gatherTipPending(tx, now, resp); - await gatherPurchasePending(tx, now, resp); - await gatherRecoupPending(tx, now, resp); - await gatherBackupPending(tx, now, resp); + await gatherExchangePending(ws, tx, now, resp); + await gatherRefreshPending(ws, tx, now, resp); + await gatherWithdrawalPending(ws, tx, now, resp); + await gatherProposalPending(ws, tx, now, resp); + await gatherDepositPending(ws, tx, now, resp); + await gatherTipPending(ws, tx, now, resp); + await gatherPurchasePending(ws, tx, now, resp); + await gatherRecoupPending(ws, tx, now, resp); + await gatherBackupPending(ws, tx, now, resp); return resp; }); } diff --git a/packages/taler-wallet-core/src/pending-types.ts b/packages/taler-wallet-core/src/pending-types.ts index 61c7136df..5e0000b53 100644 --- a/packages/taler-wallet-core/src/pending-types.ts +++ b/packages/taler-wallet-core/src/pending-types.ts @@ -26,7 +26,6 @@ */ import { TalerErrorDetail, - BalancesResponse, AbsoluteTime, TalerProtocolTimestamp, } from "@gnu-taler/taler-util"; @@ -204,6 +203,16 @@ export interface PendingTaskInfoCommon { givesLifeness: boolean; /** + * Operation is active and waiting for a longpoll result. + */ + isLongpolling: boolean; + + /** + * Operation is waiting to be executed. + */ + isDue: boolean; + + /** * Timestamp when the pending operation should be executed next. */ timestampDue: AbsoluteTime; diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 484507d6b..aa3810035 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -512,14 +512,14 @@ async function runTaskLoop( ); continue; } - - minDue = AbsoluteTime.min(minDue, p.timestampDue); - if (AbsoluteTime.isExpired(p.timestampDue) && !ws.activeLongpoll[p.id]) { - numDue++; - } if (p.givesLifeness) { numGivingLiveness++; } + if (!p.isDue) { + continue; + } + minDue = AbsoluteTime.min(minDue, p.timestampDue); + numDue++; } if (opts.stopWhenDone && numGivingLiveness === 0 && iteration !== 0) { |