Skip to content

pass the graphql result 'around' unexpected rollbacks#168

Merged
barneyb merged 1 commit intomasterfrom
more-auth
Oct 25, 2025
Merged

pass the graphql result 'around' unexpected rollbacks#168
barneyb merged 1 commit intomasterfrom
more-auth

Conversation

@barneyb
Copy link
Member

@barneyb barneyb commented Oct 23, 2025

When Spring "unexpectedly" rolls back a transaction after a non-exceptional GraphQL execution completes, log and pass through the GraphQL result. This happens when something (e.g., a missing principal) causes an exception that rolls back the transaction, but which is then resolved into a data fetching error (e.g., "Unauthorized"). Since GraphQL doesn't use HTTP semantics, need to ensure Spring Web doesn't either.

@barneyb barneyb self-assigned this Oct 23, 2025
}
// Otherwise the execution was successful (almost certainly
// with errors), so log and return the result to the client.
log.warn("Unexpected rollback", ure);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could this leave the data in a bad state? is the expectation that the client would retry?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. The trivially reproducible case is querying for some field that requires authentication without a valid token. See the ExecutionResult below.

Under the covers, a principalAccess.getUser() call in the library.recipes field resolver threw a NoUserPrincipalException, which was caught and translated into a data fetching exception (by Spring), which gets added to the graphql errors. Then the part I don't fully understand occurs, where Spring exits the transaction, notices that it was rolled back when it didn't think it should have been, and throws.

From Spring's AbstractPlatformTransactionManager:

// Throw UnexpectedRollbackException if we have a global rollback-only
// marker but still didn't get a corresponding exception from commit.

From a bit of digging, I think this happens when the transaction is marked as doomed, but an exception doesn't actually propagate all the way out to the transaction boundary (nor raised during commit). There's no exception to justify the rollback, so it's "unexpected".

In short, I believe that this is Spring saying "I rolled back your transaction because of something, but I don't have the exception, so I'll give you this one as a substitute." As such, it's safe to ignore, since it means the exception was resolved into a fetch error and not rethrown, so the result already has the interesting info. Which it does: Unauthorized.

{
  "errors": [
    {
      "message": "Unauthorized",
      "locations": [
        {
          "line": 9,
          "column": 150
        }
      ],
      "path": [
        "library",
        "recipes"
      ],
      "extensions": {
        "classification": "UNAUTHORIZED"
      }
    }
  ],
  "data": null
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this is why your explanations are vastly superior to any other source of information.

@barneyb barneyb merged commit 954f5c7 into master Oct 25, 2025
1 check passed
@barneyb barneyb deleted the more-auth branch October 25, 2025 00:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants