fix(db): validate connection before use to recover stale sessions - #695
Merged
Conversation
Database holds a single long-lived JDBC Connection reused for every query.
When the server (or a proxy such as MaxScale) closes the idle session, the
driver does not know it is dead, so the next query throws and the player is
kicked before reaching the server list ("A database error has occured").
BC_AUTO_RECONNECT's &autoReconnect=true URL param does not provide recovery:
MariaDB Connector/J removed autoReconnect in 3.0.0 [1]; PostgreSQL JDBC never
had it [2]; MySQL Connector/J still defines it [3] but its own property
description states it is "not recommended" due to session-state and
data-consistency side effects [4]. Validate the connection with
Connection.isValid() (a JDBC 4.0 protocol ping [1, 5]) before use and reopen on
failure, gated by the existing autoReconnect option so it remains the single
switch for recovery across all drivers.
[1] https://github.com/mariadb-corporation/mariadb-docs/blob/main/connectors/mariadb-connector-j/about-mariadb-connector-j.md
[2] https://jdbc.postgresql.org/documentation/use/
[3] https://github.com/mysql/mysql-connector-j/blob/trunk/src/main/core-api/java/com/mysql/cj/conf/PropertyKey.java
[4] https://github.com/mysql/mysql-connector-j/blob/trunk/src/main/resources/com/mysql/cj/LocalizedErrorMessages.properties
[5] https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html#isValid-int-
PaulW
force-pushed
the
fix/mariadb-stale-connection
branch
from
September 1, 2026 16:19
ad7cce0 to
68b4462
Compare
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.
Summary
Databaseholds a single long-lived JDBCConnectionreused for every query.When the server (or a proxy such as MaxScale) closes the idle session, the
driver does not know the connection is dead, so the next query throws and the
player is kicked before they reach the server list. This validates the
connection before use and reopens it if it is dead.
Problem
After the process has been idle for a while (~10 minutes, matching a typical
wait_timeout/ proxy idle timeout), the first player to connect is kickedand the log shows:
Repeated attempts keep failing, then one eventually succeeds and the service
works until the next idle period.
Root cause
Database.openConnection()opens oneConnectionat startup; every queryin
DataUtilreuses that same connection. There is no pool, so a stalesession is neither detected nor replaced.
BC_AUTO_RECONNECT=trueappends&autoReconnect=trueto the JDBC URL, butthat option is a no-op on recent drivers:
autoReconnect— it is listed under"Removed options" (since 1.1.7, removed in 3.0.0) and silently ignored
[1].
autoReconnectconnection parameter at all, soit is silently ignored [2].
autoReconnect(andautoReconnectForPools) in the current driver source [3];its own property description states "The use of this feature is not
recommended, because it has side effects related to session state and data
consistency" [4].
BedrockConnect.javaonly reopens whenconnection.isClosed()is alreadytrue— which the driver does not reportfor a server-closed session — so it cannot reliably recover from the stale
session.
The fix
Database.getConnection()validates the connection withConnection.isValid()— a standard JDBC 4.0 liveness check (a protocol ping) — and reopens it on
failure:
isValid(2)is the lightweight ping recommended by the MariaDB Connector/Jdocs ("Connection.isValid() is doing a ping") [1]. It is
standard JDBC 4.0 (Since 1.6) [5], so it works for the
mysql,mariadbandpostgresbranches.synchronizedserialises the validate-and-reopen so two query threads cannotrace a reconnect and clobber the shared
connection.autoReconnectso the existingBC_AUTO_RECONNECToptionremains the single switch for connection recovery across all drivers:
true— validate + reopen (for MariaDB/PostgreSQL this is the recovery,since their
autoReconnectURL param does nothing; for MySQL it supplementsthe driver's own
autoReconnect, which its docs advise against relying on[4]).
false— return the raw connection (original behaviour), so an operator candeliberately surface stale-session failures rather than mask them.
DataUtil) and the keep-alive timer, whichalso calls
getConnection().Why this approach
A driver-level
autoReconnectis not a reliable solution: it is removed inMariaDB Connector/J 3.x [1], non-existent in PostgreSQL JDBC
[2], and MySQL Connector/J's documentation advises against
relying on it [4]. The recommended pattern across all three
drivers is to validate connections before use (which a connection pool does
internally via
isValid()). This change applies that same validation to theexisting single-connection model, gated by the existing
BC_AUTO_RECONNECToption, with no new dependencies.
Testing
Deployed behind a MaxScale proxy whose service
wait_timeoutis 540s (9 min).After the fix, a player connecting after a 20-minute idle period connects
cleanly with no
A database error has occured. Behaviour forBC_AUTO_RECONNECT=true(the default) is unchanged recovery;falsereturnsthe original raw-connection behaviour.
References
autoReconnect,since 1.1.7, removed in 3.0.0) and "How to Do a Lightweight Ping"
(
Connection.isValid()is a protocol ping):https://github.com/mariadb-corporation/mariadb-docs/blob/main/connectors/mariadb-connector-j/about-mariadb-connector-j.md
autoReconnectparameter exists):https://jdbc.postgresql.org/documentation/use/
PropertyKey—autoReconnectandautoReconnectForPoolsare still defined in the current driver source:https://github.com/mysql/mysql-connector-j/blob/trunk/src/main/core-api/java/com/mysql/cj/conf/PropertyKey.java
LocalizedErrorMessages.properties— theautoReconnectproperty description: "The use of this feature is not recommended, because it
has side effects related to session state and data consistency when
applications don't handle SQLExceptions properly":
https://github.com/mysql/mysql-connector-j/blob/trunk/src/main/resources/com/mysql/cj/LocalizedErrorMessages.properties
java.sql.Connection.isValid(int)javadoc (Oracle Java SE 8) — "Thedriver shall submit a query on the connection or use some other mechanism
that positively verifies the connection is still valid" (Since 1.6 / JDBC
4.0):
https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html#isValid-int-