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");
}
}
}
}