Files
james.maesandClaude Sonnet 5.5 024cfc21a9
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 1e25bb5a243dfdcd95d4b9a4f780200ff8c8778a] (pull_request) Successful in 12s
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 9s
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 6s
maven / build (push) Successful in 11m4s
build / maven (push) Successful in 11m4s
release: prepare Collaboration 1.0 (Core RC.22 BOM, Redis RC.1, current status docs)
Replace the default-active Core snapshot profile with the released Core
4.1.0-RC.22 BOM, pin qbit-redis to its first release candidate, and replace
the snapshot-era status text with the current 1.0 status.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 17:25:50 -06:00

2.6 KiB

Core dependency: after-rollback callbacks

Collaboration's retained-upload and cleanup paths compensate external object storage only after a transaction's rollback is confirmed. They need two methods on com.kingsrook.qqq.backend.core.actions.QBackendTransaction. These are included in QQQ Core 4.1.0-RC.22, which this module imports through the BOM; Core 4.1.0-RC.21 does not declare them.

Required API (reviewed implementation: b6fb2382, third-party-src/qqq-core)

qqq-backend-core, class QBackendTransaction:

public boolean supportsRollbackCallbacks()                  // default: false
public void addAfterRollbackCallback(Runnable callback)      // throws UnsupportedOperationException unless supportsRollbackCallbacks(); Objects.requireNonNull(callback)
protected void runAfterRollbackCallbacks()                   // run once, in order, then clear; exceptions (incl. LinkageError) logged and swallowed

Pending rollback callbacks are preserved when a required operation marks the transaction rollback-only, and run only after confirmed JDBC rollback. A failed physical commit clears them because its outcome is uncertain. Successful commit discards them.

qqq-backend-module-rdbms, class RDBMSTransaction:

@Override public boolean supportsRollbackCallbacks()                 // true
@Override public void addAfterRollbackCallback(Runnable callback)    // super.add...; then rolledBackSinceCommit = false
// rollback()/close(): after connection.rollback() succeeds -> rolledBackSinceCommit = true; runAfterRollbackCallbacks()
// close() rolls back (and so runs callbacks) unless a rollback already happened since the last commit / callback registration

Callers in this module

  • attachments/AttachmentsService.storeUpload: refuses an ambient transaction where !supportsRollbackCallbacks(); registers storage compensation.
  • attachments/AttachmentCleanupQueue.beginUpload: registers the same cleanup on both addAfterRollbackCallback and addAfterCommitCallback.

Core tests to carry with the change

  • Base class: supportsRollbackCallbacks() is false; addAfterRollbackCallback throws UnsupportedOperationException.
  • RDBMS: callback runs after rollback(); does not run after commit(); runs when a transaction with a registered callback is closed without commit; a callback registered after an earlier rollback still runs on close; a throwing callback does not stop later callbacks.
  • Integration (already in this module, needs the core): attachments/AttachmentTransactionsPostgresTest (needs Docker) incl. interruptedUploadSurvivesLostRollbackCallback.