vendor CancellationToken
This commit is contained in:
parent
e5f21ec5bb
commit
24b7110776
285
packages/taler-util/src/CancellationToken.ts
Normal file
285
packages/taler-util/src/CancellationToken.ts
Normal file
@ -0,0 +1,285 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Conrad Reuter
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
const NOOP = () => {};
|
||||
|
||||
/**
|
||||
* A token that can be passed around to inform consumers of the token that a
|
||||
* certain operation has been cancelled.
|
||||
*/
|
||||
class CancellationToken {
|
||||
private _reason: any;
|
||||
private _callbacks?: Set<(reason?: any) => void> = new Set();
|
||||
|
||||
/**
|
||||
* A cancellation token that is already cancelled.
|
||||
*/
|
||||
public static readonly CANCELLED: CancellationToken = new CancellationToken(
|
||||
true,
|
||||
true,
|
||||
);
|
||||
|
||||
/**
|
||||
* A cancellation token that is never cancelled.
|
||||
*/
|
||||
public static readonly CONTINUE: CancellationToken = new CancellationToken(
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
/**
|
||||
* Whether the token has been cancelled.
|
||||
*/
|
||||
public get isCancelled(): boolean {
|
||||
return this._isCancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the token can be cancelled.
|
||||
*/
|
||||
public get canBeCancelled(): boolean {
|
||||
return this._canBeCancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Why this token has been cancelled.
|
||||
*/
|
||||
public get reason(): any {
|
||||
if (this.isCancelled) {
|
||||
return this._reason;
|
||||
} else {
|
||||
throw new Error("This token is not cancelled.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a promise that resolves when the async operation resolves,
|
||||
* or rejects when the operation is rejected or this token is cancelled.
|
||||
*/
|
||||
public racePromise<T>(asyncOperation: Promise<T>): Promise<T> {
|
||||
if (!this.canBeCancelled) {
|
||||
return asyncOperation;
|
||||
}
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
// we could use Promise.finally here as soon as it's implemented in the major browsers
|
||||
const unregister = this.onCancelled((reason) =>
|
||||
reject(new CancellationToken.CancellationError(reason)),
|
||||
);
|
||||
asyncOperation.then(
|
||||
(value) => {
|
||||
resolve(value);
|
||||
unregister();
|
||||
},
|
||||
(err) => {
|
||||
reject(err);
|
||||
unregister();
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw a {CancellationToken.CancellationError} if this token is cancelled.
|
||||
*/
|
||||
public throwIfCancelled(): void {
|
||||
if (this._isCancelled) {
|
||||
throw new CancellationToken.CancellationError(this._reason);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the callback when this token is cancelled.
|
||||
* If this token is already cancelled, the callback is invoked immediately.
|
||||
* Returns a function that unregisters the cancellation callback.
|
||||
*/
|
||||
public onCancelled(cb: (reason?: any) => void): () => void {
|
||||
if (!this.canBeCancelled) {
|
||||
return NOOP;
|
||||
}
|
||||
if (this.isCancelled) {
|
||||
cb(this.reason);
|
||||
return NOOP;
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
this._callbacks?.add(cb);
|
||||
return () => this._callbacks?.delete(cb);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
/**
|
||||
* Whether the token is already cancelled.
|
||||
*/
|
||||
private _isCancelled: boolean,
|
||||
/**
|
||||
* Whether the token can be cancelled.
|
||||
*/
|
||||
private _canBeCancelled: boolean,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Create a {CancellationTokenSource}.
|
||||
*/
|
||||
public static create(): CancellationToken.Source {
|
||||
const token = new CancellationToken(false, true);
|
||||
|
||||
const cancel = (reason?: any) => {
|
||||
if (token._isCancelled) return;
|
||||
token._isCancelled = true;
|
||||
token._reason = reason;
|
||||
token._callbacks?.forEach((cb) => cb(reason));
|
||||
dispose();
|
||||
};
|
||||
|
||||
const dispose = () => {
|
||||
token._canBeCancelled = token.isCancelled;
|
||||
delete token._callbacks; // release memory
|
||||
};
|
||||
|
||||
return { token, cancel, dispose };
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {CancellationTokenSource}.
|
||||
* The token will be cancelled automatically after the specified timeout in milliseconds.
|
||||
*/
|
||||
public static timeout(ms: number): CancellationToken.Source {
|
||||
const {
|
||||
token,
|
||||
cancel: originalCancel,
|
||||
dispose: originalDispose,
|
||||
} = CancellationToken.create();
|
||||
|
||||
let timer: NodeJS.Timeout | null;
|
||||
timer = setTimeout(() => originalCancel(CancellationToken.timeout), ms);
|
||||
const disposeTimer = () => {
|
||||
if (timer == null) return;
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
};
|
||||
|
||||
const cancel = (reason?: any) => {
|
||||
disposeTimer();
|
||||
originalCancel(reason);
|
||||
};
|
||||
|
||||
/* istanbul ignore next */
|
||||
const dispose = () => {
|
||||
disposeTimer();
|
||||
originalDispose();
|
||||
};
|
||||
|
||||
return { token, cancel, dispose };
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {CancellationToken} that is cancelled when all of the given tokens are cancelled.
|
||||
*
|
||||
* This is like {Promise<T>.all} for {CancellationToken}s.
|
||||
*/
|
||||
public static all(...tokens: CancellationToken[]): CancellationToken {
|
||||
// If *any* of the tokens cannot be cancelled, then the token we return can never be.
|
||||
if (tokens.some((token) => !token.canBeCancelled)) {
|
||||
return CancellationToken.CONTINUE;
|
||||
}
|
||||
|
||||
const combined = CancellationToken.create();
|
||||
let countdown = tokens.length;
|
||||
const handleNextTokenCancelled = () => {
|
||||
if (--countdown === 0) {
|
||||
const reasons = tokens.map((token) => token._reason);
|
||||
combined.cancel(reasons);
|
||||
}
|
||||
};
|
||||
tokens.forEach((token) => token.onCancelled(handleNextTokenCancelled));
|
||||
return combined.token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {CancellationToken} that is cancelled when at least one of the given tokens is cancelled.
|
||||
*
|
||||
* This is like {Promise<T>.race} for {CancellationToken}s.
|
||||
*/
|
||||
public static race(...tokens: CancellationToken[]): CancellationToken {
|
||||
// If *any* of the tokens is already cancelled, immediately return that token.
|
||||
for (const token of tokens) {
|
||||
if (token._isCancelled) {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
const combined = CancellationToken.create();
|
||||
let unregistrations: (() => void)[];
|
||||
const handleAnyTokenCancelled = (reason?: any) => {
|
||||
unregistrations.forEach((unregister) => unregister()); // release memory
|
||||
combined.cancel(reason);
|
||||
};
|
||||
unregistrations = tokens.map((token) =>
|
||||
token.onCancelled(handleAnyTokenCancelled),
|
||||
);
|
||||
return combined.token;
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
namespace CancellationToken {
|
||||
/**
|
||||
* Provides a {CancellationToken}, along with some methods to operate on it.
|
||||
*/
|
||||
export interface Source {
|
||||
/**
|
||||
* The token provided by this source.
|
||||
*/
|
||||
token: CancellationToken;
|
||||
|
||||
/**
|
||||
* Cancel the provided token with the given reason.
|
||||
* Do nothing if the provided token cannot be cancelled or is already cancelled.
|
||||
*/
|
||||
cancel(reason?: any): void;
|
||||
|
||||
/**
|
||||
* Dipose of the token and this source and release memory.
|
||||
*/
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The error that is thrown when a {CancellationToken} has been cancelled and a
|
||||
* consumer of the token calls {CancellationToken.throwIfCancelled} on it.
|
||||
*/
|
||||
export class CancellationError extends Error {
|
||||
public constructor(
|
||||
/**
|
||||
* The reason why the token was cancelled.
|
||||
*/
|
||||
public readonly reason: any,
|
||||
) {
|
||||
super("Operation cancelled");
|
||||
Object.setPrototypeOf(this, CancellationError.prototype);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { CancellationToken };
|
@ -31,3 +31,4 @@ export {
|
||||
crypto_sign_keyPair_fromSeed,
|
||||
} from "./nacl-fast.js";
|
||||
export { RequestThrottler } from "./RequestThrottler.js";
|
||||
export * from "./CancellationToken.js";
|
||||
|
@ -47,7 +47,6 @@
|
||||
"@gnu-taler/taler-util": "workspace:*",
|
||||
"@gnu-taler/taler-wallet-core": "workspace:*",
|
||||
"axios": "^0.25.0",
|
||||
"cancellationtoken": "^2.2.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"tslib": "^2.3.1"
|
||||
}
|
||||
|
@ -14,80 +14,79 @@
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { minimatch } from "@gnu-taler/taler-util";
|
||||
import { CancellationToken, minimatch } from "@gnu-taler/taler-util";
|
||||
import * as child_process from "child_process";
|
||||
import * as fs from "fs";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
import {
|
||||
GlobalTestState,
|
||||
runTestWithState,
|
||||
shouldLingerInTest,
|
||||
TestRunResult,
|
||||
} from "../harness/harness.js";
|
||||
import { runPaymentTest } from "./test-payment";
|
||||
import { runPaymentDemoTest } from "./test-payment-on-demo";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import * as os from "os";
|
||||
import * as child_process from "child_process";
|
||||
import { runBankApiTest } from "./test-bank-api";
|
||||
import { runClaimLoopTest } from "./test-claim-loop";
|
||||
import { runClauseSchnorrTest } from "./test-clause-schnorr.js";
|
||||
import { runDenomUnofferedTest } from "./test-denom-unoffered.js";
|
||||
import { runDepositTest } from "./test-deposit";
|
||||
import { runExchangeManagementTest } from "./test-exchange-management";
|
||||
import { runExchangeTimetravelTest } from "./test-exchange-timetravel.js";
|
||||
import { runFeeRegressionTest } from "./test-fee-regression";
|
||||
import { runLibeufinApiBankaccountTest } from "./test-libeufin-api-bankaccount";
|
||||
import { runLibeufinApiBankconnectionTest } from "./test-libeufin-api-bankconnection";
|
||||
import { runLibeufinApiFacadeTest } from "./test-libeufin-api-facade";
|
||||
import { runLibeufinApiFacadeBadRequestTest } from "./test-libeufin-api-facade-bad-request";
|
||||
import { runLibeufinApiPermissionsTest } from "./test-libeufin-api-permissions";
|
||||
import { runLibeufinApiSandboxCamtTest } from "./test-libeufin-api-sandbox-camt";
|
||||
import { runLibeufinApiSandboxTransactionsTest } from "./test-libeufin-api-sandbox-transactions";
|
||||
import { runLibeufinApiSchedulingTest } from "./test-libeufin-api-scheduling";
|
||||
import { runLibeufinApiUsersTest } from "./test-libeufin-api-users";
|
||||
import { runLibeufinBadGatewayTest } from "./test-libeufin-bad-gateway";
|
||||
import { runLibeufinBasicTest } from "./test-libeufin-basic";
|
||||
import { runLibeufinC5xTest } from "./test-libeufin-c5x";
|
||||
import { runLibeufinAnastasisFacadeTest } from "./test-libeufin-facade-anastasis";
|
||||
import { runLibeufinKeyrotationTest } from "./test-libeufin-keyrotation";
|
||||
import { runLibeufinNexusBalanceTest } from "./test-libeufin-nexus-balance";
|
||||
import { runLibeufinRefundTest } from "./test-libeufin-refund";
|
||||
import { runLibeufinRefundMultipleUsersTest } from "./test-libeufin-refund-multiple-users";
|
||||
import { runLibeufinSandboxWireTransferCliTest } from "./test-libeufin-sandbox-wire-transfer-cli";
|
||||
import { runLibeufinTutorialTest } from "./test-libeufin-tutorial";
|
||||
import { runMerchantExchangeConfusionTest } from "./test-merchant-exchange-confusion";
|
||||
import { runMerchantInstancesTest } from "./test-merchant-instances";
|
||||
import { runMerchantInstancesDeleteTest } from "./test-merchant-instances-delete";
|
||||
import { runMerchantInstancesUrlsTest } from "./test-merchant-instances-urls";
|
||||
import { runMerchantLongpollingTest } from "./test-merchant-longpolling";
|
||||
import { runMerchantRefundApiTest } from "./test-merchant-refund-api";
|
||||
import { runMerchantSpecPublicOrdersTest } from "./test-merchant-spec-public-orders.js";
|
||||
import { runPayAbortTest } from "./test-pay-abort";
|
||||
import { runPayPaidTest } from "./test-pay-paid";
|
||||
import { runPaymentTest } from "./test-payment";
|
||||
import { runPaymentClaimTest } from "./test-payment-claim";
|
||||
import { runPaymentFaultTest } from "./test-payment-fault";
|
||||
import { runPaymentForgettableTest } from "./test-payment-forgettable.js";
|
||||
import { runPaymentIdempotencyTest } from "./test-payment-idempotency";
|
||||
import { runPaymentMultipleTest } from "./test-payment-multiple";
|
||||
import { runPaymentDemoTest } from "./test-payment-on-demo";
|
||||
import { runPaymentTransientTest } from "./test-payment-transient";
|
||||
import { runPaymentZeroTest } from "./test-payment-zero.js";
|
||||
import { runPaywallFlowTest } from "./test-paywall-flow";
|
||||
import { runRefundTest } from "./test-refund";
|
||||
import { runRefundAutoTest } from "./test-refund-auto";
|
||||
import { runRefundGoneTest } from "./test-refund-gone";
|
||||
import { runRefundIncrementalTest } from "./test-refund-incremental";
|
||||
import { runRefundTest } from "./test-refund";
|
||||
import { runRevocationTest } from "./test-revocation";
|
||||
import { runTimetravelAutorefreshTest } from "./test-timetravel-autorefresh";
|
||||
import { runTimetravelWithdrawTest } from "./test-timetravel-withdraw";
|
||||
import { runTippingTest } from "./test-tipping";
|
||||
import { runWalletBackupBasicTest } from "./test-wallet-backup-basic";
|
||||
import { runWalletBackupDoublespendTest } from "./test-wallet-backup-doublespend";
|
||||
import { runWalletDblessTest } from "./test-wallet-dbless.js";
|
||||
import { runWallettestingTest } from "./test-wallettesting";
|
||||
import { runTestWithdrawalManualTest } from "./test-withdrawal-manual";
|
||||
import { runWithdrawalAbortBankTest } from "./test-withdrawal-abort-bank";
|
||||
import { runWithdrawalBankIntegratedTest } from "./test-withdrawal-bank-integrated";
|
||||
import { runMerchantExchangeConfusionTest } from "./test-merchant-exchange-confusion";
|
||||
import { runLibeufinBasicTest } from "./test-libeufin-basic";
|
||||
import { runLibeufinC5xTest } from "./test-libeufin-c5x";
|
||||
import { runLibeufinNexusBalanceTest } from "./test-libeufin-nexus-balance";
|
||||
import { runLibeufinBadGatewayTest } from "./test-libeufin-bad-gateway";
|
||||
import { runLibeufinKeyrotationTest } from "./test-libeufin-keyrotation";
|
||||
import { runLibeufinRefundTest } from "./test-libeufin-refund";
|
||||
import { runLibeufinRefundMultipleUsersTest } from "./test-libeufin-refund-multiple-users";
|
||||
import { runLibeufinTutorialTest } from "./test-libeufin-tutorial";
|
||||
import { runLibeufinApiPermissionsTest } from "./test-libeufin-api-permissions";
|
||||
import { runLibeufinApiFacadeTest } from "./test-libeufin-api-facade";
|
||||
import { runLibeufinApiFacadeBadRequestTest } from "./test-libeufin-api-facade-bad-request";
|
||||
import { runLibeufinAnastasisFacadeTest } from "./test-libeufin-facade-anastasis";
|
||||
import { runLibeufinApiSchedulingTest } from "./test-libeufin-api-scheduling";
|
||||
import { runLibeufinApiBankconnectionTest } from "./test-libeufin-api-bankconnection";
|
||||
import { runLibeufinApiUsersTest } from "./test-libeufin-api-users";
|
||||
import { runLibeufinApiBankaccountTest } from "./test-libeufin-api-bankaccount";
|
||||
import { runLibeufinApiSandboxTransactionsTest } from "./test-libeufin-api-sandbox-transactions";
|
||||
import { runLibeufinApiSandboxCamtTest } from "./test-libeufin-api-sandbox-camt";
|
||||
import { runLibeufinSandboxWireTransferCliTest } from "./test-libeufin-sandbox-wire-transfer-cli";
|
||||
import { runDepositTest } from "./test-deposit";
|
||||
import CancellationToken from "cancellationtoken";
|
||||
import { runMerchantInstancesTest } from "./test-merchant-instances";
|
||||
import { runMerchantInstancesUrlsTest } from "./test-merchant-instances-urls";
|
||||
import { runWalletBackupBasicTest } from "./test-wallet-backup-basic";
|
||||
import { runMerchantInstancesDeleteTest } from "./test-merchant-instances-delete";
|
||||
import { runWalletBackupDoublespendTest } from "./test-wallet-backup-doublespend";
|
||||
import { runPaymentForgettableTest } from "./test-payment-forgettable.js";
|
||||
import { runPaymentZeroTest } from "./test-payment-zero.js";
|
||||
import { runMerchantSpecPublicOrdersTest } from "./test-merchant-spec-public-orders.js";
|
||||
import { runExchangeTimetravelTest } from "./test-exchange-timetravel.js";
|
||||
import { runDenomUnofferedTest } from "./test-denom-unoffered.js";
|
||||
import { runWithdrawalFakebankTest } from "./test-withdrawal-fakebank.js";
|
||||
import { runClauseSchnorrTest } from "./test-clause-schnorr.js";
|
||||
import { runWalletDblessTest } from "./test-wallet-dbless.js";
|
||||
import { runTestWithdrawalManualTest } from "./test-withdrawal-manual";
|
||||
|
||||
/**
|
||||
* Test runner.
|
||||
|
@ -31,6 +31,7 @@ import {
|
||||
TalerErrorDetail,
|
||||
Codec,
|
||||
j2s,
|
||||
CancellationToken,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { TalerErrorCode } from "@gnu-taler/taler-util";
|
||||
import { makeErrorDetail, TalerError } from "../errors.js";
|
||||
@ -53,7 +54,18 @@ export interface HttpResponse {
|
||||
export interface HttpRequestOptions {
|
||||
method?: "POST" | "PUT" | "GET";
|
||||
headers?: { [name: string]: string };
|
||||
|
||||
/**
|
||||
* Timeout after which the request should be aborted.
|
||||
*/
|
||||
timeout?: Duration;
|
||||
|
||||
/**
|
||||
* Cancellation token that should abort the request when
|
||||
* cancelled.
|
||||
*/
|
||||
cancellationToken?: CancellationToken;
|
||||
|
||||
body?: string | ArrayBuffer | ArrayBufferView;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user