From f5d194dfc61ae0d358a27b994861dc20b83cf98e Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 28 Mar 2022 23:59:16 +0200 Subject: wallet: cancellation for deposit --- packages/taler-wallet-core/src/db.ts | 2 +- .../taler-wallet-core/src/internal-wallet-state.ts | 8 ++- .../taler-wallet-core/src/operations/deposits.ts | 40 +++++++++++---- .../taler-wallet-core/src/operations/exchanges.ts | 60 ++++++++++++++++++---- .../src/operations/transactions.ts | 4 +- packages/taler-wallet-core/src/wallet.ts | 15 +++++- 6 files changed, 103 insertions(+), 26 deletions(-) (limited to 'packages/taler-wallet-core') diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 69606b8ff..e3da35975 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -550,7 +550,7 @@ export interface ExchangeRecord { /** * Retry status for fetching updated information about the exchange. */ - retryInfo: RetryInfo; + retryInfo?: RetryInfo; } /** diff --git a/packages/taler-wallet-core/src/internal-wallet-state.ts b/packages/taler-wallet-core/src/internal-wallet-state.ts index 5ecf796ed..6b964cdf7 100644 --- a/packages/taler-wallet-core/src/internal-wallet-state.ts +++ b/packages/taler-wallet-core/src/internal-wallet-state.ts @@ -35,6 +35,7 @@ import { AmountJson, DenominationPubKey, TalerProtocolTimestamp, + CancellationToken, } from "@gnu-taler/taler-util"; import { CryptoDispatcher } from "./crypto/workers/cryptoDispatcher.js"; import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js"; @@ -200,9 +201,14 @@ export interface InternalWalletState { memoGetBalance: AsyncOpMemoSingle; memoProcessRefresh: AsyncOpMemoMap; memoProcessRecoup: AsyncOpMemoMap; - memoProcessDeposit: AsyncOpMemoMap; cryptoApi: TalerCryptoInterface; + /** + * Cancellation token for the currently running + * deposit operation, if any. + */ + taskCancellationSourceForDeposit?: CancellationToken.Source; + timerGroup: TimerGroup; stopped: boolean; diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index 501e9b76b..c11c45dc6 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -21,6 +21,7 @@ import { AbsoluteTime, AmountJson, Amounts, + CancellationToken, canonicalJson, codecForDepositSuccess, ContractTerms, @@ -125,23 +126,34 @@ async function reportDepositGroupError( export async function processDepositGroup( ws: InternalWalletState, depositGroupId: string, - forceNow = false, + options: { + forceNow?: boolean; + cancellationToken?: CancellationToken; + } = {}, ): Promise { - await ws.memoProcessDeposit.memo(depositGroupId, async () => { - const onOpErr = (err: TalerErrorDetail): Promise => - reportDepositGroupError(ws, depositGroupId, err); - return await guardOperationException( - async () => await processDepositGroupImpl(ws, depositGroupId, forceNow), - onOpErr, - ); - }); + if (ws.taskCancellationSourceForDeposit) { + ws.taskCancellationSourceForDeposit.cancel(); + } + const onOpErr = (err: TalerErrorDetail): Promise => + reportDepositGroupError(ws, depositGroupId, err); + return await guardOperationException( + async () => await processDepositGroupImpl(ws, depositGroupId, options), + onOpErr, + ); } +/** + * @see {processDepositGroup} + */ async function processDepositGroupImpl( ws: InternalWalletState, depositGroupId: string, - forceNow = false, + options: { + forceNow?: boolean; + cancellationToken?: CancellationToken; + } = {}, ): Promise { + const forceNow = options.forceNow ?? false; const depositGroup = await ws.db .mktx((x) => ({ depositGroups: x.depositGroups, @@ -170,6 +182,8 @@ async function processDepositGroupImpl( "", ); + // Check for cancellation before expensive operations. + options.cancellationToken?.throwIfCancelled(); const depositPermissions = await generateDepositPermissions( ws, depositGroup.payCoinSelection, @@ -196,9 +210,13 @@ async function processDepositGroupImpl( denom_pub_hash: perm.h_denom, merchant_pub: depositGroup.merchantPub, }; + // Check for cancellation before making network request. + options.cancellationToken?.throwIfCancelled(); const url = new URL(`coins/${perm.coin_pub}/deposit`, perm.exchange_url); logger.info(`depositing to ${url}`); - const httpResp = await ws.http.postJson(url.href, requestBody); + const httpResp = await ws.http.postJson(url.href, requestBody, { + cancellationToken: options.cancellationToken, + }); await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess()); await ws.db .mktx((x) => ({ depositGroups: x.depositGroups })) diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index 09449c875..fe1c9ef35 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -61,7 +61,11 @@ import { readSuccessResponseTextOrThrow, } from "../util/http.js"; import { DbAccess, GetReadOnlyAccess } from "../util/query.js"; -import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; +import { + initRetryInfo, + RetryInfo, + updateRetryInfoTimeout, +} from "../util/retries.js"; import { WALLET_CACHE_BREAKER_CLIENT_VERSION, WALLET_EXCHANGE_PROTOCOL_VERSION, @@ -102,7 +106,7 @@ function denominationRecordFromKeys( return d; } -async function handleExchangeUpdateError( +async function reportExchangeUpdateError( ws: InternalWalletState, baseUrl: string, err: TalerErrorDetail, @@ -114,14 +118,44 @@ async function handleExchangeUpdateError( if (!exchange) { return; } - exchange.retryInfo.retryCounter++; - updateRetryInfoTimeout(exchange.retryInfo); exchange.lastError = err; await tx.exchanges.put(exchange); }); - if (err) { - ws.notify({ type: NotificationType.ExchangeOperationError, error: err }); - } + ws.notify({ type: NotificationType.ExchangeOperationError, error: err }); +} + +async function resetExchangeUpdateRetry( + ws: InternalWalletState, + baseUrl: string, +): Promise { + await ws.db + .mktx((x) => ({ exchanges: x.exchanges })) + .runReadWrite(async (tx) => { + const exchange = await tx.exchanges.get(baseUrl); + if (!exchange) { + return; + } + delete exchange.lastError; + exchange.retryInfo = initRetryInfo(); + await tx.exchanges.put(exchange); + }); +} + +async function incrementExchangeUpdateRetry( + ws: InternalWalletState, + baseUrl: string, +): Promise { + await ws.db + .mktx((x) => ({ exchanges: x.exchanges })) + .runReadWrite(async (tx) => { + const exchange = await tx.exchanges.get(baseUrl); + if (!exchange) { + return; + } + delete exchange.lastError; + exchange.retryInfo = RetryInfo.increment(exchange.retryInfo); + await tx.exchanges.put(exchange); + }); } export function getExchangeRequestTimeout(): Duration { @@ -349,7 +383,7 @@ export async function updateExchangeFromUrl( exchangeDetails: ExchangeDetailsRecord; }> { const onOpErr = (e: TalerErrorDetail): Promise => - handleExchangeUpdateError(ws, baseUrl, e); + reportExchangeUpdateError(ws, baseUrl, e); return await guardOperationException( () => updateExchangeFromUrlImpl(ws, baseUrl, acceptedFormat, forceNow), onOpErr, @@ -543,6 +577,12 @@ async function updateExchangeFromUrlImpl( return { exchange, exchangeDetails }; } + if (forceNow) { + await resetExchangeUpdateRetry(ws, baseUrl); + } else { + await incrementExchangeUpdateRetry(ws, baseUrl); + } + logger.info("updating exchange /keys info"); const timeout = getExchangeRequestTimeout(); @@ -624,8 +664,8 @@ async function updateExchangeFromUrlImpl( termsOfServiceAcceptedTimestamp: TalerProtocolTimestamp.now(), }; // FIXME: only update if pointer got updated - r.lastError = undefined; - r.retryInfo = initRetryInfo(); + delete r.lastError; + delete r.retryInfo; r.lastUpdate = TalerProtocolTimestamp.now(); r.nextUpdate = keysInfo.expiry; // New denominations might be available. diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index cb312154e..bb5306189 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -444,7 +444,9 @@ export async function retryTransaction( switch (type) { case TransactionType.Deposit: const depositGroupId = rest[0]; - processDepositGroup(ws, depositGroupId, true); + processDepositGroup(ws, depositGroupId, { + forceNow: true, + }); break; case TransactionType.Withdrawal: const withdrawalGroupId = rest[0]; diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 943051153..abd11faab 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -78,6 +78,7 @@ import { URL, WalletNotification, Duration, + CancellationToken, } from "@gnu-taler/taler-util"; import { timeStamp } from "console"; import { @@ -271,9 +272,19 @@ async function processOnePendingOperation( case PendingTaskType.ExchangeCheckRefresh: await autoRefresh(ws, pending.exchangeBaseUrl); break; - case PendingTaskType.Deposit: - await processDepositGroup(ws, pending.depositGroupId); + case PendingTaskType.Deposit: { + const cts = CancellationToken.create(); + ws.taskCancellationSourceForDeposit = cts; + try { + await processDepositGroup(ws, pending.depositGroupId, { + cancellationToken: cts.token, + }); + } finally { + cts.dispose(); + delete ws.taskCancellationSourceForDeposit; + } break; + } case PendingTaskType.Backup: await processBackupForProvider(ws, pending.backupProviderBaseUrl); break; -- cgit v1.2.3