diff --git a/src/main/java/com/samskivert/depot/impl/jdbc/MySQLLiaison.java b/src/main/java/com/samskivert/depot/impl/jdbc/MySQLLiaison.java index 4b11fb8..f30b2b8 100644 --- a/src/main/java/com/samskivert/depot/impl/jdbc/MySQLLiaison.java +++ b/src/main/java/com/samskivert/depot/impl/jdbc/MySQLLiaison.java @@ -29,10 +29,29 @@ public class MySQLLiaison extends BaseLiaison @Override // from DatabaseLiaison public boolean isTransientException (SQLException sqe) { + // SQL state class "08" means connection exception per the SQL standard; this covers + // lost connections, timeouts, broken pipes, etc. without needing to match message text + String sqlState = sqe.getSQLState(); + if (sqlState != null && sqlState.startsWith("08")) { + return true; + } + + // also check the causal chain for connection exceptions (MySQL Connector/J's + // CommunicationsException is a SQLNonTransientConnectionException which has state "08") + Throwable cause = sqe.getCause(); + if (cause instanceof SQLException) { + String causeState = ((SQLException)cause).getSQLState(); + if (causeState != null && causeState.startsWith("08")) { + return true; + } + } + + // fall back to message matching for older drivers or unexpected exception formats String msg = sqe.getMessage(); return (msg != null && (msg.indexOf("Lost connection") != -1 || msg.indexOf("link failure") != -1 || msg.indexOf("Broken pipe") != -1 || + msg.indexOf("disconnected by the server") != -1 || msg.indexOf("The last packet successfully received") != -1)); } diff --git a/src/main/java/com/samskivert/depot/impl/jdbc/PostgreSQLLiaison.java b/src/main/java/com/samskivert/depot/impl/jdbc/PostgreSQLLiaison.java index 716d5b7..332602d 100644 --- a/src/main/java/com/samskivert/depot/impl/jdbc/PostgreSQLLiaison.java +++ b/src/main/java/com/samskivert/depot/impl/jdbc/PostgreSQLLiaison.java @@ -29,10 +29,44 @@ public class PostgreSQLLiaison extends BaseLiaison // from DatabaseLiaison public boolean isTransientException (SQLException sqe) { - // TODO: Add more error messages here as we encounter them. + if (isTransientState(sqe.getSQLState())) { + return true; + } + // also check the causal chain for connection exceptions + Throwable cause = sqe.getCause(); + if (cause instanceof SQLException && isTransientState(((SQLException)cause).getSQLState())) { + return true; + } + + // fall back to message matching for older drivers or unexpected exception formats String msg = sqe.getMessage(); return (msg != null && - msg.indexOf("An I/O error occured while sending to the backend") != -1); + (msg.contains("An I/O error") || + msg.contains("Connection reset") || + msg.contains("Connection refused") || + msg.contains("Connection timed out") || + msg.contains("Broken pipe") || + msg.contains("unexpected EOF on client connection") || + msg.contains("terminating connection due to administrator command") || + msg.contains("This connection has been closed") || + msg.contains("No more data to read from socket"))); + } + + private static boolean isTransientState (String sqlState) { + if (sqlState == null) return false; + // SQL state class "08" means connection exception (08000 connection_exception, + // 08001 sqlclient_unable_to_establish_sqlconnection, 08003 connection_does_not_exist, + // 08006 connection_failure, 08P01 protocol_violation) + if (sqlState.startsWith("08")) return true; + // Class "57P" covers operator intervention: 57P01 admin_shutdown, + // 57P02 crash_shutdown, 57P03 cannot_connect_now + if (sqlState.startsWith("57P")) return true; + // Class "53" covers insufficient resources: 53000 insufficient_resources, + // 53100 disk_full, 53200 out_of_memory, 53300 too_many_connections + if (sqlState.startsWith("53")) return true; + // 40001 serialization_failure, 40P01 deadlock_detected + if ("40001".equals(sqlState) || "40P01".equals(sqlState)) return true; + return false; } @Override