{"record":{"id":"33a7164f8b3e2db0","repo":"apache/seatunnel","slug":"unable-to-rollback-prepared-transaction-xid","errorCode":null,"errorMessage":"unable to rollback prepared transaction, xid={}","messagePattern":"unable to rollback prepared transaction, xid=(.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/JdbcExactlyOnceSinkWriter.java","lineNumber":259,"sourceCode":"        long candidate = txIdHint;\n        if (candidate <= lastGeneratedTxId) {\n            checkState(lastGeneratedTxId != Long.MAX_VALUE, \"tx id exhausted\");\n            candidate = lastGeneratedTxId + 1;\n        }\n        lastGeneratedTxId = candidate;\n        return candidate;\n    }\n\n    private void rollbackPrepareXidQuietly() {\n        if (prepareXid == null || !xaFacade.isOpen()) {\n            return;\n        }\n        Xid xid = prepareXid;\n        try {\n            LOG.debug(\"rollback prepared transaction, xid={}\", xid);\n            xaFacade.rollback(xid);\n        } catch (Exception e) {\n            LOG.warn(\"unable to rollback prepared transaction, xid={}\", xid, e);\n        } finally {\n            prepareXid = null;\n        }\n    }\n\n    private void rollbackPrepareXidOrThrow(Exception beginTxException) {\n        if (prepareXid == null) {\n            return;\n        }\n        Xid xid = prepareXid;\n        if (!xaFacade.isOpen()) {\n            throw new JdbcConnectorException(\n                    JdbcConnectorErrorCode.XA_OPERATION_FAILED,\n                    String.format(\n                            \"unable to rollback prepared transaction because xaFacade is closed, xid=%s\",\n                            xid),\n                    beginTxException);\n        }","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/apache/seatunnel/blob/cf67b549a7a6c35fa0beb12d83c62892427ea919/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/JdbcExactlyOnceSinkWriter.java#L241-L277","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect the attached exception 'e' in the log line to identify the XA error code (e.g. XAER_RMFAIL vs XAER_NOTA)","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","Check DB connectivity and reconnect before retrying abort","Manually resolve in-doubt transactions if recovery cannot (e.g. xa recover; xa rollback <xid> in MySQL/Oracle)","Keep xid generation parameters (subtask index, job id) stable across restarts so recovery can find the prepared xids"],"exampleFix":"// before: swallowing without cleanup of in-doubt tx\n} catch (Exception e) {\n    LOG.warn(\"unable to rollback prepared transaction, xid={}\", xid, e);\n}\n// after: schedule recovery instead of silently dropping\n} catch (Exception e) {\n    LOG.warn(\"unable to rollback prepared transaction, xid={}\", xid, e);\n    xaGroupOps.recoverAndRollback(context, sinkcontext, xidGenerator, null);\n}","handlingStrategy":"retry","validationCode":"// before abort, check the facade/connection is still usable\nif (xaFacade != null) {\n    // verify DB reachable so rollback has a chance\n    java.sql.Connection probe = dataSource.getConnection();\n    probe.isValid(5);\n    probe.close();\n}","typeGuard":null,"tryCatchPattern":"try {\n    xaFacade.rollback(xid);\n} catch (XAException e) {\n    // XAER_NOTA (code -4): already rolled back/unknown — safe to ignore;\n    // otherwise ensure the job restarts so XA recovery sweeps the in-doubt xid\n    LOG.warn(\"rollback of {} failed, code={}\", xid, e.errorCode, e);\n}","preventionTips":["Enable keepalive/short socketTimeout so dead DB connections are detected early","Never change xidGenerator or job/subtask identity config between restarts, or recovery cannot find prepared xids","Grant the sink DB user recovery privileges so startup recovery can clean in-doubt transactions","Monitor the database for in-doubt XA transactions after any abort"],"tags":["jdbc","xa","distributed-transaction","rollback-failure"],"backgroundTag":"xa-rollback-failed","analyzedSha":"cf67b549a7a6c35fa0beb12d83c62892427ea919","analyzedAt":"2026-09-10T21:44:55.265Z","contentChangedAt":"2026-09-10T21:44:55.265Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}