diff options
author | Florian Dold <florian@dold.me> | 2023-06-20 11:40:06 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2023-06-20 11:40:06 +0200 |
commit | 9c708251f92e6691ebba80fa8d129c6c04cec618 (patch) | |
tree | edf46c7b3f9386697a4ea697c2d66f66323a6d3e /packages/taler-wallet-core/src/util | |
parent | 54f0c82999833132baf83995526025ac56d6fe06 (diff) | |
download | wallet-core-9c708251f92e6691ebba80fa8d129c6c04cec618.tar.gz wallet-core-9c708251f92e6691ebba80fa8d129c6c04cec618.tar.bz2 wallet-core-9c708251f92e6691ebba80fa8d129c6c04cec618.zip |
wallet-core: emit DD37 self-transition notifications with errors
Diffstat (limited to 'packages/taler-wallet-core/src/util')
-rw-r--r-- | packages/taler-wallet-core/src/util/retries.ts | 306 |
1 files changed, 0 insertions, 306 deletions
diff --git a/packages/taler-wallet-core/src/util/retries.ts b/packages/taler-wallet-core/src/util/retries.ts index e85eb0a6b..e602d4702 100644 --- a/packages/taler-wallet-core/src/util/retries.ts +++ b/packages/taler-wallet-core/src/util/retries.ts @@ -50,309 +50,3 @@ import { assertUnreachable } from "./assertUnreachable.js"; const logger = new Logger("util/retries.ts"); -export enum OperationAttemptResultType { - Finished = "finished", - Pending = "pending", - Error = "error", - Longpoll = "longpoll", -} - -export type OperationAttemptResult<TSuccess = unknown, TPending = unknown> = - | OperationAttemptFinishedResult<TSuccess> - | OperationAttemptErrorResult - | OperationAttemptLongpollResult - | OperationAttemptPendingResult<TPending>; - -export namespace OperationAttemptResult { - export function finishedEmpty(): OperationAttemptResult<unknown, unknown> { - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; - } - export function pendingEmpty(): OperationAttemptResult<unknown, unknown> { - return { - type: OperationAttemptResultType.Pending, - result: undefined, - }; - } - export function longpoll(): OperationAttemptResult<unknown, unknown> { - return { - type: OperationAttemptResultType.Longpoll, - }; - } -} - -export interface OperationAttemptFinishedResult<T> { - type: OperationAttemptResultType.Finished; - result: T; -} - -export interface OperationAttemptPendingResult<T> { - type: OperationAttemptResultType.Pending; - result: T; -} - -export interface OperationAttemptErrorResult { - type: OperationAttemptResultType.Error; - errorDetail: TalerErrorDetail; -} - -export interface OperationAttemptLongpollResult { - type: OperationAttemptResultType.Longpoll; -} - -export interface RetryInfo { - firstTry: AbsoluteTime; - nextRetry: AbsoluteTime; - retryCounter: number; -} - -export interface RetryPolicy { - readonly backoffDelta: Duration; - readonly backoffBase: number; - readonly maxTimeout: Duration; -} - -const defaultRetryPolicy: RetryPolicy = { - backoffBase: 1.5, - backoffDelta: Duration.fromSpec({ seconds: 1 }), - maxTimeout: Duration.fromSpec({ minutes: 2 }), -}; - -function updateTimeout( - r: RetryInfo, - p: RetryPolicy = defaultRetryPolicy, -): void { - const now = AbsoluteTime.now(); - if (now.t_ms === "never") { - throw Error("assertion failed"); - } - if (p.backoffDelta.d_ms === "forever") { - r.nextRetry = AbsoluteTime.never(); - return; - } - - const nextIncrement = - p.backoffDelta.d_ms * Math.pow(p.backoffBase, r.retryCounter); - - const t = - now.t_ms + - (p.maxTimeout.d_ms === "forever" - ? nextIncrement - : Math.min(p.maxTimeout.d_ms, nextIncrement)); - r.nextRetry = AbsoluteTime.fromMilliseconds(t); -} - -export namespace RetryInfo { - export function getDuration( - r: RetryInfo | undefined, - p: RetryPolicy = defaultRetryPolicy, - ): Duration { - if (!r) { - // If we don't have any retry info, run immediately. - return { d_ms: 0 }; - } - if (p.backoffDelta.d_ms === "forever") { - return { d_ms: "forever" }; - } - const t = p.backoffDelta.d_ms * Math.pow(p.backoffBase, r.retryCounter); - return { - d_ms: - p.maxTimeout.d_ms === "forever" ? t : Math.min(p.maxTimeout.d_ms, t), - }; - } - - export function reset(p: RetryPolicy = defaultRetryPolicy): RetryInfo { - const now = AbsoluteTime.now(); - const info = { - firstTry: now, - nextRetry: now, - retryCounter: 0, - }; - updateTimeout(info, p); - return info; - } - - export function increment( - r: RetryInfo | undefined, - p: RetryPolicy = defaultRetryPolicy, - ): RetryInfo { - if (!r) { - return reset(p); - } - const r2 = { ...r }; - r2.retryCounter++; - updateTimeout(r2, p); - return r2; - } -} - -/** - * Parsed representation of task identifiers. - */ -export type ParsedTaskIdentifier = - | { - tag: PendingTaskType.Withdraw; - withdrawalGroupId: string; - } - | { tag: PendingTaskType.ExchangeUpdate; exchangeBaseUrl: string } - | { tag: PendingTaskType.Backup; backupProviderBaseUrl: string } - | { tag: PendingTaskType.Deposit; depositGroupId: string } - | { tag: PendingTaskType.ExchangeCheckRefresh; exchangeBaseUrl: string } - | { tag: PendingTaskType.ExchangeUpdate; exchangeBaseUrl: string } - | { tag: PendingTaskType.PeerPullDebit; peerPullPaymentIncomingId: string } - | { tag: PendingTaskType.PeerPullCredit; pursePub: string } - | { tag: PendingTaskType.PeerPushCredit; peerPushPaymentIncomingId: string } - | { tag: PendingTaskType.PeerPushDebit; pursePub: string } - | { tag: PendingTaskType.Purchase; proposalId: string } - | { tag: PendingTaskType.Recoup; recoupGroupId: string } - | { tag: PendingTaskType.TipPickup; walletTipId: string } - | { tag: PendingTaskType.Refresh; refreshGroupId: string }; - -export function parseTaskIdentifier(x: string): ParsedTaskIdentifier { - throw Error("not yet implemented"); -} - -export function constructTaskIdentifier(p: ParsedTaskIdentifier): TaskId { - switch (p.tag) { - case PendingTaskType.Backup: - return `${p.tag}:${p.backupProviderBaseUrl}` as TaskId; - case PendingTaskType.Deposit: - return `${p.tag}:${p.depositGroupId}` as TaskId; - case PendingTaskType.ExchangeCheckRefresh: - return `${p.tag}:${p.exchangeBaseUrl}` as TaskId; - case PendingTaskType.ExchangeUpdate: - return `${p.tag}:${p.exchangeBaseUrl}` as TaskId; - case PendingTaskType.PeerPullDebit: - return `${p.tag}:${p.peerPullPaymentIncomingId}` as TaskId; - case PendingTaskType.PeerPushCredit: - return `${p.tag}:${p.peerPushPaymentIncomingId}` as TaskId; - case PendingTaskType.PeerPullCredit: - return `${p.tag}:${p.pursePub}` as TaskId; - case PendingTaskType.PeerPushDebit: - return `${p.tag}:${p.pursePub}` as TaskId; - case PendingTaskType.Purchase: - return `${p.tag}:${p.proposalId}` as TaskId; - case PendingTaskType.Recoup: - return `${p.tag}:${p.recoupGroupId}` as TaskId; - case PendingTaskType.Refresh: - return `${p.tag}:${p.refreshGroupId}` as TaskId; - case PendingTaskType.TipPickup: - return `${p.tag}:${p.walletTipId}` as TaskId; - case PendingTaskType.Withdraw: - return `${p.tag}:${p.withdrawalGroupId}` as TaskId; - default: - assertUnreachable(p); - } -} - -export namespace TaskIdentifiers { - export function forWithdrawal(wg: WithdrawalGroupRecord): TaskId { - return `${PendingTaskType.Withdraw}:${wg.withdrawalGroupId}` as TaskId; - } - export function forExchangeUpdate(exch: ExchangeRecord): TaskId { - return `${PendingTaskType.ExchangeUpdate}:${exch.baseUrl}` as TaskId; - } - export function forExchangeUpdateFromUrl(exchBaseUrl: string): TaskId { - return `${PendingTaskType.ExchangeUpdate}:${exchBaseUrl}` as TaskId; - } - export function forExchangeCheckRefresh(exch: ExchangeRecord): TaskId { - return `${PendingTaskType.ExchangeCheckRefresh}:${exch.baseUrl}` as TaskId; - } - export function forTipPickup(tipRecord: TipRecord): TaskId { - return `${PendingTaskType.TipPickup}:${tipRecord.walletTipId}` as TaskId; - } - export function forRefresh(refreshGroupRecord: RefreshGroupRecord): TaskId { - return `${PendingTaskType.Refresh}:${refreshGroupRecord.refreshGroupId}` as TaskId; - } - export function forPay(purchaseRecord: PurchaseRecord): TaskId { - return `${PendingTaskType.Purchase}:${purchaseRecord.proposalId}` as TaskId; - } - export function forRecoup(recoupRecord: RecoupGroupRecord): TaskId { - return `${PendingTaskType.Recoup}:${recoupRecord.recoupGroupId}` as TaskId; - } - export function forDeposit(depositRecord: DepositGroupRecord): TaskId { - return `${PendingTaskType.Deposit}:${depositRecord.depositGroupId}` as TaskId; - } - export function forBackup(backupRecord: BackupProviderRecord): TaskId { - return `${PendingTaskType.Backup}:${backupRecord.baseUrl}` as TaskId; - } - export function forPeerPushPaymentInitiation( - ppi: PeerPushPaymentInitiationRecord, - ): TaskId { - return `${PendingTaskType.PeerPushDebit}:${ppi.pursePub}` as TaskId; - } - export function forPeerPullPaymentInitiation( - ppi: PeerPullPaymentInitiationRecord, - ): TaskId { - return `${PendingTaskType.PeerPullCredit}:${ppi.pursePub}` as TaskId; - } - export function forPeerPullPaymentDebit( - ppi: PeerPullPaymentIncomingRecord, - ): TaskId { - return `${PendingTaskType.PeerPullDebit}:${ppi.peerPullPaymentIncomingId}` as TaskId; - } - export function forPeerPushCredit( - ppi: PeerPushPaymentIncomingRecord, - ): TaskId { - return `${PendingTaskType.PeerPushCredit}:${ppi.peerPushPaymentIncomingId}` as TaskId; - } -} - -export async function scheduleRetryInTx( - ws: InternalWalletState, - tx: GetReadWriteAccess<{ - operationRetries: typeof WalletStoresV1.operationRetries; - }>, - opId: string, - errorDetail?: TalerErrorDetail, -): Promise<void> { - let retryRecord = await tx.operationRetries.get(opId); - if (!retryRecord) { - retryRecord = { - id: opId, - retryInfo: RetryInfo.reset(), - }; - if (errorDetail) { - retryRecord.lastError = errorDetail; - } - } else { - retryRecord.retryInfo = RetryInfo.increment(retryRecord.retryInfo); - if (errorDetail) { - retryRecord.lastError = errorDetail; - } else { - delete retryRecord.lastError; - } - } - await tx.operationRetries.put(retryRecord); -} - -export async function scheduleRetry( - ws: InternalWalletState, - opId: string, - errorDetail?: TalerErrorDetail, -): Promise<void> { - return await ws.db - .mktx((x) => [x.operationRetries]) - .runReadWrite(async (tx) => { - tx.operationRetries; - scheduleRetryInTx(ws, tx, opId, errorDetail); - }); -} - -/** - * Run an operation handler, expect a success result and extract the success value. - */ -export async function unwrapOperationHandlerResultOrThrow<T>( - res: OperationAttemptResult<T>, -): Promise<T> { - switch (res.type) { - case OperationAttemptResultType.Finished: - return res.result; - case OperationAttemptResultType.Error: - throw TalerError.fromUncheckedDetail(res.errorDetail); - default: - throw Error(`unexpected operation result (${res.type})`); - } -} |