-
Notifications
You must be signed in to change notification settings - Fork 38.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SQLErrorCodesFactory.getErrorCodes(DataSource) returns empty error codes if access to transactional connection fails #25681
Comments
Good point, while an empty
That said, I nevertheless wonder why a transaction-bound Connection would be prematurely closed when exception translation is being attempted. Was there a specific code path involved that possibly translates exceptions at a bad time? Or was it a transaction timeout maybe? |
Thank you for prioritizing this! Yes, very precise timing is required to exploit this race condition. This occurred in a high-traffic application server where there are plenty of opportunities to do just that due to the continuous stream of transactions. I've included part of the actual stack trace from production logs. This can actually occur quite routinely. In our cloud deployment landscape, application servers talk to database servers across availability zones and network hiccups do occur. Additionally, many database servers terminate pre-existing sessions during the momentary application of a configuration or policy change.
|
This will be available in the upcoming |
I'll treat the specific issue with the transactional connection access failing as a bug, backporting it to 5.1.x, 5.0.x and 4.3.x as well. 5.1.18 will get both mechanisms, just without any public surface area added (i.e. |
thanks |
The
SQLErrorCodes getErrorCodes(DataSource dataSource)
method of theorg.springframework.jdbc.support.SQLErrorCodesFactory
class is responsible for producing aSQLErrorCodes
instance with a repository of vendor-specificSQLException
error codes that are neatly categorized for Spring JDBC exception mapping.For example,
duplicateKeyCodes
contains codes that will be mapped toDuplicateKeyException
.This is a great convenience for applications since they can work with a comprehensive and intuitive hierarchy of exceptions rather than vendor-specific error codes.
Unfortunately,
getErrorCodes
has fallback logic that can have dramatic and damaging side effects.If a
SQLErrorCodes
instance is not yet cached for a particularDataSource
thengetErrorCodes
creates one. It does so by first obtaining the "database product name" for theDataSource
. Next it looks in a repository of error codes, keyed by database product name, for the targetSQLErrorCodes
instance. Lastly, that result is added to the cache byDataSource
.Unfortunately, obtaining the database product name is an operation on a JDBC connection and has many failure modes. For example, the thread-bound JDBC connection might have been closed for some reason.
Bizarrely,
getErrorCodes
responds to such exceptions by simply producing an emptySQLErrorCodes
with no mapping at all. That's the best it can do if it must return anSQLErrorCodes
instance since it doesn't know the database engine. Unfortunately, this changes the expected behavior of upstream components likeJdbcTemplate
, which caches theSQLErrorCodes
in anSQLErrorCodeSQLExceptionTranslator
. In other words, the exception corrupts theJdbcTemplate
exception translator and the conveniences mentioned above go out the window!Consider the following code snippet in a long-running application:
Suppose a
SQLException
occurs due to a unique constraint violation with MySQL vendor code1062
. The execution path above executes, resulting in aSQLErrorCodes
instance that has code1062
in theduplicateKeyCodes
bucket. Spring JDBC translates that to aDuplicateKeyException
and all is well!However, suppose instead that the error path above occurs resulting in an empty
SQLErrorCodes
. Now code1062
isn't represented at all and the exception translator has to employ fallback logic that results in aDataIntegrityViolationException
. The exception is not handled by the catch block and mayhem ensues.This unpredictable behavior strikes me as extremely dangerous behavior. In my case, it surfed in the production environment and had quite a noticeable impact.
Fortunately, a quick workaround is available by simply setting the exception translator and database product name up front. However, I didn't realize that until it was too late.
The issue can be trivially reproduced using a debug session in which the connection is closed. See attached screenshots.
The text was updated successfully, but these errors were encountered: