From 0d184785a3b6f063a220671001011df8079c9e53 Mon Sep 17 00:00:00 2001 From: Ryan Hendrickson Date: Wed, 17 Jun 2026 02:01:30 -0400 Subject: [PATCH] surfacing transaction errors to clients --- docusaurus-docs/docs/clients/index.md | 29 ++++++++++++++++++ docusaurus-docs/docs/clients/java.md | 38 ++++++++++++++++++++++++ docusaurus-docs/docs/clients/python.md | 31 +++++++++++++++++++ docusaurus-docs/docs/clients/raw-http.md | 22 +++++++++++++- 4 files changed, 119 insertions(+), 1 deletion(-) diff --git a/docusaurus-docs/docs/clients/index.md b/docusaurus-docs/docs/clients/index.md index d024e93d..bb3be994 100644 --- a/docusaurus-docs/docs/clients/index.md +++ b/docusaurus-docs/docs/clients/index.md @@ -52,5 +52,34 @@ transactions conflict when both transactions: When a transaction is aborted, all its changes are discarded. Transactions can be manually aborted. +#### Abort reasons + +A write-write conflict is not the only reason a commit can abort. When Dgraph +aborts a commit it now reports a *category* describing why, so clients can decide +how to react (for example, retry immediately versus back off). The category is +carried as a `": "` prefix on the gRPC `ABORTED` status message +(and on the HTTP error message), where `` is one of: + +- `conflict` — a write-write conflict with another concurrent transaction, as + described above. Retrying the transaction typically succeeds. +- `stale-startts` — the transaction's start timestamp predates the current Zero + leader's lease (for example, after a leader change). Retrying with a fresh + transaction succeeds. +- `predicate-move` — a predicate the transaction wrote is being moved between + groups, so commits on it are temporarily blocked. Retrying once the move + completes succeeds. + +For example, a conflict abort carries the message +`conflict: Transaction has been aborted. Please retry`. + +:::note +Older Dgraph servers do not emit a category prefix. Clients that parse the +reason degrade gracefully and report it as `UNKNOWN` in that case, so existing +error handling keeps working unchanged. +::: + +The official [Java](/clients/java) and [Python](/clients/python) clients parse +this category into a typed enum; see those pages for the API. + ### In this section diff --git a/docusaurus-docs/docs/clients/java.md b/docusaurus-docs/docs/clients/java.md index 730d8db4..3637f4c8 100644 --- a/docusaurus-docs/docs/clients/java.md +++ b/docusaurus-docs/docs/clients/java.md @@ -81,6 +81,44 @@ client.loginIntoNamespace("groot", "password", 123); Once logged in, the client can perform all operations allowed for that user in the specified namespace. +## Handling aborted transactions + +When a commit aborts, the client throws a `TxnConflictException`. In addition to +the full server message, the exception exposes the [abort reason](/clients#abort-reasons) +as a typed `TxnConflictException.AbortReason`, so you can branch on *why* the +commit aborted: + +```java +import io.dgraph.TxnConflictException; +import io.dgraph.TxnConflictException.AbortReason; + +Transaction txn = client.newTransaction(); +try { + // ... mutations ... + txn.commit(); +} catch (TxnConflictException e) { + switch (e.getReason()) { + case CONFLICT: + case STALE_STARTTS: + // Safe to retry with a fresh transaction. + break; + case PREDICATE_MOVE: + // A predicate is being moved between groups; retry after a short backoff. + break; + case UNKNOWN: + // No category reported (e.g. an older server). Fall back to the message. + break; + } + System.err.println("commit aborted: " + e.getMessage()); +} finally { + txn.discard(); +} +``` + +`getReason()` returns `AbortReason.UNKNOWN` when the server reports no category, +so the code above works unchanged against older Dgraph servers. `isRetryable()` +remains available for code that only needs the retry/no-retry distinction. + ## Documentation For complete API documentation, examples, and advanced usage: diff --git a/docusaurus-docs/docs/clients/python.md b/docusaurus-docs/docs/clients/python.md index 44b5fee9..7e16233f 100644 --- a/docusaurus-docs/docs/clients/python.md +++ b/docusaurus-docs/docs/clients/python.md @@ -82,6 +82,37 @@ client.login_into_namespace("groot", "password", "123") Once logged in, the client can perform all operations allowed for that user in the specified namespace. +## Handling aborted transactions + +When a commit aborts, the client raises `pydgraph.AbortedError`. In addition to +the full server message, the error exposes the [abort reason](/clients#abort-reasons) +as a typed `pydgraph.AbortReason`, so you can branch on *why* the commit aborted: + +```python +import pydgraph + +txn = client.txn() +try: + txn.mutate(set_obj={"name": "Alice"}) + txn.commit() +except pydgraph.AbortedError as e: + if e.reason in (pydgraph.AbortReason.CONFLICT, pydgraph.AbortReason.STALE_STARTTS): + # Safe to retry with a fresh transaction. + ... + elif e.reason == pydgraph.AbortReason.PREDICATE_MOVE: + # A predicate is being moved between groups; retry after a short backoff. + ... + else: # pydgraph.AbortReason.UNKNOWN + # No category reported (e.g. an older server). Fall back to the message. + ... + print("commit aborted:", e) +finally: + txn.discard() +``` + +`AbortedError.reason` is `pydgraph.AbortReason.UNKNOWN` when the server reports no +category, so the code above works unchanged against older Dgraph servers. + ## Documentation For complete API documentation, examples, and advanced usage: diff --git a/docusaurus-docs/docs/clients/raw-http.md b/docusaurus-docs/docs/clients/raw-http.md index ecd4f392..8a96fd53 100644 --- a/docusaurus-docs/docs/clients/raw-http.md +++ b/docusaurus-docs/docs/clients/raw-http.md @@ -399,7 +399,7 @@ If another client were to perform another transaction concurrently affecting the "errors": [ { "code": "Error", - "message": "Transaction has been aborted. Please retry." + "message": "conflict: Transaction has been aborted. Please retry." } ] } @@ -407,6 +407,26 @@ If another client were to perform another transaction concurrently affecting the In this case, it should be up to the user of the client to decide if they wish to retry the transaction. +The `message` is prefixed with a `": "` category describing why the +commit aborted, so a client can react appropriately. The `` is one of: + +| Code | Meaning | +| ---------------- | ----------------------------------------------------------------------------------------- | +| `conflict` | Write-write conflict with another concurrent transaction. Retrying typically succeeds. | +| `stale-startts` | The transaction's start timestamp predates the current Zero leader's lease (e.g. a leader change). Retry with a fresh transaction. | +| `predicate-move` | A predicate the transaction wrote is being moved between groups, blocking commits on it. Retry after the move completes. | + +For example, a stale start-timestamp abort returns +`stale-startts: Transaction has been aborted due to a leader change. Please retry`, +and a blocked predicate move returns +`predicate-move: Commits on predicate are blocked due to predicate move`. + +:::note +Older Dgraph servers return the bare detail with no category prefix (e.g. +`Transaction has been aborted. Please retry.`). Clients should treat a missing or +unrecognized prefix as an unknown reason rather than failing to parse. +::: + ### Abort the Transaction To abort a transaction, use the same `/commit` endpoint with the `abort=true` parameter while specifying the `startTs` value for the transaction: