apache/seatunnel · warning

unable to fail/rollback transaction, xid={}: {}

Error message

unable to fail/rollback transaction, xid={}: {}

What it means

A WARN log in XaGroupOpsImpl.failAndRollback() when rolling back a prepared transaction fails with a non-transient exception. Each xid is ended and rolled back (abort path); transient failures are logged at INFO and kept for retry, but generic Exceptions are logged at WARN and the transaction is marked failed. This typically happens during sink abort or checkpoint decline cleanup.

Source

Thrown at seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/xa/XaGroupOpsImpl.java:109

    @Override
    public GroupXaOperationResult<XidInfo> failAndRollback(Collection<XidInfo> xids) {
        GroupXaOperationResult<XidInfo> result = new GroupXaOperationResult<>();
        if (xids.isEmpty()) {
            return result;
        }
        if (LOG.isDebugEnabled()) {
            LOG.debug("rolling back {} transactions: {}", xids.size(), xids);
        }
        for (XidInfo x : xids) {
            try {
                xaFacade.failAndRollback(x.getXid());
                result.succeeded(x);
            } catch (XaFacade.TransientXaException e) {
                LOG.info("unable to fail/rollback transaction, xid={}: {}", x, e.getMessage());
                result.failedTransiently(x, e);
            } catch (Exception e) {
                LOG.warn("unable to fail/rollback transaction, xid={}: {}", x, e.getMessage());
                result.failed(x, e);
            }
        }
        if (!result.getForRetry().isEmpty()) {
            LOG.info("failed to roll back {} transactions", result.getForRetry().size());
        }
        return result;
    }

    @Override
    public void recoverAndRollback(
            JobContext context,
            SinkWriter.Context sinkContext,
            XidGenerator xidGenerator,
            Xid excludeXid) {
        Collection<Xid> recovered =
                xaFacade.recover().stream()
                        .map(

View on GitHub (pinned to cf67b549a7)

Solutions

  1. Inspect the exception cause per xid in the logs; XAER_NOTA means it was already rolled back/committed — safe to ignore
  2. Restore DB connectivity and let recovery roll back remaining prepared transactions (they will be found via xa_recover)
  3. Check for another process concurrently recovering the same xids
  4. If rollback failures are persistent, manually resolve in-doubt transactions in the DB
Defensive patterns

Strategy: try-catch

Validate before calling

// Before abort cleanup, confirm DB reachability to reduce non-transient failures
try (Connection c = dataSource.getConnection()) { c.isValid(3); }

Try / catch

try {
    xaFacade.failAndRollback(xid);
} catch (XaFacade.TransientXaException e) {
    LOG.info("transient, kept for retry: {}", xid);
} catch (Exception e) {
    LOG.warn("unable to fail/rollback transaction, xid={}: {}", xid, e.getMessage());
}

Prevention

When it happens

Trigger: For each buffered transaction, xaFacade.failAndRollback(x.getXid()) throws a non-XaFacade.TransientXaException Exception (e.g. connection dead, XA protocol error); the code logs 'unable to fail/rollback transaction, xid={}' and records result.failed(x, e).

Common situations: Aborting a checkpoint while the DB connection is broken; XAER_NOTA because the transaction was already resolved; driver errors during end(fail)/rollback.

Understand the failure class

Background: "query failed", "%w: SQL error" — wrapped database query errors in Go libraries explained — this error's family across 3 libraries.

Related errors


AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10). Data as JSON: /api/errors/ef0d645508885045. Report an issue: GitHub.