playframework / playframework/play1

ERRORs on application shutdown when HikariCP is configured with multiple databases

Open
#1,334 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

triage
Dominant language
Java
Stars
1.6k
Forks
671
Avg merge
12d 15h
Merged PRs (30d)
1

Description

Play Version (1.5.x / etc)

Play-1.5.3

Operating System (Ubuntu 15.10 / MacOS 10.10 / Windows 10)

4.15.0-65-generic #74-Ubuntu SMP Tue Sep 17 17:06:04 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

JDK (Oracle 1.8.0_72, OpenJDK 1.8.x, Azul Zing)

openjdk version "11.0.5" 2019-10-15

Expected Behavior
  1. Application does not emit "Couldn't destroy the datasource" ERRORs on shutdown in PROD mode when multiple databases are configured and HikariCP is the DataSource.
Actual Behavior

When your application shuts down in PROD mode (not DEV mode) n-many errors are logged, one for each configured database:

Couldn't destroy the datasource

These are emitted because DB.destroy() attempts to find and invoke a destroy method. The destroyMethod is discovered from ExtendedDataSource.getDestroyMethod(), which returns its destroyMethod field, which is set by DBPlugin:

   // Current datasource. This is actually deprecated. 
   String destroyMethod = dbConfig.getProperty("db.destroyMethod", "");
   DB.datasource = ds;
   DB.destroyMethod = destroyMethod;

   DB.ExtendedDatasource extDs = new DB.ExtendedDatasource(ds, destroyMethod);

If unset, db.destroyMethod is empty string. When the application shuts down in PROD mode, play.db.DB tries to destroy the data sources:

    public static void destroy(String name) {
        try {
            ExtendedDatasource extDatasource = datasources.get(name);
            if (extDatasource != null && extDatasource.getDestroyMethod() != null) {
                Method close = extDatasource.datasource.getClass().getMethod(extDatasource.getDestroyMethod(), new Class[] {});
                if (close != null) {
                    close.invoke(extDatasource.getDataSource(), new Object[] {});
                    datasources.remove(name);
                    DB.datasource = null;
                    Logger.trace("Datasource destroyed");
                }
            }
        } catch (Throwable t) {
            Logger.error("Couldn't destroy the datasource", t);
        }
    }

The conditional checks if the ExternalDataSource's destroyMethod is null, not empty string. Because the default is empty string, it tries to find and invoke a method named "" in the DataSource. This throws a java.lang.NoSuchMethodException, which is hidden because t is passed as a formatting argument to Logger.error(). The developer simply sees the error Couldn't destroy the datasource.

If the developer tries to avoid the ERRORs by explicitly setting the db.destroyMethod property, then they receive a WARN on startup:

Ignoring db.destroyMethod because running the in internal pool db.

This is because DBPlugin.changed() executes:

            } else {
                // Internal pool is c3p0, we should call the close() method to destroy it.
                check(dbConfig, "internal pool", "db.destroyMethod");

                dbConfig.put("db.destroyMethod", "close");
            }

The check method emits the warning if the given property is already set. The scope of dbConfig is local to changed() (it does not replace the global db config).

Reproducible Test Case

Configure a Play! app to use HikariCP and multiple databases. Start the application in PROD mode, and then shut it down. This emits one

Couldn't destroy the datasource

Per configured database.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with play.db.DB.destroy(String) and the DBPlugin.changed() path described in the report, then reproduce the shutdown with HikariCP and multiple databases in PROD mode. Done means shutdown no longer logs one "Couldn't destroy the datasource" ERROR per configured database, while the existing internal-pool behavior remains intact.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
database
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.