How to actually know if thread is killed or keep running when you removed the webapp from the apache tomcat 8.0.33 Server?

Viewed 181

I have a program which listen for postgres events. The application is depolyed and its working fine. The issue is when i remove the application from server i can see logs in catalina file.It looks like still the thread is running behind the scenes. How should i stop the running thread.

The logs in catalina are:

03-Sep-2020 15:41:46.025 INFO [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.startup.HostConfig.undeploy Undeploying context [/msms]
03-Sep-2020 15:41:46.035 WARNING [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesJdbc The web application [msms] registered the JDBC driver [com.impossibl.postgres.jdbc.PGDriver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered.
03-Sep-2020 15:41:46.036 WARNING [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [msms] appears to have started a thread named [PG-JDBC Housekeeper] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
 java.lang.Object.wait(Native Method)
 java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
 java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
 com.impossibl.postgres.jdbc.ThreadedHousekeeper$1.run(ThreadedHousekeeper.java:187)
03-Sep-2020 15:41:46.036 WARNING [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [msms] appears to have started a thread named [PG-JDBC I/O (1)] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
 io.netty.channel.epoll.Native.epollWait(Native Method)
 io.netty.channel.epoll.Native.epollWait(Native.java:148)
 io.netty.channel.epoll.Native.epollWait(Native.java:141)
 io.netty.channel.epoll.EpollEventLoop.epollWaitNoTimerChange(EpollEventLoop.java:290)
 io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:347)
 io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
 io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
 java.lang.Thread.run(Thread.java:745)
03-Sep-2020 15:41:46.037 SEVERE [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.loader.WebappClassLoaderBase.checkThreadLocalMapForLeaks The web application [msms] created a ThreadLocal with key of type [java.lang.ThreadLocal] (value [java.lang.ThreadLocal@24002836]) and a value of type [io.netty.util.internal.InternalThreadLocalMap] (value [io.netty.util.internal.InternalThreadLocalMap@24e9076e]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.

The following code is used.

/**
 * This program uses the pgjdbc_ng driver which has an asynchronous
 * implementation for blocking on the Postgres NOTIFY/LISTEN events.
 *
 * No polling is done using this driver. You will see a forever loop
 * "while(true)" in the main(). This is done to keep the program running and
 * listening to multiple events happening in Postgres. Normally you would just
 * take one event and then do something with it.
 *
 */
public class ListenNotify {
// Create the queue that will be shared by the producer and consumer
    private BlockingQueue queue = new ArrayBlockingQueue(10);

// Database connection
    PGConnection connection;

    public ListenNotify() {
// Get database info from environment variables
        String DBHost = System.getenv("DBHost");
        String DBName = System.getenv("DBName");
        String DBUserName = System.getenv("DBUserName");
        String DBPassword = System.getenv("DBPassword");

// Create the listener callback
        PGNotificationListener listener = new PGNotificationListener() {
            @Override
            public void notification(int processId, String channelName, String payload) {
// Add event and payload to the queue
                queue.add("/channels/" + channelName + " " + payload);
                System.out.println("*********INSIDE NOTIFICATION*************");
                System.out.println("notification = " + payload);
                Advocate advocate = ParseJSON.parseJSON(payload);
                String sent=advocate.getSent();
                String date=advocate.getDate();
                String mobile = advocate.getMobile();
                String message = advocate.getMessage();
                System.out.println("sent::" + sent);
                System.out.println("message::" + message);
                System.out.println("mobile::" + mobile);
                System.out.println("date::" + date);
                sendSMS sendSMS = new sendSMS();
                Integer ack = sendSMS.sendSMS(mobile, message);
                System.out.println("ack::" + ack);
            }
        };

        try {
// Create a data source for logging into the db
            PGDataSource dataSource = new PGDataSource();
            dataSource.setHost("172.16.2.32");
            dataSource.setPort(5432);
            dataSource.setDatabaseName("smsdb");
            dataSource.setUser("postgres");
            dataSource.setPassword("postgres");

// Log into the db
            connection = (PGConnection) dataSource.getConnection();

// add the callback listener created earlier to the connection
            connection.addNotificationListener(listener);

// Tell Postgres to send NOTIFY q_event to our connection and listener
            Statement statement = connection.createStatement();

            DatabaseMetaData dbmd = connection.getMetaData();
            try (ResultSet tables = dbmd.getTables(null, null, "%", new String[] { "TABLE" })) {
                while (tables.next()) {
                    System.out.println(tables.getString("TABLE_NAME"));
                }
            }

            statement.execute("LISTEN q_event");
            statement.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * @return shared queue
     */
    public BlockingQueue getQueue() {
        return queue;
    }

    /**
     *
     * main entry point
     *
     * @param args
     */
    public static void main(String[] args) {
// Create a new listener
        ListenNotify ln = new ListenNotify();

// Get the shared queue
        BlockingQueue queue = ln.getQueue();

// Loop forever pulling messages off the queue
        while (true) {
            try {
// queue blocks until something is placed on it
                String msg = (String) queue.take();

// Do something with the event
                System.out.println(msg);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Even this class does not deregister the driver.And more over The problem with this approach (forcefully deregistering in context listener) is that some drivers maintain an internal registration state. That means that while the driver is then removed from the DriverManager by the listener its internal state thinks it's still registered. This in turn means it will fail to reregister upon reload. Ouch!

H2 and PostgreSQL are two drivers which behave like that (links point to relevant source line).

MyWebAppContextListener.java

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;

public class MyWebAppContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("************** Starting up! **************");
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("************** Shutting down! **************");
        System.out.println("Destroying Context...");
        // ... First close any background tasks which may be using the DB ...
        // ... Then close any DB connection pools ...

        // Now deregister JDBC drivers in this context's ClassLoader:
        // Get the webapp's ClassLoader
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        // Loop through all drivers

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

            if (driver.getClass().getClassLoader() == cl) {
                try {
                    System.out.println("Deregistering JDBC driver {}");
                    DriverManager.deregisterDriver(driver);

                } catch (SQLException ex) {
                    System.out.println("Error deregistering JDBC driver {}");
                    ex.printStackTrace();
                }
            } else {
                   // driver was not registered by the webapp's ClassLoader and may be in use elsewhere
                System.out
                        .println("Not deregistering JDBC driver {} as it does not belong to this webapp's ClassLoader");
            }
        }
    }

}
0 Answers
Related