[#1011] Wait out the connection limit an account is given of its own - #1018
Open
vharseko wants to merge 1 commit into
Open
[#1011] Wait out the connection limit an account is given of its own#1018vharseko wants to merge 1 commit into
vharseko wants to merge 1 commit into
Conversation
…is given of its own The gate deciding whether a refused connect is waited out or reported knew the limits of the server alone, so a grant's MAX_USER_CONNECTIONS (mysql 1226) and a profile's SESSIONS_PER_USER (ORA-02391) failed every borrower at once instead of waiting for a connection of the pool to come back. Both join the codes they belong with, along with ORA-00018; 1226 is reported at once where its message names a resource granted per hour, which no wait clears. mysql 1129 is left out on purpose: a blocked host stays blocked until the host cache is flushed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1011.
The defect
CachedConnection.isWorthRetrying(SQLException, ConnectDialect)is the one gate that decides whether a connect the database refused is waited out or reported: the borrow loop of the pool asks it, and so does the catalog connect ofJDBCStorage. It answers yes for SQLState53300/57P03and for the vendor codes the dialect lists — and those codes are the limits of the server. The limit an account is given of its own falls through them, so a database momentarily out of connections for this account is indistinguishable from a password it will never accept: the borrow fails at once instead of waiting its deadline out, and a borrow is not somethingJDBCStorage.write()replays — it is taken outside the loop on purpose, so the operation fails with it.Measured against the images the suites use, both refusals arrive with nothing but the vendor code to go on:
GRANT … MAX_USER_CONNECTIONS 1, second connectSQLSyntaxErrorException/42000/ 1226GRANT … MAX_CONNECTIONS_PER_HOUR 1, second connectSQLSyntaxErrorException/42000/ 1226SESSIONS_PER_USER 1, second connectSQLException/61000/ 2391max_user_connections=1(already retried)SQLSyntaxErrorException/42000/ 1203What changed
MAX_USER_CONNECTIONSof a grant — the connections this account may hold at once, which a connection of this pool going back clears, exactly as the limit of the server does — and the resources granted per hour, which only the top of the hour clears. Waiting one of those out would cost every borrow the wholepool.timeout, a worker thread parked in each, for as long as the hour lasts. They are told apart by the resource the server names in the message: it is filled in as a literal of its own rather than translated with the rest of the text. A message that names none — a proxy that rewrote it, a driver that kept the code and dropped the text — is taken for the concurrent limit: that is the one an account is given in practice, and the wait it costs is bounded by the deadline of the borrow, while reading it as permanent fails an operation a connection of ours would have served. That isaccountLimitCodes, asked of the message of the same link the code came from.SESSIONS_PER_USERof the account's profile, the per-account sibling of the processes of the instance; ORA-00018 is the sessions of the instance, the global sibling that was missing beside it. All three are cleared by a session ending.ConnectDialect.isWorthRetryingtakes theSQLExceptionrather than its code alone, since one of these verdicts now reads the message beside it.Not in this change
CONNECTION LIMITare both reported as53300, which this gate already waits out.jdbc:mariadb:is a dialect this class does not know at all, so such a url has no connect bound and no read bound either — whichreportUnknownDialectwarns about since [#872] Bound the connect of the JDBC pool and report a connect it cannot make #876. Giving it a dialect is its own change, and the driver is not one this server ships.JDBCStorage.scopeOfreads a stamp connect refused with 1226 or 1203 asFailureScope.TREE, so the table of that tree is remembered as unstampable for the life of the backend where a momentary limit was met. Same root cause — mysql puts these in the syntax error class — different predicate and a different consequence (the comment is a diagnostic aid), so it belongs in an issue of its own rather than in this change.Verification
mysql:9.2with mysql-connector-j 9.2 andgvenzl/oracle-free:23.26.2-slim-faststartwith ojdbc8 - by granting an accountMAX_USER_CONNECTIONS 1, thenMAX_CONNECTIONS_PER_HOUR 1, then a profile withSESSIONS_PER_USER 1, and opening one connection more than each allows. ORA-00018 is the one code here that is read and not measured: an instance cannot be brought below its own session floor to provoke it - oracle recomputessessionsupwards fromprocesses- which is the footing ORA-00020 and 12516-12520 stand on already.CachedConnectionTestCase- six new tests, watched failing first: the per-account limit of mysql, the same code naming an hourly resource, the same code naming no resource at all, a host the server blocked, and the two oracle session limits. Four of them fail on master; the two that must stay "reported at once" - the hourly resource and the blocked host - pass there too and are what pins this change to the width it has.MySqlTestCase.testAPerAccountConnectionLimitIsWorthRetrying- new, and watched failing against master's classification with the connection the server actually refused:expected [true] but found [false]on the verdict, the message readingUser 'limited1011' has exceeded the 'max_user_connections' resource (current value: 1). It neededsequential = trueon the class, whichPgSqlTestCaseandOracleTestCasecarry already:TestListenerasks that of the class a running test is declared by, so a suite made of inherited tests alone only meets the rule when it declares one of its own.CachedConnectionTestCase105,MySqlTestCase79,PgSqlTestCase79,JDBCStorageRetryTest67,JDBCStatementBoundTestCase44,jdbc.EncryptedTestCase35,cassandra.EncryptedTestCase35,CatalogConnectionTestCase13,StampConnectionTestCase5.OracleTestCaseandMsSqlTestCaseare not among them - neither comes up on this machine - so the oracle codes are covered by the unit tests and the measurement above, and left to CI beyond that. The oracle suite could not carry a test of its own either way: it connects as the application account, which may create neither a profile nor a user.