{"record":{"id":"1935d900e8afbd86","repo":"alibaba/druid","slug":"onfatalerror-activecount-onfatalerrormaxactiv","errorCode":null,"errorMessage":"onFatalError, activeCount {}, onFatalErrorMaxActive {}","messagePattern":"onFatalError, activeCount (.+?), onFatalErrorMaxActive (.+?)","errorType":"exception","errorClass":"SQLException","httpStatus":null,"severity":"critical","filePath":"core/src/main/java/com/alibaba/druid/pool/DruidDataSource.java","lineNumber":1650,"sourceCode":"                    StringBuilder errorMsg = new StringBuilder();\n                    errorMsg.append(\"onFatalError, activeCount \")\n                            .append(activeCount)\n                            .append(\", onFatalErrorMaxActive \")\n                            .append(onFatalErrorMaxActive);\n\n                    if (lastFatalErrorTimeMillis > 0) {\n                        errorMsg.append(\", time '\")\n                                .append(StringUtils.formatDateTime19(\n                                        lastFatalErrorTimeMillis, TimeZone.getDefault()))\n                                .append(\"'\");\n                    }\n\n                    if (lastFatalErrorSql != null) {\n                        errorMsg.append(\", sql \\n\")\n                                .append(lastFatalErrorSql);\n                    }\n\n                    throw new SQLException(\n                            errorMsg.toString(), lastFatalError);\n                }\n\n                connectCount++;\n\n                if (createScheduler != null\n                        && poolingCount == 0\n                        && activeCount < maxActive\n                        && createDirectCountUpdater.get(this) == 0\n                        && creatingCountUpdater.get(this) == 0\n                        && createScheduler instanceof ScheduledThreadPoolExecutor) {\n                    ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) createScheduler;\n                    if (executor.getQueue().size() > 0) {\n                        if (maxWait > 0 && System.currentTimeMillis() - startTime >= maxWait) {\n                            holder = null;\n                            break;\n                        }\n                        createDirect = true;","sourceCodeStart":1632,"sourceCodeEnd":1668,"githubUrl":"https://github.com/alibaba/druid/blob/fa8dc9912637a2f729eef9f55356621fec18d40e/core/src/main/java/com/alibaba/druid/pool/DruidDataSource.java#L1632-L1668","documentation":"A circuit-breaker: once the pool has recorded a fatal error (onFatalError flag set, e.g. the DB rejected connections or a connection died from a fatal exception), if activeCount has climbed to onFatalErrorMaxActive the pool refuses new borrows and rethrows the last fatal error. This stops a sick database from being flooded with requests after it already signalled failure.","triggerScenarios":"onFatalError is true (a prior fatal SQLException passed the exceptionSorter, e.g. ORA-... / Communications link failure) AND activeCount >= onFatalErrorMaxActive (>0). The caller in getConnection() hits the guard at line 1627-1629 and the assembled error (activeCount, onFatalErrorMaxActive, time, last SQL) is thrown.","commonSituations":"Database restart or outage under load; network partition between app and DB; DB hitting max_connections so Druid's create attempts fail fatally; onFatalErrorMaxActive set very low (default behaviour) so the breaker trips almost immediately; a bad migration/lock causing fatal errors across many connections.","solutions":["Check the chained lastFatalError — it names the real root cause (e.g. 'Communications link failure'); fix that DB/network issue first.","Inspect lastFatalErrorTimeMillis and lastFatalErrorSql in the message to locate when and on which query the DB broke.","Tune onFatalErrorMaxActive upward if you want more headroom during partial outages, or disable the breaker by setting onFatalErrorMaxActive to 0 (then the guard at line 1628 is skipped).","Add an outer retry/circuit-breaker (e.g. Resilience4j) so callers degrade gracefully while the pool is in fatal state, rather than hammering getConnection()."],"exampleFix":"// disable the fatal-error breaker so callers still get normal maxWait timeouts\nddataSource.setOnFatalErrorMaxActive(0);\n\n// or keep it but let callers fall back\ntry (Connection c = dataSource.getConnection()) { ... }\ncatch (SQLException e) {\n    if (dataSource.isOnFatalError()) {\n        // serve degraded response / trip an upstream circuit breaker\n    }\n}","handlingStrategy":"fallback","validationCode":"DruidDataSource dds = (DruidDataSource) dataSource;\n// isOnFatalError / onFatalError flag plus activeCount vs onFatalErrorMaxActive\nif (dds.isOnFatalError() && dds.getOnFatalErrorMaxActive() > 0\n        && dds.getActiveCount() >= dds.getOnFatalErrorMaxActive()) {\n    throw new ServiceUnavailableException(\"pool in fatal-error breaker state\");\n}\nreturn dataSource.getConnection();","typeGuard":null,"tryCatchPattern":"try {\n    return dataSource.getConnection();\n} catch (SQLException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"onFatalError\")) {\n        // delegate to an outer circuit breaker; degrade the request\n        throw new ServiceUnavailableException(\"DB fatal breaker open\", e);\n    }\n    throw e;\n}","preventionTips":["Wrap the DataSource with an outer circuit breaker (Resilience4j) so callers degrade during fatal states.","Investigate the chained lastFatalError immediately — it is the real DB problem.","Set onFatalErrorMaxActive to 0 only if you intentionally want to disable the breaker."],"tags":["circuit-breaker","fatal-error","database-outage","connection-pool","exception-sorter"],"backgroundTag":null,"analyzedSha":"fa8dc9912637a2f729eef9f55356621fec18d40e","analyzedAt":"2026-08-14T04:55:06.789Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}