{"record":{"id":"66e6dc0834dd9d33","repo":"alibaba/druid","slug":"datasource-already-closed-at-date","errorCode":null,"errorMessage":"dataSource already closed at {date}","messagePattern":"dataSource already closed at (.+?)","errorType":"exception","errorClass":"DataSourceClosedException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/com/alibaba/druid/pool/DruidDataSource.java","lineNumber":3814,"sourceCode":"        this.logDifferentThread = logDifferentThread;\n    }\n\n    public DruidPooledConnection tryGetConnection() throws SQLException {\n        if (poolingCount == 0) {\n            return null;\n        }\n        return getConnection();\n    }\n\n    @Override\n    public int fill() throws SQLException {\n        return this.fill(this.maxActive);\n    }\n\n    @Override\n    public int fill(int toCount) throws SQLException {\n        if (closed) {\n            throw new DataSourceClosedException(\"dataSource already closed at \" + new Date(closeTimeMillis));\n        }\n\n        if (toCount < 0) {\n            throw new IllegalArgumentException(\"toCount can't not be less than zero\");\n        }\n\n        init();\n\n        if (toCount > this.maxActive) {\n            toCount = this.maxActive;\n        }\n\n        int fillCount = 0;\n        for (; ; ) {\n            try {\n                lock.lockInterruptibly();\n            } catch (InterruptedException e) {\n                connectErrorCountUpdater.incrementAndGet(this);","sourceCodeStart":3796,"sourceCodeEnd":3832,"githubUrl":"https://github.com/alibaba/druid/blob/fa8dc9912637a2f729eef9f55356621fec18d40e/core/src/main/java/com/alibaba/druid/pool/DruidDataSource.java#L3796-L3832","documentation":"Thrown by fill(int) (the API that pre-populates the pool with physical connections) when the DataSource is already closed. It is a DataSourceClosedException (SQLException subclass) and includes the timestamp at which close() was invoked, so you can tell when the pool was shut down.","triggerScenarios":"dataSource.close() has run (closed == true) and then fill() or fill(toCount) is called. The guard at line 3813 throws DataSourceClosedException with new Date(closeTimeMillis).","commonSituations":"A warm-up/fill routine running on a timer that fires after Spring context shutdown; calling fill() in an @PreDestroy-adjacent phase; reusing a DataSource bean that was closed by a previous test; init/fill ordering mistake in a custom lifecycle.","solutions":["Ensure fill() is called only after init() and before close(); move pre-warming into an init hook (Druid calls fill logic during init when configured).","If the pool is closed, create a new DruidDataSource rather than calling fill() on the dead one.","Coordinate shutdown so background fill/warm threads are stopped before the DataSource is closed."],"exampleFix":"// before\n@Scheduled(fixedDelay = 60_000)\nvoid warm() { dataSource.fill(maxActive); } // fires after shutdown -> DataSourceClosedException\n\n// after — stop the warmer before the pool closes\n@PreDestroy\nvoid shutdown() {\n    warmScheduler.shutdownNow();\n    dataSource.close();\n}","handlingStrategy":"validation","validationCode":"if (((DruidDataSource) dataSource).isClosed()) {\n    throw new IllegalStateException(\"DataSource closed; cannot fill\");\n}\nreturn dataSource.fill(toCount);","typeGuard":null,"tryCatchPattern":"try {\n    return dataSource.fill(toCount);\n} catch (DataSourceClosedException e) {\n    // pool is gone; either recreate it or skip warming\n    log.warn(\"fill skipped, pool closed at {}\", e.getMessage());\n    return 0;\n}","preventionTips":["Call fill() only between init() and close().","Stop background warmers in @PreDestroy before closing the DataSource.","Do not reuse a closed DataSource bean — build a new one."],"tags":["lifecycle","closed","fill","connection-pool","shutdown"],"backgroundTag":null,"analyzedSha":"fa8dc9912637a2f729eef9f55356621fec18d40e","analyzedAt":"2026-08-14T04:55:06.789Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}