Over a million developers have joined DZone.

Correcting the Server Shutdown Process: A Web App Detour (Part 2)

In this continuation of a series detour, learn how to tackle shutdown errors stemming from data sources when making a Java web app.

· Java Zone

Check out this 8-step guide to see how you can increase your productivity by skipping slow application redeploys and by implementing application profiling, as you code! Brought to you in partnership with ZeroTurnaround.

Introduction

In Part 1, we discussed the issue of resources not being cleaned up during shutdown and how to do this while dealing with the Root context and the Servlet context. If you want to actually try all this, check out the other article, as it has all the code.

How to Release Resources

We have our ShapeCalcServletContextListener that implements ServletContextListener, and we know that it has references to the two resources we are concerned about at shutdown:

  • Private DataSource dataSource.

  • Private ExecutorService executor.

We know that our listener is involved at the correct place in the shutdown process:

Image title

So, what remains is adding the correct logic inside the "contextDestroyed()" method.

I think there are three general steps we need to do, in order:

  1. Shut down the Executor pool, as its tasks are dealing with the DataSource.

  2. Then, shut down the DataSource itself.

  3. And finally, unregister the database driver.

Now, obviously,  step #3  (and even #2) may not have to be done if our web application had nothing to do with managing the DataSource. This part may not even be at the application level at all.  

But in our case, it is. Or it's a possibility. So let's deal with it.

One other note: Our particular pool (Hikari) is already shutting down the DataSource. Doesn't matter, we'll pretend. No harm done if we do.

Step 1: Shutd own Executor:

//shutdown any tasks using the data source
try { executor.shutdownNow(); }
catch (Exception e) { System.err.println("\n\n\nError attempting to call executor.shutdownNow() : "+e.getMessage()); }
try { executor.awaitTermination(10, TimeUnit.SECONDS); }
catch (Exception e) { System.err.println("\n\n\nError executor.awaitingTermination() : "+e.getMessage()); }


Step 2:  Shut down the DataSource:

//shutdown the datasource itself
// this probably not needed....  it's been done prior to this point
try { dataSource.getConnection().close(); }
catch (Exception e) { System.err.println("\n\n\nError datasource.getconnection.close() : "+e.getMessage()); }


Step 3: Unregister the Driver

This step is way trickier. The gist of this is that we do NOT want to do this unless it's our web app that loaded the driver to begin with. There are a few good how-tos out on the web, so I will only present the code without much more of an explanation:

        //unregister the driver

        ClassLoader cl = Thread.currentThread().getContextClassLoader();

        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while (drivers.hasMoreElements()) {
            Driver driver = drivers.nextElement();

            if (driver.getClass().getClassLoader() == cl) {

                try {
                    System.err.println("Deregistering JDBC driver "+ driver);
                    DriverManager.deregisterDriver(driver);

                } catch (Exception ex) {
                    System.err.println("ERROR Deregistering JDBC driver "+ driver + " : " + ex);
                }

            } else {
                System.err.println(
                        "Not deregistering JDBC driver {} as it does not belong to this webapp's ClassLoader");
            }
        }

I've noticed that the shutdown does not always encounter one of the errors (closing the executor).

Anyway, here is the console output during shutdown, BEFORE any of our improvements.

Sep 09, 2016 2:01:02 PM org.apache.catalina.core.ApplicationContext log

INFO: Closing Spring root WebApplicationContext

Sep 09, 2016 2:01:02 PM org.apache.catalina.loader.WebappClassLoaderBase clearReferencesJdbc

WARNING: The web application [webapp-p3] registered the JDBC driver [com.mysql.jdbc.Driver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered.

Sep 09, 2016 2:01:02 PM org.apache.catalina.loader.WebappClassLoaderBase clearReferencesThreads

WARNING: The web application [webapp-p3] appears to have started a thread named [Abandoned connection cleanup thread] but has failed to stop it. This is very likely to create a memory leak. 

Let's BDS and see what happens.

Here is the shutdown output now:

Error datasource.getconnection.close() : HikariDataSource HikariDataSource (HikariPool-1) has been closed.

Deregistering JDBC driver com.mysql.jdbc.Driver@2d1ce8f0

Sep 09, 2016 2:24:50 PM org.apache.catalina.core.ApplicationContext log

INFO: Closing Spring root WebApplicationContext

Sep 09, 2016 2:24:50 PM org.apache.catalina.loader.WebappClassLoaderBase clearReferencesThreads

WARNING: The web application [webapp-p3] appears to have started a thread named [Abandoned connection cleanup thread] but has failed to stop it. This is very likely to create a memory leak.

The first error is okay — we are trying to shutdown something that was already handled. Not a problem.

Secondly, notice the unregistering problem from the previous output has gone away.  Good.

However, we STILL have the last problem.

This seems to be an issue with MySQL.

So in our case, we could add this:

try {
    System.err.println("Shutting down if MySql JDBC driver "+ driver);
    if (driver.getClass().getName().toLowerCase().contains("mysql")) {
        com.mysql.jdbc.AbandonedConnectionCleanupThread.shutdown();
    }
} catch (Exception ex) {
    System.err.println("ERROR Shutting down MySql JDBC driver "+ driver + " : " + ex);
}

There are probably better ways to do the above, but we're just pretending, not really doing production-ready code, so this is good enough.

Here is the complete code for our listener again:

package com.eli.calc.shape.listeners.impl;

import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import javax.sql.DataSource;

import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

//@WebListener
@Component
public class ShapeCalcServletContextListener implements ServletContextListener {


    private ApplicationContext ctx;

    private DataSource dataSource;

    private ExecutorService executor;


    public ShapeCalcServletContextListener(ApplicationContext ctx) { this.ctx = ctx; }


    @Override
    public void contextDestroyed(ServletContextEvent event) {

        System.err.println("\n\n\nContext is being Destroyed.....SHUTTING DOWN!!!!\n\n\n");

        //shutdown any tasks using the data source
        try { executor.shutdownNow(); }
        catch (Exception e) { System.err.println("\n\n\nError attempting to call executor.shutdownNow() : "+e.getMessage()); }
        try { executor.awaitTermination(10, TimeUnit.SECONDS); }
        catch (Exception e) { System.err.println("\n\n\nError executor.awaitingTermination() : "+e.getMessage()); }


        //shutdown the datasource itself
        // this probably not needed....  it's been done prior to this point
        try { dataSource.getConnection().close(); }
        catch (Exception e) { System.err.println("\n\n\nError datasource.getconnection.close() : "+e.getMessage()); }

        //unregister the driver

        ClassLoader cl = Thread.currentThread().getContextClassLoader();

        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while (drivers.hasMoreElements()) {
            Driver driver = drivers.nextElement();

            if (driver.getClass().getClassLoader() == cl) {

                try {
                    System.err.println("Shutting down if MySql JDBC driver "+ driver);
                    if (driver.getClass().getName().toLowerCase().contains("mysql")) {
                        com.mysql.jdbc.AbandonedConnectionCleanupThread.shutdown();
                    }
                } catch (Exception ex) {
                    System.err.println("ERROR Shutting down MySql JDBC driver "+ driver + " : " + ex);
                }

                try {
                    System.err.println("Deregistering JDBC driver "+ driver);
                    DriverManager.deregisterDriver(driver);

                } catch (Exception ex) {
                    System.err.println("ERROR Deregistering JDBC driver "+ driver + " : " + ex);
                }

            } else {
                System.err.println(
                        "Not deregistering JDBC driver {} as it does not belong to this webapp's ClassLoader");
            }
        }

    }

    @Override
    public void contextInitialized(ServletContextEvent event) {

        System.err.println("\n\n\nELI:Context is Initializing...STARTING UP!!!!\n\n\n");

        dataSource = ctx.getBean(DataSource.class);
        executor = ctx.getBean(ExecutorService.class);

    }

}


We BDS. We shut down.

Error datasource.getconnection.close() : HikariDataSource HikariDataSource (HikariPool-1) has been closed.

Shutting down if MySql JDBC driver com.mysql.jdbc.Driver@2024f3ec

Deregistering JDBC driver com.mysql.jdbc.Driver@2024f3ec

Sep 09, 2016 2:33:34 PM org.apache.catalina.core.ApplicationContext log

INFO: Closing Spring root WebApplicationContext

Sep 09, 2016 2:33:34 PM org.apache.coyote.AbstractProtocol stop

INFO: Stopping ProtocolHandler ["http-nio-8080"]

Sep 09, 2016 2:33:34 PM org.apache.coyote.AbstractProtocol stop

INFO: Stopping ProtocolHandler ["ajp-nio-8009"]

Sep 09, 2016 2:33:34 PM org.apache.coyote.AbstractProtocol destroy

INFO: Destroying ProtocolHandler ["http-nio-8080"]

Sep 09, 2016 2:33:34 PM org.apache.coyote.AbstractProtocol destroy

INFO: Destroying ProtocolHandler ["ajp-nio-8009"]


And if we compare the above to the previous shutdown output, we see that last issue is gone.

The Latest Code

Get the latest web app code here.

And here is the latest code for our component or service, which is used by the above web app.

What's Next?

OK, that wraps up our side-trip into memory-leaks, resource-shutdowns, listeners, and dealing with root context and servlet context.

In the next article we will take up where we left off from here, completing our web application.

Stay tuned!

The Java Zone is brought to you in partnership with ZeroTurnaround. Check out this 8-step guide to see how you can increase your productivity by skipping slow application redeploys and by implementing application profiling, as you code!

Topics:
java 8 ,spring ,datasource ,server ,memory leaks ,shutdown

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}