Skip to content
This repository has been archived by the owner on Oct 9, 2023. It is now read-only.

Implement and test configurable transaction timeouts #197

Merged
merged 41 commits into from
Jan 14, 2022

Conversation

flyingsilverfin
Copy link
Member

@flyingsilverfin flyingsilverfin commented Dec 21, 2021

What is the goal of this PR?

Transactions now have a transaction timeouts, which are configurable via transaction options (typedb/typedb#6487). We implemented the new transaction option, and test them via new BDD scenarios. This also caused us to simplify and make more obvious when transaction errors occur when using an open transaction.

What are the changes implemented in this PR?

  • Add option for transaction timeout (default: 5 mins)
  • when errors are received from GRPC, we record them in the transaction stream to return immediately, rather than waiting to pull them from the transaction stream queue
  • add missing BDD steps for session and transaction option configuration
  • Add missing await class to various test steps in BDD, plus importantly make the close() methods have the right asyncronous/promise signature that ensures users wanting to close() will know to await as well.
  • update grpc and protobuf versions to the latest

@flyingsilverfin flyingsilverfin marked this pull request as ready for review January 10, 2022 17:32
@flyingsilverfin flyingsilverfin changed the title Transaction timeout Implement and test transaction timeouts Jan 10, 2022
Copy link
Member Author

@flyingsilverfin flyingsilverfin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still want to see if I can reduce size of package.lock diff... or is it ok that's 14k lines change?? Manually updated yarn.lock because i didn't want a 10k differ there too :(

update: done!

@@ -33,6 +33,7 @@ namespace Opts {
prefetchSize?: number;
prefetch?: boolean;
sessionIdleTimeoutMillis?: number;
transactionTimeoutMillis?: number;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new transaction option: millis before timeout

Comment on lines +151 to +159
get transactionTimeoutMillis() {
return this._transactionTimeoutMillis;
}

set transactionTimeoutMillis(millis: number) {
if (millis < 1) {
throw new TypeDBClientError(NEGATIVE_VALUE_NOT_ALLOWED.message(millis));
}
this._sessionIdleTimeoutMillis = value;
this._transactionTimeoutMillis = millis;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setter/getter for transaction timeouts

export const CLUSTER_TOKEN_CREDENTIAL_INVALID = new Client(16, (args: Stringable[]) => `Invalid token credential.`);
export const CLUSTER_INVALID_ROOT_CA_PATH = new Client(17, (args: Stringable[]) => `The provided Root CA path '${args[0]}' does not exist`);
export const UNRECOGNISED_SESSION_TYPE = new Client(18, (args: Stringable[]) => `Session type '${args[0]}' was not recognised.`);
export const TRANSACTION_CLOSED_WITH_ERRORS = new Client(4, (args) => `The transaction has been closed with error(s): \n${args[0]}.`)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new exception to report a transaction is closed with errors

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for consistency with other constants in this file let's add the type annotation of args:

Suggested change
export const TRANSACTION_CLOSED_WITH_ERRORS = new Client(4, (args) => `The transaction has been closed with error(s): \n${args[0]}.`)
export const TRANSACTION_CLOSED_WITH_ERRORS = new Client(4, (args: Stringable[]) => `The transaction has been closed with error(s): \n${args[0]}.`)

Comment on lines +22 to +37
import {Transaction} from "typedb-protocol/common/transaction_pb";
import {ConceptManager} from "../api/concept/ConceptManager";
import {TypeDBOptions} from "../api/connection/TypeDBOptions";
import {TransactionType, TypeDBTransaction} from "../api/connection/TypeDBTransaction";
import {LogicManager} from "../api/logic/LogicManager";
import {QueryManager} from "../api/query/QueryManager";
import {ErrorMessage} from "../common/errors/ErrorMessage";
import {TypeDBClientError} from "../common/errors/TypeDBClientError";
import {RequestBuilder} from "../common/rpc/RequestBuilder";
import {Stream} from "../common/util/Stream";
import {ConceptManagerImpl} from "../concept/ConceptManagerImpl";
import {LogicManagerImpl} from "../logic/LogicManagerImpl";
import {QueryManagerImpl} from "../query/QueryManagerImpl";
import {BidirectionalStream} from "../stream/BidirectionalStream";
import {TypeDBSessionImpl} from "./TypeDBSessionImpl";
import assert = require("assert");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

auto format....

Comment on lines 120 to 125
private throwTransactionClosed(): void {
assert(!this.isOpen());
const errors = this._bidirectionalStream.getErrors();
if (errors.length == 0) throw new TypeDBClientError(TRANSACTION_CLOSED);
else throw new TypeDBClientError(TRANSACTION_CLOSED_WITH_ERRORS.message(errors));
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can differentiate between a transaction closed with errors and one without errors

Comment on lines 30 to 34
Before(async () => {
before();
setSessionOptions(TypeDBOptions.cluster({"infer": true}));
setTransactionOptions(TypeDBOptions.cluster({"infer": true}));
});
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initialising a cluster scenario involves the normal before() and then setting various session/transaction options we always have on

Comment on lines 30 to 34
Before(async () => {
before();
setSessionOptions(TypeDBOptions.core({"infer": true}));
setTransactionOptions(TypeDBOptions.core({"infer": true}));
});
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same thing here

setTransactionOptions(TypeDBOptions.cluster({"infer": true}));
});

After(async () => after());
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for symmetry we also call the after() in the ConnectionStepsBase.ts class here

Comment on lines +92 to +98
Given('set session option {word} to: {word}', async function (option: string, value: string) {
if (!(option in optionSetters)) {
throw ("Unrecognised option: " + option);
} else {
optionSetters[option](sessionOptions, value);
}
});
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setter for connection options

@@ -48,7 +48,7 @@ echo Successfully unarchived TypeDB $PRODUCT distribution.
echo Starting TypeDB $PRODUCT Server
mkdir ./typedb-distribution/"$DIRECTORY"/typedb_test
if [[ $PRODUCT == "Core" ]]; then
./typedb-distribution/"$DIRECTORY"/typedb server --data typedb_test &
./typedb-distribution/"$DIRECTORY"/typedb server --storage.data typedb_test &
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new test server argument format!

@@ -79,6 +79,7 @@ behaviour_steps_common = [
"//test/behaviour/connection/session:SessionSteps.ts",
"//test/behaviour/connection/transaction:TransactionSteps.ts",
"//test/behaviour/typeql:TypeQLSteps.ts",
"//test/behaviour/util:UtilSteps.ts",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

following other clients' we extract the wait steps into their own UtilSteps file

export const CLUSTER_TOKEN_CREDENTIAL_INVALID = new Client(16, (args: Stringable[]) => `Invalid token credential.`);
export const CLUSTER_INVALID_ROOT_CA_PATH = new Client(17, (args: Stringable[]) => `The provided Root CA path '${args[0]}' does not exist`);
export const UNRECOGNISED_SESSION_TYPE = new Client(18, (args: Stringable[]) => `Session type '${args[0]}' was not recognised.`);
export const TRANSACTION_CLOSED_WITH_ERRORS = new Client(4, (args) => `The transaction has been closed with error(s): \n${args[0]}.`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for consistency with other constants in this file let's add the type annotation of args:

Suggested change
export const TRANSACTION_CLOSED_WITH_ERRORS = new Client(4, (args) => `The transaction has been closed with error(s): \n${args[0]}.`)
export const TRANSACTION_CLOSED_WITH_ERRORS = new Client(4, (args: Stringable[]) => `The transaction has been closed with error(s): \n${args[0]}.`)

import {QueryManagerImpl} from "../query/QueryManagerImpl";
import {BidirectionalStream} from "../stream/BidirectionalStream";
import {TypeDBSessionImpl} from "./TypeDBSessionImpl";
import assert = require("assert");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've never used assert before in Client NodeJS's production code.

Unlike in Java, there is no clean way to turn off assertions in NodeJS. (Methods typically used include hot module replacement - literally stripping the statements out of the source code before running it - and modifying the assert prototype to replace assert with a no-op function at runtime!)

I think we're better off replacing it with throw new TypeDBClientError. We've done this elsewhere (see TypeDBClientImpl.session which throws a Client NodeJS-only error if the session ID already exists).

package.json Outdated Show resolved Hide resolved
}
}
else if (element.isDone() && !this._error) throw new TypeDBClientError(TRANSACTION_CLOSED);
else if (element.isDone() && this._error) throw new TypeDBClientError(TRANSACTION_CLOSED_WITH_ERRORS.message(this._error));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember this came up in conversation: did we agree to leave the error there even after reading it for the first time? (seems like the right thing to do, just confirming intent)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep we did!


BeforeAll(async () => {
setClient(await TypeDB.clusterClient([TypeDB.DEFAULT_ADDRESS], new TypeDBCredential("admin", "password", process.env.ROOT_CA)));
});

Before(async () => {
before();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're emulating inheritance here. The Cucumber structure doesn't allow for actual inheritance because it's based on top-level functions, not class functions.

So it might be clearer to call these methods beforeBase and afterBase. It makes it more obvious that they are only a partial implementation of Before - the equivalent of calling super() in Java (or JS).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can do!

load("@vaticle_dependencies//tool/checkstyle:rules.bzl", "checkstyle_test")

exports_files(["Util.ts", "UtilSteps.ts"])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I wonder if it's weird having a Util and a UtilSteps in the same package, given that (almost) every other package in behaviour contains only one .ts file, namely <packageName>Steps.ts.

Might be ok!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eh this is how it is in the other client's i'm keeping it!

@flyingsilverfin flyingsilverfin changed the title Implement and test transaction timeouts Implement and test configurable transaction timeouts Jan 14, 2022
@flyingsilverfin flyingsilverfin modified the milestones: Bug Fixes, Technical Debt, TypeDB Storage Optimisation Jan 14, 2022
@flyingsilverfin flyingsilverfin merged commit d476865 into typedb:master Jan 14, 2022
@flyingsilverfin flyingsilverfin deleted the transaction-timeout branch January 14, 2022 20:41
flyingsilverfin added a commit to typedb/typedb-driver-python that referenced this pull request Jan 20, 2022
## What is the goal of this PR?

We align with Client NodeJS version 2.6.1 (some of the work in typedb/typedb-driver-nodejs#197), which implements a better error propagation mechanism: when an exception occurs, we store it against all the transaction's active transmit queues to retrieve whenever the user tries to perform an operation in the transaction anywhere.

## What are the changes implemented in this PR?

* store errors received from gRPC against each receive queue
* return a new exception type, transaction is closed with errors, which throws the errors from all queues (note that this can be duplicate if there are multiple open transmit queues that have been given the same error)
* we clean up queues that are no longer needed, so we minimise the number of times the user sees the same exception
flyingsilverfin added a commit to typedb/typedb-driver that referenced this pull request Jan 21, 2022
## What is the goal of this PR?

We align with Client NodeJS version 2.6.1 (mostly the work in typedb/typedb-driver-nodejs#197), which implements a better error propagation mechanism: when an exception occurs, we store it against all the transaction's active transmit queues to retrieve whenever the user tries to perform an operation in the transaction anywhere, ensuring the user always sees the originating error.

## What are the changes implemented in this PR?

* store errors received from gRPC against each receive queue
* return a new exception type, `transaction is closed with errors`, which throws the errors from all queues (note that this can be duplicate if there are multiple open transmit queues that have been given the same error)
* we clean up queues that are no longer needed, so we minimise the number of times the user sees the same exception
* update terminology around `ResponseCollector`, calling internal collectors `response_queues` instead of `collectors` (a collector made of collectors is a bit strange)
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants