gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: vendor CancellationToken


From: gnunet
Subject: [taler-wallet-core] branch master updated: vendor CancellationToken
Date: Mon, 28 Mar 2022 20:24:26 +0200

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new 24b71107 vendor CancellationToken
24b71107 is described below

commit 24b71107765172b568803fad5fb79474674b147a
Author: Florian Dold <florian@dold.me>
AuthorDate: Mon Mar 28 20:23:47 2022 +0200

    vendor CancellationToken
---
 packages/taler-util/src/CancellationToken.ts       | 285 +++++++++++++++++++++
 packages/taler-util/src/index.ts                   |   1 +
 packages/taler-wallet-cli/package.json             |   1 -
 .../src/integrationtests/testrunner.ts             |  85 +++---
 packages/taler-wallet-core/src/util/http.ts        |  12 +
 5 files changed, 340 insertions(+), 44 deletions(-)

diff --git a/packages/taler-util/src/CancellationToken.ts 
b/packages/taler-util/src/CancellationToken.ts
new file mode 100644
index 00000000..13480527
--- /dev/null
+++ b/packages/taler-util/src/CancellationToken.ts
@@ -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 };
diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts
index 573b4a5c..199218d6 100644
--- a/packages/taler-util/src/index.ts
+++ b/packages/taler-util/src/index.ts
@@ -31,3 +31,4 @@ export {
   crypto_sign_keyPair_fromSeed,
 } from "./nacl-fast.js";
 export { RequestThrottler } from "./RequestThrottler.js";
+export * from "./CancellationToken.js";
diff --git a/packages/taler-wallet-cli/package.json 
b/packages/taler-wallet-cli/package.json
index 96f69939..e4309056 100644
--- a/packages/taler-wallet-cli/package.json
+++ b/packages/taler-wallet-cli/package.json
@@ -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"
   }
diff --git a/packages/taler-wallet-cli/src/integrationtests/testrunner.ts 
b/packages/taler-wallet-cli/src/integrationtests/testrunner.ts
index 3839266c..d8dc569d 100644
--- a/packages/taler-wallet-cli/src/integrationtests/testrunner.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/testrunner.ts
@@ -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.
diff --git a/packages/taler-wallet-core/src/util/http.ts 
b/packages/taler-wallet-core/src/util/http.ts
index 31e38b60..9ccd560d 100644
--- a/packages/taler-wallet-core/src/util/http.ts
+++ b/packages/taler-wallet-core/src/util/http.ts
@@ -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;
 }
 

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]