summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2024-02-18 23:34:57 +0100
committerFlorian Dold <florian@dold.me>2024-02-18 23:34:57 +0100
commitb55bf0779946b6a1554a687e05841d131b9951b3 (patch)
tree0eeb2df50228280ba92140f06a7037d9ba8baf0d
parent90c7dc49b7d377b4b3e691f45b84b51f6b03f9af (diff)
downloadwallet-core-b55bf0779946b6a1554a687e05841d131b9951b3.tar.gz
wallet-core-b55bf0779946b6a1554a687e05841d131b9951b3.tar.bz2
wallet-core-b55bf0779946b6a1554a687e05841d131b9951b3.zip
fix non-termination in run-until done
Also add a test for this.
-rw-r--r--packages/taler-harness/src/integrationtests/test-wallet-cli-termination.ts101
-rw-r--r--packages/taler-harness/src/integrationtests/testrunner.ts2
-rw-r--r--packages/taler-wallet-core/src/shepherd.ts48
3 files changed, 148 insertions, 3 deletions
diff --git a/packages/taler-harness/src/integrationtests/test-wallet-cli-termination.ts b/packages/taler-harness/src/integrationtests/test-wallet-cli-termination.ts
new file mode 100644
index 000000000..4f015799f
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-wallet-cli-termination.ts
@@ -0,0 +1,101 @@
+/*
+ This file is part of GNU Taler
+ (C) 2024 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Imports.
+ */
+import { AmountString } from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
+import {
+ BankService,
+ ExchangeService,
+ GlobalTestState,
+ MerchantService,
+ WalletCli,
+ generateRandomPayto,
+ setupDb,
+} from "../harness/harness.js";
+
+/**
+ * Test that run-until-done of taler-wallet-cli terminates.
+ */
+export async function runWalletCliTerminationTest(t: GlobalTestState) {
+ const db = await setupDb(t);
+
+ const coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS"));
+
+ const bank = await BankService.create(t, {
+ allowRegistrations: true,
+ currency: "TESTKUDOS",
+ database: db.connStr,
+ httpPort: 8082,
+ });
+
+ const exchange = ExchangeService.create(t, {
+ name: "testexchange-1",
+ currency: "TESTKUDOS",
+ httpPort: 8081,
+ database: db.connStr,
+ });
+
+ const merchant = await MerchantService.create(t, {
+ name: "testmerchant-1",
+ currency: "TESTKUDOS",
+ httpPort: 8083,
+ database: db.connStr,
+ });
+
+ const exchangeBankAccount = await bank.createExchangeAccount(
+ "myexchange",
+ "x",
+ );
+ exchange.addBankAccount("1", exchangeBankAccount);
+
+ bank.setSuggestedExchange(exchange, exchangeBankAccount.accountPaytoUri);
+
+ await bank.start();
+
+ await bank.pingUntilAvailable();
+
+ exchange.addCoinConfigList(coinConfig);
+
+ await exchange.start();
+ await exchange.pingUntilAvailable();
+
+ merchant.addExchange(exchange);
+
+ await merchant.start();
+ await merchant.pingUntilAvailable();
+
+ await merchant.addInstanceWithWireAccount({
+ id: "default",
+ name: "Default Instance",
+ paytoUris: [generateRandomPayto("merchant-default")],
+ });
+
+ const wallet = new WalletCli(t, "wallet");
+
+ await wallet.client.call(WalletApiOperation.WithdrawTestBalance, {
+ corebankApiBaseUrl: bank.corebankApiBaseUrl,
+ exchangeBaseUrl: exchange.baseUrl,
+ amount: "TESTKUDOS:20" as AmountString,
+ });
+
+ await wallet.runUntilDone();
+}
+
+runWalletCliTerminationTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts
index 624b230d3..f4401c63c 100644
--- a/packages/taler-harness/src/integrationtests/testrunner.ts
+++ b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -86,6 +86,7 @@ import { runTermOfServiceFormatTest } from "./test-tos-format.js";
import { runWalletBackupBasicTest } from "./test-wallet-backup-basic.js";
import { runWalletBackupDoublespendTest } from "./test-wallet-backup-doublespend.js";
import { runWalletBalanceTest } from "./test-wallet-balance.js";
+import { runWalletCliTerminationTest } from "./test-wallet-cli-termination.js";
import { runWalletCryptoWorkerTest } from "./test-wallet-cryptoworker.js";
import { runWalletDblessTest } from "./test-wallet-dbless.js";
import { runWalletDd48Test } from "./test-wallet-dd48.js";
@@ -189,6 +190,7 @@ const allTests: TestMainFunction[] = [
runWalletDd48Test,
runCurrencyScopeTest,
runWalletRefreshTest,
+ runWalletCliTerminationTest,
runOtpTest,
];
diff --git a/packages/taler-wallet-core/src/shepherd.ts b/packages/taler-wallet-core/src/shepherd.ts
index 4aea2d15d..a3735e78f 100644
--- a/packages/taler-wallet-core/src/shepherd.ts
+++ b/packages/taler-wallet-core/src/shepherd.ts
@@ -77,6 +77,32 @@ interface ShepherdInfo {
cts: CancellationToken.Source;
}
+/**
+ * Check if a task is alive, i.e. whether it prevents
+ * the main task loop from exiting.
+ */
+function taskGivesLiveness(taskId: string): boolean {
+ const parsedTaskId = parseTaskIdentifier(taskId);
+ switch (parsedTaskId.tag) {
+ case PendingTaskType.Backup:
+ case PendingTaskType.ExchangeUpdate:
+ return false;
+ case PendingTaskType.Deposit:
+ case PendingTaskType.PeerPullCredit:
+ case PendingTaskType.PeerPullDebit:
+ case PendingTaskType.PeerPushCredit:
+ case PendingTaskType.Refresh:
+ case PendingTaskType.Recoup:
+ case PendingTaskType.RewardPickup:
+ case PendingTaskType.Withdraw:
+ case PendingTaskType.PeerPushDebit:
+ case PendingTaskType.Purchase:
+ return true;
+ default:
+ assertUnreachable(parsedTaskId);
+ }
+}
+
export class TaskScheduler {
private sheps: Map<TaskId, ShepherdInfo> = new Map();
@@ -89,6 +115,8 @@ export class TaskScheduler {
async loadTasksFromDb(): Promise<void> {
const activeTasks = await getActiveTaskIds(this.ws);
+ logger.info(`active tasks from DB: ${j2s(activeTasks)}`);
+
for (const tid of activeTasks.taskIds) {
this.startShepherdTask(tid);
}
@@ -98,10 +126,24 @@ export class TaskScheduler {
logger.info("Running task loop.");
this.ws.isTaskLoopRunning = true;
await this.loadTasksFromDb();
+ logger.info("loaded!");
+ logger.info(`sheps: ${this.sheps.size}`);
while (true) {
- if (opts.stopWhenDone && this.sheps.size === 0) {
- logger.info("Breaking out of task loop (no more work).");
- break;
+ if (opts.stopWhenDone) {
+ let alive = false;
+ const taskIds = [...this.sheps.keys()];
+ logger.info(`current task IDs: ${j2s(taskIds)}`);
+ logger.info(`sheps: ${this.sheps.size}`);
+ for (const taskId of taskIds) {
+ if (taskGivesLiveness(taskId)) {
+ alive = true;
+ break;
+ }
+ }
+ if (!alive) {
+ logger.info("Breaking out of task loop (no more work).");
+ break;
+ }
}
if (this.ws.stopped) {
logger.info("Breaking out of task loop (wallet stopped).");