Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the crash button and instructions for a demo. #165

Merged
merged 3 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions bank/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,43 @@ npm start

DBOS Cloud comes with a [monitoring dashboard](https://docs.dbos.dev/cloud-tutorials/monitoring-dashboard) automatically. To find the URL for your dashboard, execute `npx dbos-cloud dashboard url`. The dashboard includes execution traces.

## A Demo of Workflow Recovery

We can simulate a catastrophic failure during a transfer. Namely the following case:
1. we try to send a transfer from Bank A to Bank B
1. we make the transfer fail, as if bank B went offline
2. we crash Bank A before it has a chance to recover and undo the transfer

DBOS workflows are guaranteed to continue where they left off. This means that when Bank A is restarted, it will continue undoing the transfer. Shortly after restart, Bank A returns to a consistent state with the funds back in the source account.

To replicate this, perform the following:
1. in `withdrawWorkflow` (bank-backend/src/workflows/txnhistory.workflows.ts) uncomment the sleep block like so:
```ts
if (!remoteRes) {
// Sleep for 10 seconds before undoing the transaction
for (let i = 0; i < 10; i++) {
ctxt.logger.info("Sleeping")
await ctxt.sleepms(1000)
}

// Undo withdrawal with a deposit.
const undoRes = await ctxt.invoke(BankTransactionHistory).updateAcctTransactionFunc(data.fromAccountId, data, true, result);
if (undoRes !== result) {
throw new Error(`Mismatch: Original txnId: ${result}, undo txnId: ${undoRes}`);
}
throw new Error("Failed to deposit to remote bank; transaction reversed");
}
```
2. Restart or redeploy Bank A with this change.
3. Stop Bank B: `CTRL+C` the app if running locally or something like `npx dbos-cloud app delete bank_b` if in the cloud.

This demo is time sensitive as you'll have a 10-second sleep window to crash Bank A. Adjust the above `sleep` loop if you nee more time. Read these steps ahead of time to get a sense for what to do:
1. Initiate a withdrawal from Bank A to Bank B
2. Because of the above `sleep` loop, you won't see the effect. You can quickly close the "Withdraw" window and refresh the browser to see that the funds left the account. Now, quickly press the red "Crash!" button.
3. If running in DBOS cloud, wait a few seconds. If you are running locally, restart Bank A by hand.
4. After restarting the app, log in again to `https://localhost:8089`. Observe the funds returning to the sender account.
5. In the log for the app, you should see the failed transfer. The 10 `Sleeping` statements, interrupted by a crash, then restart, workflow recovery and finally the "transaction reversed" error message.

## Further Reading
To get started with DBOS Transact, check out the [quickstart](https://docs.dbos.dev/getting-started/quickstart) and [docs](https://docs.dbos.dev/).

Expand Down
11 changes: 4 additions & 7 deletions bank/bank-backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion bank/bank-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"license": "ISC",
"private": true,
"dependencies": {
"@dbos-inc/dbos-sdk": "^1.15.9",
"@dbos-inc/dbos-sdk": "^1.16.12",
"@koa/bodyparser": "^5.0.0",
"@koa/cors": "^5.0.0",
"@koa/router": "^12.0.0",
Expand Down
10 changes: 10 additions & 0 deletions bank/bank-backend/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,16 @@ export class BankEndpoints {
}
}

// For demo purposes
export class CrashEndpoint {
@GetApi('/crash_application')
static async crashApplication(_ctx: HandlerContext) {
// For testing and demo purposes :)
process.exit(1);
return Promise.resolve();
}
}

// Helper functions to convert to the correct data types.
// Especially convert the bigint.
export function convertTransactionHistory(data: TransactionHistory): TransactionHistory {
Expand Down
19 changes: 16 additions & 3 deletions bank/bank-backend/src/workflows/txnhistory.workflows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,6 @@ export class BankTransactionHistory {
static async withdrawWorkflow(ctxt: WorkflowContext, data: TransactionHistory) {
// Withdraw first.
const result = await ctxt.invoke(BankTransactionHistory).updateAcctTransactionFunc(data.fromAccountId, data, false);

// Then, contact remote DB to deposit.
if (data.toLocation && !(data.toLocation === "cash") && !data.toLocation.startsWith(REMOTEDB_PREFIX)) {
ctxt.logger.info("Deposit to another DB: " + data.toLocation + ", account: " + data.toAccountId);
Expand All @@ -240,12 +239,26 @@ export class BankTransactionHistory {
};
const remoteRes: boolean = await ctxt.invoke(BankTransactionHistory).remoteTransferComm(remoteUrl, thReq as TransactionHistory, ctxt.workflowUUID + '-deposit');
if (!remoteRes) {
// Undo transaction is a deposit.

///////////////////////////////
// Example sleep Window. For a reliability test, uncomment the below
// Then, start a transfer to a nonexistent bank, (i.e. stop bank b).
// Wait for app to go into sleep and then crash it. The DBOS workflow
// recovery will ensure the undo transaction below is executed when
// the app restarts
//
// for (let i = 0; i < 10; i++) {
// ctxt.logger.info("Sleeping")
// await ctxt.sleepms(1000)
// }
///////////////////////////////

// Undo withdrawal with a deposit.
const undoRes = await ctxt.invoke(BankTransactionHistory).updateAcctTransactionFunc(data.fromAccountId, data, true, result);
if (undoRes !== result) {
throw new Error(`Mismatch: Original txnId: ${result}, undo txnId: ${undoRes}`);
}
throw new Error("Failed to deposit to remote bank.");
throw new Error("Failed to deposit to remote bank; transaction reversed");
}
} else {
ctxt.logger.info("Deposit to: " + data.fromLocation);
Expand Down
7 changes: 6 additions & 1 deletion bank/bank-frontend/src/app/bank.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { OwnerNameDialogComponent } from './owner-name-dialog/owner-name-dialog.
<button class="button-grey" (click)="getMsg()" type="submit">New Greeting Message</button>
<button class="button-green" (click)="createNewAccount()" type="submit">Create a New Account</button>
<button class="button" (click)="getAccounts()" type="submit">Refresh Accounts</button>

<button class="button-red" (click)="crashApp()" type="submit">Crash!</button>
</div>

<table class="table table-striped">
Expand Down Expand Up @@ -431,4 +431,9 @@ export class BankComponent {
error: (err: any) => { this.bankmsg = 'Failed to transfer! '; }
});
}

//In response to the "Crash" button - for demo purposes
crashApp(){
this._service.getResource(this.bankUrl + "/crash_application").subscribe()
}
}
12 changes: 12 additions & 0 deletions bank/bank-frontend/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@
position: relative;
}

.button-red {
margin: 10px;
padding: 15px 20px;
background-color: #ff1111;
color: white;
border: none;
cursor: pointer;
font-size: 16px;
border-radius: 10px; /* Rounded corners */
position: relative;
}

.button-grey {
margin: 10px;
padding: 15px 20px;
Expand Down
Loading