apache/seatunnel · warning

unable to rollback prepared transaction, xid={}

Error message

unable to rollback prepared transaction, xid={}

What it means

JdbcExactlyOnceSinkWriter.rollbackPrepareXidQuietly attempts to roll back a previously prepared XA transaction (prepareXid) and, because this path is best-effort during abort, it only logs a WARN when the XA rollback fails instead of throwing. The prepared transaction may then remain in-doubt on the database until XA recovery resolves it. The actual underlying exception is attached to the log entry.

Source

Thrown at seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/JdbcExactlyOnceSinkWriter.java:259

        long candidate = txIdHint;
        if (candidate <= lastGeneratedTxId) {
            checkState(lastGeneratedTxId != Long.MAX_VALUE, "tx id exhausted");
            candidate = lastGeneratedTxId + 1;
        }
        lastGeneratedTxId = candidate;
        return candidate;
    }

    private void rollbackPrepareXidQuietly() {
        if (prepareXid == null || !xaFacade.isOpen()) {
            return;
        }
        Xid xid = prepareXid;
        try {
            LOG.debug("rollback prepared transaction, xid={}", xid);
            xaFacade.rollback(xid);
        } catch (Exception e) {
            LOG.warn("unable to rollback prepared transaction, xid={}", xid, e);
        } finally {
            prepareXid = null;
        }
    }

    private void rollbackPrepareXidOrThrow(Exception beginTxException) {
        if (prepareXid == null) {
            return;
        }
        Xid xid = prepareXid;
        if (!xaFacade.isOpen()) {
            throw new JdbcConnectorException(
                    JdbcConnectorErrorCode.XA_OPERATION_FAILED,
                    String.format(
                            "unable to rollback prepared transaction because xaFacade is closed, xid=%s",
                            xid),
                    beginTxException);
        }

View on GitHub (pinned to cf67b549a7)

Solutions

  1. Inspect the attached exception 'e' in the log line to identify the XA error code (e.g. XAER_RMFAIL vs XAER_NOTA)
  2. Rely on XA recovery: on restart the sink recovers pending transactions via xaGroupOps.recoverAndRollback, so ensure the DB user has XA_RECOVER_ADMIN / SELECT on xa_recover and the xidGenerator config is unchanged
  3. Check DB connectivity and reconnect before retrying abort
  4. Manually resolve in-doubt transactions if recovery cannot (e.g. xa recover; xa rollback <xid> in MySQL/Oracle)
  5. Keep xid generation parameters (subtask index, job id) stable across restarts so recovery can find the prepared xids

Example fix

// before: swallowing without cleanup of in-doubt tx
} catch (Exception e) {
    LOG.warn("unable to rollback prepared transaction, xid={}", xid, e);
}
// after: schedule recovery instead of silently dropping
} catch (Exception e) {
    LOG.warn("unable to rollback prepared transaction, xid={}", xid, e);
    xaGroupOps.recoverAndRollback(context, sinkcontext, xidGenerator, null);
}
Defensive patterns

Strategy: retry

Validate before calling

// before abort, check the facade/connection is still usable
if (xaFacade != null) {
    // verify DB reachable so rollback has a chance
    java.sql.Connection probe = dataSource.getConnection();
    probe.isValid(5);
    probe.close();
}

Try / catch

try {
    xaFacade.rollback(xid);
} catch (XAException e) {
    // XAER_NOTA (code -4): already rolled back/unknown — safe to ignore;
    // otherwise ensure the job restarts so XA recovery sweeps the in-doubt xid
    LOG.warn("rollback of {} failed, code={}", xid, e.errorCode, e);
}

Prevention

When it happens

Trigger: abortPrepare -> rollbackPrepareXidQuietly when xaFacade.rollback(xid) throws — e.g. connection to the database lost after prepare, the resource manager no longer knows the xid, XAER_RMFAIL, or the facade/connection is already closed.

Common situations: Task failure/abort during checkpoint after xa prepare; database restart between prepare and rollback; network partition to the DB; DBA killed the in-doubt transaction; connection pool reclaimed the XA connection.

Related errors


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